Init config #68
37
.eslintrc.js
37
.eslintrc.js
|
@ -7,15 +7,14 @@ module.exports = {
|
|||
parserOptions: {
|
||||
parser: '@babel/eslint-parser',
|
||||
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
|
||||
sourceType: 'module' // Allows for the use of imports
|
||||
sourceType: 'module', // Allows for the use of imports
|
||||
},
|
||||
|
||||
env: {
|
||||
browser: true,
|
||||
'vue/setup-compiler-macros': true
|
||||
'vue/setup-compiler-macros': true,
|
||||
},
|
||||
|
||||
// Rules order is important, please avoid shuffling them
|
||||
extends: [
|
||||
// Base ESLint recommended rules
|
||||
// 'eslint:recommended',
|
||||
|
@ -27,16 +26,10 @@ module.exports = {
|
|||
// 'plugin:vue/vue3-strongly-recommended', // Priority B: Strongly Recommended (Improving Readability)
|
||||
// 'plugin:vue/vue3-recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead)
|
||||
|
||||
'standard'
|
||||
|
||||
'standard',
|
||||
],
|
||||
|
||||
plugins: [
|
||||
// https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-files
|
||||
// required to lint *.vue files
|
||||
'vue',
|
||||
|
||||
],
|
||||
plugins: ['vue', 'prettier'],
|
||||
|
||||
globals: {
|
||||
ga: 'readonly', // Google Analytics
|
||||
|
@ -48,12 +41,11 @@ module.exports = {
|
|||
__QUASAR_SSR_PWA__: 'readonly',
|
||||
process: 'readonly',
|
||||
Capacitor: 'readonly',
|
||||
chrome: 'readonly'
|
||||
chrome: 'readonly',
|
||||
},
|
||||
|
||||
// add your custom rules here
|
||||
rules: {
|
||||
|
||||
// allow async-await
|
||||
'generator-star-spacing': 'off',
|
||||
// allow paren-less arrow functions
|
||||
|
@ -72,8 +64,19 @@ module.exports = {
|
|||
'import/no-extraneous-dependencies': 'off',
|
||||
|
||||
'prefer-promise-reject-errors': 'off',
|
||||
|
||||
semi: 'off',
|
||||
// allow debugger during development only
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
|
||||
}
|
||||
}
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
extends: ['plugin:vue/vue3-essential'],
|
||||
files: ['src/**/*.{js,vue,scss}'], // Aplica ESLint solo a archivos .js, .vue y .scss dentro de src (Proyecto de quasar)
|
||||
rules: {
|
||||
semi: 'off',
|
||||
indent: ['error', 4, { SwitchCase: 1 }],
|
||||
'space-before-function-paren': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
module.exports = {
|
||||
printWidth: 80,
|
||||
tabWidth: 4,
|
||||
useTabs: false,
|
||||
singleQuote: true,
|
||||
bracketSpacing: true,
|
||||
arrowParens: 'avoid',
|
||||
trailingComma: 'none'
|
||||
};
|
|
@ -4,14 +4,7 @@
|
|||
"editor.bracketPairColorization.enabled": true,
|
||||
"editor.guides.bracketPairs": true,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
|
||||
"editor.codeActionsOnSave": [
|
||||
"source.fixAll.eslint"
|
||||
],
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"vue"
|
||||
]
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.codeActionsOnSave": ["source.fixAll.eslint"],
|
||||
"eslint.validate": ["javascript", "javascriptreact", "typescript", "vue"]
|
||||
}
|
|
@ -1,34 +1,25 @@
|
|||
#!/usr/bin/env groovy
|
||||
|
||||
def BRANCH_ENV = [
|
||||
test: 'test',
|
||||
master: 'production'
|
||||
]
|
||||
def remote = [:]
|
||||
|
||||
node {
|
||||
stage('Setup') {
|
||||
env.NODE_ENV = BRANCH_ENV[env.BRANCH_NAME] ?: 'dev'
|
||||
|
||||
echo "NODE_NAME: ${env.NODE_NAME}"
|
||||
echo "WORKSPACE: ${env.WORKSPACE}"
|
||||
}
|
||||
}
|
||||
pipeline {
|
||||
agent any
|
||||
environment {
|
||||
PROJECT_NAME = 'hedera-web'
|
||||
STACK_NAME = "${env.PROJECT_NAME}-${env.BRANCH_NAME}"
|
||||
}
|
||||
stages {
|
||||
stage('Checkout') {
|
||||
steps {
|
||||
script {
|
||||
def packageJson = readJSON file: 'package.json'
|
||||
env.VERSION = packageJson.version
|
||||
|
||||
switch (env.BRANCH_NAME) {
|
||||
case 'master':
|
||||
env.NODE_ENV = 'production'
|
||||
env.MAIN_REPLICAS = 3
|
||||
env.CRON_REPLICAS = 1
|
||||
break
|
||||
case 'test':
|
||||
env.NODE_ENV = 'test'
|
||||
env.MAIN_REPLICAS = 1
|
||||
env.CRON_REPLICAS = 0
|
||||
break
|
||||
}
|
||||
}
|
||||
setEnv()
|
||||
}
|
||||
}
|
||||
stage('Debuild') {
|
||||
when {
|
||||
anyOf {
|
||||
|
@ -38,31 +29,28 @@ pipeline {
|
|||
}
|
||||
agent {
|
||||
docker {
|
||||
image 'registry.verdnatura.es/debuild:2.21.3-vn2'
|
||||
image 'registry.verdnatura.es/verdnatura/debuild:2.23.4-vn7'
|
||||
registryUrl 'https://registry.verdnatura.es/'
|
||||
registryCredentialsId 'docker-registry'
|
||||
args '-v /mnt/appdata/reprepro:/reprepro'
|
||||
}
|
||||
}
|
||||
steps {
|
||||
sh 'debuild -us -uc -b'
|
||||
sh 'vn-includedeb stretch'
|
||||
sh 'mkdir -p debuild'
|
||||
sh 'mv ../hedera-web_* debuild'
|
||||
|
||||
script {
|
||||
def files = findFiles(glob: 'debuild/*.changes')
|
||||
files.each { file -> env.CHANGES_FILE = file.name }
|
||||
}
|
||||
|
||||
configFileProvider([
|
||||
configFile(fileId: "dput.cf", variable: 'DPUT_CONFIG')
|
||||
]) {
|
||||
sshagent(credentials: ['jenkins-agent']) {
|
||||
sh 'dput --config "$DPUT_CONFIG" verdnatura "debuild/$CHANGES_FILE"'
|
||||
}
|
||||
}
|
||||
stage('Container') {
|
||||
when {
|
||||
anyOf {
|
||||
branch 'master'
|
||||
branch 'test'
|
||||
}
|
||||
}
|
||||
environment {
|
||||
CREDS = credentials('docker-registry')
|
||||
}
|
||||
steps {
|
||||
sh 'docker login --username $CREDS_USR --password $CREDS_PSW $REGISTRY'
|
||||
sh 'docker-compose build --build-arg BUILD_ID=$BUILD_ID --parallel'
|
||||
sh 'docker-compose push'
|
||||
}
|
||||
}
|
||||
stage('Deploy') {
|
||||
|
@ -73,15 +61,41 @@ pipeline {
|
|||
}
|
||||
}
|
||||
environment {
|
||||
DOCKER_HOST = "${env.SWARM_HOST}"
|
||||
CREDS = credentials('docker-registry')
|
||||
IMAGE = "$REGISTRY/verdnatura/hedera-web"
|
||||
}
|
||||
steps {
|
||||
sh "docker stack deploy --with-registry-auth --compose-file docker-compose.yml ${env.STACK_NAME}"
|
||||
script {
|
||||
def packageJson = readJSON file: 'package.json'
|
||||
env.VERSION = "${packageJson.version}"
|
||||
env.TAG = "${packageJson.version}-build${env.BUILD_ID}"
|
||||
}
|
||||
|
||||
sh 'docker-compose build --build-arg BUILD_ID=$BUILD_ID --parallel'
|
||||
sh 'docker login --username $CREDS_USR --password $CREDS_PSW $REGISTRY'
|
||||
sh 'docker push $IMAGE:$TAG'
|
||||
|
||||
script {
|
||||
if (env.BRANCH_NAME == 'master') {
|
||||
sh 'docker tag $IMAGE:$TAG $IMAGE:latest'
|
||||
sh 'docker push $IMAGE:latest'
|
||||
}
|
||||
}
|
||||
|
||||
withKubeConfig([
|
||||
serverUrl: "$KUBERNETES_API",
|
||||
credentialsId: 'kubernetes',
|
||||
namespace: 'salix'
|
||||
]) {
|
||||
sh 'kubectl set image deployment/hedera-web-$BRANCH_NAME hedera-web-$BRANCH_NAME=$IMAGE:$TAG'
|
||||
sh 'kubectl set image deployment/hedera-web-cron-$BRANCH_NAME hedera-web-cron-$BRANCH_NAME=$IMAGE:$TAG'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
unsuccessful {
|
||||
setEnv()
|
||||
sendEmail()
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
|
@ -3,7 +3,8 @@
|
|||
"version": "22.48.2",
|
||||
"description": "Verdnatura web page",
|
||||
"license": "GPL-3.0",
|
||||
"author": "Juan Ferrer Toribio <juan@verdnatura.es>",
|
||||
"productName": "Salix",
|
||||
"author": "Verdnatura",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.verdnatura.es/hedera-web"
|
||||
|
@ -17,12 +18,15 @@
|
|||
"assets-webpack-plugin": "^7.1.1",
|
||||
"babel-loader": "^9.1.0",
|
||||
"bundle-loader": "^0.5.6",
|
||||
"eslint": "^8.10.0",
|
||||
"css-loader": "^5.2.7",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-config-standard": "^17.0.0",
|
||||
"eslint-plugin-import": "^2.19.1",
|
||||
"eslint-plugin-n": "^15.0.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-promise": "^6.0.0",
|
||||
"eslint-plugin-vue": "^9.0.0",
|
||||
"eslint-plugin-vue": "^9.27.0",
|
||||
"eslint-webpack-plugin": "^3.1.1",
|
||||
"file-loader": "^6.2.0",
|
||||
"fs-extra": "^10.1.0",
|
||||
|
@ -31,6 +35,10 @@
|
|||
"json-loader": "^0.5.7",
|
||||
"mini-css-extract-plugin": "^2.7.0",
|
||||
"node-sass": "^7.0.1",
|
||||
"postcss": "^8.4.39",
|
||||
"postcss-import": "^13.0.0",
|
||||
"postcss-loader": "^4.3.0",
|
||||
"postcss-url": "^10.1.3",
|
||||
"raw-loader": "^4.0.2",
|
||||
"sass-loader": "^12.6.0",
|
||||
"style-loader": "^3.3.1",
|
||||
|
|
|
@ -104,15 +104,14 @@ module.exports = configure(function (ctx) {
|
|||
type: 'http'
|
||||
},
|
||||
port: 8080,
|
||||
open: true, // opens browser window automatically
|
||||
|
||||
open: false,
|
||||
// static: __dirname,
|
||||
headers: { 'Access-Control-Allow-Origin': '*' },
|
||||
// stats: { chunks: false },
|
||||
proxy: {
|
||||
'/api': 'http://localhost:3000',
|
||||
'/': {
|
||||
target: 'http://localhost/projects/hedera-web',
|
||||
target: 'http://localhost:3001',
|
||||
bypass: (req) => req.path !== '/' ? req.path : null
|
||||
}
|
||||
}
|
||||
|
@ -121,7 +120,7 @@ module.exports = configure(function (ctx) {
|
|||
// https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-framework
|
||||
framework: {
|
||||
config: {},
|
||||
|
||||
autoImportComponentCase: 'pascal',
|
||||
// iconSet: 'material-icons', // Quasar icon set
|
||||
// lang: 'en-US', // Quasar language pack
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from 'vue'
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'App'
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
export default async ({ app }) => {
|
||||
/*
|
||||
window.addEventListener('error',
|
||||
|
|
|
@ -1,18 +1,23 @@
|
|||
import { boot } from 'quasar/wrappers'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import messages from 'src/i18n'
|
||||
import { boot } from 'quasar/wrappers';
|
||||
import { createI18n } from 'vue-i18n';
|
||||
import messages from 'src/i18n';
|
||||
|
||||
export default boot(({ app }) => {
|
||||
const i18n = createI18n({
|
||||
locale: 'es-ES',
|
||||
locale: navigator.language || navigator.userLanguage,
|
||||
fallbackLocale: 'en',
|
||||
globalInjection: true,
|
||||
missingWarn: false,
|
||||
fallbackWarn: false,
|
||||
legacy: false,
|
||||
silentTranslationWarn: true,
|
||||
silentFallbackWarn: true,
|
||||
messages
|
||||
})
|
||||
|
||||
});
|
||||
export default boot(({ app }) => {
|
||||
// Set i18n instance on app
|
||||
app.use(i18n)
|
||||
app.use(i18n);
|
||||
|
||||
window.i18n = i18n.global
|
||||
})
|
||||
window.i18n = i18n.global;
|
||||
});
|
||||
|
||||
export { i18n };
|
||||
|
|
|
@ -28,7 +28,7 @@ a.link {
|
|||
}
|
||||
.q-card {
|
||||
border-radius: 7px;
|
||||
box-shadow: 0 0 3px rgba(0, 0, 0, .1);
|
||||
box-shadow: 0 0 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.q-page-sticky.fixed-bottom-right {
|
||||
margin: 18px;
|
||||
|
|
|
@ -12,17 +12,17 @@
|
|||
// to match your app's branding.
|
||||
// Tip: Use the "Theme Builder" on Quasar's documentation website.
|
||||
|
||||
$primary : #1A1A1A;
|
||||
$secondary : #26A69A;
|
||||
$primary: #1a1a1a;
|
||||
$secondary: #26a69a;
|
||||
$accent: #8cc63f;
|
||||
|
||||
$dark : #1D1D1D;
|
||||
$dark: #1d1d1d;
|
||||
$dark-page: #121212;
|
||||
|
||||
$positive : #21BA45;
|
||||
$negative : #C10015;
|
||||
$info : #31CCEC;
|
||||
$warning : #F2C037;
|
||||
$positive: #21ba45;
|
||||
$negative: #c10015;
|
||||
$info: #31ccec;
|
||||
$warning: #f2c037;
|
||||
|
||||
// Width
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
%margin-auto {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
|
|
@ -22,15 +22,7 @@ export default {
|
|||
'Friday',
|
||||
'Saturday'
|
||||
],
|
||||
daysShort: [
|
||||
'Sun',
|
||||
'Mon',
|
||||
'Tue',
|
||||
'Wed',
|
||||
'Thu',
|
||||
'Fri',
|
||||
'Sat'
|
||||
],
|
||||
daysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
||||
months: [
|
||||
'January',
|
||||
'February',
|
||||
|
|
|
@ -22,15 +22,7 @@ export default {
|
|||
'Viernes',
|
||||
'Sábado'
|
||||
],
|
||||
daysShort: [
|
||||
'Do',
|
||||
'Lu',
|
||||
'Mi',
|
||||
'Mi',
|
||||
'Ju',
|
||||
'Vi',
|
||||
'Sa'
|
||||
],
|
||||
daysShort: ['Do', 'Lu', 'Mi', 'Mi', 'Ju', 'Vi', 'Sa'],
|
||||
months: [
|
||||
'Enero',
|
||||
'Febrero',
|
||||
|
|
|
@ -57,8 +57,10 @@ export class Connection extends JsonConnection {
|
|||
})
|
||||
|
||||
for (let j = 0; j < rows.length; j++) {
|
||||
const row = data[j] = {}
|
||||
for (let k = 0; k < columns.length; k++) { row[columns[k].name] = rows[j][k] }
|
||||
const row = (data[j] = {})
|
||||
for (let k = 0; k < columns.length; k++) {
|
||||
row[columns[k].name] = rows[j][k]
|
||||
}
|
||||
}
|
||||
|
||||
for (let j = 0; j < columns.length; j++) {
|
||||
|
@ -74,14 +76,20 @@ export class Connection extends JsonConnection {
|
|||
}
|
||||
|
||||
if (castFunc !== null) {
|
||||
if (col.def != null) { col.def = castFunc(col.def) }
|
||||
if (col.def != null) {
|
||||
col.def = castFunc(col.def)
|
||||
}
|
||||
|
||||
for (let k = 0; k < data.length; k++) {
|
||||
if (data[k][col.name] != null) { data[k][col.name] = castFunc(data[k][col.name]) }
|
||||
if (data[k][col.name] != null) {
|
||||
data[k][col.name] = castFunc(data[k][col.name])
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { results.push(json[i]) }
|
||||
}
|
||||
} else {
|
||||
results.push(json[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
|
@ -100,7 +108,7 @@ export class Connection extends JsonConnection {
|
|||
* @return {ResultSet} The result
|
||||
*/
|
||||
async execQuery (query, params) {
|
||||
const sql = query.replace(/#\w+/g, key => {
|
||||
const sql = query.replace(/#\w+/g, (key) => {
|
||||
const value = params[key.substring(1)]
|
||||
return value ? this.renderValue(value) : key
|
||||
})
|
||||
|
@ -128,7 +136,7 @@ export class Connection extends JsonConnection {
|
|||
case 'number':
|
||||
return v
|
||||
case 'boolean':
|
||||
return (v) ? 'TRUE' : 'FALSE'
|
||||
return v ? 'TRUE' : 'FALSE'
|
||||
case 'string':
|
||||
return "'" + v.replace(this.regexp, this.replaceFunc) + "'"
|
||||
default:
|
||||
|
@ -136,8 +144,12 @@ export class Connection extends JsonConnection {
|
|||
if (!isNaN(v.getTime())) {
|
||||
const unixTime = parseInt(fixTz(v).getTime() / 1000)
|
||||
return 'DATE(FROM_UNIXTIME(' + unixTime + '))'
|
||||
} else { return '0000-00-00' }
|
||||
} else { return 'NULL' }
|
||||
} else {
|
||||
return '0000-00-00'
|
||||
}
|
||||
} else {
|
||||
return 'NULL'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,16 +163,14 @@ export class Connection extends JsonConnection {
|
|||
|
||||
// TODO: Read time zone from db configuration
|
||||
const tz = { timeZone: 'Europe/Madrid' }
|
||||
const isLocal = Intl
|
||||
.DateTimeFormat()
|
||||
.resolvedOptions()
|
||||
.timeZone === tz.timeZone
|
||||
const isLocal = Intl.DateTimeFormat().resolvedOptions().timeZone === tz.timeZone
|
||||
|
||||
function fixTz (date) {
|
||||
if (isLocal) return date
|
||||
|
||||
const localDate = new Date(date.toLocaleString('en-US', tz))
|
||||
const hasTime = localDate.getHours() ||
|
||||
const hasTime =
|
||||
localDate.getHours() ||
|
||||
localDate.getMinutes() ||
|
||||
localDate.getSeconds() ||
|
||||
localDate.getMilliseconds()
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import { Result } from './result'
|
||||
|
||||
/**
|
||||
|
@ -26,10 +25,13 @@ export class ResultSet {
|
|||
}
|
||||
|
||||
fetch () {
|
||||
if (this.error) { throw this.error }
|
||||
if (this.error) {
|
||||
throw this.error
|
||||
}
|
||||
|
||||
if (this.results !== null &&
|
||||
this.results.length > 0) { return this.results.shift() }
|
||||
if (this.results !== null && this.results.length > 0) {
|
||||
return this.results.shift()
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
@ -61,9 +63,13 @@ export class ResultSet {
|
|||
fetchObject () {
|
||||
const result = this.fetch()
|
||||
|
||||
if (result !== null &&
|
||||
if (
|
||||
result !== null &&
|
||||
result.data instanceof Array &&
|
||||
result.data.length > 0) { return result.data[0] }
|
||||
result.data.length > 0
|
||||
) {
|
||||
return result.data[0]
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
@ -76,8 +82,7 @@ export class ResultSet {
|
|||
fetchData () {
|
||||
const result = this.fetch()
|
||||
|
||||
if (result !== null &&
|
||||
result.data instanceof Array) {
|
||||
if (result !== null && result.data instanceof Array) {
|
||||
return result.data
|
||||
}
|
||||
|
||||
|
@ -92,7 +97,9 @@ export class ResultSet {
|
|||
fetchValue () {
|
||||
const row = this.fetchRow()
|
||||
|
||||
if (row instanceof Array && row.length > 0) { return row[0] }
|
||||
if (row instanceof Array && row.length > 0) {
|
||||
return row[0]
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
@ -105,9 +112,11 @@ export class ResultSet {
|
|||
fetchRow () {
|
||||
const result = this.fetch()
|
||||
|
||||
if (result !== null &&
|
||||
if (
|
||||
result !== null &&
|
||||
result.data instanceof Array &&
|
||||
result.data.length > 0) {
|
||||
result.data.length > 0
|
||||
) {
|
||||
const object = result.data[0]
|
||||
const row = new Array(result.columns.length)
|
||||
for (let i = 0; i < row.length; i++) {
|
||||
|
|
|
@ -19,7 +19,9 @@ export class Result {
|
|||
col.index = i
|
||||
this.columnMap[col.name] = col
|
||||
}
|
||||
} else { this.columnMap = null }
|
||||
} else {
|
||||
this.columnMap = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -54,7 +56,9 @@ export class Result {
|
|||
next () {
|
||||
this.row++
|
||||
|
||||
if (this.row >= this.data.length) { return false }
|
||||
if (this.row >= this.data.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import { VnObject } from './object'
|
||||
import { JsonException } from './json-exception'
|
||||
|
||||
|
@ -34,7 +33,9 @@ export class JsonConnection extends VnObject {
|
|||
const elements = form.elements
|
||||
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
if (elements[i].name) { params[elements[i].name] = elements[i].value }
|
||||
if (elements[i].name) {
|
||||
params[elements[i].name] = elements[i].value
|
||||
}
|
||||
}
|
||||
|
||||
return this.sendWithUrl('POST', form.action, params)
|
||||
|
@ -93,32 +94,39 @@ export class JsonConnection extends VnObject {
|
|||
}
|
||||
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
request.onreadystatechange =
|
||||
() => this._onStateChange(request, resolve, reject)
|
||||
request.onreadystatechange = () =>
|
||||
this._onStateChange(request, resolve, reject)
|
||||
})
|
||||
|
||||
request.send(config.data)
|
||||
|
||||
this._requestsCount++
|
||||
|
||||
if (this._requestsCount === 1) { this.emit('loading-changed', true) }
|
||||
if (this._requestsCount === 1) {
|
||||
this.emit('loading-changed', true)
|
||||
}
|
||||
|
||||
return promise
|
||||
}
|
||||
|
||||
_onStateChange (request, resolve, reject) {
|
||||
if (request.readyState !== 4) { return }
|
||||
if (request.readyState !== 4) {
|
||||
return
|
||||
}
|
||||
|
||||
this._requestsCount--
|
||||
|
||||
if (this._requestsCount === 0) { this.emit('loading-changed', false) }
|
||||
if (this._requestsCount === 0) {
|
||||
this.emit('loading-changed', false)
|
||||
}
|
||||
|
||||
let data = null
|
||||
let error = null
|
||||
try {
|
||||
if (request.status === 0) {
|
||||
const err = new JsonException()
|
||||
err.message = 'The server does not respond, please check your Internet connection'
|
||||
err.message =
|
||||
'The server does not respond, please check your Internet connection'
|
||||
err.statusCode = request.status
|
||||
throw err
|
||||
}
|
||||
|
@ -144,8 +152,12 @@ export class JsonConnection extends VnObject {
|
|||
let json
|
||||
let jsData
|
||||
|
||||
if (request.responseText) { json = JSON.parse(request.responseText) }
|
||||
if (json) { jsData = json.data || json }
|
||||
if (request.responseText) {
|
||||
json = JSON.parse(request.responseText)
|
||||
}
|
||||
if (json) {
|
||||
jsData = json.data || json
|
||||
}
|
||||
|
||||
if (request.status >= 200 && request.status < 300) {
|
||||
data = jsData
|
||||
|
@ -181,6 +193,8 @@ export class JsonConnection extends VnObject {
|
|||
if (error) {
|
||||
this.emit('error', error)
|
||||
reject(error)
|
||||
} else { resolve(data) }
|
||||
} else {
|
||||
resolve(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
/**
|
||||
* The main base class. Manages the signal system. Objects based on this class
|
||||
* can be instantiated declaratively using XML.
|
||||
|
@ -45,7 +44,9 @@ export class VnObject {
|
|||
* @param {Object} props Properties
|
||||
*/
|
||||
setProperties (props) {
|
||||
for (const prop in props) { this[prop] = props[prop] }
|
||||
for (const prop in props) {
|
||||
this[prop] = props[prop]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -62,7 +63,9 @@ export class VnObject {
|
|||
unref () {
|
||||
this._refCount--
|
||||
|
||||
if (this._refCount === 0) { this._destroy() }
|
||||
if (this._refCount === 0) {
|
||||
this._destroy()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -91,14 +94,16 @@ export class VnObject {
|
|||
*/
|
||||
on (id, callback, instance) {
|
||||
if (!(callback instanceof Function)) {
|
||||
console.warn('Vn.Object: Invalid callback for signal \'%s\'', id)
|
||||
console.warn("Vn.Object: Invalid callback for signal '%s'", id)
|
||||
return
|
||||
}
|
||||
|
||||
this._signalInit()
|
||||
let callbacks = this._thisArg.signals[id]
|
||||
|
||||
if (!callbacks) { callbacks = this._thisArg.signals[id] = [] }
|
||||
if (!callbacks) {
|
||||
callbacks = this._thisArg.signals[id] = []
|
||||
}
|
||||
|
||||
callbacks.push({
|
||||
blocked: false,
|
||||
|
@ -115,15 +120,23 @@ export class VnObject {
|
|||
* @param {boolean} block %true for lock the signal, %false for unlock
|
||||
*/
|
||||
blockSignal (id, callback, block, instance) {
|
||||
if (!this._thisArg) { return }
|
||||
if (!this._thisArg) {
|
||||
return
|
||||
}
|
||||
|
||||
const callbacks = this._thisArg.signals[id]
|
||||
|
||||
if (!callbacks) { return }
|
||||
if (!callbacks) {
|
||||
return
|
||||
}
|
||||
|
||||
for (let i = 0; i < callbacks.length; i++) {
|
||||
if (callbacks[i].callback === callback &&
|
||||
callbacks[i].instance === instance) { callbacks[i].blocked = block }
|
||||
if (
|
||||
callbacks[i].callback === callback &&
|
||||
callbacks[i].instance === instance
|
||||
) {
|
||||
callbacks[i].blocked = block
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,19 +146,27 @@ export class VnObject {
|
|||
* @param {string} id The signal identifier
|
||||
*/
|
||||
emit (id) {
|
||||
if (!this._thisArg) { return }
|
||||
if (!this._thisArg) {
|
||||
return
|
||||
}
|
||||
|
||||
const callbacks = this._thisArg.signals[id]
|
||||
|
||||
if (!callbacks) { return }
|
||||
if (!callbacks) {
|
||||
return
|
||||
}
|
||||
|
||||
const callbackArgs = []
|
||||
callbackArgs.push(this)
|
||||
|
||||
for (let i = 1; i < arguments.length; i++) { callbackArgs.push(arguments[i]) }
|
||||
for (let i = 1; i < arguments.length; i++) {
|
||||
callbackArgs.push(arguments[i])
|
||||
}
|
||||
|
||||
for (let i = 0; i < callbacks.length; i++) {
|
||||
if (!callbacks[i].blocked) { callbacks[i].callback.apply(callbacks[i].instance, callbackArgs) }
|
||||
if (!callbacks[i].blocked) {
|
||||
callbacks[i].callback.apply(callbacks[i].instance, callbackArgs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -157,14 +178,20 @@ export class VnObject {
|
|||
* @param {Object} instance The instance
|
||||
*/
|
||||
disconnect (id, callback, instance) {
|
||||
if (!this._thisArg) { return }
|
||||
if (!this._thisArg) {
|
||||
return
|
||||
}
|
||||
|
||||
const callbacks = this._thisArg.signals[id]
|
||||
|
||||
if (callbacks) {
|
||||
for (let i = callbacks.length; i--;) {
|
||||
if (callbacks[i].callback === callback &&
|
||||
callbacks[i].instance === instance) { callbacks.splice(i, 1) }
|
||||
if (
|
||||
callbacks[i].callback === callback &&
|
||||
callbacks[i].instance === instance
|
||||
) {
|
||||
callbacks.splice(i, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -175,7 +202,9 @@ export class VnObject {
|
|||
* @param {Object} instance The instance
|
||||
*/
|
||||
disconnectByInstance (instance) {
|
||||
if (!this._thisArg) { return }
|
||||
if (!this._thisArg) {
|
||||
return
|
||||
}
|
||||
|
||||
const signals = this._thisArg.signals
|
||||
|
||||
|
@ -184,7 +213,9 @@ export class VnObject {
|
|||
|
||||
if (callbacks) {
|
||||
for (let i = callbacks.length; i--;) {
|
||||
if (callbacks[i].instance === instance) { callbacks.splice(i, 1) }
|
||||
if (callbacks[i].instance === instance) {
|
||||
callbacks.splice(i, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -196,11 +227,15 @@ export class VnObject {
|
|||
* but should always call the parent method.
|
||||
*/
|
||||
_destroy () {
|
||||
if (!this._thisArg) { return }
|
||||
if (!this._thisArg) {
|
||||
return
|
||||
}
|
||||
|
||||
const links = this._thisArg.links
|
||||
|
||||
for (const key in links) { this._unlink(links[key]) }
|
||||
for (const key in links) {
|
||||
this._unlink(links[key])
|
||||
}
|
||||
|
||||
this._thisArg = null
|
||||
}
|
||||
|
@ -219,15 +254,21 @@ export class VnObject {
|
|||
const newObject = prop[key]
|
||||
const oldObject = this[key]
|
||||
|
||||
if (oldObject) { this._unlink(oldObject) }
|
||||
if (oldObject) {
|
||||
this._unlink(oldObject)
|
||||
}
|
||||
|
||||
this[key] = newObject
|
||||
|
||||
if (newObject) {
|
||||
links[key] = newObject.ref()
|
||||
|
||||
for (const signal in handlers) { newObject.on(signal, handlers[signal], this) }
|
||||
} else if (oldObject) { links[key] = undefined }
|
||||
for (const signal in handlers) {
|
||||
newObject.on(signal, handlers[signal], this)
|
||||
}
|
||||
} else if (oldObject) {
|
||||
links[key] = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
<template>
|
||||
<q-layout id="bg" class="fullscreen row justify-center items-center layout-view scroll">
|
||||
<QLayout
|
||||
id="bg"
|
||||
class="fullscreen row justify-center items-center layout-view scroll"
|
||||
>
|
||||
<div class="column q-pa-md row items-center justify-center">
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition>
|
||||
|
@ -7,7 +10,7 @@
|
|||
</transition>
|
||||
</router-view>
|
||||
</div>
|
||||
</q-layout>
|
||||
</QLayout>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -27,5 +30,5 @@
|
|||
<script>
|
||||
export default {
|
||||
name: 'LoginLayout'
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,84 +1,80 @@
|
|||
<template>
|
||||
<q-layout view="lHh Lpr lFf">
|
||||
<q-header reveal>
|
||||
<q-toolbar>
|
||||
<q-btn
|
||||
<QLayout view="lHh Lpr lFf">
|
||||
<QHeader>
|
||||
<QToolbar>
|
||||
<QBtn
|
||||
flat
|
||||
dense
|
||||
round
|
||||
icon="menu"
|
||||
aria-label="Menu"
|
||||
@click="toggleLeftDrawer"/>
|
||||
<q-toolbar-title>
|
||||
@click="toggleLeftDrawer"
|
||||
/>
|
||||
<QToolbarTitle>
|
||||
{{ $app.title }}
|
||||
<div
|
||||
v-if="$app.subtitle"
|
||||
class="subtitle text-caption">
|
||||
<div v-if="$app.subtitle" class="subtitle text-caption">
|
||||
{{ $app.subtitle }}
|
||||
</div>
|
||||
</q-toolbar-title>
|
||||
<div id="actions" ref="actions">
|
||||
</div>
|
||||
<q-btn
|
||||
</QToolbarTitle>
|
||||
<div id="actions" ref="actions"></div>
|
||||
<QBtn
|
||||
v-if="$app.useRightDrawer"
|
||||
@click="$app.rightDrawerOpen = !$app.rightDrawerOpen"
|
||||
aria-label="Menu"
|
||||
flat
|
||||
dense
|
||||
round>
|
||||
<q-icon name="menu"/>
|
||||
</q-btn>
|
||||
</q-toolbar>
|
||||
</q-header>
|
||||
<q-drawer
|
||||
v-model="leftDrawerOpen"
|
||||
:width="250"
|
||||
show-if-above>
|
||||
<q-toolbar class="logo">
|
||||
<img src="statics/logo-dark.svg">
|
||||
</q-toolbar>
|
||||
round
|
||||
>
|
||||
<QIcon name="menu" />
|
||||
</QBtn>
|
||||
</QToolbar>
|
||||
</QHeader>
|
||||
<QDrawer v-model="leftDrawerOpen" :width="250" show-if-above>
|
||||
<QToolbar class="logo">
|
||||
<img src="statics/logo-dark.svg" />
|
||||
</QToolbar>
|
||||
<div class="user-info">
|
||||
<div>
|
||||
<span id="user-name">{{(user.nickname)}}</span>
|
||||
<q-btn flat icon="logout" alt="_Exit" @click="logout()"/>
|
||||
<span id="user-name">{{ user.nickname }}</span>
|
||||
<QBtn flat icon="logout" alt="_Exit" @click="logout()" />
|
||||
</div>
|
||||
<div id="supplant" class="supplant">
|
||||
<span id="supplanted">{{ supplantedUser }}</span>
|
||||
<q-btn flat icon="logout" alt="_Exit"/>
|
||||
<QBtn flat icon="logout" alt="_Exit" />
|
||||
</div>
|
||||
</div>
|
||||
<q-list
|
||||
v-for="item in essentialLinks"
|
||||
:key="item.id">
|
||||
<q-item
|
||||
v-if="!item.childs"
|
||||
:to="`/${item.path}`">
|
||||
<q-item-section>
|
||||
<q-item-label>{{item.description}}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-expansion-item
|
||||
<QList v-for="item in essentialLinks" :key="item.id">
|
||||
<QItem v-if="!item.childs" :to="`/${item.path}`">
|
||||
<QItemSection>
|
||||
<QItemLabel>{{ item.description }}</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QExpansionItem
|
||||
v-if="item.childs"
|
||||
:label="item.description"
|
||||
expand-separator>
|
||||
<q-list>
|
||||
<q-item
|
||||
expand-separator
|
||||
>
|
||||
<QList>
|
||||
<QItem
|
||||
v-for="subitem in item.childs"
|
||||
:key="subitem.id"
|
||||
:to="`/${subitem.path}`"
|
||||
class="q-pl-lg">
|
||||
<q-item-section>
|
||||
<q-item-label>{{subitem.description}}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-expansion-item>
|
||||
</q-list>
|
||||
</q-drawer>
|
||||
<q-page-container>
|
||||
class="q-pl-lg"
|
||||
>
|
||||
<QItemSection>
|
||||
<QItemLabel>
|
||||
{{ subitem.description }}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</QList>
|
||||
</QExpansionItem>
|
||||
</QList>
|
||||
</QDrawer>
|
||||
<QPageContainer>
|
||||
<router-view />
|
||||
</q-page-container>
|
||||
</q-layout>
|
||||
</QPageContainer>
|
||||
</QLayout>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -134,7 +130,7 @@
|
|||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
@import "src/css/responsive";
|
||||
@import 'src/css/responsive';
|
||||
|
||||
.q-drawer {
|
||||
.q-item {
|
||||
|
@ -171,15 +167,15 @@
|
|||
</style>
|
||||
|
||||
<script>
|
||||
import { defineComponent, ref } from 'vue'
|
||||
import { userStore } from 'stores/user'
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { userStore } from 'stores/user';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'MainLayout',
|
||||
props: {},
|
||||
|
||||
setup() {
|
||||
const leftDrawerOpen = ref(false)
|
||||
const leftDrawerOpen = ref(false);
|
||||
|
||||
return {
|
||||
user: userStore(),
|
||||
|
@ -187,50 +183,52 @@ export default defineComponent({
|
|||
essentialLinks: ref(null),
|
||||
leftDrawerOpen,
|
||||
toggleLeftDrawer() {
|
||||
leftDrawerOpen.value = !leftDrawerOpen.value
|
||||
}
|
||||
leftDrawerOpen.value = !leftDrawerOpen.value;
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
this.$refs.actions.appendChild(this.$actions)
|
||||
await this.user.loadData()
|
||||
await this.$app.loadConfig()
|
||||
await this.fetchData()
|
||||
this.$refs.actions.appendChild(this.$actions);
|
||||
await this.user.loadData();
|
||||
await this.$app.loadConfig();
|
||||
await this.fetchData();
|
||||
},
|
||||
|
||||
methods: {
|
||||
async fetchData() {
|
||||
const sections = await this.$jApi.query('SELECT * FROM myMenu')
|
||||
const sections = await this.$jApi.query('SELECT * FROM myMenu');
|
||||
|
||||
const sectionMap = new Map()
|
||||
const sectionMap = new Map();
|
||||
for (const section of sections) {
|
||||
sectionMap.set(section.id, section)
|
||||
sectionMap.set(section.id, section);
|
||||
}
|
||||
|
||||
const sectionTree = []
|
||||
const sectionTree = [];
|
||||
for (const section of sections) {
|
||||
const parent = section.parentFk
|
||||
const parent = section.parentFk;
|
||||
if (parent) {
|
||||
const parentSection = sectionMap.get(parent)
|
||||
if (!parentSection) continue
|
||||
let childs = parentSection.childs
|
||||
if (!childs) { childs = parentSection.childs = [] }
|
||||
childs.push(section)
|
||||
const parentSection = sectionMap.get(parent);
|
||||
if (!parentSection) continue;
|
||||
let childs = parentSection.childs;
|
||||
if (!childs) {
|
||||
childs = parentSection.childs = [];
|
||||
}
|
||||
childs.push(section);
|
||||
} else {
|
||||
sectionTree.push(section)
|
||||
sectionTree.push(section);
|
||||
}
|
||||
}
|
||||
|
||||
this.essentialLinks = sectionTree
|
||||
this.essentialLinks = sectionTree;
|
||||
},
|
||||
|
||||
async logout() {
|
||||
this.user.logout()
|
||||
this.$router.push('/login')
|
||||
this.user.logout();
|
||||
this.$router.push('/login');
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import { date as qdate, format } from 'quasar'
|
||||
const { pad } = format
|
||||
|
||||
|
@ -61,7 +60,7 @@ export function elapsedTime (val) {
|
|||
if (!(val instanceof Date)) {
|
||||
val = new Date(val)
|
||||
}
|
||||
const now = (new Date()).getTime()
|
||||
const now = new Date().getTime()
|
||||
val = Math.floor((now - val.getTime()) / 1000)
|
||||
|
||||
const hours = Math.floor(val / 3600)
|
||||
|
|
|
@ -1,31 +1,28 @@
|
|||
<template>
|
||||
<div style="padding: 0;">
|
||||
<div style="padding: 0">
|
||||
<div class="q-pa-sm row items-start">
|
||||
<div
|
||||
class="new-card q-pa-sm"
|
||||
v-for="myNew in news"
|
||||
:key="myNew.id">
|
||||
<q-card>
|
||||
<q-img :src="`${$app.imageUrl}/news/full/${myNew.image}`">
|
||||
</q-img>
|
||||
<q-card-section>
|
||||
<div class="new-card q-pa-sm" v-for="myNew in news" :key="myNew.id">
|
||||
<QCard>
|
||||
<QImg :src="`${$app.imageUrl}/news/full/${myNew.image}`">
|
||||
</QImg>
|
||||
<QCardSection>
|
||||
<div class="text-h5">{{ myNew.title }}</div>
|
||||
</q-card-section>
|
||||
<q-card-section class="new-body">
|
||||
</QCardSection>
|
||||
<QCardSection class="new-body">
|
||||
<div v-html="myNew.text" />
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</QCardSection>
|
||||
</QCard>
|
||||
</div>
|
||||
</div>
|
||||
<q-page-sticky>
|
||||
<q-btn
|
||||
<QPageSticky>
|
||||
<QBtn
|
||||
fab
|
||||
icon="add_shopping_cart"
|
||||
color="accent"
|
||||
to="/ecomerce/catalog"
|
||||
:title="$t('startOrder')"
|
||||
/>
|
||||
</q-page-sticky>
|
||||
</QPageSticky>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -54,16 +51,16 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
news: []
|
||||
}
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
this.news = await this.$jApi.query(
|
||||
`SELECT title, text, image, id
|
||||
FROM news
|
||||
ORDER BY priority, created DESC`
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<Teleport :to="$actions">
|
||||
<q-input
|
||||
<QInput
|
||||
:placeholder="$t('search')"
|
||||
v-model="search"
|
||||
debounce="500"
|
||||
|
@ -8,32 +8,28 @@
|
|||
rounded
|
||||
dark
|
||||
dense
|
||||
standout>
|
||||
standout
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<q-icon
|
||||
v-if="search === ''"
|
||||
name="search"
|
||||
/>
|
||||
<q-icon
|
||||
<QIcon v-if="search === ''" name="search" />
|
||||
<QIcon
|
||||
v-else
|
||||
name="clear"
|
||||
class="cursor-pointer"
|
||||
@click="search = ''"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
<q-btn
|
||||
</QInput>
|
||||
<QBtn
|
||||
:icon="$t(viewMode == 'list' ? 'view_list' : 'grid_on')"
|
||||
:label="$t(viewMode == 'list' ? 'listView' : 'gridView')"
|
||||
@click="onViewModeClick()"
|
||||
rounded
|
||||
no-caps/>
|
||||
no-caps
|
||||
/>
|
||||
</Teleport>
|
||||
<div style="padding-bottom: 5em;">
|
||||
<q-drawer
|
||||
v-model="$app.rightDrawerOpen"
|
||||
side="right"
|
||||
:width="250">
|
||||
<div style="padding-bottom: 5em">
|
||||
<QDrawer v-model="$app.rightDrawerOpen" side="right" :width="250">
|
||||
<div class="q-pa-md">
|
||||
<div class="basket-info">
|
||||
<p>{{ date(new Date()) }}</p>
|
||||
|
@ -41,27 +37,26 @@
|
|||
{{ $t('warehouse') }}
|
||||
{{ 'Algemesi' }}
|
||||
</p>
|
||||
<q-btn
|
||||
flat
|
||||
rounded
|
||||
no-caps>
|
||||
<QBtn flat rounded no-caps>
|
||||
{{ $t('modify') }}
|
||||
</q-btn>
|
||||
</QBtn>
|
||||
</div>
|
||||
<div class="q-mt-md">
|
||||
<div class="q-mb-xs text-grey-7">
|
||||
{{ $t('category') }}
|
||||
<q-icon
|
||||
<QIcon
|
||||
v-if="category"
|
||||
style="font-size: 1.3em;"
|
||||
style="font-size: 1.3em"
|
||||
name="cancel"
|
||||
class="cursor-pointer"
|
||||
:title="$t('deleteFilter')"
|
||||
@click="$router.push({params: {category: null}})"
|
||||
@click="
|
||||
$router.push({ params: { category: null } })
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="categories">
|
||||
<q-btn
|
||||
<QBtn
|
||||
flat
|
||||
round
|
||||
class="category q-pa-sm"
|
||||
|
@ -69,17 +64,17 @@
|
|||
:class="{ active: category == cat.id }"
|
||||
:key="cat.id"
|
||||
:title="cat.name"
|
||||
:to="{params: {category: cat.id, type: null}}">
|
||||
<img :src="`statics/category/${cat.code}.svg`">
|
||||
</q-btn>
|
||||
:to="{ params: { category: cat.id, type: null } }"
|
||||
>
|
||||
<img :src="`statics/category/${cat.code}.svg`" />
|
||||
</QBtn>
|
||||
</div>
|
||||
</div>
|
||||
<div class="q-mt-md"
|
||||
v-if="category || search">
|
||||
<div class="q-mt-md" v-if="category || search">
|
||||
<div class="q-mb-xs text-grey-7">
|
||||
{{ $t('filterBy') }}
|
||||
</div>
|
||||
<q-select
|
||||
<QSelect
|
||||
v-model="type"
|
||||
option-value="id"
|
||||
option-label="name"
|
||||
|
@ -88,9 +83,11 @@
|
|||
clearable
|
||||
:label="$t('family')"
|
||||
@filter="filterType"
|
||||
@input="$router.push({params: {type: type && type.id}})"
|
||||
@input="
|
||||
$router.push({ params: { type: type && type.id } })
|
||||
"
|
||||
/>
|
||||
<q-select
|
||||
<QSelect
|
||||
v-model="order"
|
||||
input-debounce="0"
|
||||
:options="orderOptions"
|
||||
|
@ -98,16 +95,13 @@
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="q-pa-md"
|
||||
v-if="typeId || search">
|
||||
<div class="q-mb-md"
|
||||
v-for="tag in tags"
|
||||
:key="tag.uid">
|
||||
<div class="q-pa-md" v-if="typeId || search">
|
||||
<div class="q-mb-md" v-for="tag in tags" :key="tag.uid">
|
||||
<div class="q-mb-xs text-caption text-grey-7">
|
||||
{{ tag.name }}
|
||||
<q-icon
|
||||
<QIcon
|
||||
v-if="tag.hasFilter"
|
||||
style="font-size: 1.3em;"
|
||||
style="font-size: 1.3em"
|
||||
name="cancel"
|
||||
:title="$t('deleteFilter')"
|
||||
class="cursor-pointer"
|
||||
|
@ -117,8 +111,9 @@
|
|||
<div v-if="!tag.useRange">
|
||||
<div
|
||||
v-for="value in tag.values.slice(0, tag.showCount)"
|
||||
:key="value">
|
||||
<q-checkbox
|
||||
:key="value"
|
||||
>
|
||||
<QCheckbox
|
||||
v-model="tag.filter"
|
||||
:dense="true"
|
||||
:val="value"
|
||||
|
@ -129,22 +124,24 @@
|
|||
<div v-if="tag.values.length > tag.showCount">
|
||||
<span
|
||||
class="cursor-pointer text-blue"
|
||||
@click="tag.showCount = Infinity">
|
||||
<q-icon name="keyboard_arrow_down" />
|
||||
@click="tag.showCount = Infinity"
|
||||
>
|
||||
<QIcon name="keyboard_arrow_down" />
|
||||
{{ $t('viewMore') }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="tag.showCount == Infinity">
|
||||
<span
|
||||
class="cursor-pointer text-blue"
|
||||
@click="tag.showCount = tag.initialCount">
|
||||
<q-icon name="keyboard_arrow_up" />
|
||||
@click="tag.showCount = tag.initialCount"
|
||||
>
|
||||
<QIcon name="keyboard_arrow_up" />
|
||||
{{ $t('viewLess') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="q-mx-md">
|
||||
<q-range
|
||||
<QRange
|
||||
class="q-mt-lg"
|
||||
v-if="tag.useRange"
|
||||
v-model="tag.filter"
|
||||
|
@ -161,113 +158,119 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</q-drawer>
|
||||
<q-infinite-scroll
|
||||
</QDrawer>
|
||||
<QInfiniteScroll
|
||||
@load="onLoad"
|
||||
scroll-taget="html"
|
||||
:offset="800"
|
||||
:disable="disableScroll">
|
||||
:disable="disableScroll"
|
||||
>
|
||||
<div class="q-pa-md row justify-center q-gutter-md">
|
||||
<q-spinner
|
||||
v-if="isLoading"
|
||||
color="primary"
|
||||
size="50px">
|
||||
</q-spinner>
|
||||
<QSpinner v-if="isLoading" color="primary" size="50px">
|
||||
</QSpinner>
|
||||
<div
|
||||
v-if="items && !items.length"
|
||||
class="text-subtitle1 text-grey-7 q-pa-md">
|
||||
class="text-subtitle1 text-grey-7 q-pa-md"
|
||||
>
|
||||
{{ $t('noItemsFound') }}
|
||||
</div>
|
||||
<div
|
||||
v-if="!items && !isLoading"
|
||||
class="text-subtitle1 text-grey-7 q-pa-md">
|
||||
class="text-subtitle1 text-grey-7 q-pa-md"
|
||||
>
|
||||
{{ $t('pleaseSetFilter') }}
|
||||
</div>
|
||||
<q-card
|
||||
class="my-card"
|
||||
v-for="item in items"
|
||||
:key="item.id">
|
||||
<QCard class="my-card" v-for="item in items" :key="item.id">
|
||||
<img :src="`${$imageBase}/catalog/200x200/${item.image}`" />
|
||||
<q-card-section>
|
||||
<QCardSection>
|
||||
<div class="name text-subtitle1">
|
||||
{{ item.longName }}
|
||||
</div>
|
||||
<div class="sub-name text-uppercase text-subtitle1 text-grey-7 ellipsize q-pt-xs">
|
||||
<div
|
||||
class="sub-name text-uppercase text-subtitle1 text-grey-7 ellipsize q-pt-xs"
|
||||
>
|
||||
{{ item.subName }}
|
||||
</div>
|
||||
<div class="tags q-pt-xs">
|
||||
<div v-for="tag in item.tags" :key="tag.tagFk">
|
||||
<span class="text-grey-7">{{tag.tag.name}}</span> {{tag.value}}
|
||||
<span class="text-grey-7">{{
|
||||
tag.tag.name
|
||||
}}</span>
|
||||
{{ tag.value }}
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-card-actions class="actions justify-between">
|
||||
</QCardSection>
|
||||
<QCardActions class="actions justify-between">
|
||||
<div class="q-pl-sm">
|
||||
<span class="available bg-green text-white">{{item.available}}</span>
|
||||
<span class="available bg-green text-white">{{
|
||||
item.available
|
||||
}}</span>
|
||||
{{ $t('from') }}
|
||||
<span class="price">{{currency(item.buy?.price3)}}</span>
|
||||
<span class="price">{{
|
||||
currency(item.buy?.price3)
|
||||
}}</span>
|
||||
</div>
|
||||
<q-btn
|
||||
<QBtn
|
||||
icon="add_shopping_cart"
|
||||
:title="$t('buy')"
|
||||
@click="showItem(item)"
|
||||
flat>
|
||||
</q-btn>
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
flat
|
||||
>
|
||||
</QBtn>
|
||||
</QCardActions>
|
||||
</QCard>
|
||||
</div>
|
||||
<template v-slot:loading>
|
||||
<div class="row justify-center q-my-md">
|
||||
<q-spinner color="primary" name="dots" size="40px" />
|
||||
<QSpinner color="primary" name="dots" size="40px" />
|
||||
</div>
|
||||
</template>
|
||||
</q-infinite-scroll>
|
||||
<q-dialog v-model="showItemDialog">
|
||||
<q-card style="width: 25em;">
|
||||
<q-img
|
||||
</QInfiniteScroll>
|
||||
<QDialog v-model="showItemDialog">
|
||||
<QCard style="width: 25em">
|
||||
<QImg
|
||||
:src="`${$imageBase}/catalog/200x200/${item.image}`"
|
||||
:ratio="5/3">
|
||||
:ratio="5 / 3"
|
||||
>
|
||||
<div class="absolute-bottom text-center q-pa-xs">
|
||||
<div class="text-subtitle1">
|
||||
{{ item.longName }}
|
||||
</div>
|
||||
</div>
|
||||
</q-img>
|
||||
<q-card-section>
|
||||
<div class="text-uppercase text-subtitle1 text-grey-7 ellipsize">
|
||||
</QImg>
|
||||
<QCardSection>
|
||||
<div
|
||||
class="text-uppercase text-subtitle1 text-grey-7 ellipsize"
|
||||
>
|
||||
{{ item.subName }}
|
||||
</div>
|
||||
<div class="text-grey-7">
|
||||
#{{item.id}}
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<div class="text-grey-7">#{{ item.id }}</div>
|
||||
</QCardSection>
|
||||
<QCardSection>
|
||||
<div v-for="tag in item.tags" :key="tag.tagFk">
|
||||
<span class="text-grey-7">{{tag.tag.name}}</span> {{tag.value}}
|
||||
<span class="text-grey-7">{{ tag.tag.name }}</span>
|
||||
{{ tag.value }}
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-card-actions align="right">
|
||||
<q-btn
|
||||
@click="showItemDialog = false"
|
||||
flat>
|
||||
</QCardSection>
|
||||
<QCardActions align="right">
|
||||
<QBtn @click="showItemDialog = false" flat>
|
||||
{{ $t('cancel') }}
|
||||
</q-btn>
|
||||
<q-btn
|
||||
@click="showItemDialog = false"
|
||||
flat>
|
||||
</QBtn>
|
||||
<QBtn @click="showItemDialog = false" flat>
|
||||
{{ $t('accept') }}
|
||||
</q-btn>
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
<q-page-sticky>
|
||||
<q-btn
|
||||
</QBtn>
|
||||
</QCardActions>
|
||||
</QCard>
|
||||
</QDialog>
|
||||
<QPageSticky>
|
||||
<QBtn
|
||||
fab
|
||||
to="/ecomerce/basket"
|
||||
icon="shopping_cart"
|
||||
color="accent"
|
||||
:title="$t('shoppingCart')"/>
|
||||
</q-page-sticky>
|
||||
:title="$t('shoppingCart')"
|
||||
/>
|
||||
</QPageSticky>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -284,7 +287,7 @@
|
|||
|
||||
& > p {
|
||||
margin: 0;
|
||||
padding: .4em 0;
|
||||
padding: 0.4em 0;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
@ -298,7 +301,7 @@
|
|||
width: 55px;
|
||||
|
||||
&.active {
|
||||
background: rgba(0, 0, 0, .08);
|
||||
background: rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
& > img {
|
||||
height: 40px;
|
||||
|
@ -311,8 +314,8 @@
|
|||
overflow: hidden;
|
||||
}
|
||||
.available {
|
||||
padding: .15em;
|
||||
border-radius: .2em;
|
||||
padding: 0.15em;
|
||||
border-radius: 0.2em;
|
||||
font-size: 1.3em;
|
||||
}
|
||||
.price {
|
||||
|
@ -323,7 +326,8 @@
|
|||
max-width: 17.5em;
|
||||
height: 32.5em;
|
||||
overflow: hidden;
|
||||
.name, .sub-name {
|
||||
.name,
|
||||
.sub-name {
|
||||
line-height: 1.3em;
|
||||
}
|
||||
.ellipsize {
|
||||
|
@ -339,11 +343,11 @@
|
|||
</style>
|
||||
|
||||
<script>
|
||||
import { date, currency } from 'src/lib/filters.js'
|
||||
import { date as qdate } from 'quasar'
|
||||
import axios from 'axios'
|
||||
import { date, currency } from 'src/lib/filters.js';
|
||||
import { date as qdate } from 'quasar';
|
||||
import axios from 'axios';
|
||||
|
||||
const CancelToken = axios.CancelToken
|
||||
const CancelToken = axios.CancelToken;
|
||||
|
||||
export default {
|
||||
name: 'HederaCatalog',
|
||||
|
@ -376,30 +380,36 @@ export default {
|
|||
{
|
||||
label: this.$t('relevancy'),
|
||||
value: 'relevancy DESC, longName'
|
||||
}, {
|
||||
},
|
||||
{
|
||||
label: this.$t('name'),
|
||||
value: 'longName'
|
||||
}, {
|
||||
},
|
||||
{
|
||||
label: this.$t('siceAsc'),
|
||||
value: 'size ASC'
|
||||
}, {
|
||||
},
|
||||
{
|
||||
label: this.$t('sizeDesc'),
|
||||
value: 'size DESC'
|
||||
}, {
|
||||
},
|
||||
{
|
||||
label: this.$t('priceAsc'),
|
||||
value: 'price ASC'
|
||||
}, {
|
||||
},
|
||||
{
|
||||
label: this.$t('priceDesc'),
|
||||
value: 'price DESC'
|
||||
}, {
|
||||
},
|
||||
{
|
||||
label: this.$t('available'),
|
||||
value: 'available'
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.$app.useRightDrawer = true
|
||||
this.$app.useRightDrawer = true;
|
||||
},
|
||||
async mounted() {
|
||||
this.categories = await this.$jApi.query(
|
||||
|
@ -408,32 +418,32 @@ export default {
|
|||
JOIN vn.itemCategoryL10n l ON l.id = c.id
|
||||
WHERE c.display
|
||||
ORDER BY display`
|
||||
)
|
||||
this.onRouteChange(this.$route)
|
||||
);
|
||||
this.onRouteChange(this.$route);
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.clearTimeoutAndRequest()
|
||||
this.clearTimeoutAndRequest();
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
this.onRouteChange(to)
|
||||
next()
|
||||
this.onRouteChange(to);
|
||||
next();
|
||||
},
|
||||
watch: {
|
||||
categories() {
|
||||
this.refreshTitle()
|
||||
this.refreshTitle();
|
||||
},
|
||||
orgTypes() {
|
||||
this.refreshTitle()
|
||||
this.refreshTitle();
|
||||
},
|
||||
order() {
|
||||
this.loadItems()
|
||||
this.loadItems();
|
||||
},
|
||||
date() {
|
||||
this.loadItems()
|
||||
this.loadItems();
|
||||
},
|
||||
async category(value) {
|
||||
this.orgTypes = []
|
||||
if (!value) return
|
||||
this.orgTypes = [];
|
||||
if (!value) return;
|
||||
|
||||
const res = await this.$jApi.execQuery(
|
||||
`CALL myBasket_getAvailable;
|
||||
|
@ -447,103 +457,104 @@ export default {
|
|||
ORDER BY t.\`order\`, l.name;
|
||||
DROP TEMPORARY TABLE tmp.itemAvailable;`,
|
||||
{ category: value }
|
||||
)
|
||||
res.fetch()
|
||||
this.orgTypes = res.fetchData()
|
||||
);
|
||||
res.fetch();
|
||||
this.orgTypes = res.fetchData();
|
||||
},
|
||||
search(value) {
|
||||
const location = { params: this.$route.params }
|
||||
if (value) location.query = { search: value }
|
||||
this.$router.push(location)
|
||||
const location = { params: this.$route.params };
|
||||
if (value) location.query = { search: value };
|
||||
this.$router.push(location);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
date,
|
||||
currency,
|
||||
onViewModeClick() {
|
||||
this.viewMode = this.viewMode === 'list' ? 'grid' : 'list'
|
||||
this.viewMode = this.viewMode === 'list' ? 'grid' : 'list';
|
||||
},
|
||||
onRouteChange(route) {
|
||||
let { category, type } = route.params
|
||||
let { category, type } = route.params;
|
||||
|
||||
category = parseInt(category) || null
|
||||
type = parseInt(type) || null
|
||||
category = parseInt(category) || null;
|
||||
type = parseInt(type) || null;
|
||||
|
||||
this.category = category
|
||||
this.typeId = category ? type : null
|
||||
this.search = route.query.search || ''
|
||||
this.tags = []
|
||||
this.category = category;
|
||||
this.typeId = category ? type : null;
|
||||
this.search = route.query.search || '';
|
||||
this.tags = [];
|
||||
|
||||
this.refreshTitle()
|
||||
this.loadItems()
|
||||
this.refreshTitle();
|
||||
this.loadItems();
|
||||
},
|
||||
refreshTitle() {
|
||||
let title = this.$t(this.$router.currentRoute.value.name)
|
||||
let subtitle
|
||||
let title = this.$t(this.$router.currentRoute.value.name);
|
||||
let subtitle;
|
||||
|
||||
if (this.category) {
|
||||
const category = this.categories.find(i => i.id === this.category) || {}
|
||||
title = category.name
|
||||
const category =
|
||||
this.categories.find(i => i.id === this.category) || {};
|
||||
title = category.name;
|
||||
}
|
||||
|
||||
if (this.typeId) {
|
||||
this.type = this.orgTypes.find(i => i.id === this.typeId)
|
||||
subtitle = title
|
||||
title = this.type && this.type.name
|
||||
this.type = this.orgTypes.find(i => i.id === this.typeId);
|
||||
subtitle = title;
|
||||
title = this.type && this.type.name;
|
||||
} else {
|
||||
this.type = null
|
||||
this.type = null;
|
||||
}
|
||||
|
||||
this.$app.$patch({ title, subtitle })
|
||||
this.$app.$patch({ title, subtitle });
|
||||
},
|
||||
clearTimeoutAndRequest() {
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout)
|
||||
this.timeout = null
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = null;
|
||||
}
|
||||
if (this.source) {
|
||||
this.source.cancel()
|
||||
this.source = null
|
||||
this.source.cancel();
|
||||
this.source = null;
|
||||
}
|
||||
},
|
||||
loadItemsDelayed() {
|
||||
this.clearTimeoutAndRequest()
|
||||
this.timeout = setTimeout(() => this.loadItems(), 500)
|
||||
this.clearTimeoutAndRequest();
|
||||
this.timeout = setTimeout(() => this.loadItems(), 500);
|
||||
},
|
||||
loadItems() {
|
||||
this.items = null
|
||||
this.isLoading = true
|
||||
this.limit = this.pageSize
|
||||
this.disableScroll = false
|
||||
this.isLoading = false
|
||||
this.items = null;
|
||||
this.isLoading = true;
|
||||
this.limit = this.pageSize;
|
||||
this.disableScroll = false;
|
||||
this.isLoading = false;
|
||||
// this.loadItemsBase().finally(() => (this.isLoading = false))
|
||||
},
|
||||
onLoad(index, done) {
|
||||
if (this.isLoading) return done()
|
||||
this.limit += this.pageSize
|
||||
done()
|
||||
if (this.isLoading) return done();
|
||||
this.limit += this.pageSize;
|
||||
done();
|
||||
// this.loadItemsBase().finally(done)
|
||||
},
|
||||
loadItemsBase() {
|
||||
this.clearTimeoutAndRequest()
|
||||
this.clearTimeoutAndRequest();
|
||||
|
||||
if (!(this.category || this.typeId || this.search)) {
|
||||
this.tags = []
|
||||
return Promise.resolve(true)
|
||||
this.tags = [];
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
const tagFilter = []
|
||||
const tagFilter = [];
|
||||
|
||||
for (const tag of this.tags) {
|
||||
if (tag.hasFilter) {
|
||||
tagFilter.push({
|
||||
tagFk: tag.id,
|
||||
values: tag.filter
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.source = CancelToken.source()
|
||||
this.source = CancelToken.source();
|
||||
|
||||
const params = {
|
||||
dated: this.orderDate,
|
||||
|
@ -553,101 +564,107 @@ export default {
|
|||
order: this.order.value,
|
||||
limit: this.limit,
|
||||
tagFilter
|
||||
}
|
||||
};
|
||||
const config = {
|
||||
params,
|
||||
cancelToken: this.source.token
|
||||
}
|
||||
return this.$axios.get('Items/catalog', config)
|
||||
};
|
||||
return this.$axios
|
||||
.get('Items/catalog', config)
|
||||
.then(res => this.onItemsGet(res))
|
||||
.catch(err => this.onItemsError(err))
|
||||
.finally(() => (this.cancel = null))
|
||||
.finally(() => (this.cancel = null));
|
||||
},
|
||||
onItemsError(err) {
|
||||
if (err.__CANCEL__) return
|
||||
this.disableScroll = true
|
||||
throw err
|
||||
if (err.__CANCEL__) return;
|
||||
this.disableScroll = true;
|
||||
throw err;
|
||||
},
|
||||
onItemsGet(res) {
|
||||
for (const tag of res.data.tags) {
|
||||
tag.uid = this.uid++
|
||||
tag.uid = this.uid++;
|
||||
|
||||
if (tag.filter) {
|
||||
tag.hasFilter = true
|
||||
tag.useRange =
|
||||
tag.filter.max ||
|
||||
tag.filter.min
|
||||
tag.hasFilter = true;
|
||||
tag.useRange = tag.filter.max || tag.filter.min;
|
||||
} else {
|
||||
tag.useRange = tag.isQuantitative &&
|
||||
tag.values.length > this.maxTags
|
||||
this.resetTagFilter(tag)
|
||||
tag.useRange =
|
||||
tag.isQuantitative && tag.values.length > this.maxTags;
|
||||
this.resetTagFilter(tag);
|
||||
}
|
||||
|
||||
if (tag.values) {
|
||||
tag.initialCount = this.maxTags
|
||||
tag.initialCount = this.maxTags;
|
||||
if (Array.isArray(tag.filter)) {
|
||||
tag.initialCount = Math.max(tag.initialCount, tag.filter.length)
|
||||
tag.initialCount = Math.max(
|
||||
tag.initialCount,
|
||||
tag.filter.length
|
||||
);
|
||||
}
|
||||
tag.showCount = tag.initialCount
|
||||
tag.showCount = tag.initialCount;
|
||||
}
|
||||
}
|
||||
|
||||
this.items = res.data.items
|
||||
this.tags = res.data.tags
|
||||
this.disableScroll = this.items.length < this.limit
|
||||
this.items = res.data.items;
|
||||
this.tags = res.data.tags;
|
||||
this.disableScroll = this.items.length < this.limit;
|
||||
},
|
||||
onRangeChange(tag, delay) {
|
||||
tag.hasFilter = true
|
||||
tag.hasFilter = true;
|
||||
|
||||
if (!delay) this.loadItems()
|
||||
else this.loadItemsDelayed()
|
||||
if (!delay) this.loadItems();
|
||||
else this.loadItemsDelayed();
|
||||
},
|
||||
onCheck(tag) {
|
||||
tag.hasFilter = tag.filter.length > 0
|
||||
this.loadItems()
|
||||
tag.hasFilter = tag.filter.length > 0;
|
||||
this.loadItems();
|
||||
},
|
||||
resetTagFilter(tag) {
|
||||
tag.hasFilter = false
|
||||
tag.hasFilter = false;
|
||||
|
||||
if (tag.useRange) {
|
||||
tag.filter = {
|
||||
min: tag.min,
|
||||
max: tag.max
|
||||
}
|
||||
};
|
||||
} else {
|
||||
tag.filter = []
|
||||
tag.filter = [];
|
||||
}
|
||||
},
|
||||
onResetTagFilterClick(tag) {
|
||||
this.resetTagFilter(tag)
|
||||
this.loadItems()
|
||||
this.resetTagFilter(tag);
|
||||
this.loadItems();
|
||||
},
|
||||
filterType(val, update) {
|
||||
if (val === '') {
|
||||
update(() => { this.types = this.orgTypes })
|
||||
update(() => {
|
||||
this.types = this.orgTypes;
|
||||
});
|
||||
} else {
|
||||
update(() => {
|
||||
const needle = val.toLowerCase()
|
||||
this.types = this.orgTypes.filter(type =>
|
||||
type.name.toLowerCase().indexOf(needle) > -1)
|
||||
})
|
||||
const needle = val.toLowerCase();
|
||||
this.types = this.orgTypes.filter(
|
||||
type => type.name.toLowerCase().indexOf(needle) > -1
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
showItem(item) {
|
||||
this.item = item
|
||||
this.showItemDialog = true
|
||||
this.item = item;
|
||||
this.showItemDialog = true;
|
||||
|
||||
const conf = this.$state.catalogConfig
|
||||
const conf = this.$state.catalogConfig;
|
||||
const params = {
|
||||
dated: this.orderDate,
|
||||
addressFk: conf.addressFk,
|
||||
agencyModeFk: conf.agencyModeFk
|
||||
}
|
||||
this.$axios.get(`Items/${item.id}/calcCatalog`, { params })
|
||||
.then(res => (this.lots = res.data))
|
||||
}
|
||||
};
|
||||
this.$axios
|
||||
.get(`Items/${item.id}/calcCatalog`, { params })
|
||||
.then(res => (this.lots = res.data));
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
|
|
|
@ -1,81 +1,96 @@
|
|||
<template>
|
||||
<Teleport :to="$actions">
|
||||
<q-select
|
||||
<QSelect
|
||||
v-model="year"
|
||||
:options="years"
|
||||
color="white"
|
||||
dark
|
||||
standout
|
||||
dense
|
||||
rounded />
|
||||
rounded
|
||||
/>
|
||||
</Teleport>
|
||||
<div class="vn-w-sm">
|
||||
<div
|
||||
v-if="!invoices?.length"
|
||||
class="text-subtitle1 text-center text-grey-7 q-pa-md">
|
||||
class="text-subtitle1 text-center text-grey-7 q-pa-md"
|
||||
>
|
||||
{{ $t('noInvoicesFound') }}
|
||||
</div>
|
||||
<q-card v-if="invoices?.length">
|
||||
<q-table
|
||||
<QCard v-if="invoices?.length">
|
||||
<QTable
|
||||
:columns="columns"
|
||||
:pagination="pagination"
|
||||
:rows="invoices"
|
||||
row-key="id"
|
||||
hide-header
|
||||
hide-bottom>
|
||||
hide-bottom
|
||||
>
|
||||
<template v-slot:body="props">
|
||||
<q-tr :props="props">
|
||||
<q-td key="ref" :props="props">
|
||||
<QTr :props="props">
|
||||
<QTd key="ref" :props="props">
|
||||
{{ props.row.ref }}
|
||||
</q-td>
|
||||
<q-td key="issued" :props="props">
|
||||
</QTd>
|
||||
<QTd key="issued" :props="props">
|
||||
{{ date(props.row.issued, 'ddd, MMMM Do') }}
|
||||
</q-td>
|
||||
<q-td key="amount" :props="props">
|
||||
</QTd>
|
||||
<QTd key="amount" :props="props">
|
||||
{{ currency(props.row.amount) }}
|
||||
</q-td>
|
||||
<q-td key="hasPdf" :props="props">
|
||||
<q-btn
|
||||
</QTd>
|
||||
<QTd key="hasPdf" :props="props">
|
||||
<QBtn
|
||||
v-if="props.row.hasPdf"
|
||||
icon="download"
|
||||
:title="$t('downloadInvoicePdf')"
|
||||
:href="invoiceUrl(props.row.id)"
|
||||
target="_blank"
|
||||
flat
|
||||
round/>
|
||||
<q-icon
|
||||
round
|
||||
/>
|
||||
<QIcon
|
||||
v-else
|
||||
name="warning"
|
||||
:title="$t('notDownloadable')"
|
||||
color="warning"
|
||||
size="24px"/>
|
||||
</q-td>
|
||||
</q-tr>
|
||||
size="24px"
|
||||
/>
|
||||
</QTd>
|
||||
</QTr>
|
||||
</template>
|
||||
</q-table>
|
||||
</q-card>
|
||||
</QTable>
|
||||
</QCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { date, currency } from 'src/lib/filters.js'
|
||||
import { date, currency } from 'src/lib/filters.js';
|
||||
|
||||
export default {
|
||||
name: 'OrdersPendingIndex',
|
||||
data() {
|
||||
const curYear = (new Date()).getFullYear()
|
||||
const years = []
|
||||
const curYear = new Date().getFullYear();
|
||||
const years = [];
|
||||
|
||||
for (let year = curYear - 5; year <= curYear; year++) {
|
||||
years.push(year)
|
||||
years.push(year);
|
||||
}
|
||||
|
||||
return {
|
||||
columns: [
|
||||
{ name: 'ref', label: 'serial', field: 'ref', align: 'left' },
|
||||
{ name: 'issued', label: 'issued', field: 'issued', align: 'left' },
|
||||
{
|
||||
name: 'issued',
|
||||
label: 'issued',
|
||||
field: 'issued',
|
||||
align: 'left'
|
||||
},
|
||||
{ name: 'amount', label: 'amount', field: 'amount' },
|
||||
{ name: 'hasPdf', label: 'download', field: 'hasPdf', align: 'center' }
|
||||
{
|
||||
name: 'hasPdf',
|
||||
label: 'download',
|
||||
field: 'hasPdf',
|
||||
align: 'center'
|
||||
}
|
||||
],
|
||||
pagination: {
|
||||
rowsPerPage: 0
|
||||
|
@ -83,16 +98,16 @@ export default {
|
|||
year: curYear,
|
||||
years,
|
||||
invoices: null
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
await this.loadData()
|
||||
await this.loadData();
|
||||
},
|
||||
|
||||
watch: {
|
||||
async year() {
|
||||
await this.loadData()
|
||||
await this.loadData();
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -104,7 +119,7 @@ export default {
|
|||
const params = {
|
||||
from: new Date(this.year, 0),
|
||||
to: new Date(this.year, 11, 31, 23, 59, 59)
|
||||
}
|
||||
};
|
||||
this._invoices = await this.$jApi.query(
|
||||
`SELECT id, ref, issued, amount, hasPdf
|
||||
FROM myInvoice
|
||||
|
@ -112,18 +127,21 @@ export default {
|
|||
ORDER BY issued DESC
|
||||
LIMIT 500`,
|
||||
params
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
invoiceUrl(id) {
|
||||
return '?' + new URLSearchParams({
|
||||
return (
|
||||
'?' +
|
||||
new URLSearchParams({
|
||||
srv: 'rest:dms/invoice',
|
||||
invoice: id,
|
||||
access_token: this.$user.token
|
||||
}).toString()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
|
|
|
@ -2,66 +2,68 @@
|
|||
<Teleport :to="$actions">
|
||||
<div class="balance">
|
||||
<span class="label">{{ $t('balance') }}</span>
|
||||
<span
|
||||
class="amount"
|
||||
:class="{negative: debt < 0}">
|
||||
<span class="amount" :class="{ negative: debt < 0 }">
|
||||
{{ currency(debt || 0) }}
|
||||
</span>
|
||||
<q-icon
|
||||
<QIcon
|
||||
name="info"
|
||||
:title="$t('paymentInfo')"
|
||||
class="info"
|
||||
size="24px"/>
|
||||
size="24px"
|
||||
/>
|
||||
</div>
|
||||
<q-btn
|
||||
<QBtn
|
||||
icon="payments"
|
||||
:label="$t('makePayment')"
|
||||
@click="onPayClick()"
|
||||
rounded
|
||||
no-caps/>
|
||||
<q-btn
|
||||
no-caps
|
||||
/>
|
||||
<QBtn
|
||||
to="/ecomerce/basket"
|
||||
icon="shopping_cart"
|
||||
:label="$t('shoppingCart')"
|
||||
rounded
|
||||
no-caps/>
|
||||
no-caps
|
||||
/>
|
||||
</Teleport>
|
||||
<div class="vn-w-sm">
|
||||
<div
|
||||
v-if="!orders?.length"
|
||||
class="text-subtitle1 text-center text-grey-7 q-pa-md">
|
||||
class="text-subtitle1 text-center text-grey-7 q-pa-md"
|
||||
>
|
||||
{{ $t('noOrdersFound') }}
|
||||
</div>
|
||||
<q-card v-if="orders?.length">
|
||||
<q-list bordered separator padding >
|
||||
<q-item
|
||||
<QCard v-if="orders?.length">
|
||||
<QList bordered separator padding>
|
||||
<QItem
|
||||
v-for="order in orders"
|
||||
:key="order.id"
|
||||
:to="`ticket/${order.id}`"
|
||||
clickable
|
||||
v-ripple>
|
||||
<q-item-section>
|
||||
<q-item-label>
|
||||
v-ripple
|
||||
>
|
||||
<QItemSection>
|
||||
<QItemLabel>
|
||||
{{ date(order.landed, 'ddd, MMMM Do') }}
|
||||
</q-item-label>
|
||||
<q-item-label caption>#{{order.id}}</q-item-label>
|
||||
<q-item-label caption>{{order.nickname}}</q-item-label>
|
||||
<q-item-label caption>{{order.agency}}</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side top>
|
||||
{{order.total}}€
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-card>
|
||||
<q-page-sticky>
|
||||
<q-btn
|
||||
</QItemLabel>
|
||||
<QItemLabel caption>#{{ order.id }}</QItemLabel>
|
||||
<QItemLabel caption>{{ order.nickname }}</QItemLabel>
|
||||
<QItemLabel caption>{{ order.agency }}</QItemLabel>
|
||||
</QItemSection>
|
||||
<QItemSection side top> {{ order.total }}€ </QItemSection>
|
||||
</QItem>
|
||||
</QList>
|
||||
</QCard>
|
||||
<QPageSticky>
|
||||
<QBtn
|
||||
fab
|
||||
icon="add_shopping_cart"
|
||||
color="accent"
|
||||
to="/ecomerce/catalog"
|
||||
:title="$t('startOrder')"/>
|
||||
</q-page-sticky>
|
||||
:title="$t('startOrder')"
|
||||
/>
|
||||
</QPageSticky>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -91,8 +93,8 @@
|
|||
</style>
|
||||
|
||||
<script>
|
||||
import { date, currency } from 'src/lib/filters.js'
|
||||
import { tpvStore } from 'stores/tpv'
|
||||
import { date, currency } from 'src/lib/filters.js';
|
||||
import { tpvStore } from 'stores/tpv';
|
||||
|
||||
export default {
|
||||
name: 'OrdersPendingIndex',
|
||||
|
@ -101,18 +103,14 @@ export default {
|
|||
orders: null,
|
||||
debt: 0,
|
||||
tpv: tpvStore()
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
await this.tpv.check(this.$route)
|
||||
await this.tpv.check(this.$route);
|
||||
|
||||
this.orders = await this.$jApi.query(
|
||||
'CALL myTicket_list(NULL, NULL)'
|
||||
)
|
||||
this.debt = await this.$jApi.getValue(
|
||||
'SELECT -myClient_getDebt(NULL)'
|
||||
)
|
||||
this.orders = await this.$jApi.query('CALL myTicket_list(NULL, NULL)');
|
||||
this.debt = await this.$jApi.getValue('SELECT -myClient_getDebt(NULL)');
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -120,22 +118,22 @@ export default {
|
|||
currency,
|
||||
|
||||
async onPayClick() {
|
||||
let amount = -this.debt
|
||||
amount = amount <= 0 ? null : amount
|
||||
let amount = -this.debt;
|
||||
amount = amount <= 0 ? null : amount;
|
||||
|
||||
let defaultAmountStr = ''
|
||||
let defaultAmountStr = '';
|
||||
if (amount !== null) {
|
||||
defaultAmountStr = amount
|
||||
defaultAmountStr = amount;
|
||||
}
|
||||
amount = prompt(this.$t('amountToPay'), defaultAmountStr)
|
||||
amount = prompt(this.$t('amountToPay'), defaultAmountStr);
|
||||
|
||||
if (amount != null) {
|
||||
amount = parseFloat(amount.replace(',', '.'))
|
||||
await this.tpv.pay(amount)
|
||||
}
|
||||
amount = parseFloat(amount.replace(',', '.'));
|
||||
await this.tpv.pay(amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
|
|
|
@ -1,60 +1,76 @@
|
|||
<template>
|
||||
<Teleport :to="$actions">
|
||||
<q-btn
|
||||
<QBtn
|
||||
icon="print"
|
||||
:label="$t('printDeliveryNote')"
|
||||
@click="onPrintClick()"
|
||||
rounded
|
||||
no-caps/>
|
||||
no-caps
|
||||
/>
|
||||
</Teleport>
|
||||
<div>
|
||||
<q-card class="vn-w-sm">
|
||||
<q-card-section>
|
||||
<QCard class="vn-w-sm">
|
||||
<QCardSection>
|
||||
<div class="text-h6">#{{ ticket.id }}</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
</QCardSection>
|
||||
<QCardSection>
|
||||
<div class="text-h6">{{ $t('shippingInformation') }}</div>
|
||||
<div>{{$t('preparation')}} {{date(ticket.shipped, 'ddd, MMMM Do')}}</div>
|
||||
<div>{{$t('delivery')}} {{date(ticket.shipped, 'ddd, MMMM Do')}}</div>
|
||||
<div>{{$t(ticket.method != 'PICKUP' ? 'agency' : 'warehouse')}} {{ticket.agency}}</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<div>
|
||||
{{ $t('preparation') }}
|
||||
{{ date(ticket.shipped, 'ddd, MMMM Do') }}
|
||||
</div>
|
||||
<div>
|
||||
{{ $t('delivery') }}
|
||||
{{ date(ticket.shipped, 'ddd, MMMM Do') }}
|
||||
</div>
|
||||
<div>
|
||||
{{ $t(ticket.method != 'PICKUP' ? 'agency' : 'warehouse') }}
|
||||
{{ ticket.agency }}
|
||||
</div>
|
||||
</QCardSection>
|
||||
<QCardSection>
|
||||
<div class="text-h6">{{ $t('deliveryAddress') }}</div>
|
||||
<div>{{ ticket.nickname }}</div>
|
||||
<div>{{ ticket.street }}</div>
|
||||
<div>{{ticket.postalCode}} {{ticket.city}} ({{ticket.province}})</div>
|
||||
</q-card-section>
|
||||
<q-separator inset />
|
||||
<q-list v-for="row in rows" :key="row.itemFk">
|
||||
<q-item>
|
||||
<q-item-section avatar>
|
||||
<q-avatar size="68px">
|
||||
<img :src="`${$app.imageUrl}/catalog/200x200/${row.image}`">
|
||||
</q-avatar>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label lines="1">
|
||||
<div>
|
||||
{{ ticket.postalCode }} {{ ticket.city }} ({{
|
||||
ticket.province
|
||||
}})
|
||||
</div>
|
||||
</QCardSection>
|
||||
<QSeparator inset />
|
||||
<QList v-for="row in rows" :key="row.itemFk">
|
||||
<QItem>
|
||||
<QItemSection avatar>
|
||||
<QAvatar size="68px">
|
||||
<img
|
||||
:src="`${$app.imageUrl}/catalog/200x200/${row.image}`"
|
||||
/>
|
||||
</QAvatar>
|
||||
</QItemSection>
|
||||
<QItemSection>
|
||||
<QItemLabel lines="1">
|
||||
{{ row.concept }}
|
||||
</q-item-label>
|
||||
<q-item-label lines="1" caption>
|
||||
</QItemLabel>
|
||||
<QItemLabel lines="1" caption>
|
||||
{{ row.value5 }} {{ row.value6 }} {{ row.value7 }}
|
||||
</q-item-label>
|
||||
<q-item-label lines="1">
|
||||
</QItemLabel>
|
||||
<QItemLabel lines="1">
|
||||
{{ row.quantity }} x {{ currency(row.price) }}
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side class="total">
|
||||
<q-item-label>
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
<QItemSection side class="total">
|
||||
<QItemLabel>
|
||||
<span class="discount" v-if="row.discount">
|
||||
{{ currency(discountSubtotal(row)) }} -
|
||||
{{ currency(row.discount) }} =
|
||||
</span>
|
||||
{{ currency(subtotal(row)) }}
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-card>
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</QList>
|
||||
</QCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -65,7 +81,7 @@
|
|||
</style>
|
||||
|
||||
<script>
|
||||
import { date, currency } from 'src/lib/filters.js'
|
||||
import { date, currency } from 'src/lib/filters.js';
|
||||
|
||||
export default {
|
||||
name: 'OrdersConfirmedView',
|
||||
|
@ -76,29 +92,29 @@ export default {
|
|||
rows: null,
|
||||
services: null,
|
||||
packages: null
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
const params = {
|
||||
ticket: parseInt(this.$route.params.id)
|
||||
}
|
||||
};
|
||||
this.ticket = await this.$jApi.getObject(
|
||||
'CALL myTicket_get(#ticket)',
|
||||
params
|
||||
)
|
||||
);
|
||||
this.rows = await this.$jApi.query(
|
||||
'CALL myTicket_getRows(#ticket)',
|
||||
params
|
||||
)
|
||||
);
|
||||
this.services = await this.$jApi.query(
|
||||
'CALL myTicket_getServices(#ticket)',
|
||||
params
|
||||
)
|
||||
);
|
||||
this.packages = await this.$jApi.query(
|
||||
'CALL myTicket_getPackages(#ticket)',
|
||||
params
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -106,12 +122,12 @@ export default {
|
|||
currency,
|
||||
|
||||
discountSubtotal(line) {
|
||||
return line.quantity * line.price
|
||||
return line.quantity * line.price;
|
||||
},
|
||||
|
||||
subtotal(line) {
|
||||
const discount = line.discount
|
||||
return this.discountSubtotal(line) * ((100 - discount) / 100)
|
||||
const discount = line.discount;
|
||||
return this.discountSubtotal(line) * ((100 - discount) / 100);
|
||||
},
|
||||
|
||||
onPrintClick() {
|
||||
|
@ -119,9 +135,11 @@ export default {
|
|||
access_token: this.$user.token,
|
||||
recipientId: this.$user.id,
|
||||
type: 'deliveryNote'
|
||||
})
|
||||
window.open(`/api/Tickets/${this.ticket.id}/delivery-note-pdf?${params.toString()}`)
|
||||
}
|
||||
});
|
||||
window.open(
|
||||
`/api/Tickets/${this.ticket.id}/delivery-note-pdf?${params.toString()}`
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
<template>
|
||||
<div class="fullscreen bg-accent text-white text-center q-pa-md flex flex-center">
|
||||
<div
|
||||
class="fullscreen bg-accent text-white text-center q-pa-md flex flex-center"
|
||||
>
|
||||
<div>
|
||||
<div style="font-size: 30vh">
|
||||
404
|
||||
</div>
|
||||
<div style="font-size: 30vh">404</div>
|
||||
|
||||
<div class="text-h2" style="opacity:.4">
|
||||
<div class="text-h2" style="opacity: 0.4">
|
||||
Oops. Nothing here...
|
||||
</div>
|
||||
|
||||
<q-btn
|
||||
<QBtn
|
||||
class="q-mt-xl"
|
||||
color="white"
|
||||
text-color="accent"
|
||||
|
@ -23,9 +23,9 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from 'vue'
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ErrorNotFound'
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
<template>
|
||||
<q-page class="flex flex-center">
|
||||
<QPage class="flex flex-center">
|
||||
<img
|
||||
alt="Quasar logo"
|
||||
src="~assets/quasar-logo-vertical.svg"
|
||||
style="width: 200px; height: 200px"
|
||||
>
|
||||
</q-page>
|
||||
/>
|
||||
</QPage>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from 'vue'
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'IndexPage'
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -2,34 +2,27 @@
|
|||
<div class="main">
|
||||
<div class="header">
|
||||
<router-link to="/" class="block">
|
||||
<img
|
||||
src="statics/logo.svg"
|
||||
alt="Verdnatura"
|
||||
class="block"
|
||||
/>
|
||||
<img src="statics/logo.svg" alt="Verdnatura" class="block" />
|
||||
</router-link>
|
||||
</div>
|
||||
<q-form @submit="onLogin" class="q-gutter-y-md">
|
||||
<QForm @submit="onLogin" class="q-gutter-y-md">
|
||||
<div class="q-gutter-y-sm">
|
||||
<q-input
|
||||
v-model="email"
|
||||
:label="$t('user')"
|
||||
autofocus
|
||||
/>
|
||||
<q-input
|
||||
<QInput v-model="email" :label="$t('user')" autofocus />
|
||||
<QInput
|
||||
v-model="password"
|
||||
ref="password"
|
||||
:label="$t('password')"
|
||||
:type="showPwd ? 'password' : 'text'">
|
||||
:type="showPwd ? 'password' : 'text'"
|
||||
>
|
||||
<template v-slot:append>
|
||||
<q-icon
|
||||
<QIcon
|
||||
:name="showPwd ? 'visibility_off' : 'visibility'"
|
||||
class="cursor-pointer"
|
||||
@click="showPwd = !showPwd"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
<q-checkbox
|
||||
</QInput>
|
||||
<QCheckbox
|
||||
v-model="remember"
|
||||
:label="$t('remindMe')"
|
||||
class="remember"
|
||||
|
@ -37,7 +30,7 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="justify-center">
|
||||
<q-btn
|
||||
<QBtn
|
||||
type="submit"
|
||||
:label="$t('logIn')"
|
||||
class="full-width"
|
||||
|
@ -48,7 +41,7 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="justify-center">
|
||||
<q-btn
|
||||
<QBtn
|
||||
to="/"
|
||||
:label="$t('logInAsGuest')"
|
||||
class="full-width"
|
||||
|
@ -63,11 +56,15 @@
|
|||
{{ $t('haveForgottenPassword') }}
|
||||
</router-link>
|
||||
</p>
|
||||
</q-form>
|
||||
</QForm>
|
||||
<div class="footer text-center">
|
||||
<p>
|
||||
{{ $t('notACustomerYet') }}
|
||||
<a href="//verdnatura.es/register/" target="_blank" class="link">
|
||||
<a
|
||||
href="//verdnatura.es/register/"
|
||||
target="_blank"
|
||||
class="link"
|
||||
>
|
||||
{{ $t('signUp') }}
|
||||
</a>
|
||||
</p>
|
||||
|
@ -106,13 +103,13 @@ a {
|
|||
height: 50px;
|
||||
}
|
||||
.password-forgotten {
|
||||
font-size: .8rem;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.footer {
|
||||
margin-bottom: $login-margin-top;
|
||||
margin-top: $login-margin-between;
|
||||
text-align: center;
|
||||
font-size: .8rem;
|
||||
font-size: 0.8rem;
|
||||
|
||||
.contact {
|
||||
margin-top: 15px;
|
||||
|
@ -125,7 +122,7 @@ a {
|
|||
</style>
|
||||
|
||||
<script>
|
||||
import { userStore } from 'stores/user'
|
||||
import { userStore } from 'stores/user';
|
||||
|
||||
export default {
|
||||
name: 'VnLogin',
|
||||
|
@ -137,7 +134,7 @@ export default {
|
|||
password: '',
|
||||
remember: false,
|
||||
showPwd: true
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
|
@ -145,21 +142,21 @@ export default {
|
|||
this.$q.notify({
|
||||
message: this.$t('emailConfirmedSuccessfully'),
|
||||
type: 'positive'
|
||||
})
|
||||
});
|
||||
}
|
||||
if (this.$route.params.email) {
|
||||
this.email = this.$route.params.email
|
||||
this.$refs.password.focus()
|
||||
this.email = this.$route.params.email;
|
||||
this.$refs.password.focus();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async onLogin() {
|
||||
await this.user.login(this.email, this.password, this.remember)
|
||||
this.$router.push('/')
|
||||
}
|
||||
await this.user.login(this.email, this.password, this.remember);
|
||||
this.$router.push('/');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
<template>
|
||||
<div class="text-center">
|
||||
<div>
|
||||
<q-icon
|
||||
<QIcon
|
||||
name="contact_support"
|
||||
class="block q-mx-auto text-accent"
|
||||
style="font-size: 120px;"
|
||||
style="font-size: 120px"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<q-form @submit="onSend" class="q-gutter-y-md text-grey-8">
|
||||
<QForm @submit="onSend" class="q-gutter-y-md text-grey-8">
|
||||
<div class="text-h5">
|
||||
<div>
|
||||
{{ $t('dontWorry') }}
|
||||
|
@ -17,7 +17,7 @@
|
|||
{{ $t('fillData') }}
|
||||
</div>
|
||||
</div>
|
||||
<q-input
|
||||
<QInput
|
||||
v-model="email"
|
||||
:label="$t('user')"
|
||||
:rules="[val => !!val || $t('inputEmail')]"
|
||||
|
@ -27,7 +27,7 @@
|
|||
{{ $t('weSendEmail') }}
|
||||
</div>
|
||||
<div>
|
||||
<q-btn
|
||||
<QBtn
|
||||
type="submit"
|
||||
:label="$t('send')"
|
||||
class="full-width q-mt-md"
|
||||
|
@ -42,7 +42,7 @@
|
|||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</q-form>
|
||||
</QForm>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -56,7 +56,7 @@
|
|||
}
|
||||
a {
|
||||
color: inherit;
|
||||
font-size: .8rem;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
@ -66,22 +66,22 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
email: ''
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async onSend() {
|
||||
const params = {
|
||||
email: this.email
|
||||
}
|
||||
await this.$axios.post('Users/reset', params)
|
||||
};
|
||||
await this.$axios.post('Users/reset', params);
|
||||
this.$q.notify({
|
||||
message: this.$t('weHaveSentEmailToRecover'),
|
||||
type: 'positive'
|
||||
})
|
||||
this.$router.push('/login')
|
||||
}
|
||||
});
|
||||
this.$router.push('/login');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
|
|
|
@ -1,51 +1,60 @@
|
|||
<template>
|
||||
<div>
|
||||
<q-card-section>
|
||||
<q-icon
|
||||
<QCard-section>
|
||||
<QIcon
|
||||
name="check"
|
||||
class="block q-mx-auto text-accent"
|
||||
style="font-size: 120px;"
|
||||
style="font-size: 120px"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-form @submit="onRegister" ref="form" class="q-gutter-y-md">
|
||||
</QCard-section>
|
||||
<QCard-section>
|
||||
<QForm @submit="onRegister" ref="form" class="q-gutter-y-md">
|
||||
<div class="text-grey-8 text-h5 text-center">
|
||||
{{ $t('fillData') }}
|
||||
</div>
|
||||
<div class="q-gutter-y-sm">
|
||||
<q-input
|
||||
<QInput
|
||||
v-model="password"
|
||||
:label="$t('password')"
|
||||
:type="showPwd ? 'password' : 'text'"
|
||||
autofocus
|
||||
hint=""
|
||||
filled>
|
||||
filled
|
||||
>
|
||||
<template v-slot:append>
|
||||
<q-icon
|
||||
:name="showPwd ? 'visibility_off' : 'visibility'"
|
||||
<QIcon
|
||||
:name="
|
||||
showPwd ? 'visibility_off' : 'visibility'
|
||||
"
|
||||
class="cursor-pointer"
|
||||
@click="showPwd = !showPwd"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
<q-input
|
||||
</QInput>
|
||||
<QInput
|
||||
v-model="repeatPassword"
|
||||
:label="$t('repeatPassword')"
|
||||
:type="showRpPwd ? 'password' : 'text'"
|
||||
:rules="[value => value == password || $t('repeatPasswordError')]"
|
||||
:rules="[
|
||||
value =>
|
||||
value == password || $t('repeatPasswordError')
|
||||
]"
|
||||
hint=""
|
||||
filled>
|
||||
filled
|
||||
>
|
||||
<template v-slot:append>
|
||||
<q-icon
|
||||
:name="showRpPwd ? 'visibility_off' : 'visibility'"
|
||||
<QIcon
|
||||
:name="
|
||||
showRpPwd ? 'visibility_off' : 'visibility'
|
||||
"
|
||||
class="cursor-pointer"
|
||||
@click="showRpPwd = !showRpPwd"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
</QInput>
|
||||
</div>
|
||||
<div>
|
||||
<q-btn
|
||||
<QBtn
|
||||
type="submit"
|
||||
:label="$t('resetPassword')"
|
||||
class="full-width"
|
||||
|
@ -57,8 +66,8 @@
|
|||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</q-form>
|
||||
</q-card-section>
|
||||
</QForm>
|
||||
</QCard-section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -71,23 +80,27 @@ export default {
|
|||
repeatPassword: '',
|
||||
showPwd: true,
|
||||
showRpPwd: true
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async onRegister() {
|
||||
const headers = {
|
||||
Authorization: this.$route.query.access_token
|
||||
}
|
||||
await this.$axios.post('users/reset-password', {
|
||||
};
|
||||
await this.$axios.post(
|
||||
'users/reset-password',
|
||||
{
|
||||
newPassword: this.password
|
||||
}, { headers })
|
||||
},
|
||||
{ headers }
|
||||
);
|
||||
|
||||
this.$q.notify({
|
||||
message: this.$t('passwordResetSuccessfully'),
|
||||
type: 'positive'
|
||||
})
|
||||
this.$router.push('/login')
|
||||
}
|
||||
});
|
||||
this.$router.push('/login');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import { route } from 'quasar/wrappers'
|
||||
import { appStore } from 'stores/app'
|
||||
import { createRouter, createMemoryHistory, createWebHistory, createWebHashHistory } from 'vue-router'
|
||||
import {
|
||||
createRouter,
|
||||
createMemoryHistory,
|
||||
createWebHistory,
|
||||
createWebHashHistory
|
||||
} from 'vue-router'
|
||||
import routes from './routes'
|
||||
|
||||
/*
|
||||
|
@ -15,7 +20,9 @@ import routes from './routes'
|
|||
export default route(function (/* { store, ssrContext } */) {
|
||||
const createHistory = process.env.SERVER
|
||||
? createMemoryHistory
|
||||
: (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory)
|
||||
: process.env.VUE_ROUTER_MODE === 'history'
|
||||
? createWebHistory
|
||||
: createWebHashHistory
|
||||
|
||||
const Router = createRouter({
|
||||
scrollBehavior: () => ({ left: 0, top: 0 }),
|
||||
|
@ -24,7 +31,9 @@ export default route(function (/* { store, ssrContext } */) {
|
|||
// Leave this as is and make changes in quasar.conf.js instead!
|
||||
// quasar.conf.js -> build -> vueRouterMode
|
||||
// quasar.conf.js -> build -> publicPath
|
||||
history: createHistory(process.env.MODE === 'ssr' ? void 0 : process.env.VUE_ROUTER_BASE)
|
||||
history: createHistory(
|
||||
process.env.MODE === 'ssr' ? void 0 : process.env.VUE_ROUTER_BASE
|
||||
)
|
||||
})
|
||||
|
||||
Router.afterEach((to, from) => {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
const routes = [
|
||||
{
|
||||
path: '/login',
|
||||
|
@ -8,17 +7,20 @@ const routes = [
|
|||
name: 'login',
|
||||
path: '/login/:email?',
|
||||
component: () => import('pages/Login/Login.vue')
|
||||
}, {
|
||||
},
|
||||
{
|
||||
name: 'rememberPassword',
|
||||
path: '/remember-password',
|
||||
component: () => import('pages/Login/RememberPassword.vue')
|
||||
}, {
|
||||
},
|
||||
{
|
||||
name: 'resetPassword',
|
||||
path: '/reset-password',
|
||||
component: () => import('pages/Login/ResetPassword.vue')
|
||||
}
|
||||
]
|
||||
}, {
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
component: () => import('layouts/MainLayout.vue'),
|
||||
children: [
|
||||
|
@ -26,23 +28,28 @@ const routes = [
|
|||
name: '',
|
||||
path: '',
|
||||
component: () => import('src/pages/Cms/Home.vue')
|
||||
}, {
|
||||
},
|
||||
{
|
||||
name: 'home',
|
||||
path: '/cms/home',
|
||||
component: () => import('src/pages/Cms/Home.vue')
|
||||
}, {
|
||||
},
|
||||
{
|
||||
name: 'orders',
|
||||
path: '/ecomerce/orders',
|
||||
component: () => import('pages/Ecomerce/Orders.vue')
|
||||
}, {
|
||||
},
|
||||
{
|
||||
name: 'ticket',
|
||||
path: '/ecomerce/ticket/:id',
|
||||
component: () => import('pages/Ecomerce/Ticket.vue')
|
||||
}, {
|
||||
},
|
||||
{
|
||||
name: 'invoices',
|
||||
path: '/ecomerce/invoices',
|
||||
component: () => import('pages/Ecomerce/Invoices.vue')
|
||||
}, {
|
||||
},
|
||||
{
|
||||
name: 'catalog',
|
||||
path: '/ecomerce/catalog/:category?/:type?',
|
||||
component: () => import('pages/Ecomerce/Catalog.vue')
|
||||
|
|
|
@ -12,9 +12,7 @@ export const appStore = defineStore('hedera', {
|
|||
|
||||
actions: {
|
||||
async loadConfig () {
|
||||
const imageUrl = await jApi.getValue(
|
||||
'SELECT url FROM imageConfig'
|
||||
)
|
||||
const imageUrl = await jApi.getValue('SELECT url FROM imageConfig')
|
||||
this.$patch({ imageUrl })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
/* eslint-disable */
|
||||
// THIS FEATURE-FLAG FILE IS AUTOGENERATED,
|
||||
// REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING
|
||||
import "quasar/dist/types/feature-flag";
|
||||
import 'quasar/dist/types/feature-flag'
|
||||
|
||||
declare module "quasar/dist/types/feature-flag" {
|
||||
declare module 'quasar/dist/types/feature-flag' {
|
||||
interface QuasarFeatureFlags {
|
||||
store: true;
|
||||
store: true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,14 +8,16 @@ export const tpvStore = defineStore('tpv', {
|
|||
const status = route.query.tpvStatus
|
||||
if (!(order && status)) return null
|
||||
|
||||
await jApi.execQuery(
|
||||
'CALL myTpvTransaction_end(#order, #status)',
|
||||
{ order, status }
|
||||
)
|
||||
await jApi.execQuery('CALL myTpvTransaction_end(#order, #status)', {
|
||||
order,
|
||||
status
|
||||
})
|
||||
|
||||
if (status === 'ko') {
|
||||
const retry = confirm('retryPayQuestion')
|
||||
if (retry) { this.retryPay(order) }
|
||||
if (retry) {
|
||||
this.retryPay(order)
|
||||
}
|
||||
}
|
||||
|
||||
return status
|
||||
|
@ -61,7 +63,9 @@ export const tpvStore = defineStore('tpv', {
|
|||
input.name = field
|
||||
form.appendChild(input)
|
||||
|
||||
if (postValues[field]) { input.value = postValues[field] }
|
||||
if (postValues[field]) {
|
||||
input.value = postValues[field]
|
||||
}
|
||||
}
|
||||
|
||||
form.submit()
|
||||
|
@ -73,7 +77,9 @@ export const tpvStore = defineStore('tpv', {
|
|||
path += location.pathname
|
||||
path += location.search ? location.search : ''
|
||||
path += '#/ecomerce/orders'
|
||||
path += '?' + new URLSearchParams({
|
||||
path +=
|
||||
'?' +
|
||||
new URLSearchParams({
|
||||
tpvStatus: status,
|
||||
tpvOrder: '_transactionId_'
|
||||
}).toString()
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { api, jApi } from 'boot/axios'
|
||||
import { defineStore } from 'pinia';
|
||||
import { api, jApi } from 'boot/axios';
|
||||
|
||||
export const userStore = defineStore('user', {
|
||||
state: () => {
|
||||
const token =
|
||||
sessionStorage.getItem('vnToken') ||
|
||||
localStorage.getItem('vnToken')
|
||||
localStorage.getItem('vnToken');
|
||||
|
||||
return {
|
||||
token,
|
||||
id: null,
|
||||
name: null,
|
||||
nickname: null
|
||||
}
|
||||
nickname: null,
|
||||
isGuest: false
|
||||
};
|
||||
},
|
||||
|
||||
getters: {
|
||||
|
@ -21,41 +22,41 @@ export const userStore = defineStore('user', {
|
|||
|
||||
actions: {
|
||||
async login(user, password, remember) {
|
||||
const params = { user, password }
|
||||
const res = await api.post('Accounts/login', params)
|
||||
const params = { user, password };
|
||||
const res = await api.post('Accounts/login', params);
|
||||
|
||||
if (remember) {
|
||||
localStorage.setItem('vnToken', res.data.token)
|
||||
localStorage.setItem('vnToken', res.data.token);
|
||||
} else {
|
||||
sessionStorage.setItem('vnToken', res.data.token)
|
||||
sessionStorage.setItem('vnToken', res.data.token);
|
||||
}
|
||||
|
||||
this.$patch({
|
||||
token: res.data.token,
|
||||
name: user
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
async logout() {
|
||||
if (this.token != null) {
|
||||
try {
|
||||
await api.post('Accounts/logout')
|
||||
await api.post('Accounts/logout');
|
||||
} catch (e) {}
|
||||
localStorage.removeItem('vnToken')
|
||||
sessionStorage.removeItem('vnToken')
|
||||
localStorage.removeItem('vnToken');
|
||||
sessionStorage.removeItem('vnToken');
|
||||
}
|
||||
this.$reset()
|
||||
this.$reset();
|
||||
},
|
||||
|
||||
async loadData() {
|
||||
const userData = await jApi.getObject(
|
||||
'SELECT id, nickname FROM account.myUser'
|
||||
)
|
||||
);
|
||||
|
||||
this.$patch({
|
||||
id: userData.id,
|
||||
nickname: userData.nickname
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
|
|
@ -28,27 +28,34 @@ const baseConfig = {
|
|||
presets: ['@babel/preset-env']
|
||||
}
|
||||
}
|
||||
}, {
|
||||
},
|
||||
{
|
||||
test: /tinymce\/.*\/skin\.css$/i,
|
||||
use: [MiniCssExtractPlugin.loader, 'css-loader']
|
||||
}, {
|
||||
},
|
||||
{
|
||||
test: /tinymce\/.*\/content\.css$/i,
|
||||
loader: 'css-loader',
|
||||
options: { esModule: false }
|
||||
}, {
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: ['style-loader', 'css-loader'],
|
||||
exclude: [/node_modules/]
|
||||
}, {
|
||||
},
|
||||
{
|
||||
test: /\.yml$/,
|
||||
use: ['json-loader', 'yaml-loader']
|
||||
}, {
|
||||
},
|
||||
{
|
||||
test: /\.xml$/,
|
||||
use: 'raw-loader'
|
||||
}, {
|
||||
},
|
||||
{
|
||||
test: /\.ttf$/,
|
||||
type: 'asset/resource'
|
||||
}, {
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
use: [
|
||||
'style-loader',
|
||||
|
@ -60,6 +67,18 @@ const baseConfig = {
|
|||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.(woff|woff2)$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: '[name].[ext]',
|
||||
outputPath: 'fonts/'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -89,7 +108,7 @@ const baseConfig = {
|
|||
optimization: {
|
||||
runtimeChunk: true,
|
||||
splitChunks: {
|
||||
chunks: 'all',
|
||||
chunks: 'all'
|
||||
}
|
||||
},
|
||||
watchOptions: {
|
||||
|
@ -127,7 +146,7 @@ const devConfig = {
|
|||
'/api': 'http://localhost:3000',
|
||||
'/': {
|
||||
target: 'http://localhost/projects/hedera-web',
|
||||
bypass: (req) => req.path !== '/' ? req.path : null
|
||||
bypass: req => (req.path !== '/' ? req.path : null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue