22 changed files with 3211 additions and 0 deletions
-
1.gitignore
-
199.nuxt/App.js
-
778.nuxt/client.js
-
142.nuxt/components/nuxt-build-indicator.vue
-
122.nuxt/components/nuxt-child.js
-
97.nuxt/components/nuxt-error.vue
-
98.nuxt/components/nuxt-link.client.js
-
16.nuxt/components/nuxt-link.server.js
-
177.nuxt/components/nuxt-loading.vue
-
101.nuxt/components/nuxt.js
-
1.nuxt/empty.js
-
185.nuxt/index.js
-
109.nuxt/loading.html
-
3.nuxt/middleware.js
-
87.nuxt/mixins/fetch.client.js
-
48.nuxt/mixins/fetch.server.js
-
60.nuxt/router.js
-
74.nuxt/router.scrollBehavior.js
-
265.nuxt/server.js
-
616.nuxt/utils.js
-
9.nuxt/views/app.template.html
-
23.nuxt/views/error.html
@ -0,0 +1 @@ |
|||
node_modules |
@ -0,0 +1,199 @@ |
|||
import Vue from 'vue' |
|||
|
|||
import { |
|||
getMatchedComponentsInstances, |
|||
getChildrenComponentInstancesUsingFetch, |
|||
promisify, |
|||
globalHandleError, |
|||
sanitizeComponent |
|||
} from './utils' |
|||
|
|||
import NuxtLoading from './components/nuxt-loading.vue' |
|||
import NuxtBuildIndicator from './components/nuxt-build-indicator' |
|||
|
|||
import _6f6c098b from '..\\layouts\\default.vue' |
|||
import _2d297eb3 from '..\\layouts\\sign.vue' |
|||
|
|||
const layouts = { "_default": sanitizeComponent(_6f6c098b),"_sign": sanitizeComponent(_2d297eb3) } |
|||
|
|||
export default { |
|||
render (h, props) { |
|||
const loadingEl = h('NuxtLoading', { ref: 'loading' }) |
|||
|
|||
const layoutEl = h(this.layout || 'nuxt') |
|||
const templateEl = h('div', { |
|||
domProps: { |
|||
id: '__layout' |
|||
}, |
|||
key: this.layoutName |
|||
}, [layoutEl]) |
|||
|
|||
const transitionEl = h('transition', { |
|||
props: { |
|||
name: 'layout', |
|||
mode: 'out-in' |
|||
}, |
|||
on: { |
|||
beforeEnter (el) { |
|||
// Ensure to trigger scroll event after calling scrollBehavior
|
|||
window.$nuxt.$nextTick(() => { |
|||
window.$nuxt.$emit('triggerScroll') |
|||
}) |
|||
} |
|||
} |
|||
}, [templateEl]) |
|||
|
|||
return h('div', { |
|||
domProps: { |
|||
id: '__nuxt' |
|||
} |
|||
}, [ |
|||
loadingEl, |
|||
h(NuxtBuildIndicator), |
|||
transitionEl |
|||
]) |
|||
}, |
|||
|
|||
data: () => ({ |
|||
isOnline: true, |
|||
|
|||
layout: null, |
|||
layoutName: '', |
|||
|
|||
nbFetching: 0 |
|||
}), |
|||
|
|||
beforeCreate () { |
|||
Vue.util.defineReactive(this, 'nuxt', this.$options.nuxt) |
|||
}, |
|||
created () { |
|||
// Add this.$nuxt in child instances
|
|||
Vue.prototype.$nuxt = this |
|||
// add to window so we can listen when ready
|
|||
if (process.client) { |
|||
window.$nuxt = this |
|||
|
|||
this.refreshOnlineStatus() |
|||
// Setup the listeners
|
|||
window.addEventListener('online', this.refreshOnlineStatus) |
|||
window.addEventListener('offline', this.refreshOnlineStatus) |
|||
} |
|||
// Add $nuxt.error()
|
|||
this.error = this.nuxt.error |
|||
// Add $nuxt.context
|
|||
this.context = this.$options.context |
|||
}, |
|||
|
|||
mounted () { |
|||
this.$loading = this.$refs.loading |
|||
}, |
|||
watch: { |
|||
'nuxt.err': 'errorChanged' |
|||
}, |
|||
|
|||
computed: { |
|||
isOffline () { |
|||
return !this.isOnline |
|||
}, |
|||
|
|||
isFetching() { |
|||
return this.nbFetching > 0 |
|||
} |
|||
}, |
|||
|
|||
methods: { |
|||
refreshOnlineStatus () { |
|||
if (process.client) { |
|||
if (typeof window.navigator.onLine === 'undefined') { |
|||
// If the browser doesn't support connection status reports
|
|||
// assume that we are online because most apps' only react
|
|||
// when they now that the connection has been interrupted
|
|||
this.isOnline = true |
|||
} else { |
|||
this.isOnline = window.navigator.onLine |
|||
} |
|||
} |
|||
}, |
|||
|
|||
async refresh () { |
|||
const pages = getMatchedComponentsInstances(this.$route) |
|||
|
|||
if (!pages.length) { |
|||
return |
|||
} |
|||
this.$loading.start() |
|||
|
|||
const promises = pages.map((page) => { |
|||
const p = [] |
|||
|
|||
// Old fetch
|
|||
if (page.$options.fetch && page.$options.fetch.length) { |
|||
p.push(promisify(page.$options.fetch, this.context)) |
|||
} |
|||
if (page.$fetch) { |
|||
p.push(page.$fetch()) |
|||
} else { |
|||
// Get all component instance to call $fetch
|
|||
for (const component of getChildrenComponentInstancesUsingFetch(page.$vnode.componentInstance)) { |
|||
p.push(component.$fetch()) |
|||
} |
|||
} |
|||
|
|||
if (page.$options.asyncData) { |
|||
p.push( |
|||
promisify(page.$options.asyncData, this.context) |
|||
.then((newData) => { |
|||
for (const key in newData) { |
|||
Vue.set(page.$data, key, newData[key]) |
|||
} |
|||
}) |
|||
) |
|||
} |
|||
|
|||
return Promise.all(p) |
|||
}) |
|||
try { |
|||
await Promise.all(promises) |
|||
} catch (error) { |
|||
this.$loading.fail(error) |
|||
globalHandleError(error) |
|||
this.error(error) |
|||
} |
|||
this.$loading.finish() |
|||
}, |
|||
|
|||
errorChanged () { |
|||
if (this.nuxt.err && this.$loading) { |
|||
if (this.$loading.fail) { |
|||
this.$loading.fail(this.nuxt.err) |
|||
} |
|||
if (this.$loading.finish) { |
|||
this.$loading.finish() |
|||
} |
|||
} |
|||
}, |
|||
|
|||
setLayout (layout) { |
|||
if(layout && typeof layout !== 'string') { |
|||
throw new Error('[nuxt] Avoid using non-string value as layout property.') |
|||
} |
|||
|
|||
if (!layout || !layouts['_' + layout]) { |
|||
layout = 'default' |
|||
} |
|||
this.layoutName = layout |
|||
this.layout = layouts['_' + layout] |
|||
return this.layout |
|||
}, |
|||
loadLayout (layout) { |
|||
if (!layout || !layouts['_' + layout]) { |
|||
layout = 'default' |
|||
} |
|||
return Promise.resolve(layouts['_' + layout]) |
|||
} |
|||
}, |
|||
|
|||
components: { |
|||
NuxtLoading |
|||
} |
|||
} |
@ -0,0 +1,778 @@ |
|||
import Vue from 'vue' |
|||
import fetch from 'unfetch' |
|||
import middleware from './middleware.js' |
|||
import { |
|||
applyAsyncData, |
|||
promisify, |
|||
middlewareSeries, |
|||
sanitizeComponent, |
|||
resolveRouteComponents, |
|||
getMatchedComponents, |
|||
getMatchedComponentsInstances, |
|||
flatMapComponents, |
|||
setContext, |
|||
getLocation, |
|||
compile, |
|||
getQueryDiff, |
|||
globalHandleError |
|||
} from './utils.js' |
|||
import { createApp, NuxtError } from './index.js' |
|||
import fetchMixin from './mixins/fetch.client' |
|||
import NuxtLink from './components/nuxt-link.client.js' // should be included after ./index.js
|
|||
|
|||
// Fetch mixin
|
|||
if (!Vue.__nuxt__fetch__mixin__) { |
|||
Vue.mixin(fetchMixin) |
|||
Vue.__nuxt__fetch__mixin__ = true |
|||
} |
|||
|
|||
// Component: <NuxtLink>
|
|||
Vue.component(NuxtLink.name, NuxtLink) |
|||
Vue.component('NLink', NuxtLink) |
|||
|
|||
if (!global.fetch) { global.fetch = fetch } |
|||
|
|||
// Global shared references
|
|||
let _lastPaths = [] |
|||
let app |
|||
let router |
|||
|
|||
// Try to rehydrate SSR data from window
|
|||
const NUXT = window.__NUXT__ || {} |
|||
|
|||
Object.assign(Vue.config, {"silent":false,"performance":true}) |
|||
|
|||
const logs = NUXT.logs || [] |
|||
if (logs.length > 0) { |
|||
const ssrLogSyle = 'background: #2E495E;border-radius: 0.5em;color: white;font-weight: bold;padding: 2px 0.5em;' |
|||
console.group && console.group ('%cNuxt SSR', ssrLogSyle) |
|||
logs.forEach(logObj => (console[logObj.type] || console.log)(...logObj.args)) |
|||
delete NUXT.logs |
|||
console.groupEnd && console.groupEnd() |
|||
} |
|||
|
|||
// Setup global Vue error handler
|
|||
if (!Vue.config.$nuxt) { |
|||
const defaultErrorHandler = Vue.config.errorHandler |
|||
Vue.config.errorHandler = (err, vm, info, ...rest) => { |
|||
// Call other handler if exist
|
|||
let handled = null |
|||
if (typeof defaultErrorHandler === 'function') { |
|||
handled = defaultErrorHandler(err, vm, info, ...rest) |
|||
} |
|||
if (handled === true) { |
|||
return handled |
|||
} |
|||
|
|||
if (vm && vm.$root) { |
|||
const nuxtApp = Object.keys(Vue.config.$nuxt) |
|||
.find(nuxtInstance => vm.$root[nuxtInstance]) |
|||
|
|||
// Show Nuxt Error Page
|
|||
if (nuxtApp && vm.$root[nuxtApp].error && info !== 'render function') { |
|||
vm.$root[nuxtApp].error(err) |
|||
} |
|||
} |
|||
|
|||
if (typeof defaultErrorHandler === 'function') { |
|||
return handled |
|||
} |
|||
|
|||
// Log to console
|
|||
if (process.env.NODE_ENV !== 'production') { |
|||
console.error(err) |
|||
} else { |
|||
console.error(err.message || err) |
|||
} |
|||
} |
|||
Vue.config.$nuxt = {} |
|||
} |
|||
Vue.config.$nuxt.$nuxt = true |
|||
|
|||
const errorHandler = Vue.config.errorHandler || console.error |
|||
|
|||
// Create and mount App
|
|||
createApp().then(mountApp).catch(errorHandler) |
|||
|
|||
function componentOption (component, key, ...args) { |
|||
if (!component || !component.options || !component.options[key]) { |
|||
return {} |
|||
} |
|||
const option = component.options[key] |
|||
if (typeof option === 'function') { |
|||
return option(...args) |
|||
} |
|||
return option |
|||
} |
|||
|
|||
function mapTransitions (toComponents, to, from) { |
|||
const componentTransitions = (component) => { |
|||
const transition = componentOption(component, 'transition', to, from) || {} |
|||
return (typeof transition === 'string' ? { name: transition } : transition) |
|||
} |
|||
|
|||
const fromComponents = from ? getMatchedComponents(from) : [] |
|||
const maxDepth = Math.max(toComponents.length, fromComponents.length) |
|||
|
|||
const mergedTransitions = [] |
|||
for (let i=0; i<maxDepth; i++) { |
|||
// Clone original objects to prevent overrides
|
|||
const toTransitions = Object.assign({}, componentTransitions(toComponents[i])) |
|||
const transitions = Object.assign({}, componentTransitions(fromComponents[i])) |
|||
|
|||
// Combine transitions & prefer `leave` properties of "from" route
|
|||
Object.keys(toTransitions) |
|||
.filter(key => typeof toTransitions[key] !== 'undefined' && !key.toLowerCase().includes('leave')) |
|||
.forEach((key) => { transitions[key] = toTransitions[key] }) |
|||
|
|||
mergedTransitions.push(transitions) |
|||
} |
|||
return mergedTransitions |
|||
} |
|||
|
|||
async function loadAsyncComponents (to, from, next) { |
|||
// Check if route changed (this._routeChanged), only if the page is not an error (for validate())
|
|||
this._routeChanged = Boolean(app.nuxt.err) || from.name !== to.name |
|||
this._paramChanged = !this._routeChanged && from.path !== to.path |
|||
this._queryChanged = !this._paramChanged && from.fullPath !== to.fullPath |
|||
this._diffQuery = (this._queryChanged ? getQueryDiff(to.query, from.query) : []) |
|||
|
|||
if ((this._routeChanged || this._paramChanged) && this.$loading.start && !this.$loading.manual) { |
|||
this.$loading.start() |
|||
} |
|||
|
|||
try { |
|||
if (this._queryChanged) { |
|||
const Components = await resolveRouteComponents( |
|||
to, |
|||
(Component, instance) => ({ Component, instance }) |
|||
) |
|||
// Add a marker on each component that it needs to refresh or not
|
|||
const startLoader = Components.some(({ Component, instance }) => { |
|||
const watchQuery = Component.options.watchQuery |
|||
if (watchQuery === true) { |
|||
return true |
|||
} |
|||
if (Array.isArray(watchQuery)) { |
|||
return watchQuery.some(key => this._diffQuery[key]) |
|||
} |
|||
if (typeof watchQuery === 'function') { |
|||
return watchQuery.apply(instance, [to.query, from.query]) |
|||
} |
|||
return false |
|||
}) |
|||
if (startLoader && this.$loading.start && !this.$loading.manual) { |
|||
this.$loading.start() |
|||
} |
|||
} |
|||
|
|||
// Call next()
|
|||
next() |
|||
} catch (error) { |
|||
const err = error || {} |
|||
const statusCode = err.statusCode || err.status || (err.response && err.response.status) || 500 |
|||
const message = err.message || '' |
|||
|
|||
// Handle chunk loading errors
|
|||
// This may be due to a new deployment or a network problem
|
|||
if (/^Loading( CSS)? chunk (\d)+ failed\./.test(message)) { |
|||
window.location.reload(true /* skip cache */) |
|||
return // prevent error page blinking for user
|
|||
} |
|||
|
|||
this.error({ statusCode, message }) |
|||
this.$nuxt.$emit('routeChanged', to, from, err) |
|||
next() |
|||
} |
|||
} |
|||
|
|||
function applySSRData (Component, ssrData) { |
|||
if (NUXT.serverRendered && ssrData) { |
|||
applyAsyncData(Component, ssrData) |
|||
} |
|||
|
|||
Component._Ctor = Component |
|||
return Component |
|||
} |
|||
|
|||
// Get matched components
|
|||
function resolveComponents (router) { |
|||
const path = getLocation(router.options.base, router.options.mode) |
|||
|
|||
return flatMapComponents(router.match(path), async (Component, _, match, key, index) => { |
|||
// If component is not resolved yet, resolve it
|
|||
if (typeof Component === 'function' && !Component.options) { |
|||
Component = await Component() |
|||
} |
|||
// Sanitize it and save it
|
|||
const _Component = applySSRData(sanitizeComponent(Component), NUXT.data ? NUXT.data[index] : null) |
|||
match.components[key] = _Component |
|||
return _Component |
|||
}) |
|||
} |
|||
|
|||
function callMiddleware (Components, context, layout) { |
|||
let midd = [] |
|||
let unknownMiddleware = false |
|||
|
|||
// If layout is undefined, only call global middleware
|
|||
if (typeof layout !== 'undefined') { |
|||
midd = [] // Exclude global middleware if layout defined (already called before)
|
|||
layout = sanitizeComponent(layout) |
|||
if (layout.options.middleware) { |
|||
midd = midd.concat(layout.options.middleware) |
|||
} |
|||
Components.forEach((Component) => { |
|||
if (Component.options.middleware) { |
|||
midd = midd.concat(Component.options.middleware) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
midd = midd.map((name) => { |
|||
if (typeof name === 'function') { |
|||
return name |
|||
} |
|||
if (typeof middleware[name] !== 'function') { |
|||
unknownMiddleware = true |
|||
this.error({ statusCode: 500, message: 'Unknown middleware ' + name }) |
|||
} |
|||
return middleware[name] |
|||
}) |
|||
|
|||
if (unknownMiddleware) { |
|||
return |
|||
} |
|||
return middlewareSeries(midd, context) |
|||
} |
|||
|
|||
async function render (to, from, next) { |
|||
if (this._routeChanged === false && this._paramChanged === false && this._queryChanged === false) { |
|||
return next() |
|||
} |
|||
// Handle first render on SPA mode
|
|||
if (to === from) { |
|||
_lastPaths = [] |
|||
} else { |
|||
const fromMatches = [] |
|||
_lastPaths = getMatchedComponents(from, fromMatches).map((Component, i) => { |
|||
return compile(from.matched[fromMatches[i]].path)(from.params) |
|||
}) |
|||
} |
|||
|
|||
// nextCalled is true when redirected
|
|||
let nextCalled = false |
|||
const _next = (path) => { |
|||
if (from.path === path.path && this.$loading.finish) { |
|||
this.$loading.finish() |
|||
} |
|||
|
|||
if (from.path !== path.path && this.$loading.pause) { |
|||
this.$loading.pause() |
|||
} |
|||
|
|||
if (nextCalled) { |
|||
return |
|||
} |
|||
|
|||
nextCalled = true |
|||
next(path) |
|||
} |
|||
|
|||
// Update context
|
|||
await setContext(app, { |
|||
route: to, |
|||
from, |
|||
next: _next.bind(this) |
|||
}) |
|||
this._dateLastError = app.nuxt.dateErr |
|||
this._hadError = Boolean(app.nuxt.err) |
|||
|
|||
// Get route's matched components
|
|||
const matches = [] |
|||
const Components = getMatchedComponents(to, matches) |
|||
|
|||
// If no Components matched, generate 404
|
|||
if (!Components.length) { |
|||
// Default layout
|
|||
await callMiddleware.call(this, Components, app.context) |
|||
if (nextCalled) { |
|||
return |
|||
} |
|||
|
|||
// Load layout for error page
|
|||
const errorLayout = (NuxtError.options || NuxtError).layout |
|||
const layout = await this.loadLayout( |
|||
typeof errorLayout === 'function' |
|||
? errorLayout.call(NuxtError, app.context) |
|||
: errorLayout |
|||
) |
|||
|
|||
await callMiddleware.call(this, Components, app.context, layout) |
|||
if (nextCalled) { |
|||
return |
|||
} |
|||
|
|||
// Show error page
|
|||
app.context.error({ statusCode: 404, message: 'This page could not be found' }) |
|||
return next() |
|||
} |
|||
|
|||
// Update ._data and other properties if hot reloaded
|
|||
Components.forEach((Component) => { |
|||
if (Component._Ctor && Component._Ctor.options) { |
|||
Component.options.asyncData = Component._Ctor.options.asyncData |
|||
Component.options.fetch = Component._Ctor.options.fetch |
|||
} |
|||
}) |
|||
|
|||
// Apply transitions
|
|||
this.setTransitions(mapTransitions(Components, to, from)) |
|||
|
|||
try { |
|||
// Call middleware
|
|||
await callMiddleware.call(this, Components, app.context) |
|||
if (nextCalled) { |
|||
return |
|||
} |
|||
if (app.context._errored) { |
|||
return next() |
|||
} |
|||
|
|||
// Set layout
|
|||
let layout = Components[0].options.layout |
|||
if (typeof layout === 'function') { |
|||
layout = layout(app.context) |
|||
} |
|||
layout = await this.loadLayout(layout) |
|||
|
|||
// Call middleware for layout
|
|||
await callMiddleware.call(this, Components, app.context, layout) |
|||
if (nextCalled) { |
|||
return |
|||
} |
|||
if (app.context._errored) { |
|||
return next() |
|||
} |
|||
|
|||
// Call .validate()
|
|||
let isValid = true |
|||
try { |
|||
for (const Component of Components) { |
|||
if (typeof Component.options.validate !== 'function') { |
|||
continue |
|||
} |
|||
|
|||
isValid = await Component.options.validate(app.context) |
|||
|
|||
if (!isValid) { |
|||
break |
|||
} |
|||
} |
|||
} catch (validationError) { |
|||
// ...If .validate() threw an error
|
|||
this.error({ |
|||
statusCode: validationError.statusCode || '500', |
|||
message: validationError.message |
|||
}) |
|||
return next() |
|||
} |
|||
|
|||
// ...If .validate() returned false
|
|||
if (!isValid) { |
|||
this.error({ statusCode: 404, message: 'This page could not be found' }) |
|||
return next() |
|||
} |
|||
|
|||
let instances |
|||
// Call asyncData & fetch hooks on components matched by the route.
|
|||
await Promise.all(Components.map((Component, i) => { |
|||
// Check if only children route changed
|
|||
Component._path = compile(to.matched[matches[i]].path)(to.params) |
|||
Component._dataRefresh = false |
|||
const childPathChanged = Component._path !== _lastPaths[i] |
|||
// Refresh component (call asyncData & fetch) when:
|
|||
// Route path changed part includes current component
|
|||
// Or route param changed part includes current component and watchParam is not `false`
|
|||
// Or route query is changed and watchQuery returns `true`
|
|||
if (this._routeChanged && childPathChanged) { |
|||
Component._dataRefresh = true |
|||
} else if (this._paramChanged && childPathChanged) { |
|||
const watchParam = Component.options.watchParam |
|||
Component._dataRefresh = watchParam !== false |
|||
} else if (this._queryChanged) { |
|||
const watchQuery = Component.options.watchQuery |
|||
if (watchQuery === true) { |
|||
Component._dataRefresh = true |
|||
} else if (Array.isArray(watchQuery)) { |
|||
Component._dataRefresh = watchQuery.some(key => this._diffQuery[key]) |
|||
} else if (typeof watchQuery === 'function') { |
|||
if (!instances) { |
|||
instances = getMatchedComponentsInstances(to) |
|||
} |
|||
Component._dataRefresh = watchQuery.apply(instances[i], [to.query, from.query]) |
|||
} |
|||
} |
|||
if (!this._hadError && this._isMounted && !Component._dataRefresh) { |
|||
return |
|||
} |
|||
|
|||
const promises = [] |
|||
|
|||
const hasAsyncData = ( |
|||
Component.options.asyncData && |
|||
typeof Component.options.asyncData === 'function' |
|||
) |
|||
|
|||
const hasFetch = Boolean(Component.options.fetch) && Component.options.fetch.length |
|||
|
|||
const loadingIncrease = (hasAsyncData && hasFetch) ? 30 : 45 |
|||
|
|||
// Call asyncData(context)
|
|||
if (hasAsyncData) { |
|||
const promise = promisify(Component.options.asyncData, app.context) |
|||
.then((asyncDataResult) => { |
|||
applyAsyncData(Component, asyncDataResult) |
|||
|
|||
if (this.$loading.increase) { |
|||
this.$loading.increase(loadingIncrease) |
|||
} |
|||
}) |
|||
promises.push(promise) |
|||
} |
|||
|
|||
// Check disabled page loading
|
|||
this.$loading.manual = Component.options.loading === false |
|||
|
|||
// Call fetch(context)
|
|||
if (hasFetch) { |
|||
let p = Component.options.fetch(app.context) |
|||
if (!p || (!(p instanceof Promise) && (typeof p.then !== 'function'))) { |
|||
p = Promise.resolve(p) |
|||
} |
|||
p.then((fetchResult) => { |
|||
if (this.$loading.increase) { |
|||
this.$loading.increase(loadingIncrease) |
|||
} |
|||
}) |
|||
promises.push(p) |
|||
} |
|||
|
|||
return Promise.all(promises) |
|||
})) |
|||
|
|||
// If not redirected
|
|||
if (!nextCalled) { |
|||
if (this.$loading.finish && !this.$loading.manual) { |
|||
this.$loading.finish() |
|||
} |
|||
|
|||
next() |
|||
} |
|||
} catch (err) { |
|||
const error = err || {} |
|||
if (error.message === 'ERR_REDIRECT') { |
|||
return this.$nuxt.$emit('routeChanged', to, from, error) |
|||
} |
|||
_lastPaths = [] |
|||
|
|||
globalHandleError(error) |
|||
|
|||
// Load error layout
|
|||
let layout = (NuxtError.options || NuxtError).layout |
|||
if (typeof layout === 'function') { |
|||
layout = layout(app.context) |
|||
} |
|||
await this.loadLayout(layout) |
|||
|
|||
this.error(error) |
|||
this.$nuxt.$emit('routeChanged', to, from, error) |
|||
next() |
|||
} |
|||
} |
|||
|
|||
// Fix components format in matched, it's due to code-splitting of vue-router
|
|||
function normalizeComponents (to, ___) { |
|||
flatMapComponents(to, (Component, _, match, key) => { |
|||
if (typeof Component === 'object' && !Component.options) { |
|||
// Updated via vue-router resolveAsyncComponents()
|
|||
Component = Vue.extend(Component) |
|||
Component._Ctor = Component |
|||
match.components[key] = Component |
|||
} |
|||
return Component |
|||
}) |
|||
} |
|||
|
|||
function showNextPage (to) { |
|||
// Hide error component if no error
|
|||
if (this._hadError && this._dateLastError === this.$options.nuxt.dateErr) { |
|||
this.error() |
|||
} |
|||
|
|||
// Set layout
|
|||
let layout = this.$options.nuxt.err |
|||
? (NuxtError.options || NuxtError).layout |
|||
: to.matched[0].components.default.options.layout |
|||
|
|||
if (typeof layout === 'function') { |
|||
layout = layout(app.context) |
|||
} |
|||
this.setLayout(layout) |
|||
} |
|||
|
|||
// When navigating on a different route but the same component is used, Vue.js
|
|||
// Will not update the instance data, so we have to update $data ourselves
|
|||
function fixPrepatch (to, ___) { |
|||
if (this._routeChanged === false && this._paramChanged === false && this._queryChanged === false) { |
|||
return |
|||
} |
|||
|
|||
const instances = getMatchedComponentsInstances(to) |
|||
const Components = getMatchedComponents(to) |
|||
|
|||
Vue.nextTick(() => { |
|||
instances.forEach((instance, i) => { |
|||
if (!instance || instance._isDestroyed) { |
|||
return |
|||
} |
|||
|
|||
if ( |
|||
instance.constructor._dataRefresh && |
|||
Components[i] === instance.constructor && |
|||
instance.$vnode.data.keepAlive !== true && |
|||
typeof instance.constructor.options.data === 'function' |
|||
) { |
|||
const newData = instance.constructor.options.data.call(instance) |
|||
for (const key in newData) { |
|||
Vue.set(instance.$data, key, newData[key]) |
|||
} |
|||
|
|||
// Ensure to trigger scroll event after calling scrollBehavior
|
|||
window.$nuxt.$nextTick(() => { |
|||
window.$nuxt.$emit('triggerScroll') |
|||
}) |
|||
} |
|||
}) |
|||
showNextPage.call(this, to) |
|||
|
|||
// Hot reloading
|
|||
setTimeout(() => hotReloadAPI(this), 100) |
|||
}) |
|||
} |
|||
|
|||
function nuxtReady (_app) { |
|||
window.onNuxtReadyCbs.forEach((cb) => { |
|||
if (typeof cb === 'function') { |
|||
cb(_app) |
|||
} |
|||
}) |
|||
// Special JSDOM
|
|||
if (typeof window._onNuxtLoaded === 'function') { |
|||
window._onNuxtLoaded(_app) |
|||
} |
|||
// Add router hooks
|
|||
router.afterEach((to, from) => { |
|||
// Wait for fixPrepatch + $data updates
|
|||
Vue.nextTick(() => _app.$nuxt.$emit('routeChanged', to, from)) |
|||
}) |
|||
} |
|||
|
|||
const noopData = () => { return {} } |
|||
const noopFetch = () => {} |
|||
|
|||
// Special hot reload with asyncData(context)
|
|||
function getNuxtChildComponents ($parent, $components = []) { |
|||
$parent.$children.forEach(($child) => { |
|||
if ($child.$vnode && $child.$vnode.data.nuxtChild && !$components.find(c =>(c.$options.__file === $child.$options.__file))) { |
|||
$components.push($child) |
|||
} |
|||
if ($child.$children && $child.$children.length) { |
|||
getNuxtChildComponents($child, $components) |
|||
} |
|||
}) |
|||
|
|||
return $components |
|||
} |
|||
|
|||
function hotReloadAPI(_app) { |
|||
if (!module.hot) return |
|||
|
|||
let $components = getNuxtChildComponents(_app.$nuxt, []) |
|||
|
|||
$components.forEach(addHotReload.bind(_app)) |
|||
} |
|||
|
|||
function addHotReload ($component, depth) { |
|||
if ($component.$vnode.data._hasHotReload) return |
|||
$component.$vnode.data._hasHotReload = true |
|||
|
|||
var _forceUpdate = $component.$forceUpdate.bind($component.$parent) |
|||
|
|||
$component.$vnode.context.$forceUpdate = async () => { |
|||
let Components = getMatchedComponents(router.currentRoute) |
|||
let Component = Components[depth] |
|||
if (!Component) { |
|||
return _forceUpdate() |
|||
} |
|||
if (typeof Component === 'object' && !Component.options) { |
|||
// Updated via vue-router resolveAsyncComponents()
|
|||
Component = Vue.extend(Component) |
|||
Component._Ctor = Component |
|||
} |
|||
this.error() |
|||
let promises = [] |
|||
const next = function (path) { |
|||
this.$loading.finish && this.$loading.finish() |
|||
router.push(path) |
|||
} |
|||
await setContext(app, { |
|||
route: router.currentRoute, |
|||
isHMR: true, |
|||
next: next.bind(this) |
|||
}) |
|||
const context = app.context |
|||
|
|||
if (this.$loading.start && !this.$loading.manual) { |
|||
this.$loading.start() |
|||
} |
|||
|
|||
callMiddleware.call(this, Components, context) |
|||
.then(() => { |
|||
// If layout changed
|
|||
if (depth !== 0) { |
|||
return |
|||
} |
|||
|
|||
let layout = Component.options.layout || 'default' |
|||
if (typeof layout === 'function') { |
|||
layout = layout(context) |
|||
} |
|||
if (this.layoutName === layout) { |
|||
return |
|||
} |
|||
let promise = this.loadLayout(layout) |
|||
promise.then(() => { |
|||
this.setLayout(layout) |
|||
Vue.nextTick(() => hotReloadAPI(this)) |
|||
}) |
|||
return promise |
|||
}) |
|||
|
|||
.then(() => { |
|||
return callMiddleware.call(this, Components, context, this.layout) |
|||
}) |
|||
|
|||
.then(() => { |
|||
// Call asyncData(context)
|
|||
let pAsyncData = promisify(Component.options.asyncData || noopData, context) |
|||
pAsyncData.then((asyncDataResult) => { |
|||
applyAsyncData(Component, asyncDataResult) |
|||
this.$loading.increase && this.$loading.increase(30) |
|||
}) |
|||
promises.push(pAsyncData) |
|||
|
|||
// Call fetch()
|
|||
Component.options.fetch = Component.options.fetch || noopFetch |
|||
let pFetch = Component.options.fetch.length && Component.options.fetch(context) |
|||
if (!pFetch || (!(pFetch instanceof Promise) && (typeof pFetch.then !== 'function'))) { pFetch = Promise.resolve(pFetch) } |
|||
pFetch.then(() => this.$loading.increase && this.$loading.increase(30)) |
|||
promises.push(pFetch) |
|||
|
|||
return Promise.all(promises) |
|||
}) |
|||
.then(() => { |
|||
this.$loading.finish && this.$loading.finish() |
|||
_forceUpdate() |
|||
setTimeout(() => hotReloadAPI(this), 100) |
|||
}) |
|||
} |
|||
} |
|||
|
|||
async function mountApp (__app) { |
|||
// Set global variables
|
|||
app = __app.app |
|||
router = __app.router |
|||
|
|||
// Create Vue instance
|
|||
const _app = new Vue(app) |
|||
|
|||
// Load layout
|
|||
const layout = NUXT.layout || 'default' |
|||
await _app.loadLayout(layout) |
|||
_app.setLayout(layout) |
|||
|
|||
// Mounts Vue app to DOM element
|
|||
const mount = () => { |
|||
_app.$mount('#__nuxt') |
|||
|
|||
// Add afterEach router hooks
|
|||
router.afterEach(normalizeComponents) |
|||
router.afterEach(fixPrepatch.bind(_app)) |
|||
|
|||
// Listen for first Vue update
|
|||
Vue.nextTick(() => { |
|||
// Call window.{{globals.readyCallback}} callbacks
|
|||
nuxtReady(_app) |
|||
|
|||
// Enable hot reloading
|
|||
hotReloadAPI(_app) |
|||
}) |
|||
} |
|||
|
|||
// Resolve route components
|
|||
const Components = await Promise.all(resolveComponents(router)) |
|||
|
|||
// Enable transitions
|
|||
_app.setTransitions = _app.$options.nuxt.setTransitions.bind(_app) |
|||
if (Components.length) { |
|||
_app.setTransitions(mapTransitions(Components, router.currentRoute)) |
|||
_lastPaths = router.currentRoute.matched.map(route => compile(route.path)(router.currentRoute.params)) |
|||
} |
|||
|
|||
// Initialize error handler
|
|||
_app.$loading = {} // To avoid error while _app.$nuxt does not exist
|
|||
if (NUXT.error) { |
|||
_app.error(NUXT.error) |
|||
} |
|||
|
|||
// Add beforeEach router hooks
|
|||
router.beforeEach(loadAsyncComponents.bind(_app)) |
|||
router.beforeEach(render.bind(_app)) |
|||
|
|||
// If page already is server rendered and it was done on the same route path as client side render
|
|||
if (NUXT.serverRendered && NUXT.routePath === _app.context.route.path) { |
|||
mount() |
|||
return |
|||
} |
|||
|
|||
// First render on client-side
|
|||
const clientFirstMount = () => { |
|||
normalizeComponents(router.currentRoute, router.currentRoute) |
|||
showNextPage.call(_app, router.currentRoute) |
|||
// Don't call fixPrepatch.call(_app, router.currentRoute, router.currentRoute) since it's first render
|
|||
mount() |
|||
} |
|||
|
|||
render.call(_app, router.currentRoute, router.currentRoute, (path) => { |
|||
// If not redirected
|
|||
if (!path) { |
|||
clientFirstMount() |
|||
return |
|||
} |
|||
|
|||
// Add a one-time afterEach hook to
|
|||
// mount the app wait for redirect and route gets resolved
|
|||
const unregisterHook = router.afterEach((to, from) => { |
|||
unregisterHook() |
|||
clientFirstMount() |
|||
}) |
|||
|
|||
// Push the path and let route to be resolved
|
|||
router.push(path, undefined, (err) => { |
|||
if (err) { |
|||
errorHandler(err) |
|||
} |
|||
}) |
|||
}) |
|||
} |
@ -0,0 +1,142 @@ |
|||
<template> |
|||
<transition appear> |
|||
<div v-if="building" class="nuxt__build_indicator" :style="indicatorStyle"> |
|||
<svg viewBox="0 0 96 72" version="1" xmlns="http://www.w3.org/2000/svg"> |
|||
<g fill="none" fill-rule="evenodd"> |
|||
<path d="M6 66h23l1-3 21-37L40 6 6 66zM79 66h11L62 17l-5 9 22 37v3zM54 31L35 66h38z" /> |
|||
<path d="M29 69v-1-2H6L40 6l11 20 3-6L44 3s-2-3-4-3-3 1-5 3L1 63c0 1-2 3 0 6 0 1 2 2 5 2h28c-3 0-4-1-5-2z" fill="#00C58E" /> |
|||
<path d="M95 63L67 14c0-1-2-3-5-3-1 0-3 0-4 3l-4 6 3 6 5-9 28 49H79a5 5 0 0 1 0 3c-2 2-5 2-5 2h16c1 0 4 0 5-2 1-1 2-3 0-6z" fill="#00C58E" /> |
|||
<path d="M79 69v-1-2-3L57 26l-3-6-3 6-21 37-1 3a5 5 0 0 0 0 3c1 1 2 2 5 2h40s3 0 5-2zM54 31l19 35H35l19-35z" fill="#FFF" fill-rule="nonzero" /> |
|||
</g> |
|||
</svg> |
|||
{{ animatedProgress }}% |
|||
</div> |
|||
</transition> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'NuxtBuildIndicator', |
|||
data () { |
|||
return { |
|||
building: false, |
|||
progress: 0, |
|||
animatedProgress: 0, |
|||
reconnectAttempts: 0 |
|||
} |
|||
}, |
|||
computed: { |
|||
options: () => ({"position":"bottom-right","backgroundColor":"#2E495E","color":"#00C48D"}), |
|||
indicatorStyle () { |
|||
const [d1, d2] = this.options.position.split('-') |
|||
return { |
|||
[d1]: '20px', |
|||
[d2]: '20px', |
|||
'background-color': this.options.backgroundColor, |
|||
color: this.options.color |
|||
} |
|||
} |
|||
}, |
|||
watch: { |
|||
progress (val, oldVal) { |
|||
// Average progress may decrease but ignore it! |
|||
if (val < oldVal) { |
|||
return |
|||
} |
|||
// Cancel old animation |
|||
clearInterval(this._progressAnimation) |
|||
// Jump to edge immediately |
|||
if (val < 10 || val > 90) { |
|||
this.animatedProgress = val |
|||
return |
|||
} |
|||
// Animate to value |
|||
this._progressAnimation = setInterval(() => { |
|||
const diff = this.progress - this.animatedProgress |
|||
if (diff > 0) { |
|||
this.animatedProgress++ |
|||
} else { |
|||
clearInterval(this._progressAnimation) |
|||
} |
|||
}, 50) |
|||
} |
|||
}, |
|||
mounted () { |
|||
if (EventSource === undefined) { |
|||
return // Unsupported |
|||
} |
|||
this.sseConnect() |
|||
}, |
|||
beforeDestroy () { |
|||
this.sseClose() |
|||
clearInterval(this._progressAnimation) |
|||
}, |
|||
methods: { |
|||
sseConnect () { |
|||
if (this._connecting) { |
|||
return |
|||
} |
|||
this._connecting = true |
|||
this.sse = new EventSource('/_loading/sse') |
|||
this.sse.addEventListener('message', event => this.onSseMessage(event)) |
|||
}, |
|||
onSseMessage (message) { |
|||
const data = JSON.parse(message.data) |
|||
if (!data.states) { |
|||
return |
|||
} |
|||
|
|||
this.progress = Math.round(data.states.reduce((p, s) => p + s.progress, 0) / data.states.length) |
|||
|
|||
if (!data.allDone) { |
|||
this.building = true |
|||
} else { |
|||
this.$nextTick(() => { |
|||
this.building = false |
|||
this.animatedProgress = 0 |
|||
this.progress = 0 |
|||
clearInterval(this._progressAnimation) |
|||
}) |
|||
} |
|||
}, |
|||
|
|||
sseClose () { |
|||
if (this.sse) { |
|||
this.sse.close() |
|||
delete this.sse |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.nuxt__build_indicator { |
|||
box-sizing: border-box; |
|||
position: fixed; |
|||
font-family: monospace; |
|||
padding: 5px 10px; |
|||
border-radius: 5px; |
|||
box-shadow: 1px 1px 2px 0px rgba(0,0,0,0.2); |
|||
width: 88px; |
|||
z-index: 2147483647; |
|||
font-size: 16px; |
|||
line-height: 1.2rem; |
|||
} |
|||
.v-enter-active, .v-leave-active { |
|||
transition-delay: 0.2s; |
|||
transition-property: all; |
|||
transition-duration: 0.3s; |
|||
} |
|||
.v-leave-to { |
|||
opacity: 0; |
|||
transform: translateY(20px); |
|||
} |
|||
svg { |
|||
display: inline-block; |
|||
vertical-align: baseline; |
|||
width: 1.1em; |
|||
position: relative; |
|||
top: 1px; |
|||
} |
|||
</style> |
@ -0,0 +1,122 @@ |
|||
|
|||
export default { |
|||
name: 'NuxtChild', |
|||
functional: true, |
|||
props: { |
|||
nuxtChildKey: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
keepAlive: Boolean, |
|||
keepAliveProps: { |
|||
type: Object, |
|||
default: undefined |
|||
} |
|||
}, |
|||
render (_, { parent, data, props }) { |
|||
const h = parent.$createElement |
|||
|
|||
data.nuxtChild = true |
|||
const _parent = parent |
|||
const transitions = parent.$nuxt.nuxt.transitions |
|||
const defaultTransition = parent.$nuxt.nuxt.defaultTransition |
|||
|
|||
let depth = 0 |
|||
while (parent) { |
|||
if (parent.$vnode && parent.$vnode.data.nuxtChild) { |
|||
depth++ |
|||
} |
|||
parent = parent.$parent |
|||
} |
|||
data.nuxtChildDepth = depth |
|||
const transition = transitions[depth] || defaultTransition |
|||
const transitionProps = {} |
|||
transitionsKeys.forEach((key) => { |
|||
if (typeof transition[key] !== 'undefined') { |
|||
transitionProps[key] = transition[key] |
|||
} |
|||
}) |
|||
|
|||
const listeners = {} |
|||
listenersKeys.forEach((key) => { |
|||
if (typeof transition[key] === 'function') { |
|||
listeners[key] = transition[key].bind(_parent) |
|||
} |
|||
}) |
|||
if (process.client) { |
|||
// Add triggerScroll event on beforeEnter (fix #1376)
|
|||
const beforeEnter = listeners.beforeEnter |
|||
listeners.beforeEnter = (el) => { |
|||
// Ensure to trigger scroll event after calling scrollBehavior
|
|||
window.$nuxt.$nextTick(() => { |
|||
window.$nuxt.$emit('triggerScroll') |
|||
}) |
|||
if (beforeEnter) { |
|||
return beforeEnter.call(_parent, el) |
|||
} |
|||
} |
|||
} |
|||
|
|||
// make sure that leave is called asynchronous (fix #5703)
|
|||
if (transition.css === false) { |
|||
const leave = listeners.leave |
|||
|
|||
// only add leave listener when user didnt provide one
|
|||
// or when it misses the done argument
|
|||
if (!leave || leave.length < 2) { |
|||
listeners.leave = (el, done) => { |
|||
if (leave) { |
|||
leave.call(_parent, el) |
|||
} |
|||
|
|||
_parent.$nextTick(done) |
|||
} |
|||
} |
|||
} |
|||
|
|||
let routerView = h('routerView', data) |
|||
|
|||
if (props.keepAlive) { |
|||
routerView = h('keep-alive', { props: props.keepAliveProps }, [routerView]) |
|||
} |
|||
|
|||
return h('transition', { |
|||
props: transitionProps, |
|||
on: listeners |
|||
}, [routerView]) |
|||
} |
|||
} |
|||
|
|||
const transitionsKeys = [ |
|||
'name', |
|||
'mode', |
|||
'appear', |
|||
'css', |
|||
'type', |
|||
'duration', |
|||
'enterClass', |
|||
'leaveClass', |
|||
'appearClass', |
|||
'enterActiveClass', |
|||
'enterActiveClass', |
|||
'leaveActiveClass', |
|||
'appearActiveClass', |
|||
'enterToClass', |
|||
'leaveToClass', |
|||
'appearToClass' |
|||
] |
|||
|
|||
const listenersKeys = [ |
|||
'beforeEnter', |
|||
'enter', |
|||
'afterEnter', |
|||
'enterCancelled', |
|||
'beforeLeave', |
|||
'leave', |
|||
'afterLeave', |
|||
'leaveCancelled', |
|||
'beforeAppear', |
|||
'appear', |
|||
'afterAppear', |
|||
'appearCancelled' |
|||
] |
@ -0,0 +1,97 @@ |
|||
<template> |
|||
<div class="__nuxt-error-page"> |
|||
<div class="error"> |
|||
<svg xmlns="http://www.w3.org/2000/svg" width="90" height="90" fill="#DBE1EC" viewBox="0 0 48 48"> |
|||
<path d="M22 30h4v4h-4zm0-16h4v12h-4zm1.99-10C12.94 4 4 12.95 4 24s8.94 20 19.99 20S44 35.05 44 24 35.04 4 23.99 4zM24 40c-8.84 0-16-7.16-16-16S15.16 8 24 8s16 7.16 16 16-7.16 16-16 16z" /> |
|||
</svg> |
|||
|
|||
<div class="title">{{ message }}</div> |
|||
<p v-if="statusCode === 404" class="description"> |
|||
<NuxtLink class="error-link" to="/">Back to the home page</NuxtLink> |
|||
</p> |
|||
|
|||
<p class="description" v-else>An error occurred while rendering the page. Check developer tools console for details.</p> |
|||
|
|||
<div class="logo"> |
|||
<a href="https://nuxtjs.org" target="_blank" rel="noopener">Nuxt.js</a> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'NuxtError', |
|||
props: { |
|||
error: { |
|||
type: Object, |
|||
default: null |
|||
} |
|||
}, |
|||
computed: { |
|||
statusCode () { |
|||
return (this.error && this.error.statusCode) || 500 |
|||
}, |
|||
message () { |
|||
return this.error.message || 'Error' |
|||
} |
|||
}, |
|||
head () { |
|||
return { |
|||
title: this.message, |
|||
meta: [ |
|||
{ |
|||
name: 'viewport', |
|||
content: 'width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no' |
|||
} |
|||
] |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
.__nuxt-error-page { |
|||
padding: 1rem; |
|||
background: #F7F8FB; |
|||
color: #47494E; |
|||
text-align: center; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
flex-direction: column; |
|||
font-family: sans-serif; |
|||
font-weight: 100 !important; |
|||
-ms-text-size-adjust: 100%; |
|||
-webkit-text-size-adjust: 100%; |
|||
-webkit-font-smoothing: antialiased; |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
} |
|||
.__nuxt-error-page .error { |
|||
max-width: 450px; |
|||
} |
|||
.__nuxt-error-page .title { |
|||
font-size: 1.5rem; |
|||
margin-top: 15px; |
|||
color: #47494E; |
|||
margin-bottom: 8px; |
|||
} |
|||
.__nuxt-error-page .description { |
|||
color: #7F828B; |
|||
line-height: 21px; |
|||
margin-bottom: 10px; |
|||
} |
|||
.__nuxt-error-page a { |
|||
color: #7F828B !important; |
|||
text-decoration: none; |
|||
} |
|||
.__nuxt-error-page .logo { |
|||
position: fixed; |
|||
left: 12px; |
|||
bottom: 12px; |
|||
} |
|||
</style> |
@ -0,0 +1,98 @@ |
|||
import Vue from 'vue' |
|||
|
|||
const requestIdleCallback = window.requestIdleCallback || |
|||
function (cb) { |
|||
const start = Date.now() |
|||
return setTimeout(function () { |
|||
cb({ |
|||
didTimeout: false, |
|||
timeRemaining: () => Math.max(0, 50 - (Date.now() - start)) |
|||
}) |
|||
}, 1) |
|||
} |
|||
|
|||
const cancelIdleCallback = window.cancelIdleCallback || function (id) { |
|||
clearTimeout(id) |
|||
} |
|||
|
|||
const observer = window.IntersectionObserver && new window.IntersectionObserver((entries) => { |
|||
entries.forEach(({ intersectionRatio, target: link }) => { |
|||
if (intersectionRatio <= 0) { |
|||
return |
|||
} |
|||
link.__prefetch() |
|||
}) |
|||
}) |
|||
|
|||
export default { |
|||
name: 'NuxtLink', |
|||
extends: Vue.component('RouterLink'), |
|||
props: { |
|||
prefetch: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
noPrefetch: { |
|||
type: Boolean, |
|||
default: false |
|||
} |
|||
}, |
|||
mounted () { |
|||
if (this.prefetch && !this.noPrefetch) { |
|||
this.handleId = requestIdleCallback(this.observe, { timeout: 2e3 }) |
|||
} |
|||
}, |
|||
beforeDestroy () { |
|||
cancelIdleCallback(this.handleId) |
|||
|
|||
if (this.__observed) { |
|||
observer.unobserve(this.$el) |
|||
delete this.$el.__prefetch |
|||
} |
|||
}, |
|||
methods: { |
|||
observe () { |
|||
// If no IntersectionObserver, avoid prefetching
|
|||
if (!observer) { |
|||
return |
|||
} |
|||
// Add to observer
|
|||
if (this.shouldPrefetch()) { |
|||
this.$el.__prefetch = this.prefetchLink.bind(this) |
|||
observer.observe(this.$el) |
|||
this.__observed = true |
|||
} |
|||
}, |
|||
shouldPrefetch () { |
|||
return this.getPrefetchComponents().length > 0 |
|||
}, |
|||
canPrefetch () { |
|||
const conn = navigator.connection |
|||
const hasBadConnection = this.$nuxt.isOffline || (conn && ((conn.effectiveType || '').includes('2g') || conn.saveData)) |
|||
|
|||
return !hasBadConnection |
|||
}, |
|||
getPrefetchComponents () { |
|||
const ref = this.$router.resolve(this.to, this.$route, this.append) |
|||
const Components = ref.resolved.matched.map(r => r.components.default) |
|||
|
|||
return Components.filter(Component => typeof Component === 'function' && !Component.options && !Component.__prefetched) |
|||
}, |
|||
prefetchLink () { |
|||
if (!this.canPrefetch()) { |
|||
return |
|||
} |
|||
// Stop observing this link (in case of internet connection changes)
|
|||
observer.unobserve(this.$el) |
|||
const Components = this.getPrefetchComponents() |
|||
|
|||
for (const Component of Components) { |
|||
const componentOrPromise = Component() |
|||
if (componentOrPromise instanceof Promise) { |
|||
componentOrPromise.catch(() => {}) |
|||
} |
|||
Component.__prefetched = true |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,16 @@ |
|||
import Vue from 'vue' |
|||
|
|||
export default { |
|||
name: 'NuxtLink', |
|||
extends: Vue.component('RouterLink'), |
|||
props: { |
|||
prefetch: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
noPrefetch: { |
|||
type: Boolean, |
|||
default: false |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,177 @@ |
|||
<script> |
|||
export default { |
|||
name: 'NuxtLoading', |
|||
data () { |
|||
return { |
|||
percent: 0, |
|||
show: false, |
|||
canSucceed: true, |
|||
reversed: false, |
|||
skipTimerCount: 0, |
|||
rtl: false, |
|||
throttle: 200, |
|||
duration: 5000, |
|||
continuous: false |
|||
} |
|||
}, |
|||
computed: { |
|||
left () { |
|||
if (!this.continuous && !this.rtl) { |
|||
return false |
|||
} |
|||
return this.rtl |
|||
? (this.reversed ? '0px' : 'auto') |
|||
: (!this.reversed ? '0px' : 'auto') |
|||
} |
|||
}, |
|||
beforeDestroy () { |
|||
this.clear() |
|||
}, |
|||
methods: { |
|||
clear () { |
|||
clearInterval(this._timer) |
|||
clearTimeout(this._throttle) |
|||
this._timer = null |
|||
}, |
|||
start () { |
|||
this.clear() |
|||
this.percent = 0 |
|||
this.reversed = false |
|||
this.skipTimerCount = 0 |
|||
this.canSucceed = true |
|||
|
|||
if (this.throttle) { |
|||
this._throttle = setTimeout(() => this.startTimer(), this.throttle) |
|||
} else { |
|||
this.startTimer() |
|||
} |
|||
return this |
|||
}, |
|||
set (num) { |
|||
this.show = true |
|||
this.canSucceed = true |
|||
this.percent = Math.min(100, Math.max(0, Math.floor(num))) |
|||
return this |
|||
}, |
|||
get () { |
|||
return this.percent |
|||
}, |
|||
increase (num) { |
|||
this.percent = Math.min(100, Math.floor(this.percent + num)) |
|||
return this |
|||
}, |
|||
decrease (num) { |
|||
this.percent = Math.max(0, Math.floor(this.percent - num)) |
|||
return this |
|||
}, |
|||
pause () { |
|||
clearInterval(this._timer) |
|||
return this |
|||
}, |
|||
resume () { |
|||
this.startTimer() |
|||
return this |
|||
}, |
|||
finish () { |
|||
this.percent = this.reversed ? 0 : 100 |
|||
this.hide() |
|||
return this |
|||
}, |
|||
hide () { |
|||
this.clear() |
|||
setTimeout(() => { |
|||
this.show = false |
|||
this.$nextTick(() => { |
|||
this.percent = 0 |
|||
this.reversed = false |
|||
}) |
|||
}, 500) |
|||
return this |
|||
}, |
|||
fail (error) { |
|||
this.canSucceed = false |
|||
return this |
|||
}, |
|||
startTimer () { |
|||
if (!this.show) { |
|||
this.show = true |
|||
} |
|||
if (typeof this._cut === 'undefined') { |
|||
this._cut = 10000 / Math.floor(this.duration) |
|||
} |
|||
|
|||
this._timer = setInterval(() => { |
|||
/** |
|||
* When reversing direction skip one timers |
|||
* so 0, 100 are displayed for two iterations |
|||
* also disable css width transitioning |
|||
* which otherwise interferes and shows |
|||
* a jojo effect |
|||
*/ |
|||
if (this.skipTimerCount > 0) { |
|||
this.skipTimerCount-- |
|||
return |
|||
} |
|||
|
|||
if (this.reversed) { |
|||
this.decrease(this._cut) |
|||
} else { |
|||
this.increase(this._cut) |
|||
} |
|||
|
|||
if (this.continuous) { |
|||
if (this.percent >= 100) { |
|||
this.skipTimerCount = 1 |
|||
|
|||
this.reversed = !this.reversed |
|||
} else if (this.percent <= 0) { |
|||
this.skipTimerCount = 1 |
|||
|
|||
this.reversed = !this.reversed |
|||
} |
|||
} |
|||
}, 100) |
|||
} |
|||
}, |
|||
render (h) { |
|||
let el = h(false) |
|||
if (this.show) { |
|||
el = h('div', { |
|||
staticClass: 'nuxt-progress', |
|||
class: { |
|||
'nuxt-progress-notransition': this.skipTimerCount > 0, |
|||
'nuxt-progress-failed': !this.canSucceed |
|||
}, |
|||
style: { |
|||
width: this.percent + '%', |
|||
left: this.left |
|||
} |
|||
}) |
|||
} |
|||
return el |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
.nuxt-progress { |
|||
position: fixed; |
|||
top: 0px; |
|||
left: 0px; |
|||
right: 0px; |
|||
height: 2px; |
|||
width: 0%; |
|||
opacity: 1; |
|||
transition: width 0.1s, opacity 0.4s; |
|||
background-color: #3B8070; |
|||
z-index: 999999; |
|||
} |
|||
|
|||
.nuxt-progress.nuxt-progress-notransition { |
|||
transition: none; |
|||
} |
|||
|
|||
.nuxt-progress-failed { |
|||
background-color: red; |
|||
} |
|||
</style> |
@ -0,0 +1,101 @@ |
|||
import Vue from 'vue' |
|||
import { compile } from '../utils' |
|||
|
|||
import NuxtError from './nuxt-error.vue' |
|||
|
|||
import NuxtChild from './nuxt-child' |
|||
|
|||
export default { |
|||
name: 'Nuxt', |
|||
components: { |
|||
NuxtChild, |
|||
NuxtError |
|||
}, |
|||
props: { |
|||
nuxtChildKey: { |
|||
type: String, |
|||
default: undefined |
|||
}, |
|||
keepAlive: Boolean, |
|||
keepAliveProps: { |
|||
type: Object, |
|||
default: undefined |
|||
}, |
|||
name: { |
|||
type: String, |
|||
default: 'default' |
|||
} |
|||
}, |
|||
errorCaptured (error) { |
|||
// if we receive and error while showing the NuxtError component
|
|||
// capture the error and force an immediate update so we re-render
|
|||
// without the NuxtError component
|
|||
if (this.displayingNuxtError) { |
|||
this.errorFromNuxtError = error |
|||
this.$forceUpdate() |
|||
} |
|||
}, |
|||
computed: { |
|||
routerViewKey () { |
|||
// If nuxtChildKey prop is given or current route has children
|
|||
if (typeof this.nuxtChildKey !== 'undefined' || this.$route.matched.length > 1) { |
|||
return this.nuxtChildKey || compile(this.$route.matched[0].path)(this.$route.params) |
|||
} |
|||
|
|||
const [matchedRoute] = this.$route.matched |
|||
|
|||
if (!matchedRoute) { |
|||
return this.$route.path |
|||
} |
|||
|
|||
const Component = matchedRoute.components.default |
|||
|
|||
if (Component && Component.options) { |
|||
const { options } = Component |
|||
|
|||
if (options.key) { |
|||
return (typeof options.key === 'function' ? options.key(this.$route) : options.key) |
|||
} |
|||
} |
|||
|
|||
const strict = /\/$/.test(matchedRoute.path) |
|||
return strict ? this.$route.path : this.$route.path.replace(/\/$/, '') |
|||
} |
|||
}, |
|||
beforeCreate () { |
|||
Vue.util.defineReactive(this, 'nuxt', this.$root.$options.nuxt) |
|||
}, |
|||
render (h) { |
|||
// if there is no error
|
|||
if (!this.nuxt.err) { |
|||
// Directly return nuxt child
|
|||
return h('NuxtChild', { |
|||
key: this.routerViewKey, |
|||
props: this.$props |
|||
}) |
|||
} |
|||
|
|||
// if an error occured within NuxtError show a simple
|
|||
// error message instead to prevent looping
|
|||
if (this.errorFromNuxtError) { |
|||
this.$nextTick(() => (this.errorFromNuxtError = false)) |
|||
|
|||
return h('div', {}, [ |
|||
h('h2', 'An error occured while showing the error page'), |
|||
h('p', 'Unfortunately an error occured and while showing the error page another error occured'), |
|||
h('p', `Error details: ${this.errorFromNuxtError.toString()}`), |
|||
h('nuxt-link', { props: { to: '/' } }, 'Go back to home') |
|||
]) |
|||
} |
|||
|
|||
// track if we are showing the NuxtError component
|
|||
this.displayingNuxtError = true |
|||
this.$nextTick(() => (this.displayingNuxtError = false)) |
|||
|
|||
return h(NuxtError, { |
|||
props: { |
|||
error: this.nuxt.err |
|||
} |
|||
}) |
|||
} |
|||
} |
@ -0,0 +1 @@ |
|||
// This file is intentionally left empty for noop aliases
|
@ -0,0 +1,185 @@ |
|||
import Vue from 'vue' |
|||
import Meta from 'vue-meta' |
|||
import ClientOnly from 'vue-client-only' |
|||
import NoSsr from 'vue-no-ssr' |
|||
import { createRouter } from './router.js' |
|||
import NuxtChild from './components/nuxt-child.js' |
|||
import NuxtError from './components/nuxt-error.vue' |
|||
import Nuxt from './components/nuxt.js' |
|||
import App from './App.js' |
|||
import { setContext, getLocation, getRouteData, normalizeError } from './utils' |
|||
|
|||
/* Plugins */ |
|||
|
|||
import nuxt_plugin_nuxtswiperplugin_622602ba from 'nuxt_plugin_nuxtswiperplugin_622602ba' // Source: ..\\plugins\\nuxt-swiper-plugin.js (mode: 'client')
|
|||
import nuxt_plugin_elementuiplugin_5181e592 from 'nuxt_plugin_elementuiplugin_5181e592' // Source: ..\\plugins\\element-ui-plugin.js (mode: 'client')
|
|||
|
|||
// Component: <ClientOnly>
|
|||
Vue.component(ClientOnly.name, ClientOnly) |
|||
|
|||
// TODO: Remove in Nuxt 3: <NoSsr>
|
|||
Vue.component(NoSsr.name, { |
|||
...NoSsr, |
|||
render (h, ctx) { |
|||
if (process.client && !NoSsr._warned) { |
|||
NoSsr._warned = true |
|||
|
|||
console.warn('<no-ssr> has been deprecated and will be removed in Nuxt 3, please use <client-only> instead') |
|||
} |
|||
return NoSsr.render(h, ctx) |
|||
} |
|||
}) |
|||
|
|||
// Component: <NuxtChild>
|
|||
Vue.component(NuxtChild.name, NuxtChild) |
|||
Vue.component('NChild', NuxtChild) |
|||
|
|||
// Component NuxtLink is imported in server.js or client.js
|
|||
|
|||
// Component: <Nuxt>
|
|||
Vue.component(Nuxt.name, Nuxt) |
|||
|
|||
Vue.use(Meta, {"keyName":"head","attribute":"data-n-head","ssrAttribute":"data-n-head-ssr","tagIDKeyName":"hid"}) |
|||
|
|||
const defaultTransition = {"name":"page","mode":"out-in","appear":false,"appearClass":"appear","appearActiveClass":"appear-active","appearToClass":"appear-to"} |
|||
|
|||
async function createApp (ssrContext) { |
|||
const router = await createRouter(ssrContext) |
|||
|
|||
// Create Root instance
|
|||
|
|||
// here we inject the router and store to all child components,
|
|||
// making them available everywhere as `this.$router` and `this.$store`.
|
|||
const app = { |
|||
head: {"title":"AI小财神","meta":[{"charset":"utf-8"},{"name":"viewport","content":"width=device-width, initial-scale=1"},{"name":"keywords","content":"AI小财神"},{"name":"description","content":"AI小财神"}],"link":[{"rel":"icon","type":"image\u002Fx-icon","href":"\u002Ffavicon.ico"}],"style":[],"script":[]}, |
|||
|
|||
router, |
|||
nuxt: { |
|||
defaultTransition, |
|||
transitions: [defaultTransition], |
|||
setTransitions (transitions) { |
|||
if (!Array.isArray(transitions)) { |
|||
transitions = [transitions] |
|||
} |
|||
transitions = transitions.map((transition) => { |
|||
if (!transition) { |
|||
transition = defaultTransition |
|||
} else if (typeof transition === 'string') { |
|||
transition = Object.assign({}, defaultTransition, { name: transition }) |
|||
} else { |
|||
transition = Object.assign({}, defaultTransition, transition) |
|||
} |
|||
return transition |
|||
}) |
|||
this.$options.nuxt.transitions = transitions |
|||
return transitions |
|||
}, |
|||
|
|||
err: null, |
|||
dateErr: null, |
|||
error (err) { |
|||
err = err || null |
|||
app.context._errored = Boolean(err) |
|||
err = err ? normalizeError(err) : null |
|||
let nuxt = app.nuxt // to work with @vue/composition-api, see https://github.com/nuxt/nuxt.js/issues/6517#issuecomment-573280207
|
|||
if (this) { |
|||
nuxt = this.nuxt || this.$options.nuxt |
|||
} |
|||
nuxt.dateErr = Date.now() |
|||
nuxt.err = err |
|||
// Used in src/server.js
|
|||
if (ssrContext) { |
|||
ssrContext.nuxt.error = err |
|||
} |
|||
return err |
|||
} |
|||
}, |
|||
...App |
|||
} |
|||
|
|||
const next = ssrContext ? ssrContext.next : location => app.router.push(location) |
|||
// Resolve route
|
|||
let route |
|||
if (ssrContext) { |
|||
route = router.resolve(ssrContext.url).route |
|||
} else { |
|||
const path = getLocation(router.options.base, router.options.mode) |
|||
route = router.resolve(path).route |
|||
} |
|||
|
|||
// Set context to app.context
|
|||
await setContext(app, { |
|||
route, |
|||
next, |
|||
error: app.nuxt.error.bind(app), |
|||
payload: ssrContext ? ssrContext.payload : undefined, |
|||
req: ssrContext ? ssrContext.req : undefined, |
|||
res: ssrContext ? ssrContext.res : undefined, |
|||
beforeRenderFns: ssrContext ? ssrContext.beforeRenderFns : undefined, |
|||
ssrContext |
|||
}) |
|||
|
|||
const inject = function (key, value) { |
|||
if (!key) { |
|||
throw new Error('inject(key, value) has no key provided') |
|||
} |
|||
if (value === undefined) { |
|||
throw new Error(`inject('${key}', value) has no value provided`) |
|||
} |
|||
|
|||
key = '$' + key |
|||
// Add into app
|
|||
app[key] = value |
|||
|
|||
// Check if plugin not already installed
|
|||
const installKey = '__nuxt_' + key + '_installed__' |
|||
if (Vue[installKey]) { |
|||
return |
|||
} |
|||
Vue[installKey] = true |
|||
// Call Vue.use() to install the plugin into vm
|
|||
Vue.use(() => { |
|||
if (!Object.prototype.hasOwnProperty.call(Vue, key)) { |
|||
Object.defineProperty(Vue.prototype, key, { |
|||
get () { |
|||
return this.$root.$options[key] |
|||
} |
|||
}) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
// Plugin execution
|
|||
|
|||
if (process.client && typeof nuxt_plugin_nuxtswiperplugin_622602ba === 'function') { |
|||
await nuxt_plugin_nuxtswiperplugin_622602ba(app.context, inject) |
|||
} |
|||
|
|||
if (process.client && typeof nuxt_plugin_elementuiplugin_5181e592 === 'function') { |
|||
await nuxt_plugin_elementuiplugin_5181e592(app.context, inject) |
|||
} |
|||
|
|||
// If server-side, wait for async component to be resolved first
|
|||
if (process.server && ssrContext && ssrContext.url) { |
|||
await new Promise((resolve, reject) => { |
|||
router.push(ssrContext.url, resolve, () => { |
|||
// navigated to a different route in router guard
|
|||
const unregister = router.afterEach(async (to, from, next) => { |
|||
ssrContext.url = to.fullPath |
|||
app.context.route = await getRouteData(to) |
|||
app.context.params = to.params || {} |
|||
app.context.query = to.query || {} |
|||
unregister() |
|||
resolve() |
|||
}) |
|||
}) |
|||
}) |
|||
} |
|||
|
|||
return { |
|||
app, |
|||
router |
|||
} |
|||
} |
|||
|
|||
export { createApp, NuxtError } |
@ -0,0 +1,109 @@ |
|||
<style> |
|||
#nuxt-loading { |
|||
visibility: hidden; |
|||
opacity: 0; |
|||
position: absolute; |
|||
left: 0; |
|||
right: 0; |
|||
top: 0; |
|||
bottom: 0; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
flex-direction: column; |
|||
animation: nuxtLoadingIn 10s ease; |
|||
-webkit-animation: nuxtLoadingIn 10s ease; |
|||
animation-fill-mode: forwards; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
@keyframes nuxtLoadingIn { |
|||
0% { |
|||
visibility: hidden; |
|||
opacity: 0; |
|||
} |
|||
20% { |
|||
visibility: visible; |
|||
opacity: 0; |
|||
} |
|||
100% { |
|||
visibility: visible; |
|||
opacity: 1; |
|||
} |
|||
} |
|||
|
|||
@-webkit-keyframes nuxtLoadingIn { |
|||
0% { |
|||
visibility: hidden; |
|||
opacity: 0; |
|||
} |
|||
20% { |
|||
visibility: visible; |
|||
opacity: 0; |
|||
} |
|||
100% { |
|||
visibility: visible; |
|||
opacity: 1; |
|||
} |
|||
} |
|||
|
|||
#nuxt-loading>div, |
|||
#nuxt-loading>div:after { |
|||
border-radius: 50%; |
|||
width: 5rem; |
|||
height: 5rem; |
|||
} |
|||
|
|||
#nuxt-loading>div { |
|||
font-size: 10px; |
|||
position: relative; |
|||
text-indent: -9999em; |
|||
border: .5rem solid #F5F5F5; |
|||
border-left: .5rem solid #3B8070; |
|||
-webkit-transform: translateZ(0); |
|||
-ms-transform: translateZ(0); |
|||
transform: translateZ(0); |
|||
-webkit-animation: nuxtLoading 1.1s infinite linear; |
|||
animation: nuxtLoading 1.1s infinite linear; |
|||
} |
|||
|
|||
#nuxt-loading.error>div { |
|||
border-left: .5rem solid #ff4500; |
|||
animation-duration: 5s; |
|||
} |
|||
|
|||
@-webkit-keyframes nuxtLoading { |
|||
0% { |
|||
-webkit-transform: rotate(0deg); |
|||
transform: rotate(0deg); |
|||
} |
|||
100% { |
|||
-webkit-transform: rotate(360deg); |
|||
transform: rotate(360deg); |
|||
} |
|||
} |
|||
|
|||
@keyframes nuxtLoading { |
|||
0% { |
|||
-webkit-transform: rotate(0deg); |
|||
transform: rotate(0deg); |
|||
} |
|||
100% { |
|||
-webkit-transform: rotate(360deg); |
|||
transform: rotate(360deg); |
|||
} |
|||
} |
|||
</style> |
|||
|
|||
<script> |
|||
window.addEventListener('error', function () { |
|||
var e = document.getElementById('nuxt-loading'); |
|||
if (e) { |
|||
e.className += ' error'; |
|||
} |
|||
}); |
|||
</script> |
|||
|
|||
<div id="nuxt-loading" aria-live="polite" role="status"><div>Loading...</div></div> |
|||
|
|||
<!-- https://projects.lukehaas.me/css-loaders --> |
@ -0,0 +1,3 @@ |
|||
const middleware = {} |
|||
|
|||
export default middleware |
@ -0,0 +1,87 @@ |
|||
import Vue from 'vue' |
|||
import { hasFetch, normalizeError, addLifecycleHook } from '../utils' |
|||
|
|||
const isSsrHydration = (vm) => vm.$vnode && vm.$vnode.elm && vm.$vnode.elm.dataset && vm.$vnode.elm.dataset.fetchKey |
|||
const nuxtState = window.__NUXT__ |
|||
|
|||
export default { |
|||
beforeCreate () { |
|||
if (!hasFetch(this)) { |
|||
return |
|||
} |
|||
|
|||
this._fetchDelay = typeof this.$options.fetchDelay === 'number' ? this.$options.fetchDelay : 200 |
|||
|
|||
Vue.util.defineReactive(this, '$fetchState', { |
|||
pending: false, |
|||
error: null, |
|||
timestamp: Date.now() |
|||
}) |
|||
|
|||
this.$fetch = $fetch.bind(this) |
|||
addLifecycleHook(this, 'created', created) |
|||
addLifecycleHook(this, 'beforeMount', beforeMount) |
|||
} |
|||
} |
|||
|
|||
function beforeMount() { |
|||
if (!this._hydrated) { |
|||
return this.$fetch() |
|||
} |
|||
} |
|||
|
|||
function created() { |
|||
if (!isSsrHydration(this)) { |
|||
return |
|||
} |
|||
|
|||
// Hydrate component
|
|||
this._hydrated = true |
|||
this._fetchKey = +this.$vnode.elm.dataset.fetchKey |
|||
const data = nuxtState.fetch[this._fetchKey] |
|||
|
|||
// If fetch error
|
|||
if (data && data._error) { |
|||
this.$fetchState.error = data._error |
|||
return |
|||
} |
|||
|
|||
// Merge data
|
|||
for (const key in data) { |
|||
Vue.set(this.$data, key, data[key]) |
|||
} |
|||
} |
|||
|
|||
function $fetch() { |
|||
if (!this._fetchPromise) { |
|||
this._fetchPromise = $_fetch.call(this) |
|||
.then(() => { delete this._fetchPromise }) |
|||
} |
|||
return this._fetchPromise |
|||
} |
|||
|
|||
async function $_fetch() { |
|||
this.$nuxt.nbFetching++ |
|||
this.$fetchState.pending = true |
|||
this.$fetchState.error = null |
|||
this._hydrated = false |
|||
let error = null |
|||
const startTime = Date.now() |
|||
|
|||
try { |
|||
await this.$options.fetch.call(this) |
|||
} catch (err) { |
|||
error = normalizeError(err) |
|||
} |
|||
|
|||
const delayLeft = this._fetchDelay - (Date.now() - startTime) |
|||
if (delayLeft > 0) { |
|||
await new Promise(resolve => setTimeout(resolve, delayLeft)) |
|||
} |
|||
|
|||
this.$fetchState.error = error |
|||
this.$fetchState.pending = false |
|||
this.$fetchState.timestamp = Date.now() |
|||
|
|||
this.$nextTick(() => this.$nuxt.nbFetching--) |
|||
} |
@ -0,0 +1,48 @@ |
|||
import Vue from 'vue' |
|||
import { hasFetch, normalizeError, addLifecycleHook } from '../utils' |
|||
|
|||
async function serverPrefetch() { |
|||
if (!this._fetchOnServer) { |
|||
return |
|||
} |
|||
|
|||
// Call and await on $fetch
|
|||
try { |
|||
await this.$options.fetch.call(this) |
|||
} catch (err) { |
|||
this.$fetchState.error = normalizeError(err) |
|||
} |
|||
this.$fetchState.pending = false |
|||
|
|||
// Define an ssrKey for hydration
|
|||
this._fetchKey = this.$ssrContext.nuxt.fetch.length |
|||
|
|||
// Add data-fetch-key on parent element of Component
|
|||
const attrs = this.$vnode.data.attrs = this.$vnode.data.attrs || {} |
|||
attrs['data-fetch-key'] = this._fetchKey |
|||
|
|||
// Add to ssrContext for window.__NUXT__.fetch
|
|||
this.$ssrContext.nuxt.fetch.push(this.$fetchState.error ? { _error: this.$fetchState.error } : this._data) |
|||
} |
|||
|
|||
export default { |
|||
beforeCreate() { |
|||
if (!hasFetch(this)) { |
|||
return |
|||
} |
|||
|
|||
if (typeof this.$options.fetchOnServer === 'function') { |
|||
this._fetchOnServer = this.$options.fetchOnServer.call(this) !== false |
|||
} else { |
|||
this._fetchOnServer = this.$options.fetchOnServer !== false |
|||
} |
|||
|
|||
Vue.util.defineReactive(this, '$fetchState', { |
|||
pending: true, |
|||
error: null, |
|||
timestamp: Date.now() |
|||
}) |
|||
|
|||
addLifecycleHook(this, 'serverPrefetch', serverPrefetch) |
|||
} |
|||
} |
@ -0,0 +1,60 @@ |
|||
import Vue from 'vue' |
|||
import Router from 'vue-router' |
|||
import { interopDefault } from './utils' |
|||
import scrollBehavior from './router.scrollBehavior.js' |
|||
|
|||
const _578f272f = () => interopDefault(import('..\\pages\\club\\index.vue' /* webpackChunkName: "pages_club_index" */)) |
|||
const _616e5366 = () => interopDefault(import('..\\pages\\home\\index.vue' /* webpackChunkName: "pages_home_index" */)) |
|||
const _0612e07c = () => interopDefault(import('..\\pages\\login.vue' /* webpackChunkName: "pages_login" */)) |
|||
const _e5ead240 = () => interopDefault(import('..\\pages\\register.vue' /* webpackChunkName: "pages_register" */)) |
|||
const _0f3a46dd = () => interopDefault(import('..\\pages\\ucenter.vue' /* webpackChunkName: "pages_ucenter" */)) |
|||
const _3c6b3d65 = () => interopDefault(import('..\\pages\\index.vue' /* webpackChunkName: "pages_index" */)) |
|||
|
|||
// TODO: remove in Nuxt 3
|
|||
const emptyFn = () => {} |
|||
const originalPush = Router.prototype.push |
|||
Router.prototype.push = function push (location, onComplete = emptyFn, onAbort) { |
|||
return originalPush.call(this, location, onComplete, onAbort) |
|||
} |
|||
|
|||
Vue.use(Router) |
|||
|
|||
export const routerOptions = { |
|||
mode: 'history', |
|||
base: decodeURI('/'), |
|||
linkActiveClass: 'nuxt-link-active', |
|||
linkExactActiveClass: 'nuxt-link-exact-active', |
|||
scrollBehavior, |
|||
|
|||
routes: [{ |
|||
path: "/club", |
|||
component: _578f272f, |
|||
name: "club" |
|||
}, { |
|||
path: "/home", |
|||
component: _616e5366, |
|||
name: "home" |
|||
}, { |
|||
path: "/login", |
|||
component: _0612e07c, |
|||
name: "login" |
|||
}, { |
|||
path: "/register", |
|||
component: _e5ead240, |
|||
name: "register" |
|||
}, { |
|||
path: "/ucenter", |
|||
component: _0f3a46dd, |
|||
name: "ucenter" |
|||
}, { |
|||
path: "/", |
|||
component: _3c6b3d65, |
|||
name: "index" |
|||
}], |
|||
|
|||
fallback: false |
|||
} |
|||
|
|||
export function createRouter () { |
|||
return new Router(routerOptions) |
|||
} |
@ -0,0 +1,74 @@ |
|||
import { getMatchedComponents } from './utils' |
|||
|
|||
if (process.client) { |
|||
if ('scrollRestoration' in window.history) { |
|||
window.history.scrollRestoration = 'manual' |
|||
|
|||
// reset scrollRestoration to auto when leaving page, allowing page reload
|
|||
// and back-navigation from other pages to use the browser to restore the
|
|||
// scrolling position.
|
|||
window.addEventListener('beforeunload', () => { |
|||
window.history.scrollRestoration = 'auto' |
|||
}) |
|||
|
|||
// Setting scrollRestoration to manual again when returning to this page.
|
|||
window.addEventListener('load', () => { |
|||
window.history.scrollRestoration = 'manual' |
|||
}) |
|||
} |
|||
} |
|||
|
|||
export default function (to, from, savedPosition) { |
|||
// if the returned position is falsy or an empty object,
|
|||
// will retain current scroll position.
|
|||
let position = false |
|||
|
|||
// if no children detected and scrollToTop is not explicitly disabled
|
|||
const Pages = getMatchedComponents(to) |
|||
if ( |
|||
Pages.length < 2 && |
|||
Pages.every(Page => Page.options.scrollToTop !== false) |
|||
) { |
|||
// scroll to the top of the page
|
|||
position = { x: 0, y: 0 } |
|||
} else if (Pages.some(Page => Page.options.scrollToTop)) { |
|||
// if one of the children has scrollToTop option set to true
|
|||
position = { x: 0, y: 0 } |
|||
} |
|||
|
|||
// savedPosition is only available for popstate navigations (back button)
|
|||
if (savedPosition) { |
|||
position = savedPosition |
|||
} |
|||
|
|||
const nuxt = window.$nuxt |
|||
|
|||
// triggerScroll is only fired when a new component is loaded
|
|||
if (to.path === from.path && to.hash !== from.hash) { |
|||
nuxt.$nextTick(() => nuxt.$emit('triggerScroll')) |
|||
} |
|||
|
|||
return new Promise((resolve) => { |
|||
// wait for the out transition to complete (if necessary)
|
|||
nuxt.$once('triggerScroll', () => { |
|||
// coords will be used if no selector is provided,
|
|||
// or if the selector didn't match any element.
|
|||
if (to.hash) { |
|||
let hash = to.hash |
|||
// CSS.escape() is not supported with IE and Edge.
|
|||
if (typeof window.CSS !== 'undefined' && typeof window.CSS.escape !== 'undefined') { |
|||
hash = '#' + window.CSS.escape(hash.substr(1)) |
|||
} |
|||
try { |
|||
if (document.querySelector(hash)) { |
|||
// scroll to anchor by returning the selector
|
|||
position = { selector: hash } |
|||
} |
|||
} catch (e) { |
|||
console.warn('Failed to save scroll position. Please add CSS.escape() polyfill (https://github.com/mathiasbynens/CSS.escape).') |
|||
} |
|||
} |
|||
resolve(position) |
|||
}) |
|||
}) |
|||
} |
@ -0,0 +1,265 @@ |
|||
import { stringify } from 'querystring' |
|||
import Vue from 'vue' |
|||
import fetch from 'node-fetch' |
|||
import middleware from './middleware.js' |
|||
import { |
|||
applyAsyncData, |
|||
middlewareSeries, |
|||
sanitizeComponent, |
|||
getMatchedComponents, |
|||
promisify |
|||
} from './utils.js' |
|||
import fetchMixin from './mixins/fetch.server' |
|||
import { createApp, NuxtError } from './index.js' |
|||
import NuxtLink from './components/nuxt-link.server.js' // should be included after ./index.js
|
|||
|
|||
// Update serverPrefetch strategy
|
|||
Vue.config.optionMergeStrategies.serverPrefetch = Vue.config.optionMergeStrategies.created |
|||
|
|||
// Fetch mixin
|
|||
if (!Vue.__nuxt__fetch__mixin__) { |
|||
Vue.mixin(fetchMixin) |
|||
Vue.__nuxt__fetch__mixin__ = true |
|||
} |
|||
|
|||
// Component: <NuxtLink>
|
|||
Vue.component(NuxtLink.name, NuxtLink) |
|||
Vue.component('NLink', NuxtLink) |
|||
|
|||
if (!global.fetch) { global.fetch = fetch } |
|||
|
|||
const noopApp = () => new Vue({ render: h => h('div') }) |
|||
|
|||
function urlJoin () { |
|||
return Array.prototype.slice.call(arguments).join('/').replace(/\/+/g, '/') |
|||
} |
|||
|
|||
const createNext = ssrContext => (opts) => { |
|||
ssrContext.redirected = opts |
|||
// If nuxt generate
|
|||
if (!ssrContext.res) { |
|||
ssrContext.nuxt.serverRendered = false |
|||
return |
|||
} |
|||
opts.query = stringify(opts.query) |
|||
opts.path = opts.path + (opts.query ? '?' + opts.query : '') |
|||
const routerBase = '/' |
|||
if (!opts.path.startsWith('http') && (routerBase !== '/' && !opts.path.startsWith(routerBase))) { |
|||
opts.path = urlJoin(routerBase, opts.path) |
|||
} |
|||
// Avoid loop redirect
|
|||
if (opts.path === ssrContext.url) { |
|||
ssrContext.redirected = false |
|||
return |
|||
} |
|||
ssrContext.res.writeHead(opts.status, { |
|||
Location: opts.path |
|||
}) |
|||
ssrContext.res.end() |
|||
} |
|||
|
|||
// This exported function will be called by `bundleRenderer`.
|
|||
// This is where we perform data-prefetching to determine the
|
|||
// state of our application before actually rendering it.
|
|||
// Since data fetching is async, this function is expected to
|
|||
// return a Promise that resolves to the app instance.
|
|||
export default async (ssrContext) => { |
|||
// Create ssrContext.next for simulate next() of beforeEach() when wanted to redirect
|
|||
ssrContext.redirected = false |
|||
ssrContext.next = createNext(ssrContext) |
|||
// Used for beforeNuxtRender({ Components, nuxtState })
|
|||
ssrContext.beforeRenderFns = [] |
|||
// Nuxt object (window{{globals.context}}, defaults to window.__NUXT__)
|
|||
ssrContext.nuxt = { layout: 'default', data: [], fetch: [], error: null, serverRendered: true, routePath: '' } |
|||
// Create the app definition and the instance (created for each request)
|
|||
const { app, router } = await createApp(ssrContext) |
|||
const _app = new Vue(app) |
|||
// Add ssr route path to nuxt context so we can account for page navigation between ssr and csr
|
|||
ssrContext.nuxt.routePath = app.context.route.path |
|||
|
|||
// Add meta infos (used in renderer.js)
|
|||
ssrContext.meta = _app.$meta() |
|||
|
|||
// Keep asyncData for each matched component in ssrContext (used in app/utils.js via this.$ssrContext)
|
|||
ssrContext.asyncData = {} |
|||
|
|||
const beforeRender = async () => { |
|||
// Call beforeNuxtRender() methods
|
|||
await Promise.all(ssrContext.beforeRenderFns.map(fn => promisify(fn, { Components, nuxtState: ssrContext.nuxt }))) |
|||
} |
|||
|
|||
const renderErrorPage = async () => { |
|||
// Load layout for error page
|
|||
const layout = (NuxtError.options || NuxtError).layout |
|||
const errLayout = typeof layout === 'function' ? layout.call(NuxtError, app.context) : layout |
|||
ssrContext.nuxt.layout = errLayout || 'default' |
|||
await _app.loadLayout(errLayout) |
|||
_app.setLayout(errLayout) |
|||
|
|||
await beforeRender() |
|||
return _app |
|||
} |
|||
const render404Page = () => { |
|||
app.context.error({ statusCode: 404, path: ssrContext.url, message: 'This page could not be found' }) |
|||
return renderErrorPage() |
|||
} |
|||
|
|||
const s = Date.now() |
|||
|
|||
// Components are already resolved by setContext -> getRouteData (app/utils.js)
|
|||
const Components = getMatchedComponents(router.match(ssrContext.url)) |
|||
|
|||
/* |
|||
** Call global middleware (nuxt.config.js) |
|||
*/ |
|||
let midd = [] |
|||
midd = midd.map((name) => { |
|||
if (typeof name === 'function') { |
|||
return name |
|||
} |
|||
if (typeof middleware[name] !== 'function') { |
|||
app.context.error({ statusCode: 500, message: 'Unknown middleware ' + name }) |
|||
} |
|||
return middleware[name] |
|||
}) |
|||
await middlewareSeries(midd, app.context) |
|||
// ...If there is a redirect or an error, stop the process
|
|||
if (ssrContext.redirected) { |
|||
return noopApp() |
|||
} |
|||
if (ssrContext.nuxt.error) { |
|||
return renderErrorPage() |
|||
} |
|||
|
|||
/* |
|||
** Set layout |
|||
*/ |
|||
let layout = Components.length ? Components[0].options.layout : NuxtError.layout |
|||
if (typeof layout === 'function') { |
|||
layout = layout(app.context) |
|||
} |
|||
await _app.loadLayout(layout) |
|||
if (ssrContext.nuxt.error) { |
|||
return renderErrorPage() |
|||
} |
|||
layout = _app.setLayout(layout) |
|||
ssrContext.nuxt.layout = _app.layoutName |
|||
|
|||
/* |
|||
** Call middleware (layout + pages) |
|||
*/ |
|||
midd = [] |
|||
|
|||
layout = sanitizeComponent(layout) |
|||
if (layout.options.middleware) { |
|||
midd = midd.concat(layout.options.middleware) |
|||
} |
|||
|
|||
Components.forEach((Component) => { |
|||
if (Component.options.middleware) { |
|||
midd = midd.concat(Component.options.middleware) |
|||
} |
|||
}) |
|||
midd = midd.map((name) => { |
|||
if (typeof name === 'function') { |
|||
return name |
|||
} |
|||
if (typeof middleware[name] !== 'function') { |
|||
app.context.error({ statusCode: 500, message: 'Unknown middleware ' + name }) |
|||
} |
|||
return middleware[name] |
|||
}) |
|||
await middlewareSeries(midd, app.context) |
|||
// ...If there is a redirect or an error, stop the process
|
|||
if (ssrContext.redirected) { |
|||
return noopApp() |
|||
} |
|||
if (ssrContext.nuxt.error) { |
|||
return renderErrorPage() |
|||
} |
|||
|
|||
/* |
|||
** Call .validate() |
|||
*/ |
|||
let isValid = true |
|||
try { |
|||
for (const Component of Components) { |
|||
if (typeof Component.options.validate !== 'function') { |
|||
continue |
|||
} |
|||
|
|||
isValid = await Component.options.validate(app.context) |
|||
|
|||
if (!isValid) { |
|||
break |
|||
} |
|||
} |
|||
} catch (validationError) { |
|||
// ...If .validate() threw an error
|
|||
app.context.error({ |
|||
statusCode: validationError.statusCode || '500', |
|||
message: validationError.message |
|||
}) |
|||
return renderErrorPage() |
|||
} |
|||
|
|||
// ...If .validate() returned false
|
|||
if (!isValid) { |
|||
// Don't server-render the page in generate mode
|
|||
if (ssrContext._generate) { |
|||
ssrContext.nuxt.serverRendered = false |
|||
} |
|||
// Render a 404 error page
|
|||
return render404Page() |
|||
} |
|||
|
|||
// If no Components found, returns 404
|
|||
if (!Components.length) { |
|||
return render404Page() |
|||
} |
|||
|
|||
// Call asyncData & fetch hooks on components matched by the route.
|
|||
const asyncDatas = await Promise.all(Components.map((Component) => { |
|||
const promises = [] |
|||
|
|||
// Call asyncData(context)
|
|||
if (Component.options.asyncData && typeof Component.options.asyncData === 'function') { |
|||
const promise = promisify(Component.options.asyncData, app.context) |
|||
promise.then((asyncDataResult) => { |
|||
ssrContext.asyncData[Component.cid] = asyncDataResult |
|||
applyAsyncData(Component) |
|||
return asyncDataResult |
|||
}) |
|||
promises.push(promise) |
|||
} else { |
|||
promises.push(null) |
|||
} |
|||
|
|||
// Call fetch(context)
|
|||
if (Component.options.fetch && Component.options.fetch.length) { |
|||
promises.push(Component.options.fetch(app.context)) |
|||
} else { |
|||
promises.push(null) |
|||
} |
|||
|
|||
return Promise.all(promises) |
|||
})) |
|||
|
|||
if (process.env.DEBUG && asyncDatas.length) console.debug('Data fetching ' + ssrContext.url + ': ' + (Date.now() - s) + 'ms') |
|||
|
|||
// datas are the first row of each
|
|||
ssrContext.nuxt.data = asyncDatas.map(r => r[0] || {}) |
|||
|
|||
// ...If there is a redirect or an error, stop the process
|
|||
if (ssrContext.redirected) { |
|||
return noopApp() |
|||
} |
|||
if (ssrContext.nuxt.error) { |
|||
return renderErrorPage() |
|||
} |
|||
|
|||
// Call beforeNuxtRender methods & add store state
|
|||
await beforeRender() |
|||
|
|||
return _app |
|||
} |
@ -0,0 +1,616 @@ |
|||
import Vue from 'vue' |
|||
|
|||
// window.{{globals.loadedCallback}} hook
|
|||
// Useful for jsdom testing or plugins (https://github.com/tmpvar/jsdom#dealing-with-asynchronous-script-loading)
|
|||
if (process.client) { |
|||
window.onNuxtReadyCbs = [] |
|||
window.onNuxtReady = (cb) => { |
|||
window.onNuxtReadyCbs.push(cb) |
|||
} |
|||
} |
|||
|
|||
export function empty () {} |
|||
|
|||
export function globalHandleError (error) { |
|||
if (Vue.config.errorHandler) { |
|||
Vue.config.errorHandler(error) |
|||
} |
|||
} |
|||
|
|||
export function interopDefault (promise) { |
|||
return promise.then(m => m.default || m) |
|||
} |
|||
|
|||
export function hasFetch(vm) { |
|||
return vm.$options && typeof vm.$options.fetch === 'function' && !vm.$options.fetch.length |
|||
} |
|||
export function getChildrenComponentInstancesUsingFetch(vm, instances = []) { |
|||
const children = vm.$children || [] |
|||
for (const child of children) { |
|||
if (child.$fetch) { |
|||
instances.push(child) |
|||
continue; // Don't get the children since it will reload the template
|
|||
} |
|||
if (child.$children) { |
|||
getChildrenComponentInstancesUsingFetch(child, instances) |
|||
} |
|||
} |
|||
return instances |
|||
} |
|||
|
|||
export function applyAsyncData (Component, asyncData) { |
|||
if ( |
|||
// For SSR, we once all this function without second param to just apply asyncData
|
|||
// Prevent doing this for each SSR request
|
|||
!asyncData && Component.options.__hasNuxtData |
|||
) { |
|||
return |
|||
} |
|||
|
|||
const ComponentData = Component.options._originDataFn || Component.options.data || function () { return {} } |
|||
Component.options._originDataFn = ComponentData |
|||
|
|||
Component.options.data = function () { |
|||
const data = ComponentData.call(this, this) |
|||
if (this.$ssrContext) { |
|||
asyncData = this.$ssrContext.asyncData[Component.cid] |
|||
} |
|||
return { ...data, ...asyncData } |
|||
} |
|||
|
|||
Component.options.__hasNuxtData = true |
|||
|
|||
if (Component._Ctor && Component._Ctor.options) { |
|||
Component._Ctor.options.data = Component.options.data |
|||
} |
|||
} |
|||
|
|||
export function sanitizeComponent (Component) { |
|||
// If Component already sanitized
|
|||
if (Component.options && Component._Ctor === Component) { |
|||
return Component |
|||
} |
|||
if (!Component.options) { |
|||
Component = Vue.extend(Component) // fix issue #6
|
|||
Component._Ctor = Component |
|||
} else { |
|||
Component._Ctor = Component |
|||
Component.extendOptions = Component.options |
|||
} |
|||
// If no component name defined, set file path as name, (also fixes #5703)
|
|||
if (!Component.options.name && Component.options.__file) { |
|||
Component.options.name = Component.options.__file |
|||
} |
|||
return Component |
|||
} |
|||
|
|||
export function getMatchedComponents (route, matches = false, prop = 'components') { |
|||
return Array.prototype.concat.apply([], route.matched.map((m, index) => { |
|||
return Object.keys(m[prop]).map((key) => { |
|||
matches && matches.push(index) |
|||
return m[prop][key] |
|||
}) |
|||
})) |
|||
} |
|||
|
|||
export function getMatchedComponentsInstances (route, matches = false) { |
|||
return getMatchedComponents(route, matches, 'instances') |
|||
} |
|||
|
|||
export function flatMapComponents (route, fn) { |
|||
return Array.prototype.concat.apply([], route.matched.map((m, index) => { |
|||
return Object.keys(m.components).reduce((promises, key) => { |
|||
if (m.components[key]) { |
|||
promises.push(fn(m.components[key], m.instances[key], m, key, index)) |
|||
} else { |
|||
delete m.components[key] |
|||
} |
|||
return promises |
|||
}, []) |
|||
})) |
|||
} |
|||
|
|||
export function resolveRouteComponents (route, fn) { |
|||
return Promise.all( |
|||
flatMapComponents(route, async (Component, instance, match, key) => { |
|||
// If component is a function, resolve it
|
|||
if (typeof Component === 'function' && !Component.options) { |
|||
Component = await Component() |
|||
} |
|||
match.components[key] = Component = sanitizeComponent(Component) |
|||
return typeof fn === 'function' ? fn(Component, instance, match, key) : Component |
|||
}) |
|||
) |
|||
} |
|||
|
|||
export async function getRouteData (route) { |
|||
if (!route) { |
|||
return |
|||
} |
|||
// Make sure the components are resolved (code-splitting)
|
|||
await resolveRouteComponents(route) |
|||
// Send back a copy of route with meta based on Component definition
|
|||
return { |
|||
...route, |
|||
meta: getMatchedComponents(route).map((Component, index) => { |
|||
return { ...Component.options.meta, ...(route.matched[index] || {}).meta } |
|||
}) |
|||
} |
|||
} |
|||
|
|||
export async function setContext (app, context) { |
|||
// If context not defined, create it
|
|||
if (!app.context) { |
|||
app.context = { |
|||
isStatic: process.static, |
|||
isDev: true, |
|||
isHMR: false, |
|||
app, |
|||
|
|||
payload: context.payload, |
|||
error: context.error, |
|||
base: '/', |
|||
env: {} |
|||
} |
|||
// Only set once
|
|||
if (context.req) { |
|||
app.context.req = context.req |
|||
} |
|||
if (context.res) { |
|||
app.context.res = context.res |
|||
} |
|||
if (context.ssrContext) { |
|||
app.context.ssrContext = context.ssrContext |
|||
} |
|||
app.context.redirect = (status, path, query) => { |
|||
if (!status) { |
|||
return |
|||
} |
|||
app.context._redirected = true |
|||
// if only 1 or 2 arguments: redirect('/') or redirect('/', { foo: 'bar' })
|
|||
let pathType = typeof path |
|||
if (typeof status !== 'number' && (pathType === 'undefined' || pathType === 'object')) { |
|||
query = path || {} |
|||
path = status |
|||
pathType = typeof path |
|||
status = 302 |
|||
} |
|||
if (pathType === 'object') { |
|||
path = app.router.resolve(path).route.fullPath |
|||
} |
|||
// "/absolute/route", "./relative/route" or "../relative/route"
|
|||
if (/(^[.]{1,2}\/)|(^\/(?!\/))/.test(path)) { |
|||
app.context.next({ |
|||
path, |
|||
query, |
|||
status |
|||
}) |
|||
} else { |
|||
path = formatUrl(path, query) |
|||
if (process.server) { |
|||
app.context.next({ |
|||
path, |
|||
status |
|||
}) |
|||
} |
|||
if (process.client) { |
|||
// https://developer.mozilla.org/en-US/docs/Web/API/Location/replace
|
|||
window.location.replace(path) |
|||
|
|||
// Throw a redirect error
|
|||
throw new Error('ERR_REDIRECT') |
|||
} |
|||
} |
|||
} |
|||
if (process.server) { |
|||
app.context.beforeNuxtRender = fn => context.beforeRenderFns.push(fn) |
|||
} |
|||
if (process.client) { |
|||
app.context.nuxtState = window.__NUXT__ |
|||
} |
|||
} |
|||
|
|||
// Dynamic keys
|
|||
const [currentRouteData, fromRouteData] = await Promise.all([ |
|||
getRouteData(context.route), |
|||
getRouteData(context.from) |
|||
]) |
|||
|
|||
if (context.route) { |
|||
app.context.route = currentRouteData |
|||
} |
|||
|
|||
if (context.from) { |
|||
app.context.from = fromRouteData |
|||
} |
|||
|
|||
app.context.next = context.next |
|||
app.context._redirected = false |
|||
app.context._errored = false |
|||
app.context.isHMR = Boolean(context.isHMR) |
|||
app.context.params = app.context.route.params || {} |
|||
app.context.query = app.context.route.query || {} |
|||
} |
|||
|
|||
export function middlewareSeries (promises, appContext) { |
|||
if (!promises.length || appContext._redirected || appContext._errored) { |
|||
return Promise.resolve() |
|||
} |
|||
return promisify(promises[0], appContext) |
|||
.then(() => { |
|||
return middlewareSeries(promises.slice(1), appContext) |
|||
}) |
|||
} |
|||
|
|||
export function promisify (fn, context) { |
|||
let promise |
|||
if (fn.length === 2) { |
|||
console.warn('Callback-based asyncData, fetch or middleware calls are deprecated. ' + |
|||
'Please switch to promises or async/await syntax') |
|||
|
|||
// fn(context, callback)
|
|||
promise = new Promise((resolve) => { |
|||
fn(context, function (err, data) { |
|||
if (err) { |
|||
context.error(err) |
|||
} |
|||
data = data || {} |
|||
resolve(data) |
|||
}) |
|||
}) |
|||
} else { |
|||
promise = fn(context) |
|||
} |
|||
|
|||
if (promise && promise instanceof Promise && typeof promise.then === 'function') { |
|||
return promise |
|||
} |
|||
return Promise.resolve(promise) |
|||
} |
|||
|
|||
// Imported from vue-router
|
|||
export function getLocation (base, mode) { |
|||
let path = decodeURI(window.location.pathname) |
|||
if (mode === 'hash') { |
|||
return window.location.hash.replace(/^#\//, '') |
|||
} |
|||
if (base && path.indexOf(base) === 0) { |
|||
path = path.slice(base.length) |
|||
} |
|||
return (path || '/') + window.location.search + window.location.hash |
|||
} |
|||
|
|||
// Imported from path-to-regexp
|
|||
|
|||
/** |
|||
* Compile a string to a template function for the path. |
|||
* |
|||
* @param {string} str |
|||
* @param {Object=} options |
|||
* @return {!function(Object=, Object=)} |
|||
*/ |
|||
export function compile (str, options) { |
|||
return tokensToFunction(parse(str, options), options) |
|||
} |
|||
|
|||
export function getQueryDiff (toQuery, fromQuery) { |
|||
const diff = {} |
|||
const queries = { ...toQuery, ...fromQuery } |
|||
for (const k in queries) { |
|||
if (String(toQuery[k]) !== String(fromQuery[k])) { |
|||
diff[k] = true |
|||
} |
|||
} |
|||
return diff |
|||
} |
|||
|
|||
export function normalizeError (err) { |
|||
let message |
|||
if (!(err.message || typeof err === 'string')) { |
|||
try { |
|||
message = JSON.stringify(err, null, 2) |
|||
} catch (e) { |
|||
message = `[${err.constructor.name}]` |
|||
} |
|||
} else { |
|||
message = err.message || err |
|||
} |
|||
return { |
|||
...err, |
|||
message, |
|||
statusCode: (err.statusCode || err.status || (err.response && err.response.status) || 500) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* The main path matching regexp utility. |
|||
* |
|||
* @type {RegExp} |
|||
*/ |
|||
const PATH_REGEXP = new RegExp([ |
|||
// Match escaped characters that would otherwise appear in future matches.
|
|||
// This allows the user to escape special characters that won't transform.
|
|||
'(\\\\.)', |
|||
// Match Express-style parameters and un-named parameters with a prefix
|
|||
// and optional suffixes. Matches appear as:
|
|||
//
|
|||
// "/:test(\\d+)?" => ["/", "test", "\d+", undefined, "?", undefined]
|
|||
// "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined, undefined]
|
|||
// "/*" => ["/", undefined, undefined, undefined, undefined, "*"]
|
|||
'([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))' |
|||
].join('|'), 'g') |
|||
|
|||
/** |
|||
* Parse a string for the raw tokens. |
|||
* |
|||
* @param {string} str |
|||
* @param {Object=} options |
|||
* @return {!Array} |
|||
*/ |
|||
function parse (str, options) { |
|||
const tokens = [] |
|||
let key = 0 |
|||
let index = 0 |
|||
let path = '' |
|||
const defaultDelimiter = (options && options.delimiter) || '/' |
|||
let res |
|||
|
|||
while ((res = PATH_REGEXP.exec(str)) != null) { |
|||
const m = res[0] |
|||
const escaped = res[1] |
|||
const offset = res.index |
|||
path += str.slice(index, offset) |
|||
index = offset + m.length |
|||
|
|||
// Ignore already escaped sequences.
|
|||
if (escaped) { |
|||
path += escaped[1] |
|||
continue |
|||
} |
|||
|
|||
const next = str[index] |
|||
const prefix = res[2] |
|||
const name = res[3] |
|||
const capture = res[4] |
|||
const group = res[5] |
|||
const modifier = res[6] |
|||
const asterisk = res[7] |
|||
|
|||
// Push the current path onto the tokens.
|
|||
if (path) { |
|||
tokens.push(path) |
|||
path = '' |
|||
} |
|||
|
|||
const partial = prefix != null && next != null && next !== prefix |
|||
const repeat = modifier === '+' || modifier === '*' |
|||
const optional = modifier === '?' || modifier === '*' |
|||
const delimiter = res[2] || defaultDelimiter |
|||
const pattern = capture || group |
|||
|
|||
tokens.push({ |
|||
name: name || key++, |
|||
prefix: prefix || '', |
|||
delimiter, |
|||
optional, |
|||
repeat, |
|||
partial, |
|||
asterisk: Boolean(asterisk), |
|||
pattern: pattern ? escapeGroup(pattern) : (asterisk ? '.*' : '[^' + escapeString(delimiter) + ']+?') |
|||
}) |
|||
} |
|||
|
|||
// Match any characters still remaining.
|
|||
if (index < str.length) { |
|||
path += str.substr(index) |
|||
} |
|||
|
|||
// If the path exists, push it onto the end.
|
|||
if (path) { |
|||
tokens.push(path) |
|||
} |
|||
|
|||
return tokens |
|||
} |
|||
|
|||
/** |
|||
* Prettier encoding of URI path segments. |
|||
* |
|||
* @param {string} |
|||
* @return {string} |
|||
*/ |
|||
function encodeURIComponentPretty (str, slashAllowed) { |
|||
const re = slashAllowed ? /[?#]/g : /[/?#]/g |
|||
return encodeURI(str).replace(re, (c) => { |
|||
return '%' + c.charCodeAt(0).toString(16).toUpperCase() |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* Encode the asterisk parameter. Similar to `pretty`, but allows slashes. |
|||
* |
|||
* @param {string} |
|||
* @return {string} |
|||
*/ |
|||
function encodeAsterisk (str) { |
|||
return encodeURIComponentPretty(str, true) |
|||
} |
|||
|
|||
/** |
|||
* Escape a regular expression string. |
|||
* |
|||
* @param {string} str |
|||
* @return {string} |
|||
*/ |
|||
function escapeString (str) { |
|||
return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, '\\$1') |
|||
} |
|||
|
|||
/** |
|||
* Escape the capturing group by escaping special characters and meaning. |
|||
* |
|||
* @param {string} group |
|||
* @return {string} |
|||
*/ |
|||
function escapeGroup (group) { |
|||
return group.replace(/([=!:$/()])/g, '\\$1') |
|||
} |
|||
|
|||
/** |
|||
* Expose a method for transforming tokens into the path function. |
|||
*/ |
|||
function tokensToFunction (tokens, options) { |
|||
// Compile all the tokens into regexps.
|
|||
const matches = new Array(tokens.length) |
|||
|
|||
// Compile all the patterns before compilation.
|
|||
for (let i = 0; i < tokens.length; i++) { |
|||
if (typeof tokens[i] === 'object') { |
|||
matches[i] = new RegExp('^(?:' + tokens[i].pattern + ')$', flags(options)) |
|||
} |
|||
} |
|||
|
|||
return function (obj, opts) { |
|||
let path = '' |
|||
const data = obj || {} |
|||
const options = opts || {} |
|||
const encode = options.pretty ? encodeURIComponentPretty : encodeURIComponent |
|||
|
|||
for (let i = 0; i < tokens.length; i++) { |
|||
const token = tokens[i] |
|||
|
|||
if (typeof token === 'string') { |
|||
path += token |
|||
|
|||
continue |
|||
} |
|||
|
|||
const value = data[token.name || 'pathMatch'] |
|||
let segment |
|||
|
|||
if (value == null) { |
|||
if (token.optional) { |
|||
// Prepend partial segment prefixes.
|
|||
if (token.partial) { |
|||
path += token.prefix |
|||
} |
|||
|
|||
continue |
|||
} else { |
|||
throw new TypeError('Expected "' + token.name + '" to be defined') |
|||
} |
|||
} |
|||
|
|||
if (Array.isArray(value)) { |
|||
if (!token.repeat) { |
|||
throw new TypeError('Expected "' + token.name + '" to not repeat, but received `' + JSON.stringify(value) + '`') |
|||
} |
|||
|
|||
if (value.length === 0) { |
|||
if (token.optional) { |
|||
continue |
|||
} else { |
|||
throw new TypeError('Expected "' + token.name + '" to not be empty') |
|||
} |
|||
} |
|||
|
|||
for (let j = 0; j < value.length; j++) { |
|||
segment = encode(value[j]) |
|||
|
|||
if (!matches[i].test(segment)) { |
|||
throw new TypeError('Expected all "' + token.name + '" to match "' + token.pattern + '", but received `' + JSON.stringify(segment) + '`') |
|||
} |
|||
|
|||
path += (j === 0 ? token.prefix : token.delimiter) + segment |
|||
} |
|||
|
|||
continue |
|||
} |
|||
|
|||
segment = token.asterisk ? encodeAsterisk(value) : encode(value) |
|||
|
|||
if (!matches[i].test(segment)) { |
|||
throw new TypeError('Expected "' + token.name + '" to match "' + token.pattern + '", but received "' + segment + '"') |
|||
} |
|||
|
|||
path += token.prefix + segment |
|||
} |
|||
|
|||
return path |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Get the flags for a regexp from the options. |
|||
* |
|||
* @param {Object} options |
|||
* @return {string} |
|||
*/ |
|||
function flags (options) { |
|||
return options && options.sensitive ? '' : 'i' |
|||
} |
|||
|
|||
/** |
|||
* Format given url, append query to url query string |
|||
* |
|||
* @param {string} url |
|||
* @param {string} query |
|||
* @return {string} |
|||
*/ |
|||
function formatUrl (url, query) { |
|||
let protocol |
|||
const index = url.indexOf('://') |
|||
if (index !== -1) { |
|||
protocol = url.substring(0, index) |
|||
url = url.substring(index + 3) |
|||
} else if (url.startsWith('//')) { |
|||
url = url.substring(2) |
|||
} |
|||
|
|||
let parts = url.split('/') |
|||
let result = (protocol ? protocol + '://' : '//') + parts.shift() |
|||
|
|||
let path = parts.filter(Boolean).join('/') |
|||
let hash |
|||
parts = path.split('#') |
|||
if (parts.length === 2) { |
|||
[path, hash] = parts |
|||
} |
|||
|
|||
result += path ? '/' + path : '' |
|||
|
|||
if (query && JSON.stringify(query) !== '{}') { |
|||
result += (url.split('?').length === 2 ? '&' : '?') + formatQuery(query) |
|||
} |
|||
result += hash ? '#' + hash : '' |
|||
|
|||
return result |
|||
} |
|||
|
|||
/** |
|||
* Transform data object to query string |
|||
* |
|||
* @param {object} query |
|||
* @return {string} |
|||
*/ |
|||
function formatQuery (query) { |
|||
return Object.keys(query).sort().map((key) => { |
|||
const val = query[key] |
|||
if (val == null) { |
|||
return '' |
|||
} |
|||
if (Array.isArray(val)) { |
|||
return val.slice().map(val2 => [key, '=', val2].join('')).join('&') |
|||
} |
|||
return key + '=' + val |
|||
}).filter(Boolean).join('&') |
|||
} |
|||
|
|||
export function addLifecycleHook(vm, hook, fn) { |
|||
if (!vm.$options[hook]) { |
|||
vm.$options[hook] = [] |
|||
} |
|||
if (!vm.$options[hook].includes(fn)) { |
|||
vm.$options[hook].push(fn) |
|||
} |
|||
} |
@ -0,0 +1,9 @@ |
|||
<!DOCTYPE html> |
|||
<html {{ HTML_ATTRS }}> |
|||
<head {{ HEAD_ATTRS }}> |
|||
{{ HEAD }} |
|||
</head> |
|||
<body {{ BODY_ATTRS }}> |
|||
{{ APP }} |
|||
</body> |
|||
</html> |
@ -0,0 +1,23 @@ |
|||
<!DOCTYPE html> |
|||
<html> |
|||
<head> |
|||
<title>Server error</title> |
|||
<meta charset="utf-8"> |
|||
<meta content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" name=viewport> |
|||
<style> |
|||
.__nuxt-error-page{padding: 1rem;background:#f7f8fb;color:#47494e;text-align:center;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;font-family:sans-serif;font-weight:100!important;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-font-smoothing:antialiased;position:absolute;top:0;left:0;right:0;bottom:0}.__nuxt-error-page .error{max-width:450px}.__nuxt-error-page .title{font-size:24px;font-size:1.5rem;margin-top:15px;color:#47494e;margin-bottom:8px}.__nuxt-error-page .description{color:#7f828b;line-height:21px;margin-bottom:10px}.__nuxt-error-page a{color:#7f828b!important;text-decoration:none}.__nuxt-error-page .logo{position:fixed;left:12px;bottom:12px} |
|||
</style> |
|||
</head> |
|||
<body> |
|||
<div class="__nuxt-error-page"> |
|||
<div class="error"> |
|||
<svg xmlns="http://www.w3.org/2000/svg" width="90" height="90" fill="#DBE1EC" viewBox="0 0 48 48"><path d="M22 30h4v4h-4zm0-16h4v12h-4zm1.99-10C12.94 4 4 12.95 4 24s8.94 20 19.99 20S44 35.05 44 24 35.04 4 23.99 4zM24 40c-8.84 0-16-7.16-16-16S15.16 8 24 8s16 7.16 16 16-7.16 16-16 16z"/></svg> |
|||
<div class="title">Server error</div> |
|||
<div class="description">{{ message }}</div> |
|||
</div> |
|||
<div class="logo"> |
|||
<a href="https://nuxtjs.org" target="_blank" rel="noopener">Nuxt.js</a> |
|||
</div> |
|||
</div> |
|||
</body> |
|||
</html> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue