This commit is contained in:
Xuan Loi
2025-12-05 17:53:49 +07:00
commit 56f3509d4d
187 changed files with 30840 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
node_modules

214
.nuxt/App.js Normal file
View 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
View 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
View 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
View 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)
}
}
}

View 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>

View 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'
]

View 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>

View 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
}
}
}
}

View 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
}
}
}

View 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
View 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
}
})
}
}

View 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])
}

View 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
View 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
View File

@@ -0,0 +1 @@
// This file is intentionally left empty for noop aliases

46
.nuxt/image.js Normal file
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,3 @@
const middleware = {}
export default middleware

View 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--)
}

View 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
View 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
}

View 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
View 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
View 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
View 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
View 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
View 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"
}
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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 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
View 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
View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

22
package.json Normal file
View 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
View 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
View File

@@ -0,0 +1,9 @@
<template>
</template>
<script>
export default {
created() {
this.$router.push('/signin')
}
}
</script>

213
pages/signin.vue Executable file
View 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
View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,5 @@
#!/bin/bash
git add .
git commit -m 'changes'
git push

4
rundev.sh Executable file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

3
static/icon/1.svg Normal file
View 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
View 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
View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

10
static/icon/add.svg Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

44
static/icon/add2.svg Normal file
View 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
View 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
View 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
View 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

View 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
View 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

View 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
View 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
View 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
View 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
View 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

View 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
View 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
View 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

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
static/icon/care1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

1
static/icon/check.svg Normal file
View 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
View 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

Some files were not shown because too many files have changed in this diff Show More