LbScroll, FullImage, ImageEditor
This commit is contained in:
parent
b06dacc4ce
commit
29acd913ee
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"name": "Container",
|
||||||
|
"base": "PersistedModel",
|
||||||
|
"idInjection": true
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
{
|
||||||
|
"name": "ImageCollectionSize",
|
||||||
|
"base": "PersistedModel",
|
||||||
|
"options": {
|
||||||
|
"mysql": {
|
||||||
|
"table": "hedera.imageCollectionSize"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "Number",
|
||||||
|
"id": true,
|
||||||
|
"description": "Identifier"
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"type": "Number",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"type": "Number",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"crop": {
|
||||||
|
"type": "Boolean",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"relations": {
|
||||||
|
"collection": {
|
||||||
|
"type": "belongsTo",
|
||||||
|
"model": "ImageCollection",
|
||||||
|
"foreignKey": "collectionFk"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,11 +9,11 @@
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
"type": "Number",
|
"type": "Number",
|
||||||
|
"id": true,
|
||||||
"description": "Identifier"
|
"description": "Identifier"
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"id": true,
|
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
"desc": {
|
"desc": {
|
||||||
|
@ -28,17 +28,21 @@
|
||||||
"type": "Number",
|
"type": "Number",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
"schema": {
|
"model": {
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
"table": {
|
"property": {
|
||||||
"type": "String",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"column": {
|
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"required": true
|
"required": true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"relations": {
|
||||||
|
"sizes": {
|
||||||
|
"type": "hasMany",
|
||||||
|
"model": "ImageCollectionSize",
|
||||||
|
"foreignKey": "collectionFk",
|
||||||
|
"property": "id"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,3 +1,6 @@
|
||||||
|
const md5 = require('md5');
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
const sharp = require('sharp');
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethod('upload', {
|
Self.remoteMethod('upload', {
|
||||||
|
@ -5,26 +8,14 @@ module.exports = Self => {
|
||||||
accessType: 'WRITE',
|
accessType: 'WRITE',
|
||||||
accepts: [
|
accepts: [
|
||||||
{
|
{
|
||||||
arg: 'file',
|
|
||||||
type: 'String',
|
|
||||||
description: 'The image file'
|
|
||||||
}, {
|
|
||||||
arg: 'name',
|
|
||||||
type: 'String',
|
|
||||||
description: 'The image name'
|
|
||||||
}, {
|
|
||||||
arg: 'collectionFk',
|
|
||||||
type: 'String',
|
|
||||||
description: 'The collection'
|
|
||||||
}, {
|
|
||||||
arg: 'ctx',
|
arg: 'ctx',
|
||||||
type: 'Object',
|
type: 'Object',
|
||||||
http: {source: 'context'}
|
http: {source: 'context'}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
returns: {
|
returns: {
|
||||||
type: 'Boolean',
|
type: Self.modelName,
|
||||||
description: 'Success or failed',
|
description: 'The resulting file instance',
|
||||||
root: true,
|
root: true,
|
||||||
},
|
},
|
||||||
http: {
|
http: {
|
||||||
|
@ -33,7 +24,134 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.upload = async (file, name, collectionFk) => {
|
Self.upload = async ctx => {
|
||||||
let $ = Self.app.models;
|
let app = Self.app;
|
||||||
|
let $ = app.models;
|
||||||
|
let storageConnector = app.dataSources.storage.connector;
|
||||||
|
|
||||||
|
let tx = await Self.beginTransaction({});
|
||||||
|
let myOptions = {transaction: tx};
|
||||||
|
|
||||||
|
async function getContainer(name) {
|
||||||
|
let container;
|
||||||
|
try {
|
||||||
|
container = await $.Container.getContainer(name);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code === 'ENOENT') {
|
||||||
|
container = await $.Container.createContainer({
|
||||||
|
name: name
|
||||||
|
});
|
||||||
|
} else throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Upload file to temporary path
|
||||||
|
|
||||||
|
let tempContainer = await getContainer('temp');
|
||||||
|
let uploaded = await $.Container.upload(tempContainer.name, ctx.req, ctx.result, {});
|
||||||
|
let files = Object.values(uploaded.files).map(file => file[0]);
|
||||||
|
let args = {};
|
||||||
|
|
||||||
|
for (let key in uploaded.fields)
|
||||||
|
args[key] = uploaded.fields[key][0];
|
||||||
|
|
||||||
|
if (!/^[a-z0-9_]+$/.test(args.name))
|
||||||
|
throw new Error('Bad file name');
|
||||||
|
|
||||||
|
let collection = await $.ImageCollection.findOne({
|
||||||
|
fields: [
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'maxWidth',
|
||||||
|
'maxHeight',
|
||||||
|
'model',
|
||||||
|
'property'
|
||||||
|
],
|
||||||
|
where: {name: args.collectionFk},
|
||||||
|
include: {
|
||||||
|
relation: 'sizes',
|
||||||
|
scope: {
|
||||||
|
fields: ['width', 'height', 'crop']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let md5Hash = md5(args.name).substring(0, 4);
|
||||||
|
let md5Path = md5Hash.match(/(..?)/g).join('/');
|
||||||
|
let rootPath = storageConnector.client.root;
|
||||||
|
|
||||||
|
let file = files[0];
|
||||||
|
let data = {
|
||||||
|
name: args.name,
|
||||||
|
collectionFk: args.collectionFk
|
||||||
|
};
|
||||||
|
let newImage = await Self.upsertWithWhere(data, {
|
||||||
|
name: args.name,
|
||||||
|
collectionFk: args.collectionFk,
|
||||||
|
updated: (new Date).getTime()
|
||||||
|
}, myOptions);
|
||||||
|
|
||||||
|
// Resizes and saves the image
|
||||||
|
|
||||||
|
let extension = file.name.split('.').pop().toLowerCase();
|
||||||
|
let srcPath = `${rootPath}/${tempContainer.name}/${file.name}`;
|
||||||
|
let collectionDir = `${rootPath}/${args.collectionFk}`;
|
||||||
|
let dstDir = `${collectionDir}/${md5Path}/${args.name}`;
|
||||||
|
let dstFile = `full.${extension}`;
|
||||||
|
|
||||||
|
let resizeOpts = {
|
||||||
|
withoutEnlargement: true,
|
||||||
|
fit: 'inside'
|
||||||
|
};
|
||||||
|
|
||||||
|
await fs.mkdir(dstDir, {recursive: true});
|
||||||
|
await sharp(srcPath)
|
||||||
|
.resize(collection.maxWidth, collection.maxHeight, resizeOpts)
|
||||||
|
.toFile(`${dstDir}/${dstFile}`);
|
||||||
|
|
||||||
|
let sizes = collection.sizes();
|
||||||
|
for (let size of sizes) {
|
||||||
|
let dstFile = `${size.width}x${size.height}.${extension}`;
|
||||||
|
let resizeOpts = {
|
||||||
|
withoutEnlargement: true,
|
||||||
|
fit: size.crop ? 'cover' : 'inside'
|
||||||
|
};
|
||||||
|
|
||||||
|
await sharp(srcPath)
|
||||||
|
.resize(size.width, size.height, resizeOpts)
|
||||||
|
.toFile(`${dstDir}/${dstFile}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updates items with matching id, when option is checked
|
||||||
|
|
||||||
|
if (args.updateMatching === 'true') {
|
||||||
|
if (!collection.model || !collection.property)
|
||||||
|
throw new Error('Matching model settings not defined');
|
||||||
|
|
||||||
|
let model = app.models[collection.model];
|
||||||
|
|
||||||
|
if (!model)
|
||||||
|
throw new Error('Matching model not found');
|
||||||
|
|
||||||
|
let item = await model.findById(args.name, null, myOptions);
|
||||||
|
|
||||||
|
if (item)
|
||||||
|
await item.updateAttribute(
|
||||||
|
collection.property,
|
||||||
|
args.name,
|
||||||
|
myOptions
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.unlink(srcPath);
|
||||||
|
await tx.commit();
|
||||||
|
return newImage;
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"name": "Image",
|
"name": "Image",
|
||||||
"base": "PersistedModel",
|
"base": "PersistedModel",
|
||||||
"options": {
|
"options": {
|
||||||
"mysql": {
|
"mysql": {
|
||||||
"table": "hedera.image"
|
"table": "hedera.image"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -16,19 +16,17 @@
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
"collectionFk": {
|
||||||
|
"type": "String",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
"updated": {
|
"updated": {
|
||||||
"type": "Number"
|
"type": "Number"
|
||||||
},
|
},
|
||||||
"nRefs": {
|
"nRefs": {
|
||||||
"type": "Number",
|
"type": "Number",
|
||||||
"required": true
|
"required": true,
|
||||||
|
"default": 0
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"relations": {
|
|
||||||
"collection": {
|
|
||||||
"type": "belongsTo",
|
|
||||||
"model": "ImageCollection",
|
|
||||||
"foreignKey": "collectionFk"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -21,8 +21,7 @@
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
"image": {
|
"image": {
|
||||||
"type": "String",
|
"type": "String"
|
||||||
"required": true
|
|
||||||
},
|
},
|
||||||
"created": {
|
"created": {
|
||||||
"type": "Date",
|
"type": "Date",
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -13,12 +13,16 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"compression": "^1.0.3",
|
"compression": "^1.0.3",
|
||||||
"cors": "^2.5.2",
|
"cors": "^2.5.2",
|
||||||
"helmet": "^3.10.0",
|
"fs-extra": "^8.1.0",
|
||||||
"loopback": "^3.22.0",
|
"helmet": "^3.19.0",
|
||||||
|
"loopback": "^3.26.0",
|
||||||
"loopback-boot": "^2.6.5",
|
"loopback-boot": "^2.6.5",
|
||||||
"loopback-component-explorer": "^6.2.0",
|
"loopback-component-explorer": "^6.2.0",
|
||||||
|
"loopback-component-storage": "^3.6.2",
|
||||||
"loopback-connector-mysql": "^5.4.1",
|
"loopback-connector-mysql": "^5.4.1",
|
||||||
|
"md5": "^2.2.1",
|
||||||
"serve-favicon": "^2.0.1",
|
"serve-favicon": "^2.0.1",
|
||||||
|
"sharp": "^0.22.1",
|
||||||
"strong-error-handler": "^3.0.0"
|
"strong-error-handler": "^3.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -2,5 +2,17 @@
|
||||||
"db": {
|
"db": {
|
||||||
"name": "db",
|
"name": "db",
|
||||||
"connector": "memory"
|
"connector": "memory"
|
||||||
|
},
|
||||||
|
"storage": {
|
||||||
|
"name": "storage",
|
||||||
|
"connector": "loopback-component-storage",
|
||||||
|
"provider": "filesystem",
|
||||||
|
"root": "./dist/image",
|
||||||
|
"maxFileSize": "10485760",
|
||||||
|
"allowedContentTypes": [
|
||||||
|
"image/png",
|
||||||
|
"image/jpeg",
|
||||||
|
"image/jpg"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,6 +71,9 @@
|
||||||
"Client": {
|
"Client": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
},
|
},
|
||||||
|
"Container": {
|
||||||
|
"dataSource": "storage"
|
||||||
|
},
|
||||||
"Country": {
|
"Country": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
},
|
},
|
||||||
|
@ -86,6 +89,9 @@
|
||||||
"ImageCollection": {
|
"ImageCollection": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
},
|
},
|
||||||
|
"ImageCollectionSize": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
"ItemCategory": {
|
"ItemCategory": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
|
|
||||||
import { date as qdate } from 'quasar'
|
import { date as qdate, format } from 'quasar'
|
||||||
|
const { pad } = format
|
||||||
|
|
||||||
export default async ({ app, Vue }) => {
|
export default async ({ app, Vue }) => {
|
||||||
let i18n = app.i18n
|
let i18n = app.i18n
|
||||||
|
@ -58,8 +59,26 @@ export default async ({ app, Vue }) => {
|
||||||
return relDate(val) + ' ' + qdate.formatDate(val, 'H:mm:ss')
|
return relDate(val) + ' ' + qdate.formatDate(val, 'H:mm:ss')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function elapsedTime (val) {
|
||||||
|
if (val == null) return val
|
||||||
|
if (!(val instanceof Date)) {
|
||||||
|
val = new Date(val)
|
||||||
|
}
|
||||||
|
let now = (new Date()).getTime()
|
||||||
|
val = Math.floor((now - val.getTime()) / 1000)
|
||||||
|
|
||||||
|
let hours = Math.floor(val / 3600)
|
||||||
|
val -= hours * 3600
|
||||||
|
let minutes = Math.floor(val / 60)
|
||||||
|
val -= minutes * 60
|
||||||
|
let seconds = val
|
||||||
|
|
||||||
|
return `${pad(hours, 2)}:${pad(minutes, 2)}:${pad(seconds, 2)}`
|
||||||
|
}
|
||||||
|
|
||||||
Vue.filter('currency', currency)
|
Vue.filter('currency', currency)
|
||||||
Vue.filter('date', date)
|
Vue.filter('date', date)
|
||||||
Vue.filter('relDate', relDate)
|
Vue.filter('relDate', relDate)
|
||||||
Vue.filter('relTime', relTime)
|
Vue.filter('relTime', relTime)
|
||||||
|
Vue.filter('elapsedTime', elapsedTime)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<q-dialog v-model="showDialog">
|
||||||
|
<q-card style="width: 80em;">
|
||||||
|
<q-img
|
||||||
|
:src="`${$imageBase}/${collection}/${size}/${value}`"
|
||||||
|
@click="showDialog = false">
|
||||||
|
</q-img>
|
||||||
|
<q-btn
|
||||||
|
v-if="editable"
|
||||||
|
@click="showImgEditor = true"
|
||||||
|
:title="$t('edit')"
|
||||||
|
icon="edit"
|
||||||
|
round
|
||||||
|
color="accent"
|
||||||
|
class="absolute-bottom-right"
|
||||||
|
style="bottom: .6em; right: .6em;"/>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
<image-editor
|
||||||
|
v-if="editable"
|
||||||
|
v-model="image"
|
||||||
|
:collection="collection"
|
||||||
|
:show="showImgEditor"
|
||||||
|
@response="showImgEditor = false"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ImageEditor from 'components/ImageEditor'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'FullImage',
|
||||||
|
components: {
|
||||||
|
ImageEditor
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
value: {},
|
||||||
|
collection: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: 'full'
|
||||||
|
},
|
||||||
|
editable: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
image: null,
|
||||||
|
showImgEditor: false,
|
||||||
|
showDialog: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
image (val) {
|
||||||
|
this.$emit('input', val)
|
||||||
|
},
|
||||||
|
value (val) {
|
||||||
|
this.image = val
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
show () {
|
||||||
|
this.showDialog = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,162 @@
|
||||||
|
<template>
|
||||||
|
<q-dialog v-model="show" persistent>
|
||||||
|
<q-card style="width: 25em;">
|
||||||
|
<q-card-section>
|
||||||
|
<q-select
|
||||||
|
v-model="image"
|
||||||
|
:label="$t('image')"
|
||||||
|
:options="images"
|
||||||
|
@filter="onFilter"
|
||||||
|
option-value="name"
|
||||||
|
option-label="name"
|
||||||
|
@new-value="onAdd"
|
||||||
|
input-debounce="250"
|
||||||
|
emit-value
|
||||||
|
clearable
|
||||||
|
use-input
|
||||||
|
hide-selected
|
||||||
|
fill-input>
|
||||||
|
<template v-slot:option="scope">
|
||||||
|
<q-item
|
||||||
|
v-bind="scope.itemProps"
|
||||||
|
v-on="scope.itemEvents">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-avatar>
|
||||||
|
<img :src="`${$imageBase}/${collection}/200x200/${scope.opt.name}`">
|
||||||
|
</q-avatar>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>{{scope.opt.name}}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<q-avatar>
|
||||||
|
<img :src="`${$imageBase}/${collection}/200x200/${image}`">
|
||||||
|
</q-avatar>
|
||||||
|
</template>
|
||||||
|
<template v-slot:after>
|
||||||
|
<q-btn
|
||||||
|
icon="add_a_photo"
|
||||||
|
@click="showUploader = !showUploader"
|
||||||
|
:title="$t('edit')"
|
||||||
|
:disable="!image"
|
||||||
|
:color="showUploader ? 'primary' : null"
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
dense/>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
<q-slide-transition>
|
||||||
|
<q-uploader
|
||||||
|
ref="uploader"
|
||||||
|
v-show="showUploader"
|
||||||
|
:url="`${$apiBase}/Images/upload`"
|
||||||
|
:form-fields="formFields"
|
||||||
|
auto-upload
|
||||||
|
class="q-mt-md"
|
||||||
|
flat
|
||||||
|
bordered/>
|
||||||
|
</q-slide-transition>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-actions align="right">
|
||||||
|
<q-btn
|
||||||
|
:label="$t('cancel')"
|
||||||
|
@click="onCancel"
|
||||||
|
color="primary"
|
||||||
|
flat/>
|
||||||
|
<q-btn
|
||||||
|
:label="$t('accept')"
|
||||||
|
@click="onAccept"
|
||||||
|
color="primary"
|
||||||
|
flat/>
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'ImageEditor',
|
||||||
|
props: {
|
||||||
|
value: {},
|
||||||
|
collection: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
image: null,
|
||||||
|
showUploader: false,
|
||||||
|
images: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
image () {
|
||||||
|
if (this.$refs.uploader) {
|
||||||
|
this.$refs.uploader.reset()
|
||||||
|
}
|
||||||
|
if (!this.image) {
|
||||||
|
this.showUploader = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
value () {
|
||||||
|
this.image = this.value
|
||||||
|
},
|
||||||
|
showUploader () {
|
||||||
|
this.$refs.uploader.reset()
|
||||||
|
},
|
||||||
|
show () {
|
||||||
|
if (!this.show) {
|
||||||
|
this.showUploader = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onFilter (val, update, abort) {
|
||||||
|
let filter = {
|
||||||
|
where: {
|
||||||
|
name: { like: `${val}%` },
|
||||||
|
collectionFk: this.collection
|
||||||
|
},
|
||||||
|
limit: !val || val.length < 2 ? 50 : undefined
|
||||||
|
}
|
||||||
|
this.$axios.get(`Images`, { params: { filter } })
|
||||||
|
.then(res => {
|
||||||
|
update(() => (this.images = res.data))
|
||||||
|
})
|
||||||
|
.catch(() => abort())
|
||||||
|
},
|
||||||
|
onAdd (inputValue, done) {
|
||||||
|
done({ name: inputValue }, 'toggle')
|
||||||
|
this.showUploader = true
|
||||||
|
// this.$refs.uploader.pickFiles()
|
||||||
|
},
|
||||||
|
onAccept () {
|
||||||
|
this.$emit('input', this.image)
|
||||||
|
this.$emit('response', 'accept')
|
||||||
|
},
|
||||||
|
onCancel () {
|
||||||
|
this.image = this.value
|
||||||
|
this.$emit('response', 'cancel')
|
||||||
|
},
|
||||||
|
formFields () {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'collectionFk',
|
||||||
|
value: this.collection
|
||||||
|
}, {
|
||||||
|
name: 'name',
|
||||||
|
value: this.image
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,173 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
v-if="status == 'clear' || status == 'empty'"
|
||||||
|
class="text-subtitle1 text-center text-grey-7 q-pa-md">
|
||||||
|
<span v-if="status == 'clear'">
|
||||||
|
{{$t('setSearchFilter')}}
|
||||||
|
</span>
|
||||||
|
<span v-if="status == 'empty'">
|
||||||
|
{{$t('noDataFound')}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<q-infinite-scroll
|
||||||
|
ref="scroll"
|
||||||
|
@load="onLoad"
|
||||||
|
scroll-taget="html">
|
||||||
|
<slot></slot>
|
||||||
|
<template slot="loading">
|
||||||
|
<div class="row justify-center q-my-md">
|
||||||
|
<q-spinner color="primary" size="40px"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</q-infinite-scroll>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { CancelToken } from 'axios'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'LbScroll',
|
||||||
|
props: {
|
||||||
|
request: {
|
||||||
|
type: Function,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
autoRefresh: {
|
||||||
|
type: Number,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
data: null,
|
||||||
|
source: null,
|
||||||
|
isLoading: false,
|
||||||
|
index: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
status () {
|
||||||
|
if (this.isLoading && !this.data) {
|
||||||
|
return 'loading'
|
||||||
|
} else if (!this.data) {
|
||||||
|
return 'clear'
|
||||||
|
} else if (this.data.length === 0) {
|
||||||
|
return 'empty'
|
||||||
|
} else {
|
||||||
|
return 'ready'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeDestroy () {
|
||||||
|
this.clearTimeout()
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
data (val) {
|
||||||
|
this.$emit('data', val)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
get (path, params) {
|
||||||
|
this.cancelRequest()
|
||||||
|
this.isLoading = true
|
||||||
|
this.source = CancelToken.source()
|
||||||
|
let config = {
|
||||||
|
params,
|
||||||
|
cancelToken: this.source.token
|
||||||
|
}
|
||||||
|
return this.$axios.get(path, config)
|
||||||
|
.then(res => {
|
||||||
|
this.source = null
|
||||||
|
this.isLoading = false
|
||||||
|
return res
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
if (!err.__CANCEL__) {
|
||||||
|
this.source = null
|
||||||
|
this.isLoading = false
|
||||||
|
}
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
},
|
||||||
|
cancelRequest () {
|
||||||
|
if (this.source) {
|
||||||
|
this.source.cancel()
|
||||||
|
this.source = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
doRequest () {
|
||||||
|
this.cancelRequest()
|
||||||
|
this.clearTimeout()
|
||||||
|
|
||||||
|
let config = this.request(this.index)
|
||||||
|
if (!config) {
|
||||||
|
this.data = null
|
||||||
|
return Promise.resolve(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
let params = config.params || {}
|
||||||
|
let filter = config.filter
|
||||||
|
let limit
|
||||||
|
|
||||||
|
if (filter) {
|
||||||
|
params.filter = filter
|
||||||
|
limit = filter.limit
|
||||||
|
|
||||||
|
if (!limit) {
|
||||||
|
limit = this.index * 30
|
||||||
|
filter.limit = limit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.get(config.url, params)
|
||||||
|
.then(res => {
|
||||||
|
res.limit = limit
|
||||||
|
this.data = res.data
|
||||||
|
return res
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.setTimeout()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
setTimeout () {
|
||||||
|
this.clearTimeout()
|
||||||
|
if (this.autoRefresh) {
|
||||||
|
this.timeout = setTimeout(
|
||||||
|
() => this.refresh(), this.autoRefresh * 1000)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clearTimeout () {
|
||||||
|
if (this.timeout) {
|
||||||
|
clearTimeout(this.timeout)
|
||||||
|
this.timeout = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad (index, done) {
|
||||||
|
this.index = index
|
||||||
|
this.doRequest()
|
||||||
|
.then(res => {
|
||||||
|
if (!res) return done(true)
|
||||||
|
done(!res.limit || res.data.length < res.limit)
|
||||||
|
return res
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
done(true)
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
},
|
||||||
|
reload () {
|
||||||
|
this.data = null
|
||||||
|
let scroll = this.$refs.scroll
|
||||||
|
scroll.stop()
|
||||||
|
scroll.reset()
|
||||||
|
scroll.resume()
|
||||||
|
scroll.trigger()
|
||||||
|
},
|
||||||
|
refresh () {
|
||||||
|
this.doRequest()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,8 +1,17 @@
|
||||||
import Toolbar from './Toolbar'
|
import Toolbar from './Toolbar'
|
||||||
|
import LbScroll from './LbScroll'
|
||||||
|
import FullImage from './FullImage'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Toolbar
|
Toolbar,
|
||||||
|
LbScroll,
|
||||||
|
FullImage
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
data: null
|
||||||
|
}
|
||||||
},
|
},
|
||||||
beforeRouteEnter (to, from, next) {
|
beforeRouteEnter (to, from, next) {
|
||||||
next(vm => {})
|
next(vm => {})
|
||||||
|
|
|
@ -178,6 +178,13 @@ export default {
|
||||||
priority: 'Priority',
|
priority: 'Priority',
|
||||||
text: 'Text',
|
text: 'Text',
|
||||||
|
|
||||||
|
// Images
|
||||||
|
collection: 'Collection',
|
||||||
|
updateMatchingId: 'Update items with matching id',
|
||||||
|
uploadAutomatically: 'Upload automatically',
|
||||||
|
imagesUploadSuccess: 'Images uploaded successfully',
|
||||||
|
imagesUploadFailed: 'Some images could not be uploaded',
|
||||||
|
|
||||||
// User
|
// User
|
||||||
accessLog: 'Access log',
|
accessLog: 'Access log',
|
||||||
|
|
||||||
|
|
|
@ -178,6 +178,13 @@ export default {
|
||||||
priority: 'Prioridad',
|
priority: 'Prioridad',
|
||||||
text: 'Texto',
|
text: 'Texto',
|
||||||
|
|
||||||
|
// Images
|
||||||
|
collection: 'Colección',
|
||||||
|
updateMatchingId: 'Actualizar ítems con id coincidente',
|
||||||
|
uploadAutomatically: 'Subir automáticamente',
|
||||||
|
imagesUploadSuccess: 'Imágenes subidas correctamente',
|
||||||
|
imagesUploadFailed: 'Algunas imágenes no se ha podido subir',
|
||||||
|
|
||||||
// User
|
// User
|
||||||
accessLog: 'Registro de accesos',
|
accessLog: 'Registro de accesos',
|
||||||
|
|
||||||
|
|
|
@ -159,7 +159,7 @@ export default {
|
||||||
this.$q.notify({
|
this.$q.notify({
|
||||||
message: this.$t(message),
|
message: this.$t(message),
|
||||||
icon: 'check',
|
icon: 'check',
|
||||||
color: 'green-6'
|
color: 'green'
|
||||||
})
|
})
|
||||||
this.$router.go(-1)
|
this.$router.go(-1)
|
||||||
})
|
})
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
:label="$t('refreshRate')"
|
:label="$t('refreshRate')"
|
||||||
v-model="rate"
|
v-model="rate"
|
||||||
:options="rates"
|
:options="rates"
|
||||||
@input="setTimeout"
|
@input="$refs.scroll.reload()"
|
||||||
emit-value>
|
emit-value>
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<q-icon name="timer"/>
|
<q-icon name="timer"/>
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
:label="$t('orderBy')"
|
:label="$t('orderBy')"
|
||||||
v-model="order"
|
v-model="order"
|
||||||
:options="orderOptions"
|
:options="orderOptions"
|
||||||
@input="refresh"
|
@input="$refs.scroll.reload()"
|
||||||
emit-value>
|
emit-value>
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<q-icon name="sort"/>
|
<q-icon name="sort"/>
|
||||||
|
@ -38,20 +38,15 @@
|
||||||
</div>
|
</div>
|
||||||
</q-drawer>
|
</q-drawer>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<lb-scroll
|
||||||
v-if="connections && !connections.length"
|
|
||||||
class="text-subtitle1 text-center text-grey-7 q-pa-md">
|
|
||||||
{{$t('noDataFound')}}
|
|
||||||
</div>
|
|
||||||
<q-infinite-scroll
|
|
||||||
ref="scroll"
|
ref="scroll"
|
||||||
@load="onLoad"
|
:request="request"
|
||||||
scroll-taget="html"
|
@data="onData"
|
||||||
:offset="500">
|
:auto-refresh="rate">
|
||||||
<q-card class="vn-w-md">
|
<q-card class="vn-w-md">
|
||||||
<q-list bordered separator>
|
<q-list bordered separator>
|
||||||
<q-item
|
<q-item
|
||||||
v-for="conn in connections"
|
v-for="conn in data"
|
||||||
:key="conn.id"
|
:key="conn.id"
|
||||||
:to="`/access-log/${conn.userVisit.user.id}`"
|
:to="`/access-log/${conn.userVisit.user.id}`"
|
||||||
:title="$t('accessLog')"
|
:title="$t('accessLog')"
|
||||||
|
@ -61,7 +56,7 @@
|
||||||
{{conn.userVisit.user.nickname}}
|
{{conn.userVisit.user.nickname}}
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
<q-item-label caption>
|
<q-item-label caption>
|
||||||
{{conn.lastUpdate | relTime}}
|
{{conn.lastUpdate | elapsedTime}}
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
<q-item-label caption>
|
<q-item-label caption>
|
||||||
{{conn.userVisit.access.agent.platform}} -
|
{{conn.userVisit.access.agent.platform}} -
|
||||||
|
@ -72,21 +67,15 @@
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
</q-card>
|
</q-card>
|
||||||
<template slot="loading">
|
</lb-scroll>
|
||||||
<div class="row justify-center q-my-md">
|
|
||||||
<q-spinner color="primary" name="dots" size="40px" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</q-infinite-scroll>
|
|
||||||
</div>
|
</div>
|
||||||
<q-page-sticky position="bottom-right" :offset="[18, 18]">
|
<q-page-sticky position="bottom-right" :offset="[18, 18]">
|
||||||
<q-btn
|
<q-btn
|
||||||
fab
|
fab
|
||||||
icon="refresh"
|
icon="refresh"
|
||||||
color="accent"
|
color="accent"
|
||||||
@click="refresh"
|
@click="$refs.scroll.refresh()"
|
||||||
:title="$t('refresh')"
|
:title="$t('refresh')"/>
|
||||||
/>
|
|
||||||
</q-page-sticky>
|
</q-page-sticky>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -99,9 +88,7 @@ export default {
|
||||||
mixins: [Page],
|
mixins: [Page],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
connections: null,
|
data: [],
|
||||||
pageSize: 30,
|
|
||||||
limit: 0,
|
|
||||||
rate: 5,
|
rate: 5,
|
||||||
count: null,
|
count: null,
|
||||||
rates: [
|
rates: [
|
||||||
|
@ -110,6 +97,7 @@ export default {
|
||||||
value: null
|
value: null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
where: { userVisitFk: { neq: null } },
|
||||||
order: 'lastUpdate DESC',
|
order: 'lastUpdate DESC',
|
||||||
orderOptions: [
|
orderOptions: [
|
||||||
{
|
{
|
||||||
|
@ -134,77 +122,51 @@ export default {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeDestroy () {
|
|
||||||
this.clearTimeout()
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
onLoad (index, done) {
|
request () {
|
||||||
this.limit = this.pageSize * index
|
return {
|
||||||
this.refresh()
|
url: 'UserSessions',
|
||||||
.then(() => {
|
filter: {
|
||||||
done(this.connections.length < this.limit)
|
fields: ['created', 'lastUpdate', 'userVisitFk'],
|
||||||
})
|
order: this.order,
|
||||||
.catch((err) => {
|
where: this.where,
|
||||||
done(true)
|
include: {
|
||||||
throw err
|
relation: 'userVisit',
|
||||||
})
|
scope: {
|
||||||
},
|
fields: ['accessFk', 'userFk'],
|
||||||
refresh () {
|
include: [
|
||||||
this.clearTimeout()
|
{
|
||||||
let where = { userVisitFk: { neq: null } }
|
relation: 'access',
|
||||||
let filter = {
|
scope: {
|
||||||
fields: ['created', 'lastUpdate', 'userVisitFk'],
|
fields: ['agentFk'],
|
||||||
order: this.order,
|
include: {
|
||||||
limit: this.limit,
|
relation: 'agent',
|
||||||
where,
|
scope: {
|
||||||
include: {
|
fields: [
|
||||||
relation: 'userVisit',
|
'platform',
|
||||||
scope: {
|
'browser',
|
||||||
fields: ['accessFk', 'userFk'],
|
'version'
|
||||||
include: [
|
]
|
||||||
{
|
}
|
||||||
relation: 'access',
|
|
||||||
scope: {
|
|
||||||
fields: ['agentFk'],
|
|
||||||
include: {
|
|
||||||
relation: 'agent',
|
|
||||||
scope: {
|
|
||||||
fields: [
|
|
||||||
'platform',
|
|
||||||
'browser',
|
|
||||||
'version'
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
relation: 'user',
|
||||||
|
scope: {
|
||||||
|
fields: ['id', 'nickname']
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, {
|
]
|
||||||
relation: 'user',
|
}
|
||||||
scope: {
|
|
||||||
fields: ['id', 'nickname']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.$axios.get(`UserSessions`, { params: { filter } })
|
},
|
||||||
.then(res => (this.connections = res.data))
|
onData (data) {
|
||||||
.then(() => this.$axios.get(`UserSessions/count`, { params: { where } }))
|
this.data = data
|
||||||
|
let params = { where: this.where }
|
||||||
|
this.$axios.get(`UserSessions/count`, { params })
|
||||||
.then(res => (this.count = res.data.count))
|
.then(res => (this.count = res.data.count))
|
||||||
.finally(() => this.setTimeout())
|
|
||||||
},
|
|
||||||
setTimeout () {
|
|
||||||
this.clearTimeout()
|
|
||||||
if (this.rate) {
|
|
||||||
this.timeout = setTimeout(
|
|
||||||
() => this.refresh(), this.rate * 1000)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
clearTimeout () {
|
|
||||||
if (this.timeout) {
|
|
||||||
clearTimeout(this.timeout)
|
|
||||||
this.timeout = null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,139 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="text-subtitle1 text-center text-grey-7 q-pa-lg">
|
<div class="vn-pp row justify-center">
|
||||||
Sorry this section is under construction
|
<q-card class="vn-w-md">
|
||||||
|
<q-card-section>
|
||||||
|
<q-select
|
||||||
|
v-model="collection"
|
||||||
|
:label="$t('collection')"
|
||||||
|
:options="collections"
|
||||||
|
option-value="name"
|
||||||
|
option-label="desc"
|
||||||
|
emit-value/>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section>
|
||||||
|
<q-uploader
|
||||||
|
ref="uploader"
|
||||||
|
:url="`${$apiBase}/Images/upload`"
|
||||||
|
:form-fields="formFields"
|
||||||
|
@added="onAdded"
|
||||||
|
@finish="onFinish"
|
||||||
|
:auto-upload="uploadAutomatically"
|
||||||
|
multiple
|
||||||
|
flat
|
||||||
|
bordered
|
||||||
|
style="width: 100%;">
|
||||||
|
<template v-slot:list="scope">
|
||||||
|
<q-list separator>
|
||||||
|
<q-item v-for="file in scope.files" :key="file.name">
|
||||||
|
<q-item-section>
|
||||||
|
<q-input
|
||||||
|
v-model="file.uploadName"
|
||||||
|
:readonly="file.__status == 'uploading' || file.__status == 'uploaded'"
|
||||||
|
:loading="file.__status == 'uploading'"
|
||||||
|
:error="file.__status == 'failed'"
|
||||||
|
dense>
|
||||||
|
<template v-slot:prepend
|
||||||
|
v-if="file.__img">
|
||||||
|
<q-avatar>
|
||||||
|
<img :src="file.__img.src">
|
||||||
|
</q-avatar>
|
||||||
|
</template>
|
||||||
|
<template v-slot:append
|
||||||
|
v-if="file.__status == 'uploaded'">
|
||||||
|
<q-icon
|
||||||
|
name="check"
|
||||||
|
color="green"/>
|
||||||
|
</template>
|
||||||
|
<template v-slot:after>
|
||||||
|
<q-btn
|
||||||
|
icon="delete"
|
||||||
|
@click="scope.removeFile(file)"
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
round/>
|
||||||
|
</template>
|
||||||
|
<template v-slot:hint>
|
||||||
|
{{ file.__sizeLabel }} / {{ file.__progressLabel }}
|
||||||
|
</template>
|
||||||
|
<template v-slot:error>
|
||||||
|
{{ file.xhr.statusText }}
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</template>
|
||||||
|
</q-uploader>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section>
|
||||||
|
<q-checkbox
|
||||||
|
v-model="updateMatching"
|
||||||
|
:label="$t('updateMatchingId')"/>
|
||||||
|
<q-checkbox
|
||||||
|
v-model="uploadAutomatically"
|
||||||
|
:label="$t('uploadAutomatically')"/>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'Images'
|
name: 'Images',
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
collection: 'catalog',
|
||||||
|
collections: null,
|
||||||
|
updateMatching: true,
|
||||||
|
uploadAutomatically: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
let filter = {
|
||||||
|
fields: ['id', 'name', 'desc'],
|
||||||
|
order: 'desc'
|
||||||
|
}
|
||||||
|
this.$axios.get(`ImageCollections`, { params: { filter } })
|
||||||
|
.then(res => (this.collections = res.data))
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onAdded (files) {
|
||||||
|
for (let file of files) {
|
||||||
|
file.uploadName = file.name.split('.')[0]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFinish () {
|
||||||
|
let error = this.$refs.uploader.files.findIndex(
|
||||||
|
file => file.__status === 'failed')
|
||||||
|
|
||||||
|
if (error === -1) {
|
||||||
|
this.$q.notify({
|
||||||
|
message: this.$t('imagesUploadSuccess'),
|
||||||
|
icon: 'check',
|
||||||
|
color: 'green'
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.$q.notify({
|
||||||
|
message: this.$t('imagesUploadFailed'),
|
||||||
|
icon: 'warning',
|
||||||
|
color: 'orange'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formFields (files) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'collectionFk',
|
||||||
|
value: this.collection
|
||||||
|
}, {
|
||||||
|
name: 'updateMatching',
|
||||||
|
value: this.updateMatching
|
||||||
|
}, {
|
||||||
|
name: 'name',
|
||||||
|
value: files[0].uploadName
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,14 +1,97 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="text-subtitle1 text-center text-grey-7 q-pa-lg">
|
<div class="vn-pp row justify-center">
|
||||||
Sorry this section is under construction
|
<toolbar>
|
||||||
|
<q-input
|
||||||
|
v-model="search"
|
||||||
|
debounce="500"
|
||||||
|
dark
|
||||||
|
dense
|
||||||
|
standout>
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon
|
||||||
|
v-if="search === ''"
|
||||||
|
name="search"/>
|
||||||
|
<q-icon
|
||||||
|
v-else
|
||||||
|
name="clear"
|
||||||
|
class="cursor-pointer"
|
||||||
|
@click="search = ''"/>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</toolbar>
|
||||||
|
<div>
|
||||||
|
<lb-scroll
|
||||||
|
ref="scroll"
|
||||||
|
:request="request"
|
||||||
|
@data="data = arguments[0]">
|
||||||
|
<q-card class="vn-w-md">
|
||||||
|
<q-list bordered separator>
|
||||||
|
<q-item
|
||||||
|
v-for="item in data"
|
||||||
|
:key="item.id"
|
||||||
|
clickable>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-avatar>
|
||||||
|
<img :src="`${$imageBase}/catalog/200x200/${item.image}`">
|
||||||
|
</q-avatar>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>{{item.longName}}</q-item-label>
|
||||||
|
<q-item-label caption>#{{item.id}}</q-item-label>
|
||||||
|
<q-item-label caption>{{item.image}}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-card>
|
||||||
|
</lb-scroll>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import Page from 'components/Page'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Items'
|
name: 'Items',
|
||||||
|
mixins: [Page],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
search: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeRouteUpdate (to, from, next) {
|
||||||
|
next()
|
||||||
|
this.$refs.scroll.reload()
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
search (value) {
|
||||||
|
let query
|
||||||
|
if (value) query = { search: value }
|
||||||
|
this.$router.push({ query })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
request () {
|
||||||
|
this.search = this.$route.query.search || ''
|
||||||
|
if (!this.search) return null
|
||||||
|
|
||||||
|
let where = {}
|
||||||
|
|
||||||
|
if (/^[0-9]+$/.test(this.search)) {
|
||||||
|
where.id = this.search
|
||||||
|
} else {
|
||||||
|
where.longName = { like: `%${this.search}%` }
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: 'Items',
|
||||||
|
filter: {
|
||||||
|
fields: ['id', 'longName', 'image'],
|
||||||
|
order: 'name',
|
||||||
|
where
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="vn-pp row justify-center">
|
<div class="vn-pp row justify-center">
|
||||||
<q-card class="vn-w-lg">
|
<q-card class="vn-w-lg">
|
||||||
<q-img
|
<div style="position: relative;">
|
||||||
:src="`${$imageBase}/news/full/${myNew.image}`"
|
<q-img
|
||||||
:ratio="16/4">
|
:src="`${$imageBase}/news/full/${myNew.image}`"
|
||||||
|
:ratio="3/1">
|
||||||
|
</q-img>
|
||||||
<q-btn
|
<q-btn
|
||||||
@click="editImage = true"
|
@click="showImgEditor = true"
|
||||||
:title="$t('edit')"
|
:title="$t('edit')"
|
||||||
icon="edit"
|
icon="edit"
|
||||||
round
|
round
|
||||||
color="accent"
|
color="accent"
|
||||||
class="absolute-bottom-right"
|
class="absolute-bottom-right"
|
||||||
style="bottom: .6em; right: .6em;"/>
|
style="bottom: .6em; right: .6em;"/>
|
||||||
</q-img>
|
</div>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<q-input
|
<q-input
|
||||||
v-model="myNew.title"
|
v-model="myNew.title"
|
||||||
|
@ -42,79 +44,11 @@
|
||||||
:toolbar="tools"/>
|
:toolbar="tools"/>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
<q-dialog v-model="editImage" persistent>
|
<image-editor
|
||||||
<q-card style="width: 25em;">
|
v-model="myNew.image"
|
||||||
<q-card-section>
|
collection="news"
|
||||||
<q-select
|
:show="showImgEditor"
|
||||||
v-model="image"
|
@response="showImgEditor = false"/>
|
||||||
:label="$t('image')"
|
|
||||||
:options="images"
|
|
||||||
@filter="filterImages"
|
|
||||||
option-value="name"
|
|
||||||
option-label="name"
|
|
||||||
new-value-mode="add"
|
|
||||||
@new-value="addImage"
|
|
||||||
emit-value
|
|
||||||
use-input
|
|
||||||
hide-selected
|
|
||||||
fill-input
|
|
||||||
input-debounce="0">
|
|
||||||
<template v-slot:option="scope">
|
|
||||||
<q-item
|
|
||||||
v-bind="scope.itemProps"
|
|
||||||
v-on="scope.itemEvents">
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-avatar>
|
|
||||||
<img :src="`${$imageBase}/${imageCollection}/200x200/${scope.opt.name}`">
|
|
||||||
</q-avatar>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>{{scope.opt.name}}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</template>
|
|
||||||
<template v-slot:prepend>
|
|
||||||
<q-avatar>
|
|
||||||
<img :src="`${$imageBase}/${imageCollection}/200x200/${image}`">
|
|
||||||
</q-avatar>
|
|
||||||
</template>
|
|
||||||
<template v-slot:after>
|
|
||||||
<q-btn
|
|
||||||
icon="add_a_photo"
|
|
||||||
@click="showUploader = !showUploader"
|
|
||||||
:title="$t('edit')"
|
|
||||||
:disable="!image"
|
|
||||||
:color="showUploader ? 'primary' : null"
|
|
||||||
flat
|
|
||||||
round
|
|
||||||
dense/>
|
|
||||||
</template>
|
|
||||||
</q-select>
|
|
||||||
<q-slide-transition>
|
|
||||||
<q-uploader
|
|
||||||
ref="uploader"
|
|
||||||
v-show="showUploader"
|
|
||||||
:url="`${$apiBase}/Images/upload`"
|
|
||||||
auto-upload
|
|
||||||
class="q-mt-md"
|
|
||||||
flat
|
|
||||||
bordered/>
|
|
||||||
</q-slide-transition>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-actions align="right">
|
|
||||||
<q-btn
|
|
||||||
:label="$t('cancel')"
|
|
||||||
v-close-popup
|
|
||||||
color="primary"
|
|
||||||
flat/>
|
|
||||||
<q-btn
|
|
||||||
:label="$t('accept')"
|
|
||||||
@click="onEditImage"
|
|
||||||
color="primary"
|
|
||||||
flat/>
|
|
||||||
</q-card-actions>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
||||||
<q-page-sticky position="bottom-right" :offset="[18, 18]">
|
<q-page-sticky position="bottom-right" :offset="[18, 18]">
|
||||||
<q-btn
|
<q-btn
|
||||||
:title="$t(id ? 'save' : 'create')"
|
:title="$t(id ? 'save' : 'create')"
|
||||||
|
@ -128,10 +62,14 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Page from 'components/Page'
|
import Page from 'components/Page'
|
||||||
|
import ImageEditor from 'components/ImageEditor'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'New',
|
name: 'New',
|
||||||
mixins: [Page],
|
mixins: [Page],
|
||||||
|
components: {
|
||||||
|
ImageEditor
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
myNew: { text: '' },
|
myNew: { text: '' },
|
||||||
|
@ -139,10 +77,7 @@ export default {
|
||||||
tags: null,
|
tags: null,
|
||||||
priorities: [1, 2, 3],
|
priorities: [1, 2, 3],
|
||||||
image: null,
|
image: null,
|
||||||
editImage: false,
|
showImgEditor: false,
|
||||||
showUploader: false,
|
|
||||||
imageCollection: 'news',
|
|
||||||
images: [],
|
|
||||||
tools: [
|
tools: [
|
||||||
['left', 'center', 'right', 'justify'],
|
['left', 'center', 'right', 'justify'],
|
||||||
['bold', 'italic', 'underline', 'removeFormat'],
|
['bold', 'italic', 'underline', 'removeFormat'],
|
||||||
|
@ -194,19 +129,6 @@ export default {
|
||||||
watch: {
|
watch: {
|
||||||
'this.$route.params.id': function () {
|
'this.$route.params.id': function () {
|
||||||
this.loadNew()
|
this.loadNew()
|
||||||
},
|
|
||||||
image () {
|
|
||||||
this.showUploader = false
|
|
||||||
},
|
|
||||||
showUploader () {
|
|
||||||
this.$refs.uploader.reset()
|
|
||||||
},
|
|
||||||
editImage () {
|
|
||||||
if (this.editImage) {
|
|
||||||
this.image = this.myNew.image
|
|
||||||
} else {
|
|
||||||
this.showUploader = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -229,7 +151,7 @@ export default {
|
||||||
this.$axios.get(`News/${this.id}`, { params: { filter } })
|
this.$axios.get(`News/${this.id}`, { params: { filter } })
|
||||||
.then(res => (this.myNew = res.data))
|
.then(res => (this.myNew = res.data))
|
||||||
} else {
|
} else {
|
||||||
this.new = {
|
this.myNew = {
|
||||||
userFk: this.$state.userId,
|
userFk: this.$state.userId,
|
||||||
tag: 'new',
|
tag: 'new',
|
||||||
priority: 1,
|
priority: 1,
|
||||||
|
@ -253,31 +175,10 @@ export default {
|
||||||
this.$q.notify({
|
this.$q.notify({
|
||||||
message: this.$t(message),
|
message: this.$t(message),
|
||||||
icon: 'check',
|
icon: 'check',
|
||||||
color: 'green-6'
|
color: 'green'
|
||||||
})
|
})
|
||||||
this.$router.go(-1)
|
this.$router.go(-1)
|
||||||
})
|
})
|
||||||
},
|
|
||||||
filterImages (val, update, abort) {
|
|
||||||
let filter = {
|
|
||||||
where: {
|
|
||||||
name: { like: `${val}%` },
|
|
||||||
collectionFk: this.imageCollection
|
|
||||||
},
|
|
||||||
limit: !val ? 50 : undefined
|
|
||||||
}
|
|
||||||
this.$axios.get(`Images`, { params: { filter } })
|
|
||||||
.then(res => {
|
|
||||||
update(() => (this.images = res.data))
|
|
||||||
})
|
|
||||||
.catch(() => abort())
|
|
||||||
},
|
|
||||||
onEditImage () {
|
|
||||||
this.myNew.image = this.image
|
|
||||||
this.editImage = false
|
|
||||||
},
|
|
||||||
addImage (inputValue, doneFn) {
|
|
||||||
console.log(inputValue)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
clickable
|
clickable
|
||||||
v-ripple>
|
v-ripple>
|
||||||
<q-item-section avatar>
|
<q-item-section avatar>
|
||||||
<q-avatar rounded>
|
<q-avatar @click.native="onImgClick($event, myNew.image)" rounded>
|
||||||
<img :src="`${$imageBase}/news/200x200/${myNew.image}`">
|
<img :src="`${$imageBase}/news/200x200/${myNew.image}`">
|
||||||
</q-avatar>
|
</q-avatar>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
|
@ -42,6 +42,10 @@
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
<full-image
|
||||||
|
ref="fullImage"
|
||||||
|
v-model="image"
|
||||||
|
collection="news"/>
|
||||||
<q-dialog v-model="confirm" persistent>
|
<q-dialog v-model="confirm" persistent>
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section class="row items-center">
|
<q-card-section class="row items-center">
|
||||||
|
@ -74,12 +78,17 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import Page from 'components/Page'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'News',
|
name: 'News',
|
||||||
|
mixins: [Page],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
news: null,
|
news: null,
|
||||||
confirm: false
|
confirm: false,
|
||||||
|
showImage: false,
|
||||||
|
image: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
|
@ -117,6 +126,11 @@ export default {
|
||||||
this.news.splice(index, 1)
|
this.news.splice(index, 1)
|
||||||
this.$q.notify(this.$t('removedSuccess'))
|
this.$q.notify(this.$t('removedSuccess'))
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
onImgClick (event, image) {
|
||||||
|
this.image = image
|
||||||
|
this.$refs.fullImage.show()
|
||||||
|
event.preventDefault()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,28 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="vn-pp row justify-center">
|
<div class="q-pa-sm">
|
||||||
<q-card class="vn-w-md">
|
<div
|
||||||
<q-list bordered>
|
class="row justify-center"
|
||||||
|
style="max-width: 60em; margin: 0 auto;">
|
||||||
<a
|
<a
|
||||||
v-for="link in links"
|
v-for="link in links"
|
||||||
:key="link.id"
|
:key="link.id"
|
||||||
:href="link.link">
|
:href="link.link"
|
||||||
<q-item clickable>
|
class="link q-ma-sm">
|
||||||
<q-item-section avatar>
|
<q-card
|
||||||
<q-avatar square>
|
class="clickable"
|
||||||
<img :src="`${$imageBase}/link/full/${link.image}`">
|
style="width: 11em;">
|
||||||
</q-avatar>
|
<q-card-section class="row justify-center">
|
||||||
</q-item-section>
|
<img
|
||||||
<q-item-section>
|
:src="`${$imageBase}/link/full/${link.image}`"
|
||||||
<q-item-label>{{link.name}}</q-item-label>
|
style="width: 5em;">
|
||||||
<q-item-label caption>{{link.description}}</q-item-label>
|
</q-card-section>
|
||||||
</q-item-section>
|
<q-card-section style="height: 7em; overflow: hidden;">
|
||||||
</q-item>
|
<div class="text-subtitle1 ellipsis">{{link.name}}</div>
|
||||||
<q-separator/>
|
<div class="text-caption">{{link.description}}</div>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
</a>
|
</a>
|
||||||
</q-list>
|
</div>
|
||||||
</q-card>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -29,6 +31,11 @@
|
||||||
display block
|
display block
|
||||||
text-decoration inherit
|
text-decoration inherit
|
||||||
color: inherit
|
color: inherit
|
||||||
|
.q-card.clickable
|
||||||
|
transition background 300ms ease-out
|
||||||
|
&:hover
|
||||||
|
background rgba(255, 255, 255, .2)
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -8,19 +8,23 @@
|
||||||
flat/>
|
flat/>
|
||||||
</toolbar>
|
</toolbar>
|
||||||
<q-card class="vn-w-md">
|
<q-card class="vn-w-md">
|
||||||
<q-card-section class="q-gutter-md">
|
<q-card-section>
|
||||||
<q-input
|
<q-input
|
||||||
v-model="user.name"
|
v-model="user.name"
|
||||||
:label="$t('userName')"
|
:label="$t('userName')"
|
||||||
readonly/>
|
readonly/>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section class="q-gutter-md">
|
<q-card-section>
|
||||||
<q-input v-model="user.email" :label="$t('email')" />
|
<q-input
|
||||||
|
v-model="user.email"
|
||||||
|
:label="$t('email')" />
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section class="q-gutter-md">
|
<q-card-section>
|
||||||
<q-input v-model="user.nickname" :label="$t('nickname')" />
|
<q-input
|
||||||
|
v-model="user.nickname"
|
||||||
|
:label="$t('nickname')" />
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section class="q-gutter-md">
|
<q-card-section>
|
||||||
<q-select
|
<q-select
|
||||||
v-model="user.lang"
|
v-model="user.lang"
|
||||||
:label="$t('language')"
|
:label="$t('language')"
|
||||||
|
@ -29,11 +33,6 @@
|
||||||
option-label="name"
|
option-label="name"
|
||||||
emit-value/>
|
emit-value/>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section class="q-gutter-md">
|
|
||||||
<q-checkbox
|
|
||||||
v-model="receiveInvoices"
|
|
||||||
:label="$t('receiveInvoiceByEmail')" />
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
</q-card>
|
||||||
<q-dialog v-model="changePassword" persistent>
|
<q-dialog v-model="changePassword" persistent>
|
||||||
<q-card style="width: 25em;">
|
<q-card style="width: 25em;">
|
||||||
|
@ -170,7 +169,7 @@ export default {
|
||||||
this.$q.notify({
|
this.$q.notify({
|
||||||
message: this.$t('passwordChanged'),
|
message: this.$t('passwordChanged'),
|
||||||
icon: 'check',
|
icon: 'check',
|
||||||
color: 'green-6'
|
color: 'green'
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onSave () {
|
onSave () {
|
||||||
|
@ -178,7 +177,7 @@ export default {
|
||||||
.then(res => (this.$q.notify({
|
.then(res => (this.$q.notify({
|
||||||
message: this.$t('dataSaved'),
|
message: this.$t('dataSaved'),
|
||||||
icon: 'check',
|
icon: 'check',
|
||||||
color: 'green-6'
|
color: 'green'
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,58 +4,47 @@
|
||||||
<q-input
|
<q-input
|
||||||
v-model="search"
|
v-model="search"
|
||||||
debounce="500"
|
debounce="500"
|
||||||
class="q-mr-sm"
|
|
||||||
dark
|
dark
|
||||||
dense
|
dense
|
||||||
standout>
|
standout>
|
||||||
<template v-slot:append>
|
<template v-slot:append>
|
||||||
<q-icon
|
<q-icon
|
||||||
v-if="search === ''"
|
v-if="search === ''"
|
||||||
name="search"
|
name="search"/>
|
||||||
/>
|
|
||||||
<q-icon
|
<q-icon
|
||||||
v-else
|
v-else
|
||||||
name="clear"
|
name="clear"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
@click="search = ''"
|
@click="search = ''"/>
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
</toolbar>
|
</toolbar>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<lb-scroll
|
||||||
v-if="!users || !users.length"
|
ref="scroll"
|
||||||
class="text-subtitle1 text-center text-grey-7 q-pa-md">
|
:request="request"
|
||||||
<span v-if="!users">
|
@data="data = arguments[0]">
|
||||||
{{$t('setSearchFilter')}}
|
<q-card class="vn-w-md">
|
||||||
</span>
|
<q-list bordered separator>
|
||||||
<span v-else>
|
<q-item
|
||||||
{{$t('noDataFound')}}
|
v-for="user in data"
|
||||||
</span>
|
:key="user.id"
|
||||||
</div>
|
:to="`/access-log/${user.id}`"
|
||||||
<q-card class="vn-w-md">
|
:title="$t('accessLog')"
|
||||||
<q-list bordered separator>
|
clickable>
|
||||||
<q-item
|
<q-item-section>
|
||||||
v-for="user in users"
|
<q-item-label>{{user.nickname}}</q-item-label>
|
||||||
:key="user.id"
|
<q-item-label caption>#{{user.id}}</q-item-label>
|
||||||
:to="`/access-log/${user.id}`"
|
<q-item-label caption>{{user.name}}</q-item-label>
|
||||||
:title="$t('accessLog')"
|
</q-item-section>
|
||||||
clickable>
|
</q-item>
|
||||||
<q-item-section>
|
</q-list>
|
||||||
<q-item-label>{{user.nickname}}</q-item-label>
|
</q-card>
|
||||||
<q-item-label caption>#{{user.id}}</q-item-label>
|
</lb-scroll>
|
||||||
<q-item-label caption>{{user.name}}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</q-list>
|
|
||||||
</q-card>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Page from 'components/Page'
|
import Page from 'components/Page'
|
||||||
|
|
||||||
|
@ -64,51 +53,47 @@ export default {
|
||||||
mixins: [Page],
|
mixins: [Page],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
users: null,
|
|
||||||
search: ''
|
search: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
|
||||||
this.onRouteChange(this.$route)
|
|
||||||
},
|
|
||||||
beforeRouteUpdate (to, from, next) {
|
beforeRouteUpdate (to, from, next) {
|
||||||
this.onRouteChange(to)
|
|
||||||
next()
|
next()
|
||||||
|
this.$refs.scroll.reload()
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
search (value) {
|
search (value) {
|
||||||
let location = {}
|
let query
|
||||||
if (value) location.query = { search: value }
|
if (value) query = { search: value }
|
||||||
this.$router.push(location)
|
this.$router.push({ query })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onRouteChange (route) {
|
request () {
|
||||||
this.users = null
|
this.search = this.$route.query.search || ''
|
||||||
this.search = route.query.search || ''
|
if (!this.search) return null
|
||||||
if (!this.search) return
|
|
||||||
|
|
||||||
let where = {}
|
let where = {}
|
||||||
|
|
||||||
if (/^[0-9]+$/.test(this.search)) {
|
if (/^[0-9]+$/.test(this.search)) {
|
||||||
where.id = this.search
|
where.id = this.search
|
||||||
} else {
|
} else {
|
||||||
|
let like = { like: `%${this.search}%` }
|
||||||
where = {
|
where = {
|
||||||
or: [
|
or: [
|
||||||
{ name: { like: `%${this.search}%` } },
|
{ name: like },
|
||||||
{ nickname: { like: `%${this.search}%` } }
|
{ nickname: like }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let filter = {
|
return {
|
||||||
fields: ['id', 'nickname', 'name', 'active'],
|
url: 'Accounts',
|
||||||
order: 'name',
|
filter: {
|
||||||
limit: 25,
|
fields: ['id', 'nickname', 'name', 'active'],
|
||||||
where
|
order: 'name',
|
||||||
|
where
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.$axios.get('Accounts', { params: { filter } })
|
|
||||||
.then(res => (this.users = res.data))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,39 +48,38 @@
|
||||||
</div>
|
</div>
|
||||||
</q-drawer>
|
</q-drawer>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<lb-scroll
|
||||||
v-if="visits && !visits.length"
|
ref="scroll"
|
||||||
class="text-subtitle1 text-center text-grey-7 q-pa-md">
|
:request="request"
|
||||||
{{$t('noDataFound')}}
|
@data="data = arguments[0]">
|
||||||
</div>
|
<q-card class="vn-w-md">
|
||||||
<q-card class="vn-w-md">
|
<q-list bordered separator>
|
||||||
<q-list bordered separator>
|
<q-item
|
||||||
<q-item
|
v-for="visit in data"
|
||||||
v-for="visit in visits"
|
:key="visit.browser">
|
||||||
:key="visit.browser">
|
<q-item-section>
|
||||||
<q-item-section>
|
<q-item-label>
|
||||||
<q-item-label>
|
{{visit.browser}} {{visit.minVersion}} - {{visit.maxVersion}}
|
||||||
{{visit.browser}} {{visit.minVersion}} - {{visit.maxVersion}}
|
</q-item-label>
|
||||||
</q-item-label>
|
<q-item-label caption>
|
||||||
<q-item-label caption>
|
{{$t('visitsCount', [visit.visits, visit.newVisits])}}
|
||||||
{{$t('visitsCount', [visit.visits, visit.newVisits])}}
|
</q-item-label>
|
||||||
</q-item-label>
|
<q-item-label caption>
|
||||||
<q-item-label caption>
|
{{visit.lastVisit | relTime}}
|
||||||
{{visit.lastVisit | relTime}}
|
</q-item-label>
|
||||||
</q-item-label>
|
</q-item-section>
|
||||||
</q-item-section>
|
</q-item>
|
||||||
</q-item>
|
</q-list>
|
||||||
</q-list>
|
</q-card>
|
||||||
</q-card>
|
</lb-scroll>
|
||||||
</div>
|
</div>
|
||||||
<q-page-sticky position="bottom-right" :offset="[18, 18]">
|
<q-page-sticky position="bottom-right" :offset="[18, 18]">
|
||||||
<q-btn
|
<q-btn
|
||||||
fab
|
fab
|
||||||
icon="refresh"
|
icon="refresh"
|
||||||
color="accent"
|
color="accent"
|
||||||
@click="refresh"
|
@click="$refs.scroll.reload()"
|
||||||
:title="$t('refresh')"
|
:title="$t('refresh')"/>
|
||||||
/>
|
|
||||||
</q-page-sticky>
|
</q-page-sticky>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -95,7 +94,6 @@ export default {
|
||||||
data () {
|
data () {
|
||||||
let today = date.formatDate(new Date(), 'YYYY/MM/DD')
|
let today = date.formatDate(new Date(), 'YYYY/MM/DD')
|
||||||
return {
|
return {
|
||||||
visits: null,
|
|
||||||
from: today,
|
from: today,
|
||||||
to: today,
|
to: today,
|
||||||
count: null,
|
count: null,
|
||||||
|
@ -106,18 +104,15 @@ export default {
|
||||||
created () {
|
created () {
|
||||||
this.$state.useRightDrawer = true
|
this.$state.useRightDrawer = true
|
||||||
},
|
},
|
||||||
mounted () {
|
|
||||||
this.refresh()
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
refresh () {
|
request () {
|
||||||
let params = {
|
return {
|
||||||
from: new Date(this.from),
|
url: 'Visits/listByBrowser',
|
||||||
to: new Date(this.to)
|
params: {
|
||||||
|
from: new Date(this.from),
|
||||||
|
to: new Date(this.to)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.visits = null
|
|
||||||
return this.$axios.get(`Visits/listByBrowser`, { params })
|
|
||||||
.then(res => (this.visits = res.data))
|
|
||||||
},
|
},
|
||||||
optionsFn (date) {
|
optionsFn (date) {
|
||||||
return date <= this.today
|
return date <= this.today
|
||||||
|
@ -125,15 +120,15 @@ export default {
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
from () {
|
from () {
|
||||||
this.refresh()
|
this.$refs.scroll.reload()
|
||||||
},
|
},
|
||||||
to () {
|
to () {
|
||||||
this.refresh()
|
this.$refs.scroll.reload()
|
||||||
},
|
},
|
||||||
visits () {
|
data () {
|
||||||
if (this.visits) {
|
if (this.data) {
|
||||||
this.count = this.visits.reduce((a, i) => a + i.visits, 0)
|
this.count = this.data.reduce((a, i) => a + i.visits, 0)
|
||||||
this.newCount = this.visits.reduce((a, i) => a + i.newVisits, 0)
|
this.newCount = this.data.reduce((a, i) => a + i.newVisits, 0)
|
||||||
} else {
|
} else {
|
||||||
this.count = null
|
this.count = null
|
||||||
this.newCount = null
|
this.newCount = null
|
||||||
|
|
Loading…
Reference in New Issue