changes
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
214
.nuxt/App.js
Normal file
@@ -0,0 +1,214 @@
|
||||
import Vue from 'vue'
|
||||
import { decode, parsePath, withoutBase, withoutTrailingSlash, normalizeURL } from 'ufo'
|
||||
|
||||
import { getMatchedComponentsInstances, getChildrenComponentInstancesUsingFetch, promisify, globalHandleError, urlJoin, sanitizeComponent } from './utils'
|
||||
import NuxtError from './components/nuxt-error.vue'
|
||||
import NuxtLoading from './components/nuxt-loading.vue'
|
||||
import NuxtBuildIndicator from './components/nuxt-build-indicator'
|
||||
|
||||
import '../assets/styles/main.scss'
|
||||
|
||||
import '../assets/styles/style.scss'
|
||||
|
||||
import _6f6c098b from '../layouts/default.vue'
|
||||
import _777baf4e from '../layouts/infor.vue'
|
||||
|
||||
const layouts = { "_default": sanitizeComponent(_6f6c098b),"_infor": sanitizeComponent(_777baf4e) }
|
||||
|
||||
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
|
||||
this.$root.$options.$nuxt = this
|
||||
|
||||
if (process.client) {
|
||||
// add to window so we can listen when ready
|
||||
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
|
||||
},
|
||||
|
||||
async 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(async (page) => {
|
||||
let p = []
|
||||
|
||||
// Old fetch
|
||||
if (page.$options.fetch && page.$options.fetch.length) {
|
||||
p.push(promisify(page.$options.fetch, this.context))
|
||||
}
|
||||
|
||||
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])
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
// Wait for asyncData & old fetch to finish
|
||||
await Promise.all(p)
|
||||
// Cleanup refs
|
||||
p = []
|
||||
|
||||
if (page.$fetch) {
|
||||
p.push(page.$fetch())
|
||||
}
|
||||
// Get all component instance to call $fetch
|
||||
for (const component of getChildrenComponentInstancesUsingFetch(page.$vnode.componentInstance)) {
|
||||
p.push(component.$fetch())
|
||||
}
|
||||
|
||||
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) {
|
||||
if (this.$loading) {
|
||||
if (this.$loading.fail) {
|
||||
this.$loading.fail(this.nuxt.err)
|
||||
}
|
||||
if (this.$loading.finish) {
|
||||
this.$loading.finish()
|
||||
}
|
||||
}
|
||||
|
||||
let errorLayout = (NuxtError.options || NuxtError).layout;
|
||||
|
||||
if (typeof errorLayout === 'function') {
|
||||
errorLayout = errorLayout(this.context)
|
||||
}
|
||||
|
||||
this.setLayout(errorLayout)
|
||||
}
|
||||
},
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
193
.nuxt/axios.js
Normal file
@@ -0,0 +1,193 @@
|
||||
import Axios from 'axios'
|
||||
import defu from 'defu'
|
||||
|
||||
// Axios.prototype cannot be modified
|
||||
const axiosExtra = {
|
||||
setBaseURL (baseURL) {
|
||||
this.defaults.baseURL = baseURL
|
||||
},
|
||||
setHeader (name, value, scopes = 'common') {
|
||||
for (const scope of Array.isArray(scopes) ? scopes : [ scopes ]) {
|
||||
if (!value) {
|
||||
delete this.defaults.headers[scope][name];
|
||||
continue
|
||||
}
|
||||
this.defaults.headers[scope][name] = value
|
||||
}
|
||||
},
|
||||
setToken (token, type, scopes = 'common') {
|
||||
const value = !token ? null : (type ? type + ' ' : '') + token
|
||||
this.setHeader('Authorization', value, scopes)
|
||||
},
|
||||
onRequest(fn) {
|
||||
this.interceptors.request.use(config => fn(config) || config)
|
||||
},
|
||||
onResponse(fn) {
|
||||
this.interceptors.response.use(response => fn(response) || response)
|
||||
},
|
||||
onRequestError(fn) {
|
||||
this.interceptors.request.use(undefined, error => fn(error) || Promise.reject(error))
|
||||
},
|
||||
onResponseError(fn) {
|
||||
this.interceptors.response.use(undefined, error => fn(error) || Promise.reject(error))
|
||||
},
|
||||
onError(fn) {
|
||||
this.onRequestError(fn)
|
||||
this.onResponseError(fn)
|
||||
},
|
||||
create(options) {
|
||||
return createAxiosInstance(defu(options, this.defaults))
|
||||
}
|
||||
}
|
||||
|
||||
// Request helpers ($get, $post, ...)
|
||||
for (const method of ['request', 'delete', 'get', 'head', 'options', 'post', 'put', 'patch']) {
|
||||
axiosExtra['$' + method] = function () { return this[method].apply(this, arguments).then(res => res && res.data) }
|
||||
}
|
||||
|
||||
const extendAxiosInstance = axios => {
|
||||
for (const key in axiosExtra) {
|
||||
axios[key] = axiosExtra[key].bind(axios)
|
||||
}
|
||||
}
|
||||
|
||||
const createAxiosInstance = axiosOptions => {
|
||||
// Create new axios instance
|
||||
const axios = Axios.create(axiosOptions)
|
||||
axios.CancelToken = Axios.CancelToken
|
||||
axios.isCancel = Axios.isCancel
|
||||
|
||||
// Extend axios proto
|
||||
extendAxiosInstance(axios)
|
||||
|
||||
// Intercept to apply default headers
|
||||
axios.onRequest((config) => {
|
||||
config.headers = { ...axios.defaults.headers.common, ...config.headers }
|
||||
})
|
||||
|
||||
// Setup interceptors
|
||||
|
||||
setupProgress(axios)
|
||||
|
||||
return axios
|
||||
}
|
||||
|
||||
const setupProgress = (axios) => {
|
||||
if (process.server) {
|
||||
return
|
||||
}
|
||||
|
||||
// A noop loading inteterface for when $nuxt is not yet ready
|
||||
const noopLoading = {
|
||||
finish: () => { },
|
||||
start: () => { },
|
||||
fail: () => { },
|
||||
set: () => { }
|
||||
}
|
||||
|
||||
const $loading = () => {
|
||||
const $nuxt = typeof window !== 'undefined' && window['$nuxt']
|
||||
return ($nuxt && $nuxt.$loading && $nuxt.$loading.set) ? $nuxt.$loading : noopLoading
|
||||
}
|
||||
|
||||
let currentRequests = 0
|
||||
|
||||
axios.onRequest(config => {
|
||||
if (config && config.progress === false) {
|
||||
return
|
||||
}
|
||||
|
||||
currentRequests++
|
||||
})
|
||||
|
||||
axios.onResponse(response => {
|
||||
if (response && response.config && response.config.progress === false) {
|
||||
return
|
||||
}
|
||||
|
||||
currentRequests--
|
||||
if (currentRequests <= 0) {
|
||||
currentRequests = 0
|
||||
$loading().finish()
|
||||
}
|
||||
})
|
||||
|
||||
axios.onError(error => {
|
||||
if (error && error.config && error.config.progress === false) {
|
||||
return
|
||||
}
|
||||
|
||||
currentRequests--
|
||||
|
||||
if (Axios.isCancel(error)) {
|
||||
if (currentRequests <= 0) {
|
||||
currentRequests = 0
|
||||
$loading().finish()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
$loading().fail()
|
||||
$loading().finish()
|
||||
})
|
||||
|
||||
const onProgress = e => {
|
||||
if (!currentRequests || !e.total) {
|
||||
return
|
||||
}
|
||||
const progress = ((e.loaded * 100) / (e.total * currentRequests))
|
||||
$loading().set(Math.min(100, progress))
|
||||
}
|
||||
|
||||
axios.defaults.onUploadProgress = onProgress
|
||||
axios.defaults.onDownloadProgress = onProgress
|
||||
}
|
||||
|
||||
export default (ctx, inject) => {
|
||||
// runtimeConfig
|
||||
const runtimeConfig = ctx.$config && ctx.$config.axios || {}
|
||||
// baseURL
|
||||
const baseURL = process.browser
|
||||
? (runtimeConfig.browserBaseURL || runtimeConfig.browserBaseUrl || runtimeConfig.baseURL || runtimeConfig.baseUrl || 'http://localhost:3001/')
|
||||
: (runtimeConfig.baseURL || runtimeConfig.baseUrl || process.env._AXIOS_BASE_URL_ || 'http://localhost:3001/')
|
||||
|
||||
// Create fresh objects for all default header scopes
|
||||
// Axios creates only one which is shared across SSR requests!
|
||||
// https://github.com/mzabriskie/axios/blob/master/lib/defaults.js
|
||||
const headers = {
|
||||
"common": {
|
||||
"Accept": "application/json, text/plain, */*"
|
||||
},
|
||||
"delete": {},
|
||||
"get": {},
|
||||
"head": {},
|
||||
"post": {},
|
||||
"put": {},
|
||||
"patch": {}
|
||||
}
|
||||
|
||||
const axiosOptions = {
|
||||
baseURL,
|
||||
headers
|
||||
}
|
||||
|
||||
// Proxy SSR request headers headers
|
||||
if (process.server && ctx.req && ctx.req.headers) {
|
||||
const reqHeaders = { ...ctx.req.headers }
|
||||
for (const h of ["accept","cf-connecting-ip","cf-ray","content-length","content-md5","content-type","host","x-forwarded-host","x-forwarded-port","x-forwarded-proto"]) {
|
||||
delete reqHeaders[h]
|
||||
}
|
||||
axiosOptions.headers.common = { ...reqHeaders, ...axiosOptions.headers.common }
|
||||
}
|
||||
|
||||
if (process.server) {
|
||||
// Don't accept brotli encoding because Node can't parse it
|
||||
axiosOptions.headers.common['accept-encoding'] = 'gzip, deflate'
|
||||
}
|
||||
|
||||
const axios = createAxiosInstance(axiosOptions)
|
||||
|
||||
// Inject axios to the context as $axios
|
||||
ctx.$axios = axios
|
||||
inject('axios', axios)
|
||||
}
|
||||
870
.nuxt/client.js
Normal file
@@ -0,0 +1,870 @@
|
||||
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,
|
||||
isSamePath,
|
||||
urlJoin
|
||||
} 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
|
||||
let store
|
||||
|
||||
// Try to rehydrate SSR data from window
|
||||
const NUXT = window.__NUXT__ || {}
|
||||
|
||||
const $config = NUXT.config || {}
|
||||
if ($config._app) {
|
||||
__webpack_public_path__ = urlJoin($config._app.cdnURL, $config._app.assetsPath)
|
||||
}
|
||||
|
||||
Object.assign(Vue.config, {"silent":false,"performance":true})
|
||||
|
||||
const logs = NUXT.logs || []
|
||||
if (logs.length > 0) {
|
||||
const ssrLogStyle = 'background: #2E495E;border-radius: 0.5em;color: white;font-weight: bold;padding: 2px 0.5em;'
|
||||
console.group && console.group ('%cNuxt SSR', ssrLogStyle)
|
||||
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 = async (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') {
|
||||
const currentApp = vm.$root[nuxtApp]
|
||||
|
||||
// Load error layout
|
||||
let layout = (NuxtError.options || NuxtError).layout
|
||||
if (typeof layout === 'function') {
|
||||
layout = layout(currentApp.context)
|
||||
}
|
||||
if (layout) {
|
||||
await currentApp.loadLayout(layout).catch(() => {})
|
||||
}
|
||||
currentApp.setLayout(layout)
|
||||
|
||||
currentApp.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(null, NUXT.config).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 (route) {
|
||||
return flatMapComponents(route, 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, renderState) {
|
||||
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, renderState)
|
||||
}
|
||||
|
||||
async function render (to, from, next, renderState) {
|
||||
if (this._routeChanged === false && this._paramChanged === false && this._queryChanged === false) {
|
||||
return next()
|
||||
}
|
||||
// Handle first render on SPA mode
|
||||
let spaFallback = false
|
||||
if (to === from) {
|
||||
_lastPaths = []
|
||||
spaFallback = true
|
||||
} 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,
|
||||
error: (err) => {
|
||||
if (renderState.aborted) {
|
||||
return
|
||||
}
|
||||
app.nuxt.error.call(this, err)
|
||||
},
|
||||
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, undefined, renderState)
|
||||
if (nextCalled) {
|
||||
return
|
||||
}
|
||||
if (renderState.aborted) {
|
||||
next(false)
|
||||
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, renderState)
|
||||
if (nextCalled) {
|
||||
return
|
||||
}
|
||||
if (renderState.aborted) {
|
||||
next(false)
|
||||
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, undefined, renderState)
|
||||
if (nextCalled) {
|
||||
return
|
||||
}
|
||||
if (renderState.aborted) {
|
||||
next(false)
|
||||
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, renderState)
|
||||
if (nextCalled) {
|
||||
return
|
||||
}
|
||||
if (renderState.aborted) {
|
||||
next(false)
|
||||
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(async (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)
|
||||
|
||||
promise.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()
|
||||
}
|
||||
|
||||
if (renderState.aborted) {
|
||||
next(false)
|
||||
return
|
||||
}
|
||||
next()
|
||||
}
|
||||
} catch (err) {
|
||||
if (renderState.aborted) {
|
||||
next(false)
|
||||
return
|
||||
}
|
||||
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 setLayoutForNextPage (to) {
|
||||
// Set layout
|
||||
let hasError = Boolean(this.$options.nuxt.err)
|
||||
if (this._hadError && this._dateLastError === this.$options.nuxt.dateErr) {
|
||||
hasError = false
|
||||
}
|
||||
let layout = hasError
|
||||
? (NuxtError.options || NuxtError).layout
|
||||
: to.matched[0].components.default.options.layout
|
||||
|
||||
if (typeof layout === 'function') {
|
||||
layout = layout(app.context)
|
||||
}
|
||||
|
||||
this.setLayout(layout)
|
||||
}
|
||||
|
||||
function checkForErrors (app) {
|
||||
// Hide error component if no error
|
||||
if (app._hadError && app._dateLastError === app.$options.nuxt.dateErr) {
|
||||
app.error()
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
let triggerScroll = false
|
||||
|
||||
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])
|
||||
}
|
||||
|
||||
triggerScroll = true
|
||||
}
|
||||
})
|
||||
|
||||
if (triggerScroll) {
|
||||
// Ensure to trigger scroll event after calling scrollBehavior
|
||||
window.$nuxt.$nextTick(() => {
|
||||
window.$nuxt.$emit('triggerScroll')
|
||||
})
|
||||
}
|
||||
|
||||
checkForErrors(this)
|
||||
|
||||
// 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))
|
||||
|
||||
if (_app.context.isHMR) {
|
||||
const Components = getMatchedComponents(router.currentRoute)
|
||||
Components.forEach((Component) => {
|
||||
Component.prototype.constructor = Component
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
store = __app.store
|
||||
|
||||
// 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(setLayoutForNextPage.bind(_app))
|
||||
|
||||
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(app.context.route))
|
||||
|
||||
// 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))
|
||||
|
||||
// Each new invocation of render() aborts previous invocation
|
||||
let renderState = null
|
||||
const boundRender = render.bind(_app)
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (renderState) {
|
||||
renderState.aborted = true
|
||||
}
|
||||
renderState = { aborted: false }
|
||||
boundRender(to, from, next, renderState)
|
||||
})
|
||||
|
||||
// Fix in static: remove trailing slash to force hydration
|
||||
// Full static, if server-rendered: hydrate, to allow custom redirect to generated page
|
||||
|
||||
// Fix in static: remove trailing slash to force hydration
|
||||
if (NUXT.serverRendered && isSamePath(NUXT.routePath, _app.context.route.path)) {
|
||||
return mount()
|
||||
}
|
||||
|
||||
// First render on client-side
|
||||
const clientFirstMount = () => {
|
||||
normalizeComponents(router.currentRoute, router.currentRoute)
|
||||
setLayoutForNextPage.call(_app, router.currentRoute)
|
||||
checkForErrors(_app)
|
||||
// Don't call fixPrepatch.call(_app, router.currentRoute, router.currentRoute) since it's first render
|
||||
mount()
|
||||
}
|
||||
|
||||
// fix: force next tick to avoid having same timestamp when an error happen on spa fallback
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
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)
|
||||
}
|
||||
})
|
||||
},
|
||||
{ aborted: false })
|
||||
}
|
||||
47
.nuxt/components/index.js
Normal file
@@ -0,0 +1,47 @@
|
||||
export { default as Caption } from '../../components/Caption.vue'
|
||||
export { default as Logo } from '../../components/Logo.vue'
|
||||
export { default as Modal } from '../../components/Modal.vue'
|
||||
export { default as SearchBox } from '../../components/SearchBox.vue'
|
||||
export { default as SvgIcon } from '../../components/SvgIcon.vue'
|
||||
export { default as TopMenu } from '../../components/TopMenu.vue'
|
||||
export { default as DialogConfirm } from '../../components/dialog/Confirm.vue'
|
||||
export { default as DialogCountDown } from '../../components/dialog/CountDown.vue'
|
||||
export { default as DialogDelete } from '../../components/dialog/Delete.vue'
|
||||
export { default as DialogError } from '../../components/dialog/Error.vue'
|
||||
export { default as DialogInfo } from '../../components/dialog/Info.vue'
|
||||
export { default as DialogSuccess } from '../../components/dialog/Success.vue'
|
||||
export { default as SnackbarError } from '../../components/snackbar/Error.vue'
|
||||
export { default as SnackbarInfo } from '../../components/snackbar/Info.vue'
|
||||
export { default as SnackbarSnackBar } from '../../components/snackbar/SnackBar.vue'
|
||||
export { default as SnackbarSuccess } from '../../components/snackbar/Success.vue'
|
||||
|
||||
// nuxt/nuxt.js#8607
|
||||
function wrapFunctional(options) {
|
||||
if (!options || !options.functional) {
|
||||
return options
|
||||
}
|
||||
|
||||
const propKeys = Array.isArray(options.props) ? options.props : Object.keys(options.props || {})
|
||||
|
||||
return {
|
||||
render(h) {
|
||||
const attrs = {}
|
||||
const props = {}
|
||||
|
||||
for (const key in this.$attrs) {
|
||||
if (propKeys.includes(key)) {
|
||||
props[key] = this.$attrs[key]
|
||||
} else {
|
||||
attrs[key] = this.$attrs[key]
|
||||
}
|
||||
}
|
||||
|
||||
return h(options, {
|
||||
on: this.$listeners,
|
||||
attrs,
|
||||
props,
|
||||
scopedSlots: this.$scopedSlots,
|
||||
}, this.$slots.default)
|
||||
}
|
||||
}
|
||||
}
|
||||
143
.nuxt/components/nuxt-build-indicator.vue
Normal file
@@ -0,0 +1,143 @@
|
||||
<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;
|
||||
height: 0.825em;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
</style>
|
||||
121
.nuxt/components/nuxt-child.js
Normal file
@@ -0,0 +1,121 @@
|
||||
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'
|
||||
]
|
||||
98
.nuxt/components/nuxt-error.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<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">
|
||||
<a v-if="typeof $route === 'undefined'" class="error-link" href="/"></a>
|
||||
<NuxtLink v-else 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</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'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
</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>
|
||||
98
.nuxt/components/nuxt-link.client.js
Normal file
@@ -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 || !link.__prefetch) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
.nuxt/components/nuxt-link.server.js
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
178
.nuxt/components/nuxt-loading.vue
Normal file
@@ -0,0 +1,178 @@
|
||||
<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)
|
||||
clearTimeout(this._hide)
|
||||
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()
|
||||
this._hide = 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: black;
|
||||
z-index: 999999;
|
||||
}
|
||||
|
||||
.nuxt-progress.nuxt-progress-notransition {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.nuxt-progress-failed {
|
||||
background-color: red;
|
||||
}
|
||||
</style>
|
||||
101
.nuxt/components/nuxt.js
Normal file
@@ -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 occurred 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 occurred while showing the error page'),
|
||||
h('p', 'Unfortunately an error occurred and while showing the error page another error occurred'),
|
||||
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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
7
.nuxt/components/plugin.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import Vue from 'vue'
|
||||
import * as components from './index'
|
||||
|
||||
for (const name in components) {
|
||||
Vue.component(name, components[name])
|
||||
Vue.component('Lazy' + name, components[name])
|
||||
}
|
||||
24
.nuxt/components/readme.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Discovered Components
|
||||
|
||||
This is an auto-generated list of components discovered by [nuxt/components](https://github.com/nuxt/components).
|
||||
|
||||
You can directly use them in pages and other components without the need to import them.
|
||||
|
||||
**Tip:** If a component is conditionally rendered with `v-if` and is big, it is better to use `Lazy` or `lazy-` prefix to lazy load.
|
||||
|
||||
- `<Caption>` | `<caption>` (components/Caption.vue)
|
||||
- `<Logo>` | `<logo>` (components/Logo.vue)
|
||||
- `<Modal>` | `<modal>` (components/Modal.vue)
|
||||
- `<SearchBox>` | `<search-box>` (components/SearchBox.vue)
|
||||
- `<SvgIcon>` | `<svg-icon>` (components/SvgIcon.vue)
|
||||
- `<TopMenu>` | `<top-menu>` (components/TopMenu.vue)
|
||||
- `<DialogConfirm>` | `<dialog-confirm>` (components/dialog/Confirm.vue)
|
||||
- `<DialogCountDown>` | `<dialog-count-down>` (components/dialog/CountDown.vue)
|
||||
- `<DialogDelete>` | `<dialog-delete>` (components/dialog/Delete.vue)
|
||||
- `<DialogError>` | `<dialog-error>` (components/dialog/Error.vue)
|
||||
- `<DialogInfo>` | `<dialog-info>` (components/dialog/Info.vue)
|
||||
- `<DialogSuccess>` | `<dialog-success>` (components/dialog/Success.vue)
|
||||
- `<SnackbarError>` | `<snackbar-error>` (components/snackbar/Error.vue)
|
||||
- `<SnackbarInfo>` | `<snackbar-info>` (components/snackbar/Info.vue)
|
||||
- `<SnackbarSnackBar>` | `<snackbar-snack-bar>` (components/snackbar/SnackBar.vue)
|
||||
- `<SnackbarSuccess>` | `<snackbar-success>` (components/snackbar/Success.vue)
|
||||
6
.nuxt/dayjs-plugin.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
export default (context, inject) => {
|
||||
context.$dayjs = dayjs
|
||||
inject('dayjs', dayjs)
|
||||
}
|
||||
1
.nuxt/empty.js
Normal file
@@ -0,0 +1 @@
|
||||
// This file is intentionally left empty for noop aliases
|
||||
46
.nuxt/image.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import Vue from 'vue'
|
||||
import { createImage} from '~image'
|
||||
import NuxtImg from '~image/components/nuxt-img.vue'
|
||||
import NuxtPicture from '~image/components/nuxt-picture.vue'
|
||||
|
||||
import * as staticRuntime$3678 from '/home/loitx/dev/utopia/login/node_modules/@nuxt/image/dist/runtime/providers/static.js'
|
||||
import * as ipxRuntime$b9af from '/home/loitx/dev/utopia/login/node_modules/@nuxt/image/dist/runtime/providers/ipx.js'
|
||||
|
||||
const imageOptions = {
|
||||
"screens": {
|
||||
"xs": 320,
|
||||
"sm": 640,
|
||||
"md": 768,
|
||||
"lg": 1024,
|
||||
"xl": 1280,
|
||||
"xxl": 1536,
|
||||
"2xl": 1536
|
||||
},
|
||||
"presets": {},
|
||||
"provider": "ipx",
|
||||
"domains": [],
|
||||
"alias": {}
|
||||
}
|
||||
|
||||
imageOptions.providers = {
|
||||
['static']: { provider: staticRuntime$3678, defaults: {} },
|
||||
['ipx']: { provider: ipxRuntime$b9af, defaults: {} }
|
||||
}
|
||||
|
||||
Vue.component(NuxtImg.name, NuxtImg)
|
||||
Vue.component(NuxtPicture.name, NuxtPicture)
|
||||
Vue.component('NImg', NuxtImg)
|
||||
Vue.component('NPicture', NuxtPicture)
|
||||
|
||||
export default function (nuxtContext, inject) {
|
||||
const $img = createImage(imageOptions, nuxtContext)
|
||||
|
||||
if (process.static && process.server) {
|
||||
nuxtContext.beforeNuxtRender(({ nuxtState }) => {
|
||||
const ssrData = nuxtState.data[0] || {}
|
||||
ssrData._img = nuxtState._img || {}
|
||||
})
|
||||
}
|
||||
|
||||
inject('img', $img)
|
||||
}
|
||||
295
.nuxt/index.js
Normal file
@@ -0,0 +1,295 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
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'
|
||||
import { createStore } from './store.js'
|
||||
|
||||
/* Plugins */
|
||||
|
||||
import nuxt_plugin_plugin_5445efd0 from 'nuxt_plugin_plugin_5445efd0' // Source: ./components/plugin.js (mode: 'all')
|
||||
import nuxt_plugin_image_3b07603e from 'nuxt_plugin_image_3b07603e' // Source: ./image.js (mode: 'all')
|
||||
import nuxt_plugin_dayjsplugin_35159737 from 'nuxt_plugin_dayjsplugin_35159737' // Source: ./dayjs-plugin.js (mode: 'all')
|
||||
import nuxt_plugin_axios_11155a34 from 'nuxt_plugin_axios_11155a34' // Source: ./axios.js (mode: 'all')
|
||||
import nuxt_plugin_connection_5c203ed4 from 'nuxt_plugin_connection_5c203ed4' // Source: ../plugins/connection (mode: 'all')
|
||||
import nuxt_plugin_common_7195563e from 'nuxt_plugin_common_7195563e' // Source: ../plugins/common (mode: 'all')
|
||||
import nuxt_plugin_datatable_5b47efae from 'nuxt_plugin_datatable_5b47efae' // Source: ../plugins/datatable (mode: 'all')
|
||||
import nuxt_plugin_components_6fb0430c from 'nuxt_plugin_components_6fb0430c' // Source: ../plugins/components (mode: 'all')
|
||||
|
||||
// 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)
|
||||
|
||||
Object.defineProperty(Vue.prototype, '$nuxt', {
|
||||
get() {
|
||||
const globalNuxt = this.$root ? this.$root.$options.$nuxt : null
|
||||
if (process.client && !globalNuxt && typeof window !== 'undefined') {
|
||||
return window.$nuxt
|
||||
}
|
||||
return globalNuxt
|
||||
},
|
||||
configurable: true
|
||||
})
|
||||
|
||||
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"}
|
||||
|
||||
const originalRegisterModule = Vuex.Store.prototype.registerModule
|
||||
|
||||
function registerModule (path, rawModule, options = {}) {
|
||||
const preserveState = process.client && (
|
||||
Array.isArray(path)
|
||||
? !!path.reduce((namespacedState, path) => namespacedState && namespacedState[path], this.state)
|
||||
: path in this.state
|
||||
)
|
||||
return originalRegisterModule.call(this, path, rawModule, { preserveState, ...options })
|
||||
}
|
||||
|
||||
async function createApp(ssrContext, config = {}) {
|
||||
const store = createStore(ssrContext)
|
||||
const router = await createRouter(ssrContext, config, { store })
|
||||
|
||||
// Add this.$router into store actions/mutations
|
||||
store.$router = router
|
||||
|
||||
// Fix SSR caveat https://github.com/nuxt/nuxt.js/issues/3757#issuecomment-414689141
|
||||
store.registerModule = registerModule
|
||||
|
||||
// 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":"Login","htmlAttrs":{"lang":"en"},"meta":[{"charset":"utf-8"},{"name":"viewport","content":"width=device-width, initial-scale=1"},{"hid":"description","name":"description","content":""},{"name":"format-detection","content":"telephone=no"}],"link":[{"rel":"icon","type":"image\u002Fx-icon","href":"\u002Ffavicon.ico"},{"rel":"stylesheet","href":"https:\u002F\u002Ffonts.googleapis.com\u002Fcss2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@48,400,0,0"}],"style":[],"script":[]},
|
||||
|
||||
store,
|
||||
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
|
||||
}
|
||||
|
||||
// Make app available into store via this.app
|
||||
store.app = 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, {
|
||||
store,
|
||||
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,
|
||||
beforeSerializeFns: ssrContext ? ssrContext.beforeSerializeFns : undefined,
|
||||
ssrContext
|
||||
})
|
||||
|
||||
function inject(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
|
||||
// Add into context
|
||||
if (!app.context[key]) {
|
||||
app.context[key] = value
|
||||
}
|
||||
|
||||
// Add into store
|
||||
store[key] = app[key]
|
||||
|
||||
// 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.prototype, key)) {
|
||||
Object.defineProperty(Vue.prototype, key, {
|
||||
get () {
|
||||
return this.$root.$options[key]
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Inject runtime config as $config
|
||||
inject('config', config)
|
||||
|
||||
if (process.client) {
|
||||
// Replace store state before plugins execution
|
||||
if (window.__NUXT__ && window.__NUXT__.state) {
|
||||
store.replaceState(window.__NUXT__.state)
|
||||
}
|
||||
}
|
||||
|
||||
// Add enablePreview(previewData = {}) in context for plugins
|
||||
if (process.static && process.client) {
|
||||
app.context.enablePreview = function (previewData = {}) {
|
||||
app.previewData = Object.assign({}, previewData)
|
||||
inject('preview', previewData)
|
||||
}
|
||||
}
|
||||
// Plugin execution
|
||||
|
||||
if (typeof nuxt_plugin_plugin_5445efd0 === 'function') {
|
||||
await nuxt_plugin_plugin_5445efd0(app.context, inject)
|
||||
}
|
||||
|
||||
if (typeof nuxt_plugin_image_3b07603e === 'function') {
|
||||
await nuxt_plugin_image_3b07603e(app.context, inject)
|
||||
}
|
||||
|
||||
if (typeof nuxt_plugin_dayjsplugin_35159737 === 'function') {
|
||||
await nuxt_plugin_dayjsplugin_35159737(app.context, inject)
|
||||
}
|
||||
|
||||
if (typeof nuxt_plugin_axios_11155a34 === 'function') {
|
||||
await nuxt_plugin_axios_11155a34(app.context, inject)
|
||||
}
|
||||
|
||||
if (typeof nuxt_plugin_connection_5c203ed4 === 'function') {
|
||||
await nuxt_plugin_connection_5c203ed4(app.context, inject)
|
||||
}
|
||||
|
||||
if (typeof nuxt_plugin_common_7195563e === 'function') {
|
||||
await nuxt_plugin_common_7195563e(app.context, inject)
|
||||
}
|
||||
|
||||
if (typeof nuxt_plugin_datatable_5b47efae === 'function') {
|
||||
await nuxt_plugin_datatable_5b47efae(app.context, inject)
|
||||
}
|
||||
|
||||
if (typeof nuxt_plugin_components_6fb0430c === 'function') {
|
||||
await nuxt_plugin_components_6fb0430c(app.context, inject)
|
||||
}
|
||||
|
||||
// Lock enablePreview in context
|
||||
if (process.static && process.client) {
|
||||
app.context.enablePreview = function () {
|
||||
console.warn('You cannot call enablePreview() outside a plugin.')
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for async component to be resolved first
|
||||
await new Promise((resolve, reject) => {
|
||||
// Ignore 404s rather than blindly replacing URL in browser
|
||||
if (process.client) {
|
||||
const { route } = router.resolve(app.context.route.fullPath)
|
||||
if (!route.matched.length) {
|
||||
return resolve()
|
||||
}
|
||||
}
|
||||
router.replace(app.context.route.fullPath, resolve, (err) => {
|
||||
// https://github.com/vuejs/vue-router/blob/v3.4.3/src/util/errors.js
|
||||
if (!err._isRouter) return reject(err)
|
||||
if (err.type !== 2 /* NavigationFailureType.redirected */) return resolve()
|
||||
|
||||
// navigated to a different route in router guard
|
||||
const unregister = router.afterEach(async (to, from) => {
|
||||
if (process.server && ssrContext && ssrContext.url) {
|
||||
ssrContext.url = to.fullPath
|
||||
}
|
||||
app.context.route = await getRouteData(to)
|
||||
app.context.params = to.params || {}
|
||||
app.context.query = to.query || {}
|
||||
unregister()
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return {
|
||||
store,
|
||||
app,
|
||||
router
|
||||
}
|
||||
}
|
||||
|
||||
export { createApp, NuxtError }
|
||||
82
.nuxt/jsonp.js
Normal file
@@ -0,0 +1,82 @@
|
||||
const chunks = {} // chunkId => exports
|
||||
const chunksInstalling = {} // chunkId => Promise
|
||||
const failedChunks = {}
|
||||
|
||||
function importChunk(chunkId, src) {
|
||||
// Already installed
|
||||
if (chunks[chunkId]) {
|
||||
return Promise.resolve(chunks[chunkId])
|
||||
}
|
||||
|
||||
// Failed loading
|
||||
if (failedChunks[chunkId]) {
|
||||
return Promise.reject(failedChunks[chunkId])
|
||||
}
|
||||
|
||||
// Installing
|
||||
if (chunksInstalling[chunkId]) {
|
||||
return chunksInstalling[chunkId]
|
||||
}
|
||||
|
||||
// Set a promise in chunk cache
|
||||
let resolve, reject
|
||||
const promise = chunksInstalling[chunkId] = new Promise((_resolve, _reject) => {
|
||||
resolve = _resolve
|
||||
reject = _reject
|
||||
})
|
||||
|
||||
// Clear chunk data from cache
|
||||
delete chunks[chunkId]
|
||||
|
||||
// Start chunk loading
|
||||
const script = document.createElement('script')
|
||||
script.charset = 'utf-8'
|
||||
script.timeout = 120
|
||||
script.src = src
|
||||
let timeout
|
||||
|
||||
// Create error before stack unwound to get useful stacktrace later
|
||||
const error = new Error()
|
||||
|
||||
// Complete handlers
|
||||
const onScriptComplete = script.onerror = script.onload = (event) => {
|
||||
// Cleanups
|
||||
clearTimeout(timeout)
|
||||
delete chunksInstalling[chunkId]
|
||||
|
||||
// Avoid mem leaks in IE
|
||||
script.onerror = script.onload = null
|
||||
|
||||
// Verify chunk is loaded
|
||||
if (chunks[chunkId]) {
|
||||
return resolve(chunks[chunkId])
|
||||
}
|
||||
|
||||
// Something bad happened
|
||||
const errorType = event && (event.type === 'load' ? 'missing' : event.type)
|
||||
const realSrc = event && event.target && event.target.src
|
||||
error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'
|
||||
error.name = 'ChunkLoadError'
|
||||
error.type = errorType
|
||||
error.request = realSrc
|
||||
failedChunks[chunkId] = error
|
||||
reject(error)
|
||||
}
|
||||
|
||||
// Timeout
|
||||
timeout = setTimeout(() => {
|
||||
onScriptComplete({ type: 'timeout', target: script })
|
||||
}, 120000)
|
||||
|
||||
// Append script
|
||||
document.head.appendChild(script)
|
||||
|
||||
// Return promise
|
||||
return promise
|
||||
}
|
||||
|
||||
export function installJsonp() {
|
||||
window.__NUXT_JSONP__ = function (chunkId, exports) { chunks[chunkId] = exports }
|
||||
window.__NUXT_JSONP_CACHE__ = chunks
|
||||
window.__NUXT_IMPORT__ = importChunk
|
||||
}
|
||||
110
.nuxt/loading.html
Normal file
@@ -0,0 +1,110 @@
|
||||
<style>
|
||||
#nuxt-loading {
|
||||
background: white;
|
||||
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 black;
|
||||
-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 -->
|
||||
3
.nuxt/middleware.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const middleware = {}
|
||||
|
||||
export default middleware
|
||||
90
.nuxt/mixins/fetch.client.js
Normal file
@@ -0,0 +1,90 @@
|
||||
import Vue from 'vue'
|
||||
import { hasFetch, normalizeError, addLifecycleHook, createGetCounter } 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) {
|
||||
if (process.dev) {
|
||||
console.error('Error in fetch():', 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--)
|
||||
}
|
||||
69
.nuxt/mixins/fetch.server.js
Normal file
@@ -0,0 +1,69 @@
|
||||
import Vue from 'vue'
|
||||
import { hasFetch, normalizeError, addLifecycleHook, purifyData, createGetCounter } from '../utils'
|
||||
|
||||
async function serverPrefetch() {
|
||||
if (!this._fetchOnServer) {
|
||||
return
|
||||
}
|
||||
|
||||
// Call and await on $fetch
|
||||
try {
|
||||
await this.$options.fetch.call(this)
|
||||
} catch (err) {
|
||||
if (process.dev) {
|
||||
console.error('Error in fetch():', err)
|
||||
}
|
||||
this.$fetchState.error = normalizeError(err)
|
||||
}
|
||||
this.$fetchState.pending = false
|
||||
|
||||
// Define an ssrKey for hydration
|
||||
this._fetchKey = this._fetchKey || this.$ssrContext.fetchCounters['']++
|
||||
|
||||
// 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
|
||||
|
||||
if (this.$ssrContext.nuxt.fetch[this._fetchKey] !== undefined) {
|
||||
console.warn(`Duplicate fetch key detected (${this._fetchKey}). This may lead to unexpected results.`)
|
||||
}
|
||||
|
||||
this.$ssrContext.nuxt.fetch[this._fetchKey] =
|
||||
this.$fetchState.error ? { _error: this.$fetchState.error } : purifyData(this._data)
|
||||
}
|
||||
|
||||
export default {
|
||||
created() {
|
||||
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
|
||||
}
|
||||
|
||||
const defaultKey = this.$options._scopeId || this.$options.name || ''
|
||||
const getCounter = createGetCounter(this.$ssrContext.fetchCounters, defaultKey)
|
||||
|
||||
if (typeof this.$options.fetchKey === 'function') {
|
||||
this._fetchKey = this.$options.fetchKey.call(this, getCounter)
|
||||
} else {
|
||||
const key = 'string' === typeof this.$options.fetchKey ? this.$options.fetchKey : defaultKey
|
||||
this._fetchKey = key ? key + ':' + getCounter(key) : String(getCounter(key))
|
||||
}
|
||||
|
||||
// Added for remove vue undefined warning while ssr
|
||||
this.$fetch = () => {} // issue #8043
|
||||
Vue.util.defineReactive(this, '$fetchState', {
|
||||
pending: true,
|
||||
error: null,
|
||||
timestamp: Date.now()
|
||||
})
|
||||
|
||||
addLifecycleHook(this, 'serverPrefetch', serverPrefetch)
|
||||
}
|
||||
}
|
||||
63
.nuxt/router.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import Vue from 'vue'
|
||||
import Router from 'vue-router'
|
||||
import { normalizeURL, decode } from 'ufo'
|
||||
import { interopDefault } from './utils'
|
||||
import scrollBehavior from './router.scrollBehavior.js'
|
||||
|
||||
const _47a018a8 = () => interopDefault(import('../pages/signin.vue' /* webpackChunkName: "pages/signin" */))
|
||||
const _4d08ae70 = () => interopDefault(import('../pages/welcome.vue' /* webpackChunkName: "pages/welcome" */))
|
||||
const _53d24441 = () => interopDefault(import('../pages/account/recovery.vue' /* webpackChunkName: "pages/account/recovery" */))
|
||||
const _1d58cc90 = () => interopDefault(import('../pages/index.vue' /* webpackChunkName: "pages/index" */))
|
||||
|
||||
const emptyFn = () => {}
|
||||
|
||||
Vue.use(Router)
|
||||
|
||||
export const routerOptions = {
|
||||
mode: 'history',
|
||||
base: '/',
|
||||
linkActiveClass: 'nuxt-link-active',
|
||||
linkExactActiveClass: 'nuxt-link-exact-active',
|
||||
scrollBehavior,
|
||||
|
||||
routes: [{
|
||||
path: "/signin",
|
||||
component: _47a018a8,
|
||||
name: "signin"
|
||||
}, {
|
||||
path: "/welcome",
|
||||
component: _4d08ae70,
|
||||
name: "welcome"
|
||||
}, {
|
||||
path: "/account/recovery",
|
||||
component: _53d24441,
|
||||
name: "account-recovery"
|
||||
}, {
|
||||
path: "/",
|
||||
component: _1d58cc90,
|
||||
name: "index"
|
||||
}],
|
||||
|
||||
fallback: false
|
||||
}
|
||||
|
||||
export function createRouter (ssrContext, config) {
|
||||
const base = (config._app && config._app.basePath) || routerOptions.base
|
||||
const router = new Router({ ...routerOptions, base })
|
||||
|
||||
// TODO: remove in Nuxt 3
|
||||
const originalPush = router.push
|
||||
router.push = function push (location, onComplete = emptyFn, onAbort) {
|
||||
return originalPush.call(this, location, onComplete, onAbort)
|
||||
}
|
||||
|
||||
const resolve = router.resolve.bind(router)
|
||||
router.resolve = (to, current, append) => {
|
||||
if (typeof to === 'string') {
|
||||
to = normalizeURL(to)
|
||||
}
|
||||
return resolve(to, current, append)
|
||||
}
|
||||
|
||||
return router
|
||||
}
|
||||
82
.nuxt/router.scrollBehavior.js
Normal file
@@ -0,0 +1,82 @@
|
||||
import { getMatchedComponents, setScrollRestoration } from './utils'
|
||||
|
||||
if (process.client) {
|
||||
if ('scrollRestoration' in window.history) {
|
||||
setScrollRestoration('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', () => {
|
||||
setScrollRestoration('auto')
|
||||
})
|
||||
|
||||
// Setting scrollRestoration to manual again when returning to this page.
|
||||
window.addEventListener('load', () => {
|
||||
setScrollRestoration('manual')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function shouldScrollToTop(route) {
|
||||
const Pages = getMatchedComponents(route)
|
||||
if (Pages.length === 1) {
|
||||
const { options = {} } = Pages[0]
|
||||
return options.scrollToTop !== false
|
||||
}
|
||||
return Pages.some(({ options }) => options && options.scrollToTop)
|
||||
}
|
||||
|
||||
export default function (to, from, savedPosition) {
|
||||
// If the returned position is falsy or an empty object, will retain current scroll position
|
||||
let position = false
|
||||
const isRouteChanged = to !== from
|
||||
|
||||
// savedPosition is only available for popstate navigations (back button)
|
||||
if (savedPosition) {
|
||||
position = savedPosition
|
||||
} else if (isRouteChanged && shouldScrollToTop(to)) {
|
||||
position = { x: 0, y: 0 }
|
||||
}
|
||||
|
||||
const nuxt = window.$nuxt
|
||||
|
||||
if (
|
||||
// Initial load (vuejs/vue-router#3199)
|
||||
!isRouteChanged ||
|
||||
// Route hash changes
|
||||
(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 {
|
||||
const el = document.querySelector(hash)
|
||||
if (el) {
|
||||
// scroll to anchor by returning the selector
|
||||
position = { selector: hash }
|
||||
// Respect any scroll-margin-top set in CSS when scrolling to anchor
|
||||
const y = Number(getComputedStyle(el)['scroll-margin-top']?.replace('px', ''))
|
||||
if (y) {
|
||||
position.offset = { y }
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to save scroll position. Please add CSS.escape() polyfill (https://github.com/mathiasbynens/CSS.escape).')
|
||||
}
|
||||
}
|
||||
resolve(position)
|
||||
})
|
||||
})
|
||||
}
|
||||
30
.nuxt/routes.json
Normal file
@@ -0,0 +1,30 @@
|
||||
[
|
||||
{
|
||||
"name": "signin",
|
||||
"path": "/signin",
|
||||
"component": "/home/loitx/dev/utopia/login/pages/signin.vue",
|
||||
"chunkName": "pages/signin",
|
||||
"_name": "_47a018a8"
|
||||
},
|
||||
{
|
||||
"name": "welcome",
|
||||
"path": "/welcome",
|
||||
"component": "/home/loitx/dev/utopia/login/pages/welcome.vue",
|
||||
"chunkName": "pages/welcome",
|
||||
"_name": "_4d08ae70"
|
||||
},
|
||||
{
|
||||
"name": "account-recovery",
|
||||
"path": "/account/recovery",
|
||||
"component": "/home/loitx/dev/utopia/login/pages/account/recovery.vue",
|
||||
"chunkName": "pages/account/recovery",
|
||||
"_name": "_53d24441"
|
||||
},
|
||||
{
|
||||
"name": "index",
|
||||
"path": "/",
|
||||
"component": "/home/loitx/dev/utopia/login/pages/index.vue",
|
||||
"chunkName": "pages/index",
|
||||
"_name": "_1d58cc90"
|
||||
}
|
||||
]
|
||||
318
.nuxt/server.js
Normal file
@@ -0,0 +1,318 @@
|
||||
import Vue from 'vue'
|
||||
import { joinURL, normalizeURL, withQuery } from 'ufo'
|
||||
import fetch from 'node-fetch-native'
|
||||
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
|
||||
}
|
||||
|
||||
if (!Vue.__original_use__) {
|
||||
Vue.__original_use__ = Vue.use
|
||||
Vue.__install_times__ = 0
|
||||
Vue.use = function (plugin, ...args) {
|
||||
plugin.__nuxt_external_installed__ = Vue._installedPlugins.includes(plugin)
|
||||
return Vue.__original_use__(plugin, ...args)
|
||||
}
|
||||
}
|
||||
if (Vue.__install_times__ === 2) {
|
||||
Vue.__install_times__ = 0
|
||||
Vue._installedPlugins = Vue._installedPlugins.filter(plugin => {
|
||||
return plugin.__nuxt_external_installed__ === true
|
||||
})
|
||||
}
|
||||
Vue.__install_times__++
|
||||
|
||||
// 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', { domProps: { id: '__nuxt' } }) })
|
||||
|
||||
const createNext = ssrContext => (opts) => {
|
||||
// If static target, render on client-side
|
||||
ssrContext.redirected = opts
|
||||
if (ssrContext.target === 'static' || !ssrContext.res) {
|
||||
ssrContext.nuxt.serverRendered = false
|
||||
return
|
||||
}
|
||||
let fullPath = withQuery(opts.path, opts.query)
|
||||
const $config = ssrContext.nuxt.config || {}
|
||||
const routerBase = ($config._app && $config._app.basePath) || '/'
|
||||
if (!fullPath.startsWith('http') && (routerBase !== '/' && !fullPath.startsWith(routerBase))) {
|
||||
fullPath = joinURL(routerBase, fullPath)
|
||||
}
|
||||
// Avoid loop redirect
|
||||
if (decodeURI(fullPath) === decodeURI(ssrContext.url)) {
|
||||
ssrContext.redirected = false
|
||||
return
|
||||
}
|
||||
ssrContext.res.writeHead(opts.status, {
|
||||
Location: normalizeURL(fullPath)
|
||||
})
|
||||
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 = []
|
||||
// for beforeSerialize(nuxtState)
|
||||
ssrContext.beforeSerializeFns = []
|
||||
// Nuxt object (window.{{globals.context}}, defaults to window.__NUXT__)
|
||||
ssrContext.nuxt = { layout: 'default', data: [], fetch: { }, error: null , state: null, serverRendered: true, routePath: ''
|
||||
}
|
||||
|
||||
ssrContext.fetchCounters = { }
|
||||
|
||||
// Remove query from url is static target
|
||||
|
||||
// Public runtime config
|
||||
ssrContext.nuxt.config = ssrContext.runtimeConfig.public
|
||||
if (ssrContext.nuxt.config._app) {
|
||||
__webpack_public_path__ = joinURL(ssrContext.nuxt.config._app.cdnURL, ssrContext.nuxt.config._app.assetsPath)
|
||||
}
|
||||
// Create the app definition and the instance (created for each request)
|
||||
const { app, router, store } = await createApp(ssrContext, ssrContext.runtimeConfig.private)
|
||||
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 })))
|
||||
|
||||
ssrContext.rendered = () => {
|
||||
// Call beforeSerialize() hooks
|
||||
ssrContext.beforeSerializeFns.forEach(fn => fn(ssrContext.nuxt))
|
||||
|
||||
// Add the state from the vuex store
|
||||
ssrContext.nuxt.state = store.state
|
||||
}
|
||||
}
|
||||
|
||||
const renderErrorPage = async () => {
|
||||
// Don't server-render the page in static target
|
||||
if (ssrContext.target === 'static') {
|
||||
ssrContext.nuxt.serverRendered = false
|
||||
}
|
||||
|
||||
// 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(app.context.route)
|
||||
|
||||
/*
|
||||
** Dispatch store nuxtServerInit
|
||||
*/
|
||||
if (store._actions && store._actions.nuxtServerInit) {
|
||||
try {
|
||||
await store.dispatch('nuxtServerInit', app.context)
|
||||
} catch (err) {
|
||||
console.debug('Error occurred when calling nuxtServerInit: ', err.message)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
// ...If there is a redirect or an error, stop the process
|
||||
if (ssrContext.redirected) {
|
||||
return noopApp()
|
||||
}
|
||||
if (ssrContext.nuxt.error) {
|
||||
return renderErrorPage()
|
||||
}
|
||||
|
||||
/*
|
||||
** 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) {
|
||||
// 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)
|
||||
.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
|
||||
}
|
||||
65
.nuxt/store.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
let store = {};
|
||||
|
||||
(function updateModules () {
|
||||
store = normalizeRoot(require('../store/index.js'), 'store/index.js')
|
||||
|
||||
// If store is an exported method = classic mode (deprecated)
|
||||
|
||||
if (typeof store === 'function') {
|
||||
return console.warn('Classic mode for store/ is deprecated and will be removed in Nuxt 3.')
|
||||
}
|
||||
|
||||
// Enforce store modules
|
||||
store.modules = store.modules || {}
|
||||
|
||||
// If the environment supports hot reloading...
|
||||
|
||||
if (process.client && module.hot) {
|
||||
// Whenever any Vuex module is updated...
|
||||
module.hot.accept([
|
||||
'../store/index.js',
|
||||
], () => {
|
||||
// Update `root.modules` with the latest definitions.
|
||||
updateModules()
|
||||
// Trigger a hot update in the store.
|
||||
window.$nuxt.$store.hotUpdate(store)
|
||||
})
|
||||
}
|
||||
})()
|
||||
|
||||
// createStore
|
||||
export const createStore = store instanceof Function ? store : () => {
|
||||
return new Vuex.Store(Object.assign({
|
||||
strict: (process.env.NODE_ENV !== 'production')
|
||||
}, store))
|
||||
}
|
||||
|
||||
function normalizeRoot (moduleData, filePath) {
|
||||
moduleData = moduleData.default || moduleData
|
||||
|
||||
if (moduleData.commit) {
|
||||
throw new Error(`[nuxt] ${filePath} should export a method that returns a Vuex instance.`)
|
||||
}
|
||||
|
||||
if (typeof moduleData !== 'function') {
|
||||
// Avoid TypeError: setting a property that has only a getter when overwriting top level keys
|
||||
moduleData = Object.assign({}, moduleData)
|
||||
}
|
||||
return normalizeModule(moduleData, filePath)
|
||||
}
|
||||
|
||||
function normalizeModule (moduleData, filePath) {
|
||||
if (moduleData.state && typeof moduleData.state !== 'function') {
|
||||
console.warn(`'state' should be a method that returns an object in ${filePath}`)
|
||||
|
||||
const state = Object.assign({}, moduleData.state)
|
||||
// Avoid TypeError: setting a property that has only a getter when overwriting top level keys
|
||||
moduleData = Object.assign({}, moduleData, { state: () => state })
|
||||
}
|
||||
return moduleData
|
||||
}
|
||||
634
.nuxt/utils.js
Normal file
@@ -0,0 +1,634 @@
|
||||
import Vue from 'vue'
|
||||
import { isSamePath as _isSamePath, joinURL, normalizeURL, withQuery, withoutTrailingSlash } from 'ufo'
|
||||
|
||||
// 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 createGetCounter (counterObject, defaultKey = '') {
|
||||
return function getCounter (id = defaultKey) {
|
||||
if (counterObject[id] === undefined) {
|
||||
counterObject[id] = 0
|
||||
}
|
||||
return counterObject[id]++
|
||||
}
|
||||
}
|
||||
|
||||
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 purifyData(data) {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
return data
|
||||
}
|
||||
|
||||
return Object.entries(data).filter(
|
||||
([key, value]) => {
|
||||
const valid = !(value instanceof Function) && !(value instanceof Promise)
|
||||
if (!valid) {
|
||||
console.warn(`${key} is not able to be stringified. This will break in a production environment.`)
|
||||
}
|
||||
return valid
|
||||
}
|
||||
).reduce((obj, [key, value]) => {
|
||||
obj[key] = value
|
||||
return obj
|
||||
}, {})
|
||||
}
|
||||
export function getChildrenComponentInstancesUsingFetch(vm, instances = []) {
|
||||
const children = vm.$children || []
|
||||
for (const child of children) {
|
||||
if (child.$fetch) {
|
||||
instances.push(child)
|
||||
}
|
||||
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) {
|
||||
try {
|
||||
Component = await Component()
|
||||
} catch (error) {
|
||||
// Handle webpack chunk loading errors
|
||||
// This may be due to a new deployment or a network problem
|
||||
if (
|
||||
error &&
|
||||
error.name === 'ChunkLoadError' &&
|
||||
typeof window !== 'undefined' &&
|
||||
window.sessionStorage
|
||||
) {
|
||||
const timeNow = Date.now()
|
||||
const previousReloadTime = parseInt(window.sessionStorage.getItem('nuxt-reload'))
|
||||
|
||||
// check for previous reload time not to reload infinitely
|
||||
if (!previousReloadTime || previousReloadTime + 60000 < timeNow) {
|
||||
window.sessionStorage.setItem('nuxt-reload', timeNow)
|
||||
window.location.reload(true /* skip cache */)
|
||||
}
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
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,
|
||||
store: app.store,
|
||||
payload: context.payload,
|
||||
error: context.error,
|
||||
base: app.router.options.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 = withQuery(path, query)
|
||||
if (process.server) {
|
||||
app.context.next({
|
||||
path,
|
||||
status
|
||||
})
|
||||
}
|
||||
if (process.client) {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Location/assign
|
||||
window.location.assign(path)
|
||||
|
||||
// Throw a redirect error
|
||||
throw new Error('ERR_REDIRECT')
|
||||
}
|
||||
}
|
||||
}
|
||||
if (process.server) {
|
||||
app.context.beforeNuxtRender = fn => context.beforeRenderFns.push(fn)
|
||||
app.context.beforeSerialize = fn => context.beforeSerializeFns.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
|
||||
}
|
||||
|
||||
if (context.error) {
|
||||
app.context.error = context.error
|
||||
}
|
||||
|
||||
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, renderState) {
|
||||
if (!promises.length || appContext._redirected || appContext._errored || (renderState && renderState.aborted)) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
return promisify(promises[0], appContext)
|
||||
.then(() => {
|
||||
return middlewareSeries(promises.slice(1), appContext, renderState)
|
||||
})
|
||||
}
|
||||
|
||||
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) {
|
||||
if (mode === 'hash') {
|
||||
return window.location.hash.replace(/^#\//, '')
|
||||
}
|
||||
|
||||
base = decodeURI(base).slice(0, -1) // consideration is base is normalized with trailing slash
|
||||
let path = decodeURI(window.location.pathname)
|
||||
|
||||
if (base && path.startsWith(base)) {
|
||||
path = path.slice(base.length)
|
||||
}
|
||||
|
||||
const fullPath = (path || '/') + window.location.search + window.location.hash
|
||||
|
||||
return normalizeURL(fullPath)
|
||||
}
|
||||
|
||||
// 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'
|
||||
}
|
||||
|
||||
export function addLifecycleHook(vm, hook, fn) {
|
||||
if (!vm.$options[hook]) {
|
||||
vm.$options[hook] = []
|
||||
}
|
||||
if (!vm.$options[hook].includes(fn)) {
|
||||
vm.$options[hook].push(fn)
|
||||
}
|
||||
}
|
||||
|
||||
export const urlJoin = joinURL
|
||||
|
||||
export const stripTrailingSlash = withoutTrailingSlash
|
||||
|
||||
export const isSamePath = _isSamePath
|
||||
|
||||
export function setScrollRestoration (newVal) {
|
||||
try {
|
||||
window.history.scrollRestoration = newVal;
|
||||
} catch(e) {}
|
||||
}
|
||||
50
.nuxt/vetur/tags.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"Caption": {
|
||||
"description": "Auto imported from components/Caption.vue"
|
||||
},
|
||||
"Logo": {
|
||||
"description": "Auto imported from components/Logo.vue"
|
||||
},
|
||||
"Modal": {
|
||||
"description": "Auto imported from components/Modal.vue"
|
||||
},
|
||||
"SearchBox": {
|
||||
"description": "Auto imported from components/SearchBox.vue"
|
||||
},
|
||||
"SvgIcon": {
|
||||
"description": "Auto imported from components/SvgIcon.vue"
|
||||
},
|
||||
"TopMenu": {
|
||||
"description": "Auto imported from components/TopMenu.vue"
|
||||
},
|
||||
"DialogConfirm": {
|
||||
"description": "Auto imported from components/dialog/Confirm.vue"
|
||||
},
|
||||
"DialogCountDown": {
|
||||
"description": "Auto imported from components/dialog/CountDown.vue"
|
||||
},
|
||||
"DialogDelete": {
|
||||
"description": "Auto imported from components/dialog/Delete.vue"
|
||||
},
|
||||
"DialogError": {
|
||||
"description": "Auto imported from components/dialog/Error.vue"
|
||||
},
|
||||
"DialogInfo": {
|
||||
"description": "Auto imported from components/dialog/Info.vue"
|
||||
},
|
||||
"DialogSuccess": {
|
||||
"description": "Auto imported from components/dialog/Success.vue"
|
||||
},
|
||||
"SnackbarError": {
|
||||
"description": "Auto imported from components/snackbar/Error.vue"
|
||||
},
|
||||
"SnackbarInfo": {
|
||||
"description": "Auto imported from components/snackbar/Info.vue"
|
||||
},
|
||||
"SnackbarSnackBar": {
|
||||
"description": "Auto imported from components/snackbar/SnackBar.vue"
|
||||
},
|
||||
"SnackbarSuccess": {
|
||||
"description": "Auto imported from components/snackbar/Success.vue"
|
||||
}
|
||||
}
|
||||
9
.nuxt/views/app.template.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html {{ HTML_ATTRS }}>
|
||||
<head {{ HEAD_ATTRS }}>
|
||||
{{ HEAD }}
|
||||
</head>
|
||||
<body {{ BODY_ATTRS }}>
|
||||
{{ APP }}
|
||||
</body>
|
||||
</html>
|
||||
23
.nuxt/views/error.html
Normal file
@@ -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</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
7
Dockerfile
Executable file
@@ -0,0 +1,7 @@
|
||||
FROM node:18-alpine
|
||||
WORKDIR /src
|
||||
COPY . /src
|
||||
#RUN rm -rf node_modules
|
||||
RUN npm install
|
||||
RUN npm run build
|
||||
RUN npm install pm2 -g
|
||||
69
README.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# store
|
||||
|
||||
## Build Setup
|
||||
|
||||
```bash
|
||||
# install dependencies
|
||||
$ npm install
|
||||
|
||||
# serve with hot reload at localhost:3000
|
||||
$ npm run dev
|
||||
|
||||
# build for production and launch server
|
||||
$ npm run build
|
||||
$ npm run start
|
||||
|
||||
# generate static project
|
||||
$ npm run generate
|
||||
```
|
||||
|
||||
For detailed explanation on how things work, check out the [documentation](https://nuxtjs.org).
|
||||
|
||||
## Special Directories
|
||||
|
||||
You can create the following extra directories, some of which have special behaviors. Only `pages` is required; you can delete them if you don't want to use their functionality.
|
||||
|
||||
### `assets`
|
||||
|
||||
The assets directory contains your uncompiled assets such as Stylus or Sass files, images, or fonts.
|
||||
|
||||
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/assets).
|
||||
|
||||
### `components`
|
||||
|
||||
The components directory contains your Vue.js components. Components make up the different parts of your page and can be reused and imported into your pages, layouts and even other components.
|
||||
|
||||
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/components).
|
||||
|
||||
### `layouts`
|
||||
|
||||
Layouts are a great help when you want to change the look and feel of your Nuxt app, whether you want to include a sidebar or have distinct layouts for mobile and desktop.
|
||||
|
||||
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/layouts).
|
||||
|
||||
|
||||
### `pages`
|
||||
|
||||
This directory contains your application views and routes. Nuxt will read all the `*.vue` files inside this directory and setup Vue Router automatically.
|
||||
|
||||
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/get-started/routing).
|
||||
|
||||
### `plugins`
|
||||
|
||||
The plugins directory contains JavaScript plugins that you want to run before instantiating the root Vue.js Application. This is the place to add Vue plugins and to inject functions or constants. Every time you need to use `Vue.use()`, you should create a file in `plugins/` and add its path to plugins in `nuxt.config.js`.
|
||||
|
||||
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/plugins).
|
||||
|
||||
### `static`
|
||||
|
||||
This directory contains your static files. Each file inside this directory is mapped to `/`.
|
||||
|
||||
Example: `/static/robots.txt` is mapped as `/robots.txt`.
|
||||
|
||||
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/static).
|
||||
|
||||
### `store`
|
||||
|
||||
This directory contains your Vuex store files. Creating a file in this directory automatically activates Vuex.
|
||||
|
||||
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/store).
|
||||
47
assets/styles/main.scss
Executable file
@@ -0,0 +1,47 @@
|
||||
// Import Bulma's core
|
||||
@import "~bulma/sass/utilities/_all";
|
||||
|
||||
// Set your colors
|
||||
$primary: #107FFB; // #4285F4; // #0F9D58; // #009047;
|
||||
$primary-invert: findColorInvert($primary);
|
||||
$twitter: #3392ec;
|
||||
$twitter-invert: findColorInvert($twitter);
|
||||
$findata: #ff8829; //#F4F7F8;
|
||||
$findata-invert: findColorInvert($findata);
|
||||
$sidebar-width: 35%;
|
||||
//$family-primary: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
$family-primary: Arial, sans-serif;
|
||||
|
||||
// Setup $colors to use as bulma classes (e.g. 'is-twitter')s
|
||||
$colors: (
|
||||
"white": ($white, $black),
|
||||
"black": ($black, $white),
|
||||
"light": ($light, $light-invert),
|
||||
"dark": ($dark, $dark-invert),
|
||||
"primary": ($primary, $primary-invert),
|
||||
"info": ($info, $info-invert),
|
||||
"link": ($link, $link-invert),
|
||||
"success": ($success, $success-invert),
|
||||
"warning": ($warning, $warning-invert),
|
||||
"danger": ($danger, $danger-invert),
|
||||
"twitter": ($twitter, $twitter-invert),
|
||||
"findata": ($findata, $findata-invert)
|
||||
);
|
||||
|
||||
// Links
|
||||
$link: $primary;
|
||||
$link-invert: $primary-invert;
|
||||
$link-focus-border: $primary;
|
||||
$body-family: $family-primary;
|
||||
$body-size: 15px !default;
|
||||
|
||||
$site-color: hsl(0, 0%, 14%);
|
||||
$body-color: $site-color;
|
||||
|
||||
$tabborder: #107FFB; //#4285F4;
|
||||
$tabs-boxed-link-active-border-color: $tabborder;
|
||||
$tabs-border-bottom-color: $tabborder;
|
||||
$tabs-link-active-color: $tabborder;
|
||||
|
||||
// Import Bulma and Buefy styles
|
||||
@import "~bulma";
|
||||
330
assets/styles/style.scss
Executable file
@@ -0,0 +1,330 @@
|
||||
@import "~bulma/sass/utilities/_all";
|
||||
$color:(
|
||||
primary: #4285F4,
|
||||
findata: #ff8829,
|
||||
white: #FFFFFF,
|
||||
dark: #686868
|
||||
);
|
||||
$size: (
|
||||
one: 14.5px,
|
||||
two: 17px,
|
||||
three: 30px,
|
||||
four: 40px,
|
||||
five: 50px,
|
||||
six: 60px
|
||||
);
|
||||
@mixin cbox($width, $height, $font, $background) {
|
||||
display:flex;
|
||||
width: $width;
|
||||
height: $height;
|
||||
border: 1.5px solid #ff8829;
|
||||
font-size: $font;
|
||||
-webkit-border-radius: 50%;
|
||||
-moz-border-radius: 50%;
|
||||
-ms-border-radius: 50%;
|
||||
border-radius: 50%;
|
||||
color: #ff8829;
|
||||
font-weight: bold;
|
||||
background-color: $background;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
@each $name, $hex in $color {
|
||||
@each $n, $v in $size {
|
||||
.cbox-#{$name}-#{$n}{
|
||||
@include cbox($v*2, $v*2, $v*1.1, white);
|
||||
border-color: $hex ;
|
||||
color: $hex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@each $name, $hex in $color {
|
||||
@each $n, $v in $size {
|
||||
.cbox-fill-#{$name}-#{$n}{
|
||||
@include cbox($v*2, $v*2, $v, $hex);
|
||||
border-color: $hex ;
|
||||
color: findColorInvert($hex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@for $i from 10 through 40 {
|
||||
.fs-#{$i} {
|
||||
font-size: $i + px;
|
||||
}
|
||||
}
|
||||
@for $i from 10 through 40 {
|
||||
.fsb-#{$i} {
|
||||
font-size: $i + px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
.number-circle {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border: 1.5px solid #ff8829;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 60px;
|
||||
}
|
||||
|
||||
.number-circle-1 {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin: auto;
|
||||
border: 1.5px solid #E8E8E8;;
|
||||
-webkit-border-radius: 50%;
|
||||
-moz-border-radius: 50%;
|
||||
-ms-border-radius: 50%;
|
||||
border-radius: 50%;
|
||||
padding: 0;
|
||||
display: table;
|
||||
font-size: 32px;
|
||||
line-height: 32px;
|
||||
background-color: #E8E8E8;
|
||||
}
|
||||
|
||||
.close-button{
|
||||
float:right;
|
||||
cursor: pointer;
|
||||
line-height: 20px;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.text-image {
|
||||
position: absolute; /* Position the background text */
|
||||
bottom: 0; /* At the bottom. Use top:0 to append it to the top */
|
||||
background: rgb(0, 0, 0); /* Fallback color */
|
||||
background: rgba(0, 0, 0, 0.5); /* Black background with 0.5 opacity */
|
||||
color: #f1f1f1; /* Grey text */
|
||||
width: 100%; /* Full width */
|
||||
padding: 10px; /* Some padding */
|
||||
}
|
||||
.btn-circle {
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
line-height: 36px;
|
||||
font-size: 24px;
|
||||
border-radius: 50%;
|
||||
background-color: white;
|
||||
color: red;
|
||||
text-align: center;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
z-index: 1;
|
||||
box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2), 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
.tagborder {
|
||||
border: 1px solid hsl(0, 0%, 88%);
|
||||
font-size: 13px !important;
|
||||
color: hsl(0, 0%, 21%) !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.border-bottom {
|
||||
border-bottom: 1px solid hsl(0, 0%, 88%);
|
||||
}
|
||||
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.vertical-center {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.header-logo{
|
||||
background: url('/symbol.png') no-repeat center center;
|
||||
background-size: 68px;
|
||||
width: 60px
|
||||
}
|
||||
|
||||
.header-logo-main{
|
||||
background: url('/logo1.png') no-repeat center center;
|
||||
background-size: 140px;
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
.header-logo-main1{
|
||||
background: url('/logo1.png') no-repeat center center;
|
||||
background-size: 120px;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.hyperlink {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
.hyperlink:hover{
|
||||
color: #4285F4 !important;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.has-background-ceiling {background-color: #d602e3;}
|
||||
.has-background-floor {background-color: #08cfda;}
|
||||
.has-background-up {background-color: #09b007;}
|
||||
.has-background-down {background-color: #df0325; }
|
||||
.has-background-ref {background-color: #ff8829;}
|
||||
.has-text-ceiling {color: #ff25ff !important;}
|
||||
.has-text-floor {color: #1eeeee !important;}
|
||||
.has-text-up {color: #0f0 !important;}
|
||||
.has-text-down {color: #ff3737 !important;}
|
||||
.has-text-ref {color:#ffd900 !important;}
|
||||
|
||||
/* scrollbar */
|
||||
:root {
|
||||
--code-color: darkred;
|
||||
--code-bg-color: #696969;
|
||||
--code-font-size: 16px;
|
||||
--code-line-height: 1.4;
|
||||
--scroll-bar-color: #4285F4; //#696969;
|
||||
--scroll-bar-bg-color: #f6f6f6;
|
||||
}
|
||||
|
||||
pre {
|
||||
color: var(--code-color);
|
||||
font-size: var(--code-font-size);
|
||||
line-height: var(--code-line-height);
|
||||
background-color: var(--code-bg-color);
|
||||
}
|
||||
|
||||
.code-block {
|
||||
max-height: 100px;
|
||||
overflow: auto;
|
||||
padding: 8px 7px 5px 15px;
|
||||
margin: 0px 0px 0px 0px;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner { background: rgba(0,0,0,0.5); }
|
||||
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--scroll-bar-color) var(--scroll-bar-bg-color);
|
||||
}
|
||||
|
||||
/* Works on Chrome, Edge, and Safari */
|
||||
*::-webkit-scrollbar {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-track {
|
||||
background: var(--scroll-bar-bg-color);
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background-color: var(--scroll-bar-color);
|
||||
border-radius: 20px;
|
||||
border: 3px solid var(--scroll-bar-bg-color);
|
||||
}
|
||||
|
||||
@mixin pulsating($color) {
|
||||
position: absolute;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 300%;
|
||||
height: 300%;
|
||||
box-sizing: border-box;
|
||||
margin-left: -100%;
|
||||
margin-top: -100%;
|
||||
border-radius: 45px;
|
||||
background-color: $color;
|
||||
animation: pulse-ring 1.25s cubic-bezier(0.215, 0.61, 0.355, 1) infinite;
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: $color;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 0 8px rgba(0,0,0,.3);
|
||||
animation: pulse-dot 1.25s cubic-bezier(0.455, 0.03, 0.515, 0.955) -.4s infinite;
|
||||
}
|
||||
}
|
||||
|
||||
.pulsating-red {
|
||||
@include pulsating(#FF0000)
|
||||
}
|
||||
|
||||
.pulsating-yellow {
|
||||
@include pulsating(#E96F1D)
|
||||
}
|
||||
|
||||
.pulsating-blue {
|
||||
@include pulsating(#09B412)
|
||||
}
|
||||
|
||||
.pulsating-circle {
|
||||
position: absolute;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 300%;
|
||||
height: 300%;
|
||||
box-sizing: border-box;
|
||||
margin-left: -100%;
|
||||
margin-top: -100%;
|
||||
border-radius: 45px;
|
||||
background-color: #FF0000;
|
||||
animation: pulse-ring 1.25s cubic-bezier(0.215, 0.61, 0.355, 1) infinite;
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #FF0000;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 0 8px rgba(0,0,0,.3);
|
||||
animation: pulse-dot 1.25s cubic-bezier(0.455, 0.03, 0.515, 0.955) -.4s infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse-ring {
|
||||
0% {
|
||||
transform: scale(.33);
|
||||
}
|
||||
80%, 100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse-dot {
|
||||
0% {
|
||||
transform: scale(.8);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1);
|
||||
}
|
||||
100% {
|
||||
transform: scale(.8);
|
||||
}
|
||||
}
|
||||
6
build.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
#python3 envprod.py
|
||||
PROJECT="utopia"
|
||||
IMAGE="login"
|
||||
|
||||
docker build -t docker.bigdatatech.vn/$PROJECT/$IMAGE:latest .
|
||||
docker push docker.bigdatatech.vn/$PROJECT/$IMAGE:latest
|
||||
6
builddev.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
#python3 envprod.py
|
||||
PROJECT="utopia"
|
||||
IMAGE="dev-login"
|
||||
|
||||
docker build -t docker.bigdatatech.vn/$PROJECT/$IMAGE:latest .
|
||||
docker push docker.bigdatatech.vn/$PROJECT/$IMAGE:latest
|
||||
13
components/Caption.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<div class="mb-2">
|
||||
<span :class="`icon-text fsb-${size||17} ${type || 'has-text-findata'}`">
|
||||
<b class="mr-1">{{ title }}</b>
|
||||
<SvgIcon v-bind="{name: 'right.svg', type: type? type.replace('has-text-', '') : null, size: (size>=30? size*0.7 : size) || 20, alt: 'Arrow'}"></SvgIcon>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['type', 'size', 'title']
|
||||
}
|
||||
</script>
|
||||
7
components/Logo.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<a @click="$router.push('/signin')">
|
||||
<img width="90px" src="/logo.png" />
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
48
components/Modal.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<div class="modal is-active" @click="doClick">
|
||||
<div class="modal-background" :style="`opacity:${count===0? 0.7 : 0.3} !important;'`"></div>
|
||||
<div class="modal-card" :id="docid" :style="$store.state.ismobile? '' : `width:${width? width : '60%'}`">
|
||||
<header class="modal-card-head my-0 pt-4 pb-3" v-if="title">
|
||||
<p class="modal-card-title fsb-20 py-0 my-0" v-html="title"></p>
|
||||
</header>
|
||||
<section class="modal-card-body" :style="`min-height:${height? height : '700px'}`">
|
||||
<component :is="compobj" v-bind="vbind" @modalevent="modalEvent" @close="$emit('close')"></component>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['component', 'width', 'height', 'vbind', 'title'],
|
||||
data() {
|
||||
return {
|
||||
docid: this.$id(),
|
||||
count: 0
|
||||
}
|
||||
},
|
||||
created() {
|
||||
window.addEventListener('keydown', (e) => {if(e.key==='Escape') this.$emit('close')})
|
||||
const collection = document.getElementsByClassName("modal-background")
|
||||
this.count = collection.length
|
||||
},
|
||||
computed: {
|
||||
compobj() {
|
||||
return () => import(`@/components/${this.component}`)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
modalEvent(ev) {
|
||||
this.$emit(ev.name, ev.data)
|
||||
},
|
||||
doClick(e) {
|
||||
//e.stopPropagation()
|
||||
if(!e.srcElement.offsetParent) return
|
||||
if(document.getElementById(this.docid)) {
|
||||
if(!document.getElementById(this.docid).contains(e.target)) {
|
||||
this.$emit('close')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
67
components/SearchBox.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-autocomplete
|
||||
placeholder=""
|
||||
icon="magnify"
|
||||
expanded
|
||||
:data="data"
|
||||
:open-on-focus="first? true : false"
|
||||
keep-first
|
||||
:field="field"
|
||||
v-model="value"
|
||||
@typing="beginSearch"
|
||||
@select="option => doSelect(option)">
|
||||
<template slot-scope="props">
|
||||
<p>{{ props.option[field] }}</p>
|
||||
</template>
|
||||
<template slot="empty">Không có giá trị thỏa mãn</template>
|
||||
</b-autocomplete>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['api', 'field', 'column', 'first', 'optionid', 'filter', 'storeval'],
|
||||
data() {
|
||||
return {
|
||||
search: undefined,
|
||||
data: [],
|
||||
timer: undefined,
|
||||
value: undefined,
|
||||
selected: undefined,
|
||||
params: this.$findapi(this.api)['params']
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
if(this.first) {
|
||||
this.data = await this.$getdata(this.api, this.filter)
|
||||
if(this.optionid) this.selected = this.$find(this.data, {id: this.optionid})
|
||||
} else if(this.optionid) {
|
||||
this.selected = await this.$getdata(this.api, {id: this.optionid.id}, undefined, true)
|
||||
}
|
||||
if(this.selected) this.doSelect(this.selected)
|
||||
},
|
||||
methods: {
|
||||
doSelect(option) {
|
||||
if(this.$empty(option)) return
|
||||
this.$emit('option', option)
|
||||
this.selected = option
|
||||
this.value = this.selected[this.field]
|
||||
},
|
||||
async getApi(val) {
|
||||
let text = val? val.toLowerCase() : ''
|
||||
let f = {}
|
||||
this.column.map(v=>{
|
||||
f[`${v}__icontains`] = text
|
||||
})
|
||||
this.params.filter_or = f
|
||||
if(this.filter) this.params.filter = this.$copy(this.filter)
|
||||
this.data = await this.$getdata(this.api, undefined, this.params)
|
||||
},
|
||||
beginSearch(val) {
|
||||
this.search = val
|
||||
if (this.timer) clearTimeout(this.timer)
|
||||
this.timer = setTimeout(() => this.getApi(val), 150)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
40
components/SvgIcon.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<nuxt-img loading="lazy" :alt="alt" :class="`svg-${type || 'findata'}`" :style="`width: ${size || 26}px;`" :src="`/icon/${name || 'check.svg'}`"/>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['name', 'size', 'type', 'alt']
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.svg-primary {
|
||||
filter: invert(39%) sepia(86%) saturate(1828%) hue-rotate(96deg) brightness(104%) contrast(92%);
|
||||
}
|
||||
.svg-danger {
|
||||
filter: brightness(0) saturate(100%) invert(16%) sepia(100%) saturate(2983%) hue-rotate(351deg) brightness(101%) contrast(131%);
|
||||
}
|
||||
.svg-findata {
|
||||
filter: brightness(0) saturate(100%) invert(61%) sepia(85%) saturate(1880%) hue-rotate(340deg) brightness(101%) contrast(101%);
|
||||
}
|
||||
.svg-blue {
|
||||
filter: invert(9%) sepia(98%) saturate(7147%) hue-rotate(248deg) brightness(94%) contrast(145%);
|
||||
}
|
||||
.svg-green {
|
||||
filter: invert(86%) sepia(27%) saturate(7338%) hue-rotate(49deg) brightness(110%) contrast(110%);
|
||||
}
|
||||
.svg-grey {
|
||||
filter: invert(100%) sepia(0%) saturate(213%) hue-rotate(133deg) brightness(86%) contrast(93%);
|
||||
}
|
||||
.svg-gray {
|
||||
filter: invert(35%) sepia(10%) saturate(18%) hue-rotate(338deg) brightness(97%) contrast(82%);
|
||||
}
|
||||
.svg-gray1 {
|
||||
filter: invert(34%) sepia(6%) saturate(71%) hue-rotate(315deg) brightness(95%) contrast(88%);
|
||||
}
|
||||
.svg-white {
|
||||
filter: brightness(0) saturate(100%) invert(100%) sepia(2%) saturate(0%) hue-rotate(101deg) brightness(102%) contrast(102%);
|
||||
}
|
||||
.svg-black {
|
||||
filter: invert(0%) sepia(100%) saturate(0%) hue-rotate(235deg) brightness(107%) contrast(107%);
|
||||
}
|
||||
</style>
|
||||
22
components/TopMenu.vue
Executable file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<html class="has-navbar-fixed-top">
|
||||
<nav class="navbar is-fixed-top is-spaced has-shadow py-0" role="navigation">
|
||||
<div class="navbar-brand">
|
||||
<nuxt-link class="navbar-item header-logo-main" to="/"></nuxt-link>
|
||||
<a role="button" class="navbar-burger" data-target="nav-menu" ref="burger" @click="$responsiveMenu()">
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
<div class="navbar-item">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</html>
|
||||
</template>
|
||||
<script>
|
||||
</script>
|
||||
32
components/dialog/Confirm.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<div>
|
||||
<p v-html="content"></p>
|
||||
<p class="border-bottom mt-3 mb-5"></p>
|
||||
<div class="field is-grouped">
|
||||
<div class="control is-expanded">
|
||||
<button class="button is-primary" @click="confirm()">Confirm</button>
|
||||
<button class="button is-dark ml-5" @click="cancel()">Cancel</button>
|
||||
</div>
|
||||
<div class="control" v-if="duration">
|
||||
<CountDown v-bind="{duration: duration}"></CountDown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['content', 'duration'],
|
||||
methods: {
|
||||
cancel() {
|
||||
this.$store.commit('updateStore', {name: 'showmodal', data: undefined})
|
||||
this.$emit('close')
|
||||
},
|
||||
confirm() {
|
||||
let data = {action: 'confirm', time: new Date()}
|
||||
this.$store.commit('updateStore', {name: 'action', data: data})
|
||||
this.$emit('modalevent', {name: 'confirm'})
|
||||
this.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
76
components/dialog/CountDown.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div id="countdown">
|
||||
<div id="countdown-number"></div>
|
||||
<svg><circle r="18" cx="20" cy="20" color="red"></circle></svg>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['duration'],
|
||||
data() {
|
||||
return {
|
||||
timer: undefined,
|
||||
countdown: this.duration || 10
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
var countdownNumberEl = document.getElementById('countdown-number')
|
||||
countdownNumberEl.textContent = this.countdown;
|
||||
this.timer = setInterval(()=>this.startCount(), 1000)
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearInterval(this.timer)
|
||||
},
|
||||
methods: {
|
||||
startCount() {
|
||||
this.countdown -= 1
|
||||
var countdownNumberEl = document.getElementById('countdown-number')
|
||||
countdownNumberEl.textContent = this.countdown;
|
||||
if(this.countdown===0) {
|
||||
clearInterval(this.timer)
|
||||
this.$store.commit('updateStore', {name: 'showmodal', data: undefined})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
#countdown {
|
||||
position: relative;
|
||||
margin: auto;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
#countdown-number {
|
||||
color: black;
|
||||
display: inline-block;
|
||||
line-height: 40px;
|
||||
}
|
||||
svg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
transform: rotateY(-180deg) rotateZ(-90deg);
|
||||
}
|
||||
svg circle {
|
||||
stroke-dasharray: 113px;
|
||||
stroke-dashoffset: 0px;
|
||||
stroke-linecap: round;
|
||||
stroke-width: 2px;
|
||||
stroke: black;
|
||||
fill: none;
|
||||
animation: countdown 10s linear infinite forwards;
|
||||
}
|
||||
|
||||
@keyframes countdown {
|
||||
from {
|
||||
stroke-dashoffset: 0px;
|
||||
}
|
||||
to {
|
||||
stroke-dashoffset: 113px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
42
components/dialog/Delete.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div>
|
||||
<p v-html="content"></p>
|
||||
<p class="border-bottom mt-4 mb-5"></p>
|
||||
<div class="field is-grouped">
|
||||
<div class="control is-expanded">
|
||||
<button class="button is-danger" @click="remove()">Confirm</button>
|
||||
<button class="button is-dark ml-5" @click="cancel()">Cancel</button>
|
||||
</div>
|
||||
<div class="control" v-if="duration">
|
||||
<CountDown v-bind="{duration: duration}"></CountDown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['content', 'duration', 'vbind'],
|
||||
methods: {
|
||||
cancel() {
|
||||
this.$store.commit('updateStore', {name: 'showmodal', data: undefined})
|
||||
},
|
||||
async remove() {
|
||||
let pagename = this.vbind.pagename
|
||||
let pagedata = this.$store.state[pagename]
|
||||
let name = pagedata.origin_api.name
|
||||
let id = this.vbind.row.id
|
||||
let result = await this.$deleteapi(name, id)
|
||||
if(result==='error') return
|
||||
this.$snackbar('The data has been deleted from the system.', undefined, 'Success')
|
||||
let arr = Array.isArray(id)? id : [{id: id}]
|
||||
let copy = this.$copy(this.$store.state[pagename].data)
|
||||
arr.map(x=>{
|
||||
let index = copy.findIndex(v=>v.id===x.id)
|
||||
index>=0? this.$delete(copy,index) : false
|
||||
})
|
||||
this.$store.commit('updateState', {name: pagename, key: 'update', data: {data: copy}})
|
||||
this.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
29
components/dialog/Error.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="field is-grouped">
|
||||
<div class="control is-expanded pr-3" v-html="content"></div>
|
||||
<div class="control">
|
||||
<span class="material-symbols-outlined has-text-danger fs-36">error</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="border-bottom mt-3 mb-5"></p>
|
||||
<div class="field is-grouped">
|
||||
<div class="control is-expanded">
|
||||
<button class="button is-danger" @click="cancel()">Close</button>
|
||||
</div>
|
||||
<div class="control" v-if="duration">
|
||||
<CountDown v-bind="{duration: duration}"></CountDown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['content', 'duration'],
|
||||
methods: {
|
||||
cancel() {
|
||||
this.$store.commit('updateStore', {name: 'showmodal', data: undefined})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
24
components/dialog/Info.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div>
|
||||
<p v-html="content"></p>
|
||||
<p class="border-bottom mt-3 mb-5"></p>
|
||||
<div class="field is-grouped">
|
||||
<div class="control is-expanded">
|
||||
<button class="button is-dark" @click="cancel()">Close</button>
|
||||
</div>
|
||||
<div class="control" v-if="duration">
|
||||
<CountDown v-bind="{duration: duration}"></CountDown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['content', 'duration'],
|
||||
methods: {
|
||||
cancel() {
|
||||
this.$store.commit('updateStore', {name: 'showmodal', data: undefined})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
30
components/dialog/Success.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="field is-grouped">
|
||||
<div class="control is-expanded pr-3" v-html="content"></div>
|
||||
<div class="control">
|
||||
<span class="material-symbols-outlined has-text-primary fs-36">check_circle</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<p class="border-bottom mt-3 mb-5"></p>
|
||||
<div class="field is-grouped">
|
||||
<div class="control is-expanded">
|
||||
<button class="button is-primary" @click="cancel()">Close</button>
|
||||
</div>
|
||||
<div class="control" v-if="duration">
|
||||
<CountDown v-bind="{duration: duration}"></CountDown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['content', 'duration'],
|
||||
methods: {
|
||||
cancel() {
|
||||
this.$store.commit('updateStore', {name: 'showmodal', data: undefined})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
21
components/snackbar/Error.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div>
|
||||
<Caption v-bind="{title: 'Lỗi', type: 'has-text-findata'}"></Caption>
|
||||
<div class="field is-grouped mb-0">
|
||||
<div class="control is-expanded pr-3" v-html="content"></div>
|
||||
<div class="control">
|
||||
<span class="material-symbols-outlined has-text-findata fs-34">error</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['content', 'duration'],
|
||||
methods: {
|
||||
cancel() {
|
||||
this.$store.commit('updateStore', {name: 'showmodal', data: undefined})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
15
components/snackbar/Info.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<div>
|
||||
<p v-html="content"></p>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['content', 'duration'],
|
||||
methods: {
|
||||
cancel() {
|
||||
this.$store.commit('updateStore', {name: 'showmodal', data: undefined})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
77
components/snackbar/SnackBar.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="show" style="max-width: 500px;">
|
||||
<component :is="compobj" v-bind="vbind" @close="$emit('close')"></component>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['component', 'width', 'height', 'vbind', 'title'],
|
||||
data() {
|
||||
return {
|
||||
timer: undefined
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
setTimeout(()=>this.snackbar = undefined, 2900)
|
||||
},
|
||||
computed: {
|
||||
snackbar: {
|
||||
get: function() {return this.$store.state['snackbar']},
|
||||
set: function(val) {this.$store.commit('updateStore', {name: 'snackbar', data: val})}
|
||||
},
|
||||
compobj() {
|
||||
return () => import(`@/components/${this.component}`)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
show() {
|
||||
var x = document.getElementById("snackbar");
|
||||
x.className = x.className.replace("show", "")
|
||||
setTimeout(()=>this.snackbar = undefined, 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.show {
|
||||
min-width: 250px; /* Set a default minimum width */
|
||||
margin-left: -125px; /* Divide value of min-width by 2 */
|
||||
background-color: #303030; /* Black background color */
|
||||
color: white; /* White text color */
|
||||
text-align: left; /* Centered text */
|
||||
border-radius: 6px; /* Rounded borders */
|
||||
padding: 10px; /* Padding */
|
||||
position: fixed; /* Sit on top of the screen */
|
||||
z-index: 999; /* Add a z-index if needed */
|
||||
left: 50%; /* Center the snackbar */
|
||||
top: 50px; /* 50px from the top */
|
||||
visibility: visible; /* Show the snackbar */
|
||||
/* Add animation: Take 0.5 seconds to fade in and out the snackbar.
|
||||
However, delay the fade out process for 2.5 seconds */
|
||||
-webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s;
|
||||
animation: fadein 0.5s, fadeout 0.5s 2.5s;
|
||||
}
|
||||
|
||||
/* Animations to fade the snackbar in and out */
|
||||
@-webkit-keyframes fadein {
|
||||
from {top: 0; opacity: 0;}
|
||||
to {top: 50px; opacity: 1;}
|
||||
}
|
||||
|
||||
@keyframes fadein {
|
||||
from {top: 0; opacity: 0;}
|
||||
to {top: 50px; opacity: 1;}
|
||||
}
|
||||
|
||||
@-webkit-keyframes fadeout {
|
||||
from {top: 50px; opacity: 1;}
|
||||
to {top: 0; opacity: 0;}
|
||||
}
|
||||
|
||||
@keyframes fadeout {
|
||||
from {top: 50px; opacity: 1;}
|
||||
to {top: 0; opacity: 0;}
|
||||
}
|
||||
</style>
|
||||
21
components/snackbar/Success.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div>
|
||||
<Caption v-bind="{title: 'Success', type: 'has-text-primary'}"></Caption>
|
||||
<div class="field is-grouped mb-0 pb-0">
|
||||
<div class="control is-expanded pr-3 mb-0" v-html="content"></div>
|
||||
<div class="control mb-0">
|
||||
<span class="material-symbols-outlined has-text-primary fs-34">check_circle</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['content', 'duration'],
|
||||
methods: {
|
||||
cancel() {
|
||||
this.$store.commit('updateStore', {name: 'showmodal', data: undefined})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
11
ecosystem.config.cjs
Normal file
@@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: 'login',
|
||||
exec_mode: 'cluster',
|
||||
instances: 'max', // Or a number of instances
|
||||
script: './node_modules/nuxt/bin/nuxt.js',
|
||||
args: 'start'
|
||||
}
|
||||
]
|
||||
}
|
||||
13
envdev.py
Executable file
@@ -0,0 +1,13 @@
|
||||
def search_text(file, text, new_text):
|
||||
f = open(file, 'r')
|
||||
content = f.read()
|
||||
print(text, content.find(new_text))
|
||||
content = content.replace(text, new_text)
|
||||
f.close()
|
||||
with open(file, 'w') as f:
|
||||
f.write(content)
|
||||
|
||||
file = './plugins/connection.js'
|
||||
text1 = "mode = 'prod'"
|
||||
newtext1 = "mode = 'dev'"
|
||||
search_text(file, text1, newtext1)
|
||||
13
envprod.py
Executable file
@@ -0,0 +1,13 @@
|
||||
def search_text(file, text, new_text):
|
||||
f = open(file, 'r')
|
||||
content = f.read()
|
||||
print(text, content.find(new_text))
|
||||
content = content.replace(text, new_text)
|
||||
f.close()
|
||||
with open(file, 'w') as f:
|
||||
f.write(content)
|
||||
|
||||
file = './plugins/connection.js'
|
||||
text1 = "mode = 'dev'"
|
||||
newtext1 = "mode = 'prod'"
|
||||
search_text(file, text1, newtext1)
|
||||
56
layouts/default.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div>
|
||||
<Nuxt class="has-background-light" style="min-height: 100vh;" v-if="ready" />
|
||||
<Modal @close="showmodal=undefined" v-bind="showmodal" v-if="showmodal"></Modal>
|
||||
<SnackBar @close="snackbar=undefined" v-bind="snackbar" v-if="snackbar" />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
ready: false
|
||||
}
|
||||
},
|
||||
head() {
|
||||
return {title: 'Login'}
|
||||
},
|
||||
async created() {
|
||||
let connlist = this.$readyapi(['registermethod', 'authmethod', 'usertype', 'authstatus', 'common'])
|
||||
let filter = connlist.filter(v=>!v.ready)
|
||||
let result = filter.length>0? await this.$getapi(filter) : undefined
|
||||
if(result==='error') {
|
||||
this.$dialog({width: '500px', icon: 'mdi mdi-alert-circle',
|
||||
content: 'An error occurred while connecting to the data. Please try again in a few minutes.', type: 'is-danger', progress:true, duration: 6})
|
||||
} else {
|
||||
this.ready = true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
var width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth
|
||||
if(this.$empty(this.viewport)) {
|
||||
if(width<=768) this.viewport = 1 //'mobile'
|
||||
else if(width>=769 && width<=1023) this.viewport = 2 //'tablet'
|
||||
else if(width>=1024 && width<=1215) this.viewport = 3 //'desktop'
|
||||
else if(width>=1216 && width<=1407) this.viewport = 4 //'widescreen'
|
||||
else if(width>=1408) this.viewport = 5 //'fullhd'
|
||||
}
|
||||
if(this.$route.query.link) this.$store.commit("updateLink", {link: this.$route.query.link})
|
||||
if(this.$route.query.module) this.$store.commit('updateStore', {name: 'module', data: this.$route.query.module})
|
||||
},
|
||||
computed: {
|
||||
showmodal: {
|
||||
get: function() {return this.$store.state['showmodal']},
|
||||
set: function(val) {this.$store.commit('updateStore', {name: 'showmodal', data: val})}
|
||||
},
|
||||
snackbar: {
|
||||
get: function() {return this.$store.state['snackbar']},
|
||||
set: function(val) {this.$store.commit('updateStore', {name: 'snackbar', data: val})}
|
||||
},
|
||||
viewport: {
|
||||
get: function() {return this.$store.state.viewport},
|
||||
set: function(val) {this.$store.commit("updateViewPort", {viewport: val})}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
22
layouts/infor.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div>
|
||||
<Nuxt/>
|
||||
<Modal @close="showmodal=undefined" v-bind="showmodal" v-if="showmodal"></Modal>
|
||||
<SnackBar @close="snackbar=undefined" v-bind="snackbar" v-if="snackbar" />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
computed: {
|
||||
showmodal: {
|
||||
get: function() {return this.$store.state['showmodal']},
|
||||
set: function(val) {this.$store.commit('updateStore', {name: 'showmodal', data: val})}
|
||||
},
|
||||
snackbar: {
|
||||
get: function() {return this.$store.state['snackbar']},
|
||||
set: function(val) {this.$store.commit('updateStore', {name: 'snackbar', data: val})}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
64
nuxt.config.js
Normal file
@@ -0,0 +1,64 @@
|
||||
export default {
|
||||
// Global page headers: https://go.nuxtjs.dev/config-head
|
||||
server: {
|
||||
port: 3001, // default: 3000
|
||||
host: '0.0.0.0', // default: localhost
|
||||
},
|
||||
|
||||
head: {
|
||||
title: 'Login',
|
||||
htmlAttrs: {
|
||||
lang: 'en'
|
||||
},
|
||||
meta: [
|
||||
{ charset: 'utf-8' },
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
||||
{ hid: 'description', name: 'description', content: '' },
|
||||
{ name: 'format-detection', content: 'telephone=no' }
|
||||
],
|
||||
link: [
|
||||
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
|
||||
{ rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@48,400,0,0' }
|
||||
]
|
||||
},
|
||||
|
||||
// Global CSS: https://go.nuxtjs.dev/config-css
|
||||
css: [
|
||||
'~/assets/styles/main.scss',
|
||||
'~/assets/styles/style.scss',
|
||||
],
|
||||
|
||||
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
|
||||
plugins: [
|
||||
'~/plugins/connection',
|
||||
'~/plugins/common',
|
||||
'~/plugins/datatable',
|
||||
'~/plugins/components'
|
||||
],
|
||||
|
||||
// Auto import components: https://go.nuxtjs.dev/config-components
|
||||
components: true,
|
||||
|
||||
// Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
|
||||
buildModules: [
|
||||
],
|
||||
|
||||
// Modules: https://go.nuxtjs.dev/config-modules
|
||||
modules: [
|
||||
// https://go.nuxtjs.dev/axios
|
||||
'@nuxtjs/axios',
|
||||
'@nuxtjs/dayjs',
|
||||
'@nuxt/image'
|
||||
],
|
||||
|
||||
// Axios module configuration: https://go.nuxtjs.dev/config-axios
|
||||
axios: {},
|
||||
|
||||
// Build Configuration (https://go.nuxtjs.dev/config-build)
|
||||
build: {
|
||||
postcss: null,
|
||||
extend(config, { isDev, isClient }) {
|
||||
config.resolve.alias["vue"] = "vue/dist/vue.common";
|
||||
}
|
||||
}
|
||||
}
|
||||
17308
package-lock.json
generated
Normal file
22
package.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "login",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "nuxt",
|
||||
"build": "nuxt build",
|
||||
"start": "nuxt start",
|
||||
"generate": "nuxt generate"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxt/image": "^0.6.2",
|
||||
"@nuxtjs/axios": "^5.13.6",
|
||||
"@nuxtjs/dayjs": "^1.4.1",
|
||||
"bowser": "^2.11.0",
|
||||
"bulma": "^0.9.4",
|
||||
"core-js": "^3.35.1",
|
||||
"nuxt": "^2.17.3",
|
||||
"sass": "^1.77.2",
|
||||
"sass-loader": "^10.5.2"
|
||||
}
|
||||
}
|
||||
19
pages/account/recovery.vue
Executable file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<div class="columns is-centered mx-0">
|
||||
<div class="column is-10 pt-6">
|
||||
<Logo class="px-2 is-clickable"></Logo>
|
||||
<section class="hero mt-2">
|
||||
<div class="hero-body px-2 py-5">
|
||||
<article class="message is-primary">
|
||||
<div class="message-body has-text-black fs-16 py-3">
|
||||
If you forgot your password, please contact your manager for assistance with password recovery.
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {}
|
||||
</script>
|
||||
9
pages/index.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
created() {
|
||||
this.$router.push('/signin')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
213
pages/signin.vue
Executable file
@@ -0,0 +1,213 @@
|
||||
<template>
|
||||
<div class="columns is-centered px-0 mx-0">
|
||||
<div :class="`column is-${viewport>=4? 4 : 6}`">
|
||||
<div class="has-background-white px-6 py-6 mt-6">
|
||||
<Logo />
|
||||
<Caption :class="`mt-5 pt-${viewport===1? 0 : 3}`" v-bind="{title: 'Sign in', size: 19}"></Caption>
|
||||
<div class="field mt-5">
|
||||
<label class="label">User name<b class="ml-1 has-text-danger">*</b></label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" placeholder v-model="username" />
|
||||
</div>
|
||||
<p
|
||||
class="help is-danger"
|
||||
v-if="errors.find(v=>v.name==='username')"
|
||||
v-html="errors.find(v=>v.name==='username').text"></p>
|
||||
</div>
|
||||
<div class="field mt-5">
|
||||
<label class="label">Password<b class="ml-1 has-text-danger">*</b></label>
|
||||
<div class="field-body">
|
||||
<div class="field has-addons">
|
||||
<p class="control is-expanded">
|
||||
<input
|
||||
class="input"
|
||||
:type="showpass? 'text' : 'password'"
|
||||
placeholder
|
||||
v-model="password"
|
||||
/>
|
||||
</p>
|
||||
<div class="control">
|
||||
<a class="button" @click="showpass=!showpass">
|
||||
<span class="icon">
|
||||
<SvgIcon v-bind="{name: showpass? 'eye-off.svg' : 'view.svg', type: 'gray'}"/>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
class="help is-danger"
|
||||
v-if="errors.find(v=>v.name==='password')"
|
||||
>{{errors.find(v=>v.name==='password').text}}</p>
|
||||
</div>
|
||||
<div class="field is-grouped mt-5">
|
||||
<div class="control is-expanded">
|
||||
<button class="button is-primary" @click="signin()">Login</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<a class="is-primary" @click="accountRecovery()">Forgot Password?</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Bowser from "bowser"
|
||||
export default {
|
||||
head() {
|
||||
return {title: 'Signin'}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fullname: undefined,
|
||||
username: undefined,
|
||||
password: undefined,
|
||||
errors: [],
|
||||
showpass: false,
|
||||
registers: [],
|
||||
type: undefined,
|
||||
isLoaded: false,
|
||||
data: undefined,
|
||||
account: undefined
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener("keyup", (ev) => ev.key==='Enter' && this.$route.name==='signup'? this.signin() : false)
|
||||
if(!this.module) return this.$router.push('/welcome')
|
||||
},
|
||||
computed: {
|
||||
login: {
|
||||
get: function() {return this.$store.state.login},
|
||||
set: function(val) {this.$store.commit('updateLogin', {login: val})}
|
||||
},
|
||||
registermethod: {
|
||||
get: function() {return this.$store.state.registermethod},
|
||||
set: function(val) {this.$store.commit("updateRegisterMethod", {registermethod: val})}
|
||||
},
|
||||
authmethod: {
|
||||
get: function() {return this.$store.state.authmethod},
|
||||
set: function(val) {this.$store.commit("updateAuthMethod", {authmethod: val})}
|
||||
},
|
||||
authstatus: {
|
||||
get: function() {return this.$store.state.authstatus},
|
||||
set: function(val) {this.$store.commit("updateAuthStatus", {authstatus: val})}
|
||||
},
|
||||
usertype: {
|
||||
get: function() {return this.$store.state.usertype},
|
||||
set: function(val) {this.$store.commit("updateUserType", {usertype: val})}
|
||||
},
|
||||
viewport: {
|
||||
get: function() {return this.$store.state.viewport},
|
||||
set: function(val) {this.$store.commit("updateViewPort", {viewport: val})}
|
||||
},
|
||||
module: {
|
||||
get: function() {return this.$store.state['module']},
|
||||
set: function(val) {this.$store.commit('updateStore', {name: 'module', data: val})}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
checkError() {
|
||||
this.errors = []
|
||||
if (!this.$empty(this.username)) {
|
||||
this.username = this.username.trim().toLowerCase();
|
||||
}
|
||||
if (this.$empty(this.username)) {
|
||||
this.errors.push({
|
||||
name: "username",
|
||||
text: "Email không được bỏ trống."
|
||||
});
|
||||
}
|
||||
if (this.$empty(this.password)) {
|
||||
this.errors.push({
|
||||
name: "password",
|
||||
text: "Mật khẩu không được bỏ trống."
|
||||
});
|
||||
} else if (this.password.length < 6) {
|
||||
this.errors.push({
|
||||
name: "password",
|
||||
text: "Mật khẩu gồm 6 kí tự trở nên bao gồm chữ và số ."
|
||||
});
|
||||
} else if (
|
||||
!(/\d/.test(this.password) && /[a-zA-Z]/.test(this.password))
|
||||
) {
|
||||
this.errors.push({
|
||||
name: "password",
|
||||
text: "Mật khẩu gồm 6 kí tự trở nên bao gồm chữ và số ."
|
||||
})
|
||||
}
|
||||
return this.errors.length>0? true : false
|
||||
},
|
||||
async signin() {
|
||||
if(this.checkError()) return
|
||||
let conn = this.$findapi('login')
|
||||
conn.params.filter = {username: this.username, password: this.password}
|
||||
let result= await this.$getapi([conn])
|
||||
if(result==='error') {
|
||||
return this.$dialog('Đã xảy ra lỗi. Vui lòng thử lại hoặc liên hệ số hotline để được hỗ trợ.', 'Lỗi kết nối', 'Error', 10)
|
||||
}
|
||||
let data = result.find(v => v.name==="login").data.rows
|
||||
this.account = data
|
||||
this.fillData(data)
|
||||
},
|
||||
invalidLogin(data) {
|
||||
if(!data) {
|
||||
const text = 'Tài khoản hoặc mật khẩu không chính xác'
|
||||
this.errors.push({name: "username", text: text})
|
||||
this.errors.push({ name: "password", text: text})
|
||||
} else if(data.blocked) {
|
||||
this.errors.push({name: "username", text: 'Tài khoản đang bị khóa. Đăng nhập không thành công'})
|
||||
} else if(data.auth_status===1) {
|
||||
this.errors.push({name: "username", text: `Tài khoản đang chờ xác thực.`})
|
||||
}
|
||||
return this.errors.length>0? true : false
|
||||
},
|
||||
async fillData(data) {
|
||||
if(this.invalidLogin(data)) return
|
||||
//check permision
|
||||
if(this.module!=='website') {
|
||||
let userapps = await this.$getdata('userapps', {user: data.id, apps__code: this.module}, undefined, true)
|
||||
if(!userapps) return this.$router.push('/welcome')
|
||||
}
|
||||
this.login = data //store login
|
||||
if(this.$store.state.link) {
|
||||
let ele = this.$copy(data)
|
||||
//get token & redirect link
|
||||
const browser = Bowser.getParser(window.navigator.userAgent);
|
||||
let obj = {browser: browser.getBrowserName(), browser_version: browser.getBrowserVersion(), platform: browser.getPlatform().type,
|
||||
os: browser.getOSName(), user: data.id, token: this.$id()}
|
||||
ele.token = obj.token
|
||||
await this.$insertapi('authtoken', obj)
|
||||
let link = this.$store.state.link
|
||||
if(data.type===3 && link.indexOf('y99')>=0) {
|
||||
link = link.indexOf('dev')>=0? 'https://dev.ctv.y99.vn' : 'https://ctv.y99.vn'
|
||||
}
|
||||
let href = `${link}?username=${ele.username}&token=${ele.token}&fullname=${ele.fullname}&userid=${ele.id}`
|
||||
if(ele.avatar) href = `${href}&avatar=${ele.avatar}`
|
||||
window.location.href = href
|
||||
} else this.redirectUrl()
|
||||
},
|
||||
async sendNoti(obj) {
|
||||
let found = this.$findapi('notiform')
|
||||
found.params.filter = {code: 'login-alert'}
|
||||
const result = await this.$getapi([found])
|
||||
let data = result[0].data.rows[0]
|
||||
let content = data.detail
|
||||
content = content.replace('[1]', this.username)
|
||||
content = content.replace('[2]', obj.browser)
|
||||
content = content.replace('[3]', obj.browser_version)
|
||||
content = content.replace('[4]', obj.platform)
|
||||
content = content.replace('[5]', obj.os)
|
||||
content = content.replace('[6]', this.$dayjs().format("DD/MM/YYYY HH:mm"))
|
||||
let ele = {title: data.name, content: content, user: [obj.user.toString()], type: 1}
|
||||
await this.$insertapi('notification', ele)
|
||||
},
|
||||
accountRecovery() {
|
||||
this.$router.push({path: '/account/recovery'})
|
||||
},
|
||||
async redirectUrl() {
|
||||
this.$router.push(this.$route.query.href || '/welcome')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
16
pages/welcome.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<div class="columns is-centered mx-0">
|
||||
<div class="column is-10">
|
||||
<Logo class="px-2 mt-6"></Logo>
|
||||
<section class="hero mt-2">
|
||||
<div class="hero-body px-2 py-5">
|
||||
<article class="message is-primary">
|
||||
<div class="message-body has-text-black fs-18 py-3 mt-0">
|
||||
No access permission.
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
236
plugins/common.js
Executable file
@@ -0,0 +1,236 @@
|
||||
import Vue from 'vue'
|
||||
Vue.use( {
|
||||
install(Vue){
|
||||
Vue.prototype.$dialog = function(content, title, type, duration, width, height, vbind) {
|
||||
if(typeof content == 'string') {
|
||||
let vtitle = type==='Success'? `<span class="has-text-primary">${title}</span>` : title
|
||||
if(type==='Error') vtitle = `<span class="has-text-danger">${title}</span>`
|
||||
let data = {id: this.$id(), component: `dialog/${type || 'Info'}`,
|
||||
vbind: {content: content, duration: duration, vbind: vbind}, title: vtitle, width: width || '500px', height: height || '100px'}
|
||||
this.$store.commit('updateStore', {name: 'showmodal', data: data})
|
||||
} else this.$store.commit('updateStore', {name: 'showmodal', data: content})
|
||||
}
|
||||
|
||||
Vue.prototype.$snackbar = function(content, title, type, width, height) {
|
||||
if(typeof content == 'string') {
|
||||
let vtitle = type==='Success'? `<span class="has-text-primary">${title}</span>` : title
|
||||
if(type==='Error') vtitle = `<span class="has-text-danger">${title}</span>`
|
||||
let data = {id: this.$id(), component: `snackbar/${type || 'Info'}`,
|
||||
vbind: {content: content}, title: vtitle, width: width || '400px', height: height || '100px'}
|
||||
this.$store.commit('updateStore', {name: 'snackbar', data: data})
|
||||
} else this.$store.commit('updateStore', {name: 'snackbar', data: content})
|
||||
}
|
||||
|
||||
Vue.prototype.$pending = function() {
|
||||
this.$dialog({width: '500px', icon: ' mdi mdi-wrench-clock',
|
||||
content: '<p class="fs-16">Chức năng này đang được xây dựng, vui lòng trở lại sau</p>', type: 'is-dark', progress:true, duration: 5})
|
||||
}
|
||||
|
||||
Vue.prototype.$getLink = function(val) {
|
||||
if(val === undefined || val === null || val === '' || val==="") return ''
|
||||
let json = val.indexOf('{')>=0? JSON.parse(val) : {path: val}
|
||||
return json
|
||||
}
|
||||
|
||||
Vue.prototype.$timeFormat = function(startDate, endDate) {
|
||||
let milliseconds = startDate - endDate
|
||||
let secs = Math.floor(Math.abs(milliseconds) / 1000);
|
||||
let mins = Math.floor(secs / 60);
|
||||
let hours = Math.floor(mins / 60);
|
||||
let days = Math.floor(hours / 24);
|
||||
const millisecs = Math.floor(Math.abs(milliseconds)) % 1000;
|
||||
function pad2(n) { return (n < 10 ? '0' : '') + n }
|
||||
let display = undefined
|
||||
|
||||
if(days>=1) {
|
||||
display = pad2(startDate.getHours()) + ':' + pad2(startDate.getMinutes()) + ' ' + pad2(startDate.getDate()) + '/' + pad2(startDate.getMonth())
|
||||
}
|
||||
else if(hours>0) display = hours + 'h trước'
|
||||
else if(mins>0) display = mins + "' trước"
|
||||
else if(secs>0 || millisecs>0) display = 'Vừa xong'
|
||||
|
||||
return {
|
||||
days: days,
|
||||
hours: hours % 24,
|
||||
minutes: mins % 60,
|
||||
seconds: secs % 60,
|
||||
milliSeconds: millisecs,
|
||||
display: display
|
||||
}
|
||||
}
|
||||
|
||||
Vue.prototype.$errPhone = function(phone) {
|
||||
var text = undefined
|
||||
if (this.$empty(phone)) {
|
||||
text = 'Số điện thoại di động không được bỏ trống.'
|
||||
} else if (isNaN(phone)) {
|
||||
text = 'Số điện thoại di động không hợp lệ.'
|
||||
} else if(phone.length<9 || phone.length>11) {
|
||||
text = 'Số điện thoại di động phải có từ 9-11 số.'
|
||||
}
|
||||
return text
|
||||
},
|
||||
|
||||
Vue.prototype.$errEmail = function(email) {
|
||||
const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||
var text = undefined
|
||||
if (this.$empty(email)) {
|
||||
text = 'Email không được bỏ trống.'
|
||||
} else if (!(re.test(String(email).toLowerCase()))) {
|
||||
text = 'Email không hợp lệ.'
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
Vue.prototype.$errPhoneEmail = function(contact) {
|
||||
const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||
var text = undefined
|
||||
|
||||
if (this.$empty(contact)) {
|
||||
text = 'Số điện thoại di động hoặc Email không được bỏ trống.'
|
||||
} else if (!(re.test(String(contact).toLowerCase()) || !isNaN(contact))) {
|
||||
text = 'Số điện thoại di động hoặc Email không hợp lệ.'
|
||||
} else if(!isNaN(contact) && (contact.length<9 || contact.length>11)) {
|
||||
text = 'Số điện thoại di động không hợp lệ.'
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
Vue.prototype.$dummy = function(data, count) {
|
||||
let list = this.$copy(data)
|
||||
for (let index = 0; index < count; index++) {
|
||||
if(data.length<index+1) list.push({dummy: true})
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
Vue.prototype.$upload = function(file, type, user) {
|
||||
var fileFormat = [{type: 'image', format: ['.png', '.jpg', 'jpeg', '.bmp', '.gif', '.svg']},
|
||||
{type: 'video', format: ['.wmv', '.avi', '.mp4', '.flv', '.mov', '.mpg', '.amv', '.rm']}
|
||||
]
|
||||
var valid = undefined
|
||||
if(type==='image' || type==='video') {
|
||||
valid = false
|
||||
let found = fileFormat.find(v=>v.type===type)
|
||||
found.format.map(x=>{
|
||||
if(file.name.toLowerCase().indexOf(x)>=0) valid = true
|
||||
})
|
||||
}
|
||||
|
||||
if(valid===false) return {error: true, text: 'Định dạng file không hợp lệ'}
|
||||
if((type==='image' || type==='file') && file.size > 500*1024*1024) {
|
||||
return {error: true, text: 'Kích thước ' + (type==='image'? 'hình ảnh' : 'tài liệu') + ' phải dưới 500MB'}
|
||||
} else if(type==='video' && file.size > 1073741274) {
|
||||
return {error: true, text: 'Kích thước video phải dưới 1GB'}
|
||||
}
|
||||
|
||||
let data = new FormData()
|
||||
let fileName = this.$dayjs(new Date()).format("YYYYMMDDhhmmss") + '-' + file.name
|
||||
data.append('name', fileName)
|
||||
data.append('file', file)
|
||||
data.append('type', type)
|
||||
data.append('size', file.size)
|
||||
data.append('user', user)
|
||||
return {form: data, name: fileName, type: type, size: file.size, file: file}
|
||||
}
|
||||
|
||||
Vue.prototype.$change = function(obj1, obj2, list) {
|
||||
var change = false
|
||||
if(list) {
|
||||
list.map(v=>{if(obj1[v]!==obj2[v]) change = true})
|
||||
} else {
|
||||
for (var k in obj1 ) {
|
||||
if(obj1[k]!==obj2[k]) change = true
|
||||
}
|
||||
}
|
||||
return change
|
||||
}
|
||||
|
||||
Vue.prototype.$resetNull = function(obj) {
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
if(typeof value==='string') {
|
||||
let val = value.trim()
|
||||
if(val==='' || val==="") val = null
|
||||
obj[key] = val
|
||||
}
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
Vue.prototype.$responsiveMenu = function() {
|
||||
// Get all "navbar-burger" elements
|
||||
const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
|
||||
// Check if there are any navbar burgers
|
||||
if ($navbarBurgers.length > 0) {
|
||||
// Add a click event on each of them
|
||||
$navbarBurgers.forEach( el => {
|
||||
// Get the target from the "data-target" attribute
|
||||
const target = el.dataset.target;
|
||||
const $target = document.getElementById(target);
|
||||
|
||||
// Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
|
||||
el.classList.toggle('is-active');
|
||||
$target.classList.toggle('is-active');
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Vue.prototype.$copyToClipboard = function(text) {
|
||||
if (window.clipboardData && window.clipboardData.setData) {
|
||||
// IE specific code path to prevent textarea being shown while dialog is visible.
|
||||
return clipboardData.setData("Text", text);
|
||||
|
||||
} else if (document.queryCommandSupported && document.queryCommandSupported("copy")) {
|
||||
var textarea = document.createElement("textarea");
|
||||
textarea.textContent = text;
|
||||
textarea.style.position = "fixed"; // Prevent scrolling to bottom of page in MS Edge.
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
try {
|
||||
return document.execCommand("copy"); // Security exception may be thrown by some browsers.
|
||||
} catch (ex) {
|
||||
console.warn("Copy to clipboard failed.", ex);
|
||||
return false;
|
||||
} finally {
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vue.prototype.$nonAccent = function(str) {
|
||||
if(this.$empty(str)) return null
|
||||
str = str.replaceAll('/', '-').replaceAll('%', '-').replaceAll('?', '-')
|
||||
str = str.toLowerCase();
|
||||
str = str.replace(/à|á|ạ|ả|ã|â|ầ|ấ|ậ|ẩ|ẫ|ă|ằ|ắ|ặ|ẳ|ẵ/g, "a");
|
||||
str = str.replace(/è|é|ẹ|ẻ|ẽ|ê|ề|ế|ệ|ể|ễ/g, "e");
|
||||
str = str.replace(/ì|í|ị|ỉ|ĩ/g, "i");
|
||||
str = str.replace(/ò|ó|ọ|ỏ|õ|ô|ồ|ố|ộ|ổ|ỗ|ơ|ờ|ớ|ợ|ở|ỡ/g, "o");
|
||||
str = str.replace(/ù|ú|ụ|ủ|ũ|ư|ừ|ứ|ự|ử|ữ/g, "u");
|
||||
str = str.replace(/ỳ|ý|ỵ|ỷ|ỹ/g, "y");
|
||||
str = str.replace(/đ/g, "d");
|
||||
// Some system encode vietnamese combining accent as individual utf-8 characters
|
||||
str = str.replace(/\u0300|\u0301|\u0303|\u0309|\u0323/g, ""); // Huyền sắc hỏi ngã nặng
|
||||
str = str.replace(/\u02C6|\u0306|\u031B/g, ""); // Â, Ê, Ă, Ơ, Ư
|
||||
str = str.split(' ').filter(s => s).join('-')
|
||||
return str
|
||||
}
|
||||
|
||||
Vue.prototype.$linkID = function(link) {
|
||||
link = link? link : this.$route.params.slug
|
||||
if(this.$empty(link)) return
|
||||
let idx = link.lastIndexOf('-')
|
||||
let id = (idx>-1 && idx<link.length-1)? link.substring(idx+1, link.length) : undefined
|
||||
return id
|
||||
},
|
||||
|
||||
Vue.prototype.$height = function(id) {
|
||||
let doc = document.getElementById(id)
|
||||
return doc? doc.clientHeight : undefined
|
||||
}
|
||||
|
||||
Vue.prototype.$color = function(i) {
|
||||
var colors = ['#4285F4','#0F9D58', '#f8961e', '#008000', '#e01e37','#090c02', '#390099','#335c67','#C46210','#FFBF00','#288D85','#89B700','#EFDECD','#E52B50','#9F2B68','#F19CBB','#AB274F','#D3212D','#3B7A57','#FFBF00','#FF7E00','#9966CC','#3DDC84','#CD9575','#665D1E','#915C83','#841B2D','#FAEBD7']
|
||||
return i>=colors.length? ('#' + Math.floor(Math.random()*16777215).toString(16)) : colors[i]
|
||||
}
|
||||
}
|
||||
})
|
||||
11
plugins/components.js
Executable file
@@ -0,0 +1,11 @@
|
||||
import Vue from 'vue'
|
||||
import SnackBar from '@/components/snackbar/SnackBar'
|
||||
import CountDown from '@/components/dialog/CountDown'
|
||||
import Modal from '@/components/Modal'
|
||||
import SearchBox from '@/components/SearchBox'
|
||||
|
||||
const components = { SnackBar, Modal, CountDown, SearchBox}
|
||||
|
||||
Object.entries(components).forEach(([name, component]) => {
|
||||
Vue.component(name, component)
|
||||
})
|
||||
330
plugins/connection.js
Normal file
@@ -0,0 +1,330 @@
|
||||
import Vue from 'vue'
|
||||
const mode = 'dev'
|
||||
var paths = [
|
||||
{name: 'operation', url: 'https://oprtapi.bigdatatech.vn/' },
|
||||
{name: 'local', url: 'http://127.0.0.1:8000/' },
|
||||
{name: 'dev', url: 'https://utopiaapi.dev247.net/' },
|
||||
{name: 'prod', url: 'https://utopiaapi.dev247.net/' }
|
||||
]
|
||||
const path = paths.find(v=>v.name===mode).url
|
||||
const apis = [
|
||||
{name: 'upload' , url: 'upload/', params: {}},
|
||||
{name: 'image' , url: 'data/Image/', url_detail: 'data-detail/Image/', params: {}},
|
||||
{name: 'file' , url: 'data/File/', url_detail: 'data-detail/File/', params: {}},
|
||||
{name: 'user', url: 'data/User/', url_detail: 'data-detail/User/', params: {values: 'id,phone_verified,email_verified,create_time__date,username,password,avatar,fullname,display_name,type,blocked,block_reason,blocked_by,last_login,auth_method__code,auth_method__name,auth_method,auth_status,auth_status__code,auth_status__name,register_method,phone,create_time,update_time'}},
|
||||
{name: 'blockreason', commit: 'updateBlockReason', url: 'data/Block_Reason/', url_detail: 'data-detail/Block_Reason/', params: {page: -1}},
|
||||
{name: 'authstatus', commit: 'updateAuthStatus', url: 'data/Auth_Status/', url_detail: 'data-detail/Auth_Status/', params: {page: -1}},
|
||||
{name: 'authmethod', commit: 'updateAuthMethod', url: 'data/Auth_Method/', url_detail: 'data-detail/Auth_Method/', params: {page: -1}},
|
||||
{name: 'usertype', commit: 'updateUserType', url: 'data/User_Type/', url_detail: 'data-detail/User_Type/', params: {}},
|
||||
{name: 'registermethod', commit: 'updateRegisterMethod', url: 'data/Register_Method/', url_detail: 'data-detail/Register_Method/', params: {page: -1}},
|
||||
{name: 'langchoice', commit: 'updateLangChoice', url: 'data/Lang_Choice/', url_detail: 'data-detail/Lang_Choice/', params: {}},
|
||||
{name: 'userauth', url: 'data/User_Auth/', url_detail: 'data-detail/User_Auth/', params: {}},
|
||||
{name: 'accountrecovery', url: 'data/Account_Recovery/', url_detail: 'data-detail/Account_Recovery/', params: {}},
|
||||
{name: 'login', url: 'login/', params: {values: 'id,username,password,avatar,fullname,display_name,type,type__code,type__name,blocked,block_reason,block_reason__code,block_reason__name,blocked_by,last_login,auth_method,auth_method__code,auth_method__name,auth_status,auth_status__code,auth_status__name,register_method,register_method__code,register_method__name,create_time,update_time'}},
|
||||
{name: 'authtoken', url: 'auth-token/', params: {}},
|
||||
|
||||
{name: 'emailsetup', url: 'data/Email_Setup/', url_detail: 'data-detail/Email_Setup/', params: {sort: '-id'}},
|
||||
{name: 'emailsent', url: 'data/Email_Sent/', url_detail: 'data-detail/Email_Sent/', params: {values: 'id,receiver,content,content__sender__email,content__subject,content__content,status__code,status,status__name,create_time', sort: '-id'}},
|
||||
{name: 'sendemail', url: 'send-email/', path: 'operation', params: {}},
|
||||
{name: 'token', url: 'data/Token/', url_detail: 'data-detail/Token', params: {filter: {expiry: 0}}},
|
||||
{name: 'common', commit: 'updateCommon', url: 'data/Common/', url_detail: 'data-detail/Common/', params: {sort: 'index'}},
|
||||
{name: 'sex', url: 'data/Sex/', url_detail: 'data-detail/Sex/', params: {}},
|
||||
{name: 'downloadfile', url: 'download-file/', params: {}},
|
||||
{name: 'download', url: 'download/', params: {}},
|
||||
{name: 'gethash', url: 'get-hash/', params: {}},
|
||||
{name: 'userapps', url: 'data/User_Apps/', url_detail: 'data-detail/User_Apps/', params: {}}
|
||||
]
|
||||
|
||||
Vue.use( {
|
||||
install(Vue){
|
||||
Vue.prototype.$path = function(name) {
|
||||
return name? paths.find(v=>v.name===name).url : path
|
||||
}
|
||||
|
||||
Vue.prototype.$findapi = function(name) {
|
||||
const result = Array.isArray(name)? apis.filter(v=>name.findIndex(x=>v.name===x)>=0) : apis.find(v=>v.name===name)
|
||||
return this.$copy(result)
|
||||
}
|
||||
|
||||
Vue.prototype.$readyapi = function(list) {
|
||||
var array = []
|
||||
list.forEach(element => {
|
||||
let found = apis.find(v=>v.name===element)
|
||||
if(found) {
|
||||
let ele = JSON.parse(JSON.stringify(found))
|
||||
ele.ready = this.$store.state[element]? true : false
|
||||
array.push(ele)
|
||||
}
|
||||
})
|
||||
return array
|
||||
}
|
||||
|
||||
// get data
|
||||
Vue.prototype.$getapi = async function(list) {
|
||||
try {
|
||||
let arr = list.map(v => {
|
||||
let found = apis.find(v=>v.name===v.name)
|
||||
let url = (v.path? paths.find(x=>x.name===v.path).url : path) + (v.url? v.url : found.url)
|
||||
let params = v.params? v.params : (found.params===undefined? {} : found.params)
|
||||
params.login = this.$store.state.login? this.$store.state.login.id : undefined
|
||||
return {url: url, params: params}
|
||||
})
|
||||
let data = await Promise.all(arr.map(v=>this.$axios.get(v.url, {params: v.params})))
|
||||
data.map((v,i) => {
|
||||
list[i].data = v.data
|
||||
if(list[i].commit) {
|
||||
let payload = {}
|
||||
payload[list[i].name] = v.data.rows? v.data.rows : v.data
|
||||
this.$store.commit(list[i].commit, payload)
|
||||
}
|
||||
})
|
||||
return list
|
||||
} catch(err) {
|
||||
console.log(err)
|
||||
return 'error'
|
||||
}
|
||||
}
|
||||
|
||||
// insert data
|
||||
Vue.prototype.$insertapi = async function(name, data, values) {
|
||||
try {
|
||||
let found = this.$findapi(name)
|
||||
let curpath = found.path? paths.find(x=>x.name===found.path).url : path
|
||||
var rs
|
||||
if(!Array.isArray(data))
|
||||
rs = await this.$axios.post(`${curpath}${found.url}`, data, {params: {values: values}})
|
||||
else {
|
||||
let params = {action: 'import', values: values}
|
||||
rs = await this.$axios.post(`${curpath}import-data/${found.url.substring(5, found.url.length-1)}/`, data, {params: params})
|
||||
}
|
||||
|
||||
// update store
|
||||
if(found.commit) {
|
||||
if(this.$store.state[found.name]) {
|
||||
let copy = JSON.parse(JSON.stringify(this.$store.state[found.name]))
|
||||
let rows = Array.isArray(rs.data)? rs.data : [rs.data]
|
||||
rows.map(v=>{
|
||||
if(v.id && !v.error) {
|
||||
let idx = copy.findIndex(x=>x.id===v.id)
|
||||
if(idx>=0) copy[idx] = v
|
||||
else copy.push(v)
|
||||
}
|
||||
})
|
||||
this.$store.commit('updateStore', {name: found.name, data: copy})
|
||||
}
|
||||
}
|
||||
return rs.data
|
||||
} catch(err) {
|
||||
console.log(err)
|
||||
return 'error'
|
||||
}
|
||||
}
|
||||
|
||||
// update api
|
||||
Vue.prototype.$updateapi = async function(name, data, values) {
|
||||
try {
|
||||
let found = this.$findapi(name)
|
||||
let rs = await this.$axios.put(`${path}${found.url_detail}${data.id}/`, data, {params: {values: values? values : found.params.values}})
|
||||
if(found.commit) {
|
||||
let index = this.$store.state[found.name]? this.$store.state[found.name].findIndex(v=>v.id===rs.data.id) : -1
|
||||
if(index>=0) {
|
||||
var copy = JSON.parse(JSON.stringify(this.$store.state[found.name]))
|
||||
if(Array.isArray(rs.data)===false) Vue.set(copy, index, rs.data)
|
||||
else {
|
||||
rs.data.forEach(v => {
|
||||
let index = copy.findIndex(v=>v.id===v.id)
|
||||
if(index>=0) Vue.set(copy, index, v)
|
||||
})
|
||||
}
|
||||
this.$store.commit('updateStore', {name: found.name, data: copy})
|
||||
}
|
||||
}
|
||||
return rs.data
|
||||
} catch(err) {
|
||||
console.log(err)
|
||||
return 'error'
|
||||
}
|
||||
}
|
||||
|
||||
// delete data
|
||||
Vue.prototype.$deleteapi = async function(name, id) {
|
||||
try {
|
||||
let found = this.$findapi(name)
|
||||
var rs
|
||||
if(!Array.isArray(id))
|
||||
rs = await this.$axios.delete(`${path}${found.url_detail}${id}`)
|
||||
else {
|
||||
let params = {action: 'delete'}
|
||||
rs = await this.$axios.post(`${path}import-data/${found.url.substring(5,found.url.length-1)}/`, id, {params: params})
|
||||
}
|
||||
if(found.commit) {
|
||||
let copy = JSON.parse(JSON.stringify(this.$store.state[found.name]))
|
||||
if(!Array.isArray(id)) {
|
||||
let index = copy.findIndex(v=>v.id===id)
|
||||
if(index>=0) this.$delete(copy, index)
|
||||
} else {
|
||||
rs.data.forEach(element => {
|
||||
let index = copy.findIndex(v=>v.id===element.id)
|
||||
if(index>=0) this.$delete(copy, index)
|
||||
})
|
||||
}
|
||||
this.$store.commit('updateStore', {name: found.name, data: copy})
|
||||
}
|
||||
return rs.data
|
||||
} catch(err) {
|
||||
console.log(err)
|
||||
return 'error'
|
||||
}
|
||||
}
|
||||
|
||||
// insert row
|
||||
Vue.prototype.$insertrow = async function(name, data, values, pagename) {
|
||||
let result = await this.$insertapi(name, data, values)
|
||||
if(result==='error') return
|
||||
let arr = Array.isArray(result)? result : [result]
|
||||
let copy = this.$copy(this.$store.state[pagename].data)
|
||||
arr.map(x=>{
|
||||
let index = copy.findIndex(v=>v.id===x.id)
|
||||
index>=0? copy[index] = x : copy.unshift(x)
|
||||
})
|
||||
this.$store.commit('updateState', {name: pagename, key: 'data', data: copy})
|
||||
let pagedata = this.$store.state[pagename]
|
||||
if(pagedata.filters? pagedata.filters.length>0 : false) {
|
||||
this.$store.commit('updateState', {name: pagename, key: 'filterby', data: this.$copy(pagedata.filters)})
|
||||
}
|
||||
}
|
||||
|
||||
// update row
|
||||
Vue.prototype.$updaterow = async function(name, data, values, pagename) {
|
||||
let result = await this.$updateapi(name, data, values)
|
||||
if(result==='error') return
|
||||
let arr = Array.isArray(result)? result : [result]
|
||||
let copy = this.$copy(this.$store.state[pagename].data)
|
||||
arr.map(x=>{
|
||||
let index = copy.findIndex(v=>v.id===x.id)
|
||||
index>=0? copy[index] = x : copy.unshift(x)
|
||||
})
|
||||
this.$store.commit('updateState', {name: pagename, key: 'data', data: copy})
|
||||
let pagedata = this.$store.state[pagename]
|
||||
if(pagedata.filters? pagedata.filters.length>0 : false) {
|
||||
this.$store.commit('updateState', {name: pagename, key: 'filterby', data: this.$copy(pagedata.filters)})
|
||||
}
|
||||
}
|
||||
|
||||
// delete row
|
||||
Vue.prototype.$deleterow = async function(name, id, pagename, ask) {
|
||||
let self = this
|
||||
var remove = async function() {
|
||||
let result = await self.$deleteapi(name, id)
|
||||
if(result==='error') return
|
||||
let arr = Array.isArray(id)? id : [id]
|
||||
let copy = self.$copy(self.$store.state[pagename].data)
|
||||
arr.map(x=>{
|
||||
let index = copy.findIndex(v=>v.id===x)
|
||||
index>=0? self.$delete(copy,index) : false
|
||||
})
|
||||
self.$store.commit('updateState', {name: pagename, key: 'data', data: copy})
|
||||
let pagedata = this.$store.state[pagename]
|
||||
if(pagedata.filters? pagedata.filters.length>0 : false) {
|
||||
this.$store.commit('updateState', {name: pagename, key: 'filterby', data: this.$copy(pagedata.filters)})
|
||||
}
|
||||
}
|
||||
|
||||
// ask confirm
|
||||
if(ask) {
|
||||
this.$buefy.dialog.confirm({
|
||||
message: 'Bạn muốn xóa bản ghi: ' + id,
|
||||
onConfirm: () => remove()
|
||||
})
|
||||
} else remove()
|
||||
}
|
||||
|
||||
// update page
|
||||
Vue.prototype.$updatepage = function(pagename, row, action) {
|
||||
let pagedata = this.$store.state[pagename]
|
||||
let copy = this.$copy(pagedata.data)
|
||||
let idx = copy.findIndex(v=>v.id===row.id)
|
||||
if(action==='delete') this.$delete(copy, idx)
|
||||
else if(action==='insert') copy.unshift(row)
|
||||
else copy[idx] = row
|
||||
this.$store.commit('updateState', {name: pagename, key: 'data', data: copy})
|
||||
if(pagedata.filters? pagedata.filters.length>0 : false) {
|
||||
this.$store.commit('updateState', {name: pagename, key: 'filterby', data: this.$copy(pagedata.filters)})
|
||||
}
|
||||
}
|
||||
|
||||
Vue.prototype.$getdata = async function(name, filter, params, first) {
|
||||
let found = this.$findapi(name)
|
||||
if(params) found.params = params
|
||||
else if(filter) found.params.filter = filter
|
||||
let rs = await this.$getapi([found])
|
||||
return first? (rs[0].data.rows.length>0? rs[0].data.rows[0] : undefined) : rs[0].data.rows
|
||||
}
|
||||
|
||||
Vue.prototype.$getpage = function(showFilter) {
|
||||
return {data: [], fields: [], filters: [], update: undefined, action: undefined, filterby: undefined, api: {full_data: true}, origin_api: {full_data: true},
|
||||
tablesetting: undefined, setting: undefined, tabfield: true, setpage: {}, showFilter: this.$empty(showFilter)? true : showFilter}
|
||||
}
|
||||
|
||||
Vue.prototype.$setpage = function(pagename, row, api) {
|
||||
if(!this.$store.state[pagename])return
|
||||
let json = row.detail
|
||||
let fields = this.$updateSeriesFields(json.fields)
|
||||
this.$store.commit('updateState', {name: pagename, key: 'fields', data: fields})
|
||||
this.$store.commit('updateState', {name: pagename, key: 'setting', data: this.$copy(row)})
|
||||
if(json.filters) this.$store.commit('updateState', {name: pagename, key: 'filters', data: this.$copy(json.filters)})
|
||||
if(json.tablesetting) this.$store.commit('updateState', {name: pagename, key: 'tablesetting', data: json.tablesetting})
|
||||
if(api) {
|
||||
let copy = this.$copy(api)
|
||||
delete copy.data
|
||||
copy.full_data = api.data.full_data
|
||||
copy.total_rows = api.data.total_rows
|
||||
this.$store.commit('updateState', {name: pagename, key: 'api', data: copy})
|
||||
this.$store.commit('updateState', {name: pagename, key: 'origin_api', data: copy})
|
||||
}
|
||||
}
|
||||
|
||||
Vue.prototype.$findpage = function(arr) {
|
||||
var copy = this.$copy(this.$store.state.pagetrack)
|
||||
var doFind = function() {
|
||||
let found = undefined
|
||||
for (let i=1; i<=30; i++) {
|
||||
let name = `pagedata${i}`
|
||||
if(!copy[name]) {
|
||||
found = name
|
||||
copy[name] = true
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!found) console.log('pagename not found')
|
||||
return found
|
||||
}
|
||||
let result
|
||||
if(arr) {
|
||||
result = []
|
||||
arr.map(v=>{ result.push({name: v, value: doFind()})})
|
||||
} else {
|
||||
result = doFind(copy)
|
||||
}
|
||||
this.$store.commit('updateStore', {name: 'pagetrack', data: copy})
|
||||
return result
|
||||
}
|
||||
|
||||
Vue.prototype.$clearpage = function(pagename) {
|
||||
if(!pagename) return
|
||||
if(pagename==='reset') return this.$store.commit('updateStore', {name: 'pagetrack', data: {}})
|
||||
let copy = this.$copy(this.$store.state.pagetrack)
|
||||
let arr = Array.isArray(pagename)? pagename : [pagename]
|
||||
arr.map(v=>{
|
||||
copy[v] = false
|
||||
this.$store.commit('updateStore', {name: v, data: undefined})
|
||||
})
|
||||
this.$store.commit('updateStore', {name: 'pagetrack', data: copy})
|
||||
}
|
||||
|
||||
Vue.prototype.$updatepath = function(val) {
|
||||
path = `https://${val}/`
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
358
plugins/datatable.js
Executable file
@@ -0,0 +1,358 @@
|
||||
import Vue from 'vue'
|
||||
Vue.use( {
|
||||
install(Vue) {
|
||||
//==========Find & filter=================
|
||||
Vue.prototype.$find = function(arr, obj, attr) {
|
||||
const keys = Object.keys(obj)
|
||||
let found = arr.find(v=>{
|
||||
let valid = true
|
||||
keys.map(key=>{
|
||||
let val = obj[key]
|
||||
if(valid===false) return false
|
||||
else if(Array.isArray(val)) {
|
||||
if(val.findIndex(x=>x===v[key]) <0) valid = false
|
||||
} else if(!(v[key]===val)) valid = false
|
||||
})
|
||||
return valid
|
||||
})
|
||||
return found? (attr? found[attr] : found) : undefined
|
||||
}
|
||||
|
||||
Vue.prototype.$findIndex = function(arr, obj) {
|
||||
const keys = Object.keys(obj)
|
||||
return arr.findIndex(v=>{
|
||||
let valid = true
|
||||
keys.map(key=>{
|
||||
let val = obj[key]
|
||||
if(valid===false) return false
|
||||
else if(Array.isArray(val)) {
|
||||
if(val.findIndex(x=>x===v[key]) <0) valid = false
|
||||
} else if(!(v[key]===val)) valid = false
|
||||
})
|
||||
return valid
|
||||
})
|
||||
}
|
||||
|
||||
Vue.prototype.$filter = function(arr, obj, attr) {
|
||||
const keys = Object.keys(obj)
|
||||
let rows = arr.filter(v=>{
|
||||
let valid = true
|
||||
keys.map(key=>{
|
||||
let val = obj[key]
|
||||
if(valid===false) return false
|
||||
else if(Array.isArray(val)) {
|
||||
if(val.findIndex(x=>x===v[key]) <0) valid = false
|
||||
} else if(!(v[key]===val)) valid = false
|
||||
})
|
||||
return valid
|
||||
})
|
||||
return attr? rows.map(v=>v[attr]) : rows
|
||||
}
|
||||
|
||||
//=========Empty & copy============
|
||||
Vue.prototype.$id = function() {
|
||||
return Math.random().toString(36).substr(2, 9)
|
||||
}
|
||||
|
||||
Vue.prototype.$empty = function(val) {
|
||||
if(val === undefined || val === null || val === '' || val==="") return true
|
||||
return false
|
||||
},
|
||||
|
||||
Vue.prototype.$copy = function(val) {
|
||||
if(val === undefined || val === null || val === '' || val==="") return val
|
||||
return JSON.parse(JSON.stringify(val))
|
||||
}
|
||||
|
||||
Vue.prototype.$clone = function(obj) {
|
||||
if (obj === null || typeof (obj) !== 'object' || 'isActiveClone' in obj)
|
||||
return obj;
|
||||
if (obj instanceof Date)
|
||||
var temp = new obj.constructor(); //or new Date(obj);
|
||||
else
|
||||
var temp = obj.constructor();
|
||||
for (var key in obj) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
obj['isActiveClone'] = null;
|
||||
temp[key] = this.$clone(obj[key]);
|
||||
delete obj['isActiveClone'];
|
||||
}
|
||||
}
|
||||
return temp
|
||||
}
|
||||
|
||||
Vue.prototype.$delete = function(arr, idx) {
|
||||
arr.splice(idx, 1)
|
||||
}
|
||||
|
||||
Vue.prototype.$stripHtml = function(html, length) {
|
||||
if(!html) return null
|
||||
else if(typeof html!=='string') return html
|
||||
if(html? html.indexOf('<')<0 : false) {
|
||||
return length? (html.length > length? html.substring(0, length) + '...' : html ) : html
|
||||
}
|
||||
var tmp = document.createElement("DIV")
|
||||
tmp.innerHTML = html
|
||||
var val = tmp.textContent || tmp.innerText || ""
|
||||
return length? (val.length > length? val.substring(0, length) + '...' : val ) : val
|
||||
}
|
||||
|
||||
//==========Convert=================
|
||||
Vue.prototype.$isNumber = function(val) {
|
||||
if(val === undefined || val === null || val === '' || val==="") return false
|
||||
val = val.toString().replace(/,/g, "")
|
||||
return !isNaN(val)
|
||||
}
|
||||
|
||||
Vue.prototype.$numtoString = function(val, type, decimal, mindecimal) {
|
||||
if(val === undefined || val === "" || val==='' || val === null) return
|
||||
val = val.toString().replace(/,/g, "")
|
||||
if(isNaN(val)) return
|
||||
let f = decimal? {maximumFractionDigits: decimal || 0} : {}
|
||||
if(mindecimal) f['minimumFractionDigits'] = mindecimal
|
||||
return decimal? Number(val).toLocaleString(type || 'en-EN', f) : Number(val).toLocaleString(type || 'en-EN')
|
||||
}
|
||||
|
||||
Vue.prototype.$formatNumber = function(val) {
|
||||
if(val === undefined || val === "" || val==='' || val === null) return
|
||||
val = val.toString().replace(/,/g, "")
|
||||
if (val.indexOf('%') >0) {
|
||||
val = val.replace(/%/g, "")
|
||||
return isNaN(val)? undefined : Number(val)/100
|
||||
}
|
||||
return isNaN(val)? undefined : Number(val)
|
||||
}
|
||||
|
||||
Vue.prototype.$formatUnit = function(val, unit, decimal, string, mindecimal) {
|
||||
val = this.$formatNumber(val)
|
||||
if(val===undefined) return
|
||||
let percentage = (unit===0.01 || unit==="0.01")? '%' : ''
|
||||
val = unit? val/Number(unit) : val
|
||||
let f = {maximumFractionDigits: decimal || 0}
|
||||
if(mindecimal) f['minimumFractionDigits'] = mindecimal
|
||||
return string? (val.toLocaleString('en-EN', f) + percentage) : val
|
||||
}
|
||||
|
||||
//==========Calculate=================
|
||||
Vue.prototype.$calc = function(fn) {
|
||||
return new Function('return ' + fn)()
|
||||
}
|
||||
|
||||
Vue.prototype.$calculate = function(row, tags, formula, decimal, unit) {
|
||||
let val = this.$copy(formula)
|
||||
let valid = 0
|
||||
tags.forEach(v => {
|
||||
let myRegExp = new RegExp(v, 'g')
|
||||
let res = this.$formatNumber(row[v])
|
||||
if(res) valid = 1
|
||||
val = val.replace(myRegExp, `(${res || 0})`)
|
||||
})
|
||||
if(valid===0) return {success: false, value : undefined} //all values is null
|
||||
//calculate
|
||||
try {
|
||||
let value = this.$calc(val)
|
||||
if(isNaN(value) || value===Number.POSITIVE_INFINITY || value===Number.NEGATIVE_INFINITY) {
|
||||
var result = {success: false, value : value}
|
||||
} else {
|
||||
value = (value===true || value===false)? value : this.$formatUnit(value, unit, decimal, true, decimal)
|
||||
var result = {success: true, value: value}
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
var result = {success: false, value : undefined}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
Vue.prototype.$calculateFunc = function(row, cols, func, decimal, unit) {
|
||||
let value
|
||||
let arr1 = cols.map(v=>this.$formatNumber(row[v]))
|
||||
let arr = arr1.filter(v=>v)
|
||||
if(arr.length===0) return {success: false, value : undefined}
|
||||
if(func==='max') value = Math.max(...arr)
|
||||
else if(func==='min') value = Math.min(...arr)
|
||||
else if(func==='sum') value = arr.reduce((a, b) => a + b, 0)
|
||||
else if(func==='avg') {
|
||||
let total = arr.reduce((a, b) => a + b, 0)
|
||||
value = total / cols.length
|
||||
}
|
||||
if(!value) return {success: false, value: undefined}
|
||||
value = this.$formatUnit(value, unit, decimal, true, decimal)
|
||||
return {success: true, value: value}
|
||||
}
|
||||
|
||||
Vue.prototype.$calculateData = function(data, fields) {
|
||||
let arr = this.$copy(fields.filter(h=>h.formula))
|
||||
if(arr.length===0) return data
|
||||
let arr1 = arr.filter(v=>v.func)
|
||||
arr1.map(v=>{
|
||||
if(v.vals.indexOf(':')>=0) {
|
||||
let arr2 = v.vals.toLowerCase().replaceAll('c', '').split(':')
|
||||
let cols = []
|
||||
for (let i = parseInt(arr2[0]); i <= parseInt(arr2[1]); i++) {
|
||||
let field = fields.length>i? fields[i] : undefined
|
||||
if(field? (field.format==='number' && field.name!==v.name) : false) cols.push(field.name)
|
||||
}
|
||||
v.cols = cols
|
||||
} else {
|
||||
let arr2 = v.vals.toLowerCase().replaceAll('c', '').split(',')
|
||||
let cols = []
|
||||
arr2.map(v=>{
|
||||
let i = parseInt(v)
|
||||
let field = fields.length>i? fields[i] : undefined
|
||||
if(field? (field.format==='number' && field.name!==v.name) : false) cols.push(field.name)
|
||||
})
|
||||
v.cols = cols
|
||||
}
|
||||
})
|
||||
arr = this.$multiSort(arr, {level: 'asc'})
|
||||
let copy = data
|
||||
copy.map(v=>{
|
||||
arr.map(x=>{
|
||||
if(x.func) {
|
||||
let res = this.$calculateFunc(v, x.cols, x.func, x.decimal, x.unit)
|
||||
if(res? res.success : false) v[x.name] = res.value
|
||||
} else {
|
||||
let res = this.$calculate(v, x.tags, x.formula, x.decimal, x.unit)
|
||||
if(res? res.success : false) v[x.name] = res.value
|
||||
}
|
||||
})
|
||||
})
|
||||
return copy
|
||||
}
|
||||
|
||||
Vue.prototype.$summary = function(arr, fields, type) {
|
||||
let obj = {}
|
||||
if(type==='total') {
|
||||
fields.map(x=> obj[x] = arr.map(v=>v[x]? v[x] : 0).reduce((a, b) => a + b, 0))
|
||||
} else if(type==='min') {
|
||||
fields.map(x=>obj[x] = Math.min(...arr.map(v=>v[x])))
|
||||
}
|
||||
else if(type==='max') {
|
||||
fields.map(x=>obj[x] = Math.max(...arr.map(v=>v[x])))
|
||||
}
|
||||
else if(type==='count') {
|
||||
fields.map(x=>obj[x] = arr.map(v=>!this.$empty(v[x])).length)
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
//====================Array====================
|
||||
Vue.prototype.$formatArray = function(data, fields) {
|
||||
let args = fields.filter(v=>v.format==='number')
|
||||
data.map(v=>{
|
||||
args.map(x=>{
|
||||
v[x.name] = this.$empty(v[x.name])? undefined : this.$formatUnit(v[x.name], x.unit, x.decimal, true, x.decimal)
|
||||
})
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
Vue.prototype.$unique = function(arr, keyProps) {
|
||||
const kvArray = arr.map(entry => {
|
||||
const key = keyProps.map(k => entry[k]).join('|');
|
||||
return [key, entry];
|
||||
});
|
||||
const map = new Map(kvArray);
|
||||
return Array.from(map.values());
|
||||
}
|
||||
|
||||
Vue.prototype.$arrayMove = function(arr, old_index, new_index) {
|
||||
if (new_index >= arr.length) {
|
||||
var k = new_index - arr.length + 1;
|
||||
while (k--) {
|
||||
arr.push(undefined);
|
||||
}
|
||||
}
|
||||
arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
|
||||
return arr; // for testing
|
||||
}
|
||||
|
||||
Vue.prototype.$multiSort = function(array, sortObject = {}, format = {}) {
|
||||
const sortKeys = Object.keys(sortObject)
|
||||
|
||||
// Return array if no sort object is supplied.
|
||||
if (!sortKeys.length) return array
|
||||
|
||||
// Change the values of the sortObject keys to -1, 0, or 1.
|
||||
for (let key in sortObject)
|
||||
sortObject[key] = sortObject[key] === 'desc' || sortObject[key] === -1 ? -1 : (sortObject[key] === 'skip' || sortObject[key] === 0 ? 0 : 1)
|
||||
|
||||
const keySort = (a, b, direction) => {
|
||||
direction = direction !== null ? direction : 1
|
||||
if (a === b) return 0
|
||||
|
||||
// If b > a, multiply by -1 to get the reverse direction.
|
||||
return a > b ? direction : -1 * direction;
|
||||
}
|
||||
|
||||
return array.sort((a, b) => {
|
||||
let sorted = 0, index = 0
|
||||
|
||||
// Loop until sorted (-1 or 1) or until the sort keys have been processed.
|
||||
while (sorted === 0 && index < sortKeys.length) {
|
||||
const key = sortKeys[index]
|
||||
if (key) {
|
||||
const direction = sortObject[key]
|
||||
let val1 = format[key]==='number'? (this.$empty(a[key])? 0 : this.$formatNumber(a[key])) : a[key]
|
||||
let val2 = format[key]==='number'? (this.$empty(b[key])? 0 : this.$formatNumber(b[key])) : b[key]
|
||||
sorted = keySort(val1, val2, direction)
|
||||
index++
|
||||
}
|
||||
}
|
||||
return sorted
|
||||
})
|
||||
}
|
||||
|
||||
//======================Fields====================
|
||||
Vue.prototype.$createField = function(name,label,format,show, minwidth) {
|
||||
let field = {name: name, label: label, format: format, show: show, minwidth: minwidth}
|
||||
if(format==='number') {
|
||||
field.unit = '1'
|
||||
field.textalign = 'right'
|
||||
}
|
||||
return field
|
||||
}
|
||||
|
||||
Vue.prototype.$updateFields = function(pagename, field, action) {
|
||||
let pagedata = this.$store.state[pagename]
|
||||
let copy = this.$copy(pagedata.fields)
|
||||
let idx = this.$findIndex(copy, {name: field.name})
|
||||
if(action==='delete') {
|
||||
this.$delete(copy, idx)
|
||||
if(pagedata.filters? pagedata.filters.length>0 : false) {
|
||||
let index = this.$findIndex(pagedata.filters, {name: field.name})
|
||||
if(index>=0) {
|
||||
let copyFilter = this.$copy(this.pagedata.filters)
|
||||
this.$delete(copyFilter, index)
|
||||
this.$store.commit('updateState', {name: pagename, key: 'filterby', data: copyFilter})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
idx>=0? copy[idx] = field : copy.push(field)
|
||||
}
|
||||
this.$store.commit("updateState", {name: pagename, key: "fields", data: copy})
|
||||
},
|
||||
|
||||
Vue.prototype.$updateSeriesFields = function(fields) {
|
||||
fields.filter(v=>v.series).map(field=>{
|
||||
let obj = field.api==='finitem'? this.$findPeriod(field.series, 'period') : this.$findPeriod(field.series)
|
||||
field.period = field.api==='finitem'? obj.code : obj
|
||||
let idx = field.label.indexOf('[')
|
||||
field.label = (idx>0? field.label.substring(0, idx) : field.label) + '[' + (field.api==='finitem'? obj.show : obj) + ']'
|
||||
})
|
||||
return fields
|
||||
}
|
||||
|
||||
Vue.prototype.$updateSeriesFilters = function(filters, fields) {
|
||||
if(fields.filter(v=>v.series).length===0) return filters
|
||||
filters.map(v=>{
|
||||
let found = fields.find(x=>x.name===v.name)
|
||||
if(found? found.series : false) {
|
||||
v.label = found.label
|
||||
}
|
||||
})
|
||||
return filters
|
||||
}
|
||||
}
|
||||
})
|
||||
5
push.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
git add .
|
||||
git commit -m 'changes'
|
||||
git push
|
||||
4
rundev.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
python3 envdev.py
|
||||
kill -9 $(lsof -i:3001 -t) 2> /dev/null
|
||||
npm run dev
|
||||
BIN
static/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
3
static/icon/1.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg id="Flat" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
|
||||
<path d="M128,24A104,104,0,1,0,232,128,104.11791,104.11791,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.09957,88.09957,0,0,1,128,216ZM140,84v92a8,8,0,0,1-16,0V98.94434l-11.56348,7.70605a8.00008,8.00008,0,1,1-8.873-13.31445l24-15.99317A8.00039,8.00039,0,0,1,140,84Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 346 B |
3
static/icon/2.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg id="Flat" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
|
||||
<path d="M128,24A104,104,0,1,0,232,128,104.11791,104.11791,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.09957,88.09957,0,0,1,128,216Zm26.30273-93.7832-34.30566,45.77734H152a8,8,0,0,1,0,16H104.31738c-.10644.00391-.21289.00684-.31836.00684a8.00343,8.00343,0,0,1-6.30175-12.93164L141.37012,112.794a16.00416,16.00416,0,1,0-28.11621-15.01954A8,8,0,1,1,98.51758,91.542a32.00411,32.00411,0,1,1,56.01269,30.35547C154.457,122.00586,154.38184,122.1123,154.30273,122.2168Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 543 B |
3
static/icon/3.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg id="Flat" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
|
||||
<path d="M128,24A104,104,0,1,0,232,128,104.11791,104.11791,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.09957,88.09957,0,0,1,128,216Zm21.459-89.45508a35.99995,35.99995,0,0,1-50.9121,50.91113,8.00052,8.00052,0,0,1,11.31445-11.31445A19.99959,19.99959,0,1,0,124.00293,132a8,8,0,0,1-6.55469-12.58691l19.1875-27.4209H103.99707a8,8,0,0,1,0-16h48a8,8,0,0,1,6.55469,12.58691L137.5332,118.61816A35.92893,35.92893,0,0,1,149.459,126.54492Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 510 B |
3
static/icon/4.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg id="Flat" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
|
||||
<path d="M128,24A104,104,0,1,0,232,128,104.11791,104.11791,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.09957,88.09957,0,0,1,128,216Zm28-104v64a8,8,0,0,1-16,0V152H100a7.99995,7.99995,0,0,1-7.54395-10.66211l23.998-68A7.99987,7.99987,0,1,1,131.542,78.66211L111.30664,136H140V112a8,8,0,0,1,16,0Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 374 B |
3
static/icon/5.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg id="Flat" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
|
||||
<path d="M128,24A104,104,0,1,0,232,128,104.11791,104.11791,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.09957,88.09957,0,0,1,128,216ZM118.63965,92,114.542,117.21094a36.413,36.413,0,0,1,9.33985-1.209A35.99912,35.99912,0,1,1,98.35449,177.4668a7.99985,7.99985,0,1,1,11.291-11.33594,20.20173,20.20173,0,0,0,28.47461-.001,19.91646,19.91646,0,0,0-.001-28.25976,20.20412,20.20412,0,0,0-28.47461.001,7.99971,7.99971,0,0,1-13.541-6.95214l7.835-48.20215A8.00076,8.00076,0,0,1,111.835,76H152a8,8,0,0,1,0,16Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 578 B |
BIN
static/icon/add.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
10
static/icon/add.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 512 512">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M256,11C120.9,11,11,120.9,11,256s109.9,245,245,245s245-109.9,245-245S391.1,11,256,11z M256,460.2 c-112.6,0-204.2-91.6-204.2-204.2S143.4,51.8,256,51.8S460.2,143.4,460.2,256S368.6,460.2,256,460.2z"/>
|
||||
<path d="m357.6,235.6h-81.2v-81.2c0-11.3-9.1-20.4-20.4-20.4-11.3,0-20.4,9.1-20.4,20.4v81.2h-81.2c-11.3,0-20.4,9.1-20.4,20.4s9.1,20.4 20.4,20.4h81.2v81.2c0,11.3 9.1,20.4 20.4,20.4 11.3,0 20.4-9.1 20.4-20.4v-81.2h81.2c11.3,0 20.4-9.1 20.4-20.4s-9.1-20.4-20.4-20.4z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 819 B |
BIN
static/icon/add1.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
44
static/icon/add2.svg
Normal file
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<polygon points="272,128 240,128 240,240 128,240 128,272 240,272 240,384 272,384 272,272 384,272 384,240 272,240 "/>
|
||||
<path d="M256,0C114.609,0,0,114.609,0,256c0,141.391,114.609,256,256,256c141.391,0,256-114.609,256-256
|
||||
C512,114.609,397.391,0,256,0z M256,472c-119.297,0-216-96.703-216-216S136.703,40,256,40s216,96.703,216,216S375.297,472,256,472
|
||||
z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
1
static/icon/add3.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" ?><svg id="Layer_1" style="enable-background:new 0 0 128 128;" version="1.1" viewBox="0 0 128 128" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g><polygon points="60,94 68,94 68,68 94,68 94,60 68,60 68,34 60,34 60,60 34,60 34,68 60,68 "/><path d="M127,1H1v126h126V1z M119,119H9V9h110V119z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 377 B |
1
static/icon/add4.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" ?><svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="4.2333331mm" height="4.2333331mm" viewBox="0 0 4.2333331 4.2333331" version="1.1" id="svg839"><defs id="defs833"/><g id="layer1" transform="translate(73.404884,-1.9651226)"><g id="g1564"><g id="g4067" transform="translate(8.2194025e-4)"><g id="g7841"><g id="g7841-3" transform="translate(-1.8848453e-6,5.169554e-4)"><g id="g8091"><g id="g8578"><g id="g10360" transform="translate(0,8.8925e-4)"><g id="g10447" transform="translate(0.27285155)"><g id="g22519"><g id="g22540"><g id="g22938"><g id="g23361"><g id="g9487" transform="matrix(0.49999977,0.49999977,-0.49999977,0.49999977,-33.876753,37.684249)" style="stroke-width:0.374178;stroke-miterlimit:4;stroke-dasharray:none"><g id="g23535"><g id="g23432" transform="rotate(45,-71.287571,4.08127)"><g id="g64895"><path style="fill:none;stroke:#000000;stroke-width:0.374178;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke" d="m -71.288603,1.6488331 -4e-6,4.8669468" id="path9459"/><path style="fill:none;stroke:#000000;stroke-width:0.374178;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke" d="m -68.855132,4.0823085 -4.866946,-4e-6" id="path64891"/></g></g></g></g></g></g></g></g></g></g></g></g></g></g></g></g></g></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
41
static/icon/add5.svg
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 286.376 286.376" style="enable-background:new 0 0 286.376 286.376;" xml:space="preserve">
|
||||
<g id="Add">
|
||||
<path style="fill-rule:evenodd;clip-rule:evenodd;" d="M268.477,125.29H161.086V17.899c0-9.885-8.013-17.898-17.898-17.898
|
||||
s-17.898,8.013-17.898,17.898v107.39H17.9c-9.885,0-17.9,8.013-17.9,17.898c0,9.885,8.015,17.898,17.9,17.898h107.39v107.39
|
||||
c0,9.885,8.013,17.898,17.898,17.898s17.898-8.013,17.898-17.898v-107.39h107.391c9.885,0,17.898-8.014,17.898-17.898
|
||||
C286.376,133.303,278.362,125.29,268.477,125.29z"/>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 982 B |
1
static/icon/addfolder.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" ?><svg fill="none" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M11 14.5V16.5H13V14.5H15V12.5H13V10.5H11V12.5H9V14.5H11Z" fill="currentColor"/><path clip-rule="evenodd" d="M4 1.5C2.89543 1.5 2 2.39543 2 3.5V4.5C2 4.55666 2.00236 4.61278 2.00698 4.66825C0.838141 5.07811 0 6.19118 0 7.5V19.5C0 21.1569 1.34315 22.5 3 22.5H21C22.6569 22.5 24 21.1569 24 19.5V7.5C24 5.84315 22.6569 4.5 21 4.5H11.874C11.4299 2.77477 9.86384 1.5 8 1.5H4ZM9.73244 4.5C9.38663 3.9022 8.74028 3.5 8 3.5H4V4.5H9.73244ZM3 6.5C2.44772 6.5 2 6.94772 2 7.5V19.5C2 20.0523 2.44772 20.5 3 20.5H21C21.5523 20.5 22 20.0523 22 19.5V7.5C22 6.94772 21.5523 6.5 21 6.5H3Z" fill="currentColor" fill-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 745 B |
14
static/icon/apps.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-grid-dots" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||
<circle cx="5" cy="5" r="1" />
|
||||
<circle cx="12" cy="5" r="1" />
|
||||
<circle cx="19" cy="5" r="1" />
|
||||
<circle cx="5" cy="12" r="1" />
|
||||
<circle cx="12" cy="12" r="1" />
|
||||
<circle cx="19" cy="12" r="1" />
|
||||
<circle cx="5" cy="19" r="1" />
|
||||
<circle cx="12" cy="19" r="1" />
|
||||
<circle cx="19" cy="19" r="1" />
|
||||
</svg>
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 589 B |
1
static/icon/attach-file.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 353 B |
3
static/icon/az.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.70894 2.50467C6.60438 2.2026 6.31986 2 6.0002 2C5.68055 2 5.39603 2.2026 5.29146 2.50467L3.04146 9.00467C2.90597 9.39609 3.11345 9.82325 3.50487 9.95874C3.8963 10.0942 4.32345 9.88676 4.45894 9.49533L4.80348 8.5H7.19693L7.54146 9.49533C7.67696 9.88676 8.10411 10.0942 8.49554 9.95874C8.88696 9.82325 9.09444 9.39609 8.95894 9.00467L6.70894 2.50467ZM6.0002 5.0428L6.6777 7H5.32271L6.0002 5.0428ZM4.00019 11C3.58597 11 3.25019 11.3358 3.25019 11.75C3.25019 12.1642 3.58597 12.5 4.00019 12.5H6.52736L3.39363 16.8089C3.22771 17.037 3.20384 17.339 3.33185 17.5903C3.45986 17.8417 3.71809 18 4.00019 18H8.00019C8.4144 18 8.75019 17.6642 8.75019 17.25C8.75019 16.8358 8.4144 16.5 8.00019 16.5H5.47301L8.60674 12.1911C8.77266 11.963 8.79653 11.661 8.66852 11.4097C8.54051 11.1583 8.28228 11 8.00019 11H4.00019ZM14.25 2C14.6642 2 15 2.33579 15 2.75V15.3219L16.4471 13.7432C16.727 13.4379 17.2015 13.4172 17.5068 13.6971C17.8121 13.977 17.8328 14.4515 17.5529 14.7568L14.8029 17.7568C14.6608 17.9118 14.4602 18 14.25 18C14.0398 18 13.8392 17.9118 13.6971 17.7568L10.9471 14.7568C10.6672 14.4515 10.6879 13.977 10.9932 13.6971C11.2986 13.4172 11.773 13.4379 12.0529 13.7432L13.5 15.3219V2.75C13.5 2.33579 13.8358 2 14.25 2Z" fill="#212121"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
static/icon/bin.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" ?><svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:none;}</style></defs><title/><g data-name="Layer 2" id="Layer_2"><path d="M20,29H12a5,5,0,0,1-5-5V12a1,1,0,0,1,2,0V24a3,3,0,0,0,3,3h8a3,3,0,0,0,3-3V12a1,1,0,0,1,2,0V24A5,5,0,0,1,20,29Z"/><path d="M26,9H6A1,1,0,0,1,6,7H26a1,1,0,0,1,0,2Z"/><path d="M20,9H12a1,1,0,0,1-1-1V6a3,3,0,0,1,3-3h4a3,3,0,0,1,3,3V8A1,1,0,0,1,20,9ZM13,7h6V6a1,1,0,0,0-1-1H14a1,1,0,0,0-1,1Z"/><path d="M14,23a1,1,0,0,1-1-1V15a1,1,0,0,1,2,0v7A1,1,0,0,1,14,23Z"/><path d="M18,23a1,1,0,0,1-1-1V15a1,1,0,0,1,2,0v7A1,1,0,0,1,18,23Z"/></g><g id="frame"><rect class="cls-1" height="32" width="32"/></g></svg>
|
||||
|
After Width: | Height: | Size: 678 B |
3
static/icon/bin1.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" ?><svg style="enable-background:new 0 0 24 24;" version="1.1" viewBox="0 0 24 24" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><style type="text/css">
|
||||
.st0{display:none;}
|
||||
</style><g class="st0" id="grid"/><g id="icon"><path d="M23.5,6h-2.551c-0.245-1.692-1.691-3-3.449-3h-1.785c-0.245-1.692-1.691-3-3.449-3S9.061,1.308,8.816,3H6.5 C4.742,3,3.296,4.308,3.051,6H0.5C0.224,6,0,6.224,0,6.5S0.224,7,0.5,7h3v15.023C3.5,23.113,4.387,24,5.477,24h13.047 c1.09,0,1.977-0.887,1.977-1.977V7h3C23.776,7,24,6.776,24,6.5S23.776,6,23.5,6z M12.266,1c1.208,0,2.217,0.86,2.45,2H9.816 C10.048,1.86,11.058,1,12.266,1z M19.5,22.023c0,0.539-0.438,0.977-0.977,0.977H5.477C4.938,23,4.5,22.562,4.5,22.023V7h15V22.023z M4.05,6C4.283,4.86,5.292,4,6.5,4h2.766h6H17.5c1.208,0,2.217,0.86,2.45,2H4.05z"/><path d="M8,20.5c0.276,0,0.5-0.224,0.5-0.5V10c0-0.276-0.224-0.5-0.5-0.5S7.5,9.724,7.5,10v10C7.5,20.276,7.724,20.5,8,20.5z"/><path d="M12,20.5c0.276,0,0.5-0.224,0.5-0.5V10c0-0.276-0.224-0.5-0.5-0.5s-0.5,0.224-0.5,0.5v10C11.5,20.276,11.724,20.5,12,20.5z "/><path d="M16,20.5c0.276,0,0.5-0.224,0.5-0.5V10c0-0.276-0.224-0.5-0.5-0.5s-0.5,0.224-0.5,0.5v10C15.5,20.276,15.724,20.5,16,20.5z "/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
1
static/icon/building.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" ?><svg style="enable-background:new 0 0 64 64;" version="1.1" viewBox="0 0 64 64" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="_x32_5_attachment"/><g id="_x32_4_office"><g><g><path d="M31.5273,34.4512h22.5313c0.5527,0,1-0.4478,1-1v-6.1519c0-0.5522-0.4473-1-1-1H31.5273c-0.5527,0-1,0.4478-1,1v6.1519 C30.5273,34.0034,30.9746,34.4512,31.5273,34.4512z M47.5479,28.2993h5.5107v4.1519h-5.5107V28.2993z M40.0381,28.2993h5.5098 v4.1519h-5.5098V28.2993z M32.5273,28.2993h5.5107v4.1519h-5.5107V28.2993z"/><path d="M62,60.8833h-1.125V11.1647c1.1713-0.2427,2.0547-1.2807,2.0547-2.5216V7.3232c0-1.4204-1.1572-2.5757-2.5791-2.5757 H25.2354c-1.4209,0-2.5762,1.1553-2.5762,2.5757v1.3198c0,0.1985,0.0275,0.3899,0.0702,0.5757H11.792V5.5513 c0-0.5522-0.4473-1-1-1s-1,0.4478-1,1v3.6675H6.5361V2.1167c0-0.5522-0.4473-1-1-1s-1,0.4478-1,1v7.1021H3.2119 C1.9922,9.2188,1,10.2124,1,11.4341v2.8887c0,1.2192,0.9922,2.2114,2.2119,2.2114h0.1904v44.3491H2c-0.5527,0-1,0.4478-1,1 s0.4473,1,1,1h60c0.5527,0,1-0.4478,1-1S62.5527,60.8833,62,60.8833z M11.9395,60.8833v-9.8662h5.9795v9.8662H11.9395z M24.7119,60.8833h-4.793V50.0171c0-0.5522-0.4473-1-1-1h-7.9795c-0.5527,0-1,0.4478-1,1v10.8662H5.4023V16.5342h19.3096V60.8833 z M4.4023,14.5342H3.2119C3.0986,14.5342,3,14.4355,3,14.3228v-2.8887c0-0.1167,0.0967-0.2153,0.2119-0.2153h21.5v3.3154H4.4023z M41.7949,60.8833h-4.2393v-9.8662h4.2393V60.8833z M48.0313,60.8833h-4.2363v-9.8662h4.2363V60.8833z M58.875,60.8833h-8.8438 V50.0171c0-0.5522-0.4473-1-1-1H36.5557c-0.5527,0-1,0.4478-1,1v10.8662h-8.8438V11.2188H58.875V60.8833z M60.3506,9.2188H59.875 H25.7119h-0.4766c-0.3174,0-0.5762-0.2583-0.5762-0.5757V7.3232c0-0.312,0.2637-0.5757,0.5762-0.5757h35.1152 c0.3135,0,0.5791,0.2637,0.5791,0.5757v1.3198C60.9297,8.9604,60.6699,9.2188,60.3506,9.2188z"/><path d="M9.8926,29.1709h10.2031c0.5527,0,1-0.4478,1-1v-6.2256c0-0.5522-0.4473-1-1-1H9.8926c-0.5527,0-1,0.4478-1,1v6.2256 C8.8926,28.7231,9.3398,29.1709,9.8926,29.1709z M15.9941,22.9453h3.1016v4.2256h-3.1016V22.9453z M10.8926,22.9453h3.1016 v4.2256h-3.1016V22.9453z"/><path d="M9.8926,41.6436h10.2031c0.5527,0,1-0.4478,1-1v-6.2295c0-0.5522-0.4473-1-1-1H9.8926c-0.5527,0-1,0.4478-1,1v6.2295 C8.8926,41.1958,9.3398,41.6436,9.8926,41.6436z M15.9941,35.4141h3.1016v4.2295h-3.1016V35.4141z M10.8926,35.4141h3.1016 v4.2295h-3.1016V35.4141z"/><path d="M31.5273,45.4775h22.5313c0.5527,0,1-0.4478,1-1v-6.1519c0-0.5522-0.4473-1-1-1H31.5273c-0.5527,0-1,0.4478-1,1v6.1519 C30.5273,45.0298,30.9746,45.4775,31.5273,45.4775z M47.5479,39.3257h5.5107v4.1519h-5.5107V39.3257z M40.0381,39.3257h5.5098 v4.1519h-5.5098V39.3257z M32.5273,39.3257h5.5107v4.1519h-5.5107V39.3257z"/><path d="M31.5273,23.4287h22.5313c0.5527,0,1-0.4478,1-1v-6.1523c0-0.5522-0.4473-1-1-1H31.5273c-0.5527,0-1,0.4478-1,1v6.1523 C30.5273,22.981,30.9746,23.4287,31.5273,23.4287z M47.5479,17.2764h5.5107v4.1523h-5.5107V17.2764z M40.0381,17.2764h5.5098 v4.1523h-5.5098V17.2764z M32.5273,17.2764h5.5107v4.1523h-5.5107V17.2764z"/></g></g></g><g id="_x32_3_pin"/><g id="_x32_2_business_card"/><g id="_x32_1_form"/><g id="_x32_0_headset"/><g id="_x31_9_video_call"/><g id="_x31_8_letter_box"/><g id="_x31_7_papperplane"/><g id="_x31_6_laptop"/><g id="_x31_5_connection"/><g id="_x31_4_phonebook"/><g id="_x31_3_classic_telephone"/><g id="_x31_2_sending_mail"/><g id="_x31_1_man_talking"/><g id="_x31_0_date"/><g id="_x30_9_review"/><g id="_x30_8_email"/><g id="_x30_7_information"/><g id="_x30_6_phone_talking"/><g id="_x30_5_women_talking"/><g id="_x30_4_calling"/><g id="_x30_3_women"/><g id="_x30_2_writing"/><g id="_x30_1_chatting"/></svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
15
static/icon/calculator.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version='1.0' encoding='iso-8859-1'?>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 44 44" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 44 44">
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="m15,33h-4c-0.553,0-1,0.447-1,1v4c0,0.553 0.447,1 1,1h4c0.553,0 1-0.447 1-1v-4c0-0.553-0.447-1-1-1zm-1,4h-2v-2h2v2zm1-13h-4c-0.553,0-1,0.447-1,1v4c0,0.553 0.447,1 1,1h4c0.553,0 1-0.447 1-1v-4c0-0.553-0.447-1-1-1zm-1,4h-2v-2h2v2zm19-4h-4c-0.553,0-1,0.447-1,1v4c0,0.553 0.447,1 1,1h4c0.553,0 1-0.447 1-1v-4c0-0.553-0.447-1-1-1zm-1,4h-2v-2h2v2zm2-28h-24c-2.209,0-4,1.791-4,4v36c0,2.209 1.791,4 4,4h24c2.209,0 4-1.791 4-4v-36c0-2.209-1.791-4-4-4zm2,40c0,1.104-0.896,2-2,2h-24c-1.104,0-2-0.896-2-2v-36c0-1.104 0.896-2 2-2h24c1.104,0 2,0.896 2,2v36zm-3-7h-4c-0.553,0-1,0.447-1,1v4c0,0.553 0.447,1 1,1h4c0.553,0 1-0.447 1-1v-4c0-0.553-0.447-1-1-1zm-1,4h-2v-2h2v2zm-8-4h-4c-0.553,0-1,0.447-1,1v4c0,0.553 0.447,1 1,1h4c0.553,0 1-0.447 1-1v-4c0-0.553-0.447-1-1-1zm-1,4h-2v-2h2v2zm1-13h-4c-0.553,0-1,0.447-1,1v4c0,0.553 0.447,1 1,1h4c0.553,0 1-0.447 1-1v-4c0-0.553-0.447-1-1-1zm-1,4h-2v-2h2v2z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path fill-rule="evenodd" d="m33,5h-22c-0.553,0-1,0.447-1,1v14c0,0.553 0.447,1 1,1h22c0.553,0 1-0.447 1-1v-14c0-0.553-0.447-1-1-1zm-1,14h-20v-12h20v12z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
static/icon/calendar.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" ?><svg id="Layer_1" style="enable-background:new 0 0 100.353 100.353;" version="1.1" viewBox="0 0 100.353 100.353" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g><path d="M32.286,42.441h-9.762c-0.829,0-1.5,0.671-1.5,1.5v9.762c0,0.828,0.671,1.5,1.5,1.5h9.762c0.829,0,1.5-0.672,1.5-1.5 v-9.762C33.786,43.113,33.115,42.441,32.286,42.441z M30.786,52.203h-6.762v-6.762h6.762V52.203z"/><path d="M55.054,42.441h-9.762c-0.829,0-1.5,0.671-1.5,1.5v9.762c0,0.828,0.671,1.5,1.5,1.5h9.762c0.828,0,1.5-0.672,1.5-1.5 v-9.762C56.554,43.113,55.882,42.441,55.054,42.441z M53.554,52.203h-6.762v-6.762h6.762V52.203z"/><path d="M77.12,42.441h-9.762c-0.828,0-1.5,0.671-1.5,1.5v9.762c0,0.828,0.672,1.5,1.5,1.5h9.762c0.828,0,1.5-0.672,1.5-1.5v-9.762 C78.62,43.113,77.948,42.441,77.12,42.441z M75.62,52.203h-6.762v-6.762h6.762V52.203z"/><path d="M32.286,64.677h-9.762c-0.829,0-1.5,0.672-1.5,1.5v9.762c0,0.828,0.671,1.5,1.5,1.5h9.762c0.829,0,1.5-0.672,1.5-1.5 v-9.762C33.786,65.349,33.115,64.677,32.286,64.677z M30.786,74.439h-6.762v-6.762h6.762V74.439z"/><path d="M55.054,64.677h-9.762c-0.829,0-1.5,0.672-1.5,1.5v9.762c0,0.828,0.671,1.5,1.5,1.5h9.762c0.828,0,1.5-0.672,1.5-1.5 v-9.762C56.554,65.349,55.882,64.677,55.054,64.677z M53.554,74.439h-6.762v-6.762h6.762V74.439z"/><path d="M77.12,64.677h-9.762c-0.828,0-1.5,0.672-1.5,1.5v9.762c0,0.828,0.672,1.5,1.5,1.5h9.762c0.828,0,1.5-0.672,1.5-1.5v-9.762 C78.62,65.349,77.948,64.677,77.12,64.677z M75.62,74.439h-6.762v-6.762h6.762V74.439z"/><path d="M89,13.394h-9.907c-0.013,0-0.024,0.003-0.037,0.004V11.4c0-3.268-2.658-5.926-5.926-5.926s-5.926,2.659-5.926,5.926v1.994 H56.041V11.4c0-3.268-2.658-5.926-5.926-5.926s-5.926,2.659-5.926,5.926v1.994H33.025V11.4c0-3.268-2.658-5.926-5.926-5.926 s-5.926,2.659-5.926,5.926v1.995c-0.005,0-0.01-0.001-0.015-0.001h-9.905c-0.829,0-1.5,0.671-1.5,1.5V92.64 c0,0.828,0.671,1.5,1.5,1.5H89c0.828,0,1.5-0.672,1.5-1.5V14.894C90.5,14.065,89.828,13.394,89,13.394z M70.204,11.4 c0-1.614,1.312-2.926,2.926-2.926s2.926,1.312,2.926,2.926v8.277c0,1.613-1.312,2.926-2.926,2.926s-2.926-1.312-2.926-2.926V11.4z M50.115,8.474c1.613,0,2.926,1.312,2.926,2.926v8.277c0,1.613-1.312,2.926-2.926,2.926c-1.614,0-2.926-1.312-2.926-2.926v-4.643 c0.004-0.047,0.014-0.092,0.014-0.141s-0.01-0.094-0.014-0.141V11.4C47.189,9.786,48.501,8.474,50.115,8.474z M24.173,11.4 c0-1.614,1.312-2.926,2.926-2.926c1.613,0,2.926,1.312,2.926,2.926v8.277c0,1.613-1.312,2.926-2.926,2.926 c-1.614,0-2.926-1.312-2.926-2.926V11.4z M87.5,91.14H12.753V16.394h8.405c0.005,0,0.01-0.001,0.015-0.001v3.285 c0,3.268,2.659,5.926,5.926,5.926s5.926-2.658,5.926-5.926v-3.283h11.164v3.283c0,3.268,2.659,5.926,5.926,5.926 s5.926-2.658,5.926-5.926v-3.283h11.163v3.283c0,3.268,2.658,5.926,5.926,5.926s5.926-2.658,5.926-5.926V16.39 c0.013,0,0.024,0.004,0.037,0.004H87.5V91.14z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
47
static/icon/camera.svg
Normal file
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 333.668 333.668" style="enable-background:new 0 0 333.668 333.668;" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M295.101,298.649H38.561C17.295,298.649,0,281.354,0,260.088V103.703c0-21.266,17.295-38.561,38.561-38.561h52.347
|
||||
l4.582-15.457c1.87-8.458,9.602-14.666,18.696-14.666h105.297c8.837,0,16.658,6.176,18.728,14.743l0.122,0.527l4.177,14.852h52.597
|
||||
c21.266,0,38.561,17.295,38.561,38.561v156.384C333.662,281.354,316.361,298.649,295.101,298.649z M38.561,77.996
|
||||
c-14.178,0-25.707,11.53-25.707,25.707v156.384c0,14.178,11.53,25.707,25.707,25.707h256.54c14.178,0,25.707-11.53,25.707-25.707
|
||||
V103.703c0-14.178-11.53-25.707-25.707-25.707h-62.327l-7.037-25.097c-0.649-2.918-3.278-5.032-6.26-5.032H114.179
|
||||
c-3.027,0-5.598,2.069-6.26,5.039l-7.429,25.09H38.561z M166.841,259.798c-44.981,0-81.576-36.588-81.576-81.563
|
||||
c0-44.981,36.594-81.569,81.576-81.569c44.969,0,81.557,36.594,81.557,81.569C248.397,223.204,211.809,259.798,166.841,259.798z
|
||||
M166.841,109.513c-37.893,0-68.722,30.823-68.722,68.716s30.83,68.709,68.722,68.709c37.886,0,68.703-30.823,68.703-68.709
|
||||
C235.543,140.336,204.72,109.513,166.841,109.513z M286.804,101.852c-6.555,0-11.858,5.315-11.858,11.858
|
||||
c0,6.549,5.302,11.857,11.858,11.857c6.549,0,11.851-5.309,11.851-11.857C298.649,107.167,293.346,101.852,286.804,101.852z"/>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
7
static/icon/camera_switch.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="gray">
|
||||
<path color="#000" d="M2 7.36C.713 8.089.002 9.027 0 10c0 1.256 1.039 2.255 2.484 2.93 1.214.566 2.784.92 4.516 1.023v-1c-1.602-.104-3.04-.438-4.092-.93C1.64 11.431 1 10.677 1 10a7 3 0 0 1 1-1.537zm12 .001v1.1a7 3 0 0 1 1 1.54c0 .676-.64 1.43-1.908 2.022-.589.275-1.304.499-2.092.667v1.03c.937-.183 1.789-.451 2.516-.79C14.96 12.255 16 11.256 16 10c-.003-.972-.714-1.91-2-2.639z" fill-rule="evenodd" font-family="sans-serif" font-weight="400" overflow="visible" style="line-height:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000;text-transform:none;isolation:auto;mix-blend-mode:normal" white-space="normal"/>
|
||||
<path d="M7 11v5l3.5-2.5z" fill-rule="evenodd"/>
|
||||
<path color="#000" d="M5 0v1.025c-.75.018-1.41.07-1.938.36a1.88 1.88 0 0 0-.837.998C2.06 2.821 2 3.343 2 4v4c0 .658.06 1.179.225 1.617.164.439.461.79.837.998.753.416 1.674.37 2.932.385h4.012c1.258-.015 2.179.03 2.932-.385a1.88 1.88 0 0 0 .838-.998C13.94 9.179 14 8.657 14 8V4c0-.658-.06-1.179-.225-1.617a1.88 1.88 0 0 0-.837-.998c-.753-.416-1.674-.37-2.932-.385H7V0zm1 2h4c1.259.015 2.087.06 2.453.262.184.1.29.212.387.472.097.26.16.674.16 1.266v4c0 .592-.063 1.006-.16 1.266-.098.26-.203.371-.387.472-.366.202-1.194.247-2.453.262H6c-1.259-.015-2.09-.06-2.455-.262-.183-.1-.287-.212-.385-.472C3.063 9.006 3 8.592 3 8V4c0-.592.063-1.006.16-1.266.098-.26.202-.371.385-.472C3.911 2.06 4.741 2.015 6 2zm2 1a3 3 0 1 0 0 6 3 3 0 0 0 0-6zm3.5 0a.5.5 0 1 0 0 1 .5.5 0 0 0 0-1zM8 4a2 2 0 0 1 2 2 2 2 0 0 1-2 2 2 2 0 0 1-2-2 2 2 0 0 1 2-2zm0 1a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1z" font-family="sans-serif" font-weight="400" overflow="visible" style="line-height:normal;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000;text-transform:none;text-orientation:mixed;shape-padding:0;isolation:auto;mix-blend-mode:normal;marker:none" white-space="normal"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
BIN
static/icon/care.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
static/icon/care1.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
1
static/icon/check.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" ?><svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:#101820;}</style></defs><title/><g data-name="Layer 28" id="Layer_28"><path class="cls-1" d="M16,31A15,15,0,1,1,31,16,15,15,0,0,1,16,31ZM16,3A13,13,0,1,0,29,16,13,13,0,0,0,16,3Z"/><path class="cls-1" d="M13.67,22a1,1,0,0,1-.73-.32l-4.67-5a1,1,0,0,1,1.46-1.36l3.94,4.21,8.6-9.21a1,1,0,1,1,1.46,1.36l-9.33,10A1,1,0,0,1,13.67,22Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 446 B |
9
static/icon/check1.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Iconly/Light-Outline/Tick Square</title>
|
||||
<g id="Iconly/Light-Outline/Tick-Square" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Tick-Square" transform="translate(2.000000, 2.000000)" fill="#000000">
|
||||
<path d="M14.334,0 C17.723,0 20,2.378 20,5.916 L20,14.084 C20,17.622 17.723,20 14.333,20 L5.665,20 C2.276,20 0,17.622 0,14.084 L0,5.916 C0,2.378 2.276,0 5.665,0 L14.334,0 Z M14.334,1.5 L5.665,1.5 C3.135,1.5 1.5,3.233 1.5,5.916 L1.5,14.084 C1.5,16.767 3.135,18.5 5.665,18.5 L14.333,18.5 C16.864,18.5 18.5,16.767 18.5,14.084 L18.5,5.916 C18.5,3.233 16.864,1.5 14.334,1.5 Z M14.0895,7.097 C14.3825,7.39 14.3825,7.864 14.0895,8.157 L9.3435,12.903 C9.1975,13.05 9.0055,13.123 8.8135,13.123 C8.6225,13.123 8.4295,13.05 8.2835,12.903 L5.9095,10.53 C5.6165,10.237 5.6165,9.763 5.9095,9.47 C6.2025,9.177 6.6765,9.177 6.9695,9.47 L8.8135,11.312 L13.0295,7.097 C13.3225,6.804 13.7965,6.804 14.0895,7.097 Z" id="Combined-Shape"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |