chore: install prettier
This commit is contained in:
@@ -1,84 +1,105 @@
|
||||
<template>
|
||||
<div>
|
||||
<div><video ref="video" id="video" width="640" height="480" autoplay></video></div>
|
||||
<div>
|
||||
<video
|
||||
ref="video"
|
||||
id="video"
|
||||
width="640"
|
||||
height="480"
|
||||
autoplay
|
||||
></video>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<button class="button is-primary" id="snap" v-on:click="capture()">Chụp ảnh</button>
|
||||
<a class="ml-6" @click="switchView()">
|
||||
<SvgIcon v-bind="{name: 'camera_switch.svg', type: 'black', size: 40}"></SvgIcon>
|
||||
<button
|
||||
class="button is-primary"
|
||||
id="snap"
|
||||
v-on:click="capture()"
|
||||
>
|
||||
Chụp ảnh
|
||||
</button>
|
||||
<a
|
||||
class="ml-6"
|
||||
@click="switchView()"
|
||||
>
|
||||
<SvgIcon v-bind="{ name: 'camera_switch.svg', type: 'black', size: 40 }"></SvgIcon>
|
||||
</a>
|
||||
</div>
|
||||
<canvas v-show="false" ref="canvas" id="canvas" width="640" height="480"></canvas>
|
||||
<canvas
|
||||
v-show="false"
|
||||
ref="canvas"
|
||||
id="canvas"
|
||||
width="640"
|
||||
height="480"
|
||||
></canvas>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data: function() {
|
||||
return {
|
||||
video: {},
|
||||
canvas: {},
|
||||
current: 'front'
|
||||
export default {
|
||||
data: function () {
|
||||
return {
|
||||
video: {},
|
||||
canvas: {},
|
||||
current: "front",
|
||||
};
|
||||
},
|
||||
mounted: function () {
|
||||
this.openCamera();
|
||||
},
|
||||
beforeDestroy() {
|
||||
var vidTrack = this.video.srcObject.getVideoTracks();
|
||||
vidTrack.forEach((track) => {
|
||||
track.stop();
|
||||
track.enabled = false;
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
openCamera() {
|
||||
let f = this.current === "front" ? { facingMode: "user" } : { facingMode: { exact: "environment" } };
|
||||
this.video = this.$refs.video;
|
||||
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
||||
navigator.mediaDevices.getUserMedia({ video: f, audio: false }).then((stream) => {
|
||||
video.srcObject = stream;
|
||||
this.video.play();
|
||||
});
|
||||
}
|
||||
},
|
||||
mounted: function() {
|
||||
this.openCamera()
|
||||
capture() {
|
||||
this.canvas = this.$refs.canvas;
|
||||
let scale = 600 / this.video.videoWidth;
|
||||
let w = this.video.videoWidth * scale;
|
||||
let h = this.video.videoHeight * scale;
|
||||
this.canvas.width = w;
|
||||
this.canvas.height = h;
|
||||
var context = this.canvas.getContext("2d").drawImage(this.video, 0, 0, w, h);
|
||||
this.canvas.toBlob((blod) => this.saveAs(blod));
|
||||
},
|
||||
beforeDestroy() {
|
||||
var vidTrack = this.video.srcObject.getVideoTracks()
|
||||
vidTrack.forEach(track => {
|
||||
track.stop()
|
||||
track.enabled = false
|
||||
})
|
||||
async saveAs(blod) {
|
||||
var form = new FormData();
|
||||
let name = `${this.$id()}.png`;
|
||||
this.fileName = `${this.$dayjs(new Date()).format("YYYYMMDDhhmmss")}-${name}`;
|
||||
form.append("filename", this.fileName);
|
||||
form.append("name", name);
|
||||
form.append("file", blod);
|
||||
form.append("type", "image");
|
||||
form.append("size", 100);
|
||||
form.append("user", this.$store.state.login.id);
|
||||
let result = await this.$insertapi("upload", form);
|
||||
if (result === "error") return;
|
||||
let row = result.rows[0];
|
||||
const file = new File([blod], name, { type: "image/png" });
|
||||
row.source = { file: file };
|
||||
this.$emit("modalevent", { name: "selectimage", data: row });
|
||||
this.$emit("close");
|
||||
},
|
||||
methods: {
|
||||
openCamera() {
|
||||
let f = this.current==='front'? {facingMode: "user"} : {facingMode: {exact: "environment"}}
|
||||
this.video = this.$refs.video;
|
||||
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
||||
navigator.mediaDevices.getUserMedia({ video: f, audio: false }).then(stream => {
|
||||
video.srcObject = stream;
|
||||
this.video.play();
|
||||
});
|
||||
}
|
||||
},
|
||||
capture() {
|
||||
this.canvas = this.$refs.canvas;
|
||||
let scale = 600 / this.video.videoWidth;
|
||||
let w = this.video.videoWidth * scale;
|
||||
let h = this.video.videoHeight * scale;
|
||||
this.canvas.width = w;
|
||||
this.canvas.height = h;
|
||||
var context = this.canvas
|
||||
.getContext("2d")
|
||||
.drawImage(this.video,0,0,w,h);
|
||||
this.canvas.toBlob(blod=>this.saveAs(blod))
|
||||
},
|
||||
async saveAs(blod) {
|
||||
var form = new FormData();
|
||||
let name = `${this.$id()}.png`
|
||||
this.fileName = `${this.$dayjs(new Date()).format("YYYYMMDDhhmmss")}-${name}`
|
||||
form.append('filename', this.fileName)
|
||||
form.append('name', name)
|
||||
form.append('file', blod)
|
||||
form.append('type', 'image')
|
||||
form.append('size', 100)
|
||||
form.append('user', this.$store.state.login.id)
|
||||
let result = await this.$insertapi('upload', form)
|
||||
if(result==='error') return
|
||||
let row = result.rows[0]
|
||||
const file = new File([blod], name, {type: "image/png"})
|
||||
row.source = {file: file}
|
||||
this.$emit('modalevent', {name: 'selectimage', data: row})
|
||||
this.$emit('close')
|
||||
},
|
||||
switchView() {
|
||||
this.current = this.current==='front'? 'back' : 'front'
|
||||
var vidTrack = this.video.srcObject.getVideoTracks()
|
||||
vidTrack.forEach(track => {
|
||||
track.stop()
|
||||
track.enabled = false
|
||||
})
|
||||
this.openCamera()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
switchView() {
|
||||
this.current = this.current === "front" ? "back" : "front";
|
||||
var vidTrack = this.video.srcObject.getVideoTracks();
|
||||
vidTrack.forEach((track) => {
|
||||
track.stop();
|
||||
track.enabled = false;
|
||||
});
|
||||
this.openCamera();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
<template>
|
||||
<div class="card image-card m-0" style="
|
||||
outline: rgba(0, 0, 0, 0.2) solid 1px;
|
||||
box-shadow: none;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
border-radius: 6px;
|
||||
">
|
||||
<div class="is-clickable" :title="file.file__name">
|
||||
<div v-if="file.file__name.endsWith('.pdf')" class="p-2 is-flex is-justify-content-center is-align-items-center" style="min-height: 120px">
|
||||
<div
|
||||
class="card image-card m-0"
|
||||
style="outline: rgba(0, 0, 0, 0.2) solid 1px; box-shadow: none; height: 100%; overflow: hidden; border-radius: 6px"
|
||||
>
|
||||
<div
|
||||
class="is-clickable"
|
||||
:title="file.file__name"
|
||||
>
|
||||
<div
|
||||
v-if="file.file__name.endsWith('.pdf')"
|
||||
class="p-2 is-flex is-justify-content-center is-align-items-center"
|
||||
style="min-height: 120px"
|
||||
>
|
||||
<SvgIcon v-bind="{ name: 'pdf.svg', type: 'primary', size: 32 }" />
|
||||
</div>
|
||||
<div
|
||||
@@ -20,95 +24,113 @@
|
||||
:src="image"
|
||||
loading="lazy"
|
||||
alt="Hình ảnh"
|
||||
style="object-fit: contain;"
|
||||
style="object-fit: contain"
|
||||
/>
|
||||
</figure>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-content p-2">
|
||||
<p class="is-size-7" style="overflow: hidden; white-space: pre; text-overflow: ellipsis">{{ file.file__name }}</p>
|
||||
<div id="ignore" class="buttons are-small is-gap-0.5 is-justify-content-end">
|
||||
<button class="button is-white" @click="this.$copyToClipboard(`${$getpath()}static/files/${file.file__file}`)">
|
||||
<p
|
||||
class="is-size-7"
|
||||
style="overflow: hidden; white-space: pre; text-overflow: ellipsis"
|
||||
>
|
||||
{{ file.file__name }}
|
||||
</p>
|
||||
<div
|
||||
id="ignore"
|
||||
class="buttons are-small is-gap-0.5 is-justify-content-end"
|
||||
>
|
||||
<button
|
||||
class="button is-white"
|
||||
@click="this.$copyToClipboard(`${$getpath()}static/files/${file.file__file}`)"
|
||||
>
|
||||
<span class="icon">
|
||||
<SvgIcon v-bind="{ name: 'copy.svg', type: 'primary', size: 18 }" />
|
||||
</span>
|
||||
</button>
|
||||
<button class="button is-white" @click="download()">
|
||||
<button
|
||||
class="button is-white"
|
||||
@click="download()"
|
||||
>
|
||||
<span class="icon">
|
||||
<SvgIcon v-bind="{ name: 'download.svg', type: 'primary', size: 18 }" />
|
||||
</span>
|
||||
</button>
|
||||
<button v-if="$getEditRights()" class="button is-white" @click="askConfirm()">
|
||||
<button
|
||||
v-if="$getEditRights()"
|
||||
class="button is-white"
|
||||
@click="askConfirm()"
|
||||
>
|
||||
<span class="icon">
|
||||
<SvgIcon v-bind="{ name: 'bin1.svg', type: 'primary', size: 18 }" />
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<Modal
|
||||
v-if="showmodal"
|
||||
v-bind="showmodal"
|
||||
@confirm="remove()"
|
||||
@remove="remove"
|
||||
@close="showmodal=undefined"
|
||||
<Modal
|
||||
v-if="showmodal"
|
||||
v-bind="showmodal"
|
||||
@confirm="remove()"
|
||||
@remove="remove"
|
||||
@close="showmodal = undefined"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['row', 'api', 'pagename', 'file', 'image', 'show', 'extend'],
|
||||
props: ["row", "api", "pagename", "file", "image", "show", "extend"],
|
||||
data() {
|
||||
return {
|
||||
showmodal: undefined,
|
||||
display: this.show || []
|
||||
}
|
||||
display: this.show || [],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
if (!this.file || this.extend === false) return;
|
||||
this.showmodal = {
|
||||
title: this.file.file__name,
|
||||
component: 'media/ChipImage',
|
||||
title: this.file.file__name,
|
||||
component: "media/ChipImage",
|
||||
vbind: {
|
||||
extend: false,
|
||||
show: this.show,
|
||||
file: this.file,
|
||||
image: `${this.$getpath()}download/?name=${this.file.file__file || this.file.file}&type=file`
|
||||
}
|
||||
}
|
||||
image: `${this.$getpath()}download/?name=${this.file.file__file || this.file.file}&type=file`,
|
||||
},
|
||||
};
|
||||
},
|
||||
info() {
|
||||
if(!this.file) return;
|
||||
if (!this.file) return;
|
||||
this.showmodal = {
|
||||
vbind: {file: this.file},
|
||||
component: 'media/ImageAttr',
|
||||
title: 'Thông tin trích xuất từ hình ảnh',
|
||||
width: '35%',
|
||||
height: '50vh'
|
||||
}
|
||||
vbind: { file: this.file },
|
||||
component: "media/ImageAttr",
|
||||
title: "Thông tin trích xuất từ hình ảnh",
|
||||
width: "35%",
|
||||
height: "50vh",
|
||||
};
|
||||
},
|
||||
download() {
|
||||
let name = this.file? this.file.file__name || this.file.name : 'download'
|
||||
let path = `${this.$getpath()}download/?name=${this.file.file__file || this.file.file}&type=file`
|
||||
this.$download(path, name)
|
||||
let name = this.file ? this.file.file__name || this.file.name : "download";
|
||||
let path = `${this.$getpath()}download/?name=${this.file.file__file || this.file.file}&type=file`;
|
||||
this.$download(path, name);
|
||||
},
|
||||
askConfirm() {
|
||||
this.showmodal = {
|
||||
component: 'dialog/Confirm',
|
||||
title: 'Xác nhận',
|
||||
width: '500px',
|
||||
height: '100px',
|
||||
component: "dialog/Confirm",
|
||||
title: "Xác nhận",
|
||||
width: "500px",
|
||||
height: "100px",
|
||||
vbind: {
|
||||
content: 'Bạn có đồng ý xóa hình ảnh này không?',
|
||||
duration: 10
|
||||
}
|
||||
}
|
||||
content: "Bạn có đồng ý xóa hình ảnh này không?",
|
||||
duration: 10,
|
||||
},
|
||||
};
|
||||
},
|
||||
remove() {
|
||||
this.showmodal = undefined;
|
||||
this.$emit('modalevent', {name: 'remove'});
|
||||
this.$emit('remove');
|
||||
this.$emit("modalevent", { name: "remove" });
|
||||
this.$emit("remove");
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,91 +1,139 @@
|
||||
<template>
|
||||
<div class="tile is-ancestor py-5 px-3 mx-0" v-if="image">
|
||||
<div class="tile is-1"/>
|
||||
<div
|
||||
class="tile is-ancestor py-5 px-3 mx-0"
|
||||
v-if="image"
|
||||
>
|
||||
<div class="tile is-1" />
|
||||
<div class="tile is-7">
|
||||
<Cropper
|
||||
ref="cropper"
|
||||
:src="image"
|
||||
@change="onChange"
|
||||
:stencil-props="getRatio" />
|
||||
ref="cropper"
|
||||
:src="image"
|
||||
@change="onChange"
|
||||
:stencil-props="getRatio"
|
||||
/>
|
||||
</div>
|
||||
<div class="tile is-1"> </div>
|
||||
<div class="tile is-1"></div>
|
||||
<div class="tile">
|
||||
<div v-if="avatar!==true">
|
||||
<p class="mt-2 fs-16">Chọn tỷ lệ hoặc nhập chiều rộng và cao</p>
|
||||
<div v-if="avatar !== true">
|
||||
<p class="mt-2 fs-16">Chọn tỷ lệ hoặc nhập chiều rộng và cao</p>
|
||||
<div class="tags are-medium mt-2">
|
||||
<a :class="curRatio.k===v.k? 'tag is-primary' : 'tag'" v-for="(v,i) in ratios" :key="i" @click="curRatio=v">{{v.k}}</a>
|
||||
</div>
|
||||
<a
|
||||
:class="curRatio.k === v.k ? 'tag is-primary' : 'tag'"
|
||||
v-for="(v, i) in ratios"
|
||||
:key="i"
|
||||
@click="curRatio = v"
|
||||
>{{ v.k }}</a
|
||||
>
|
||||
</div>
|
||||
<div class="block mt-5">
|
||||
<b-radio v-model="radio"
|
||||
native-value="replace">
|
||||
<b-radio
|
||||
v-model="radio"
|
||||
native-value="replace"
|
||||
>
|
||||
Ghi đè
|
||||
</b-radio>
|
||||
<b-radio v-model="radio" class="ml-3"
|
||||
native-value="new">
|
||||
<b-radio
|
||||
v-model="radio"
|
||||
class="ml-3"
|
||||
native-value="new"
|
||||
>
|
||||
Tạo file mới
|
||||
</b-radio>
|
||||
</div>
|
||||
<p class="mt-4">
|
||||
<a class="button is-primary mr-4 mb-2" :class="loading? 'is-loading' : ''" @click="updateImage()">Lưu lại</a>
|
||||
</p>
|
||||
<p class="mt-2" v-if="coordinates">
|
||||
Hình ảnh cắt, {{'W: ' + coordinates.width + ', H: ' + coordinates.height + ', W/H: ' + coordinates.ratio}}
|
||||
</p>
|
||||
<p class="mt-4">
|
||||
<a
|
||||
class="button is-primary mr-4 mb-2"
|
||||
:class="loading ? 'is-loading' : ''"
|
||||
@click="updateImage()"
|
||||
>Lưu lại</a
|
||||
>
|
||||
</p>
|
||||
<p
|
||||
class="mt-2"
|
||||
v-if="coordinates"
|
||||
>
|
||||
Hình ảnh cắt,
|
||||
{{ "W: " + coordinates.width + ", H: " + coordinates.height + ", W/H: " + coordinates.ratio }}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="is-italic has-text-grey"
|
||||
v-else
|
||||
>
|
||||
* Di chuyển khung để chọn hình ảnh phù hợp
|
||||
</div>
|
||||
<div class="is-italic has-text-grey" v-else>* Di chuyển khung để chọn hình ảnh phù hợp</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { CircleStencil, Cropper } from 'vue-advanced-cropper'
|
||||
import 'vue-advanced-cropper/dist/style.css'
|
||||
export default {
|
||||
components: {
|
||||
Cropper,
|
||||
CircleStencil
|
||||
import { CircleStencil, Cropper } from "vue-advanced-cropper";
|
||||
import "vue-advanced-cropper/dist/style.css";
|
||||
export default {
|
||||
components: {
|
||||
Cropper,
|
||||
CircleStencil,
|
||||
},
|
||||
props: ["selected", "image", "avatar"],
|
||||
data() {
|
||||
return {
|
||||
coordinates: undefined,
|
||||
ratios: [
|
||||
{ k: "1/1" },
|
||||
{ k: "5/4" },
|
||||
{ k: "4/3" },
|
||||
{ k: "3/2" },
|
||||
{ k: "5/3" },
|
||||
{ k: "16/9" },
|
||||
{ k: "2/1" },
|
||||
{ k: "3/1" },
|
||||
{ k: "4/5" },
|
||||
{ k: "3/4" },
|
||||
{ k: "2/3" },
|
||||
{ k: "3/5" },
|
||||
{ k: "9/16" },
|
||||
{ k: "1/2" },
|
||||
{ k: "1/3" },
|
||||
],
|
||||
curRatio: { k: "1/1" },
|
||||
radio: this.avatar ? "new" : "replace",
|
||||
rectangle: true,
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
getRatio() {
|
||||
return { aspectRatio: this.$calc(this.curRatio.k) };
|
||||
},
|
||||
props: ['selected', 'image', 'avatar'],
|
||||
data() {
|
||||
return {
|
||||
coordinates: undefined,
|
||||
ratios: [{k: '1/1'}, {k: '5/4'}, {k:'4/3'}, {k: '3/2'}, {k: '5/3'}, {k:'16/9'}, {k: '2/1'}, {k: '3/1'}, {k: '4/5'}, {k: '3/4'}, {k: '2/3'}, {k: '3/5'}, {k: '9/16'}, {k: '1/2'}, {k: '1/3'}],
|
||||
curRatio: {k: '1/1'},
|
||||
radio: this.avatar? 'new' : 'replace',
|
||||
rectangle: true,
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange({ coordinates, canvas }) {
|
||||
this.coordinates = coordinates;
|
||||
this.coordinates.ratio = ((this.coordinates.width * 1.0) / this.coordinates.height).toFixed(2);
|
||||
},
|
||||
computed: {
|
||||
getRatio() {
|
||||
return {aspectRatio: this.$calc(this.curRatio.k)}
|
||||
}
|
||||
updateImage() {
|
||||
const { canvas } = this.$refs.cropper.getResult();
|
||||
if (canvas) canvas.toBlob((blod) => this.saveAs(blod));
|
||||
},
|
||||
methods: {
|
||||
onChange({ coordinates, canvas}) {
|
||||
this.coordinates = coordinates
|
||||
this.coordinates.ratio = (this.coordinates.width*1.00 / this.coordinates.height).toFixed(2)
|
||||
},
|
||||
updateImage() {
|
||||
const { canvas } = this.$refs.cropper.getResult()
|
||||
if (canvas) canvas.toBlob(blod=>this.saveAs(blod))
|
||||
},
|
||||
async saveAs(blod) {
|
||||
this.loading = true
|
||||
var form = new FormData();
|
||||
let name = this.selected.file.indexOf('-')>0? this.selected.file.substring(15, this.selected.file.length) : this.selected.file
|
||||
this.fileName = this.$dayjs(new Date()).format("YYYYMMDDhhmmss") + '-' + name
|
||||
if(this.radio==='replace') this.fileName = this.selected.file
|
||||
form.append('filename', this.fileName)
|
||||
form.append('name', name)
|
||||
form.append('file', blod)
|
||||
form.append('type', 'file')
|
||||
form.append('size', this.selected.size)
|
||||
form.append('user', this.$store.state.login.id)
|
||||
let result = await this.$insertapi('upload', form)
|
||||
this.loading = false
|
||||
if(result==='error') return
|
||||
this.$emit('modalevent', {name: 'image', data: result.rows[0]})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
async saveAs(blod) {
|
||||
this.loading = true;
|
||||
var form = new FormData();
|
||||
let name =
|
||||
this.selected.file.indexOf("-") > 0
|
||||
? this.selected.file.substring(15, this.selected.file.length)
|
||||
: this.selected.file;
|
||||
this.fileName = this.$dayjs(new Date()).format("YYYYMMDDhhmmss") + "-" + name;
|
||||
if (this.radio === "replace") this.fileName = this.selected.file;
|
||||
form.append("filename", this.fileName);
|
||||
form.append("name", name);
|
||||
form.append("file", blod);
|
||||
form.append("type", "file");
|
||||
form.append("size", this.selected.size);
|
||||
form.append("user", this.$store.state.login.id);
|
||||
let result = await this.$insertapi("upload", form);
|
||||
this.loading = false;
|
||||
if (result === "error") return;
|
||||
this.$emit("modalevent", { name: "image", data: result.rows[0] });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -7,8 +7,8 @@ const props = defineProps({
|
||||
image: Object,
|
||||
editImage: Function,
|
||||
downloadImage: Function,
|
||||
openDeleteImageConfirm: Function
|
||||
})
|
||||
openDeleteImageConfirm: Function,
|
||||
});
|
||||
|
||||
const url = $buildFileUrl(props.image.file__file);
|
||||
</script>
|
||||
@@ -54,6 +54,4 @@ const url = $buildFileUrl(props.image.file__file);
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<style>
|
||||
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,32 +1,65 @@
|
||||
<template>
|
||||
<div>
|
||||
<span class="tooltip">
|
||||
<span class="dot-twitter" @click="doClick()">{{ row[attr] || 0 }}</span>
|
||||
<span
|
||||
class="dot-twitter"
|
||||
@click="doClick()"
|
||||
>{{ row[attr] || 0 }}</span
|
||||
>
|
||||
<span class="tooliptext">Hồ sơ</span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['row', 'api', 'pagename', 'attr'],
|
||||
props: ["row", "api", "pagename", "attr"],
|
||||
methods: {
|
||||
async doClick() {
|
||||
let obj
|
||||
if(this.attr==='image_count') {
|
||||
let rs = await this.$getdata(this.api, {ref: this.row.id, file__type: 2})
|
||||
if(rs.length>0) {
|
||||
obj = {component: 'media/ImageShow', title: 'Hồ sơ', width: '80%', height: '70vh',
|
||||
vbind: {row: this.row, image: rs.map(v=>v.file__file), pagename: this.pagename, api: this.api}}
|
||||
let obj;
|
||||
if (this.attr === "image_count") {
|
||||
let rs = await this.$getdata(this.api, {
|
||||
ref: this.row.id,
|
||||
file__type: 2,
|
||||
});
|
||||
if (rs.length > 0) {
|
||||
obj = {
|
||||
component: "media/ImageShow",
|
||||
title: "Hồ sơ",
|
||||
width: "80%",
|
||||
height: "70vh",
|
||||
vbind: {
|
||||
row: this.row,
|
||||
image: rs.map((v) => v.file__file),
|
||||
pagename: this.pagename,
|
||||
api: this.api,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
obj = {component: 'media/ImageGallery', vbind: {row: this.row, pagename: this.pagename, api: this.api},
|
||||
width: '50%', height: '500px', title: 'Hình ảnh'}
|
||||
obj = {
|
||||
component: "media/ImageGallery",
|
||||
vbind: { row: this.row, pagename: this.pagename, api: this.api },
|
||||
width: "50%",
|
||||
height: "500px",
|
||||
title: "Hình ảnh",
|
||||
};
|
||||
}
|
||||
} else {
|
||||
let vbind = {row: this.row, api: this.api, setting: 'media-fields', filter: {ref: this.row.id, file__type: 1}}
|
||||
obj = {component: 'media/FileList', title: 'Hồ sơ', width: '50%', height: '300px', vbind: vbind}
|
||||
let vbind = {
|
||||
row: this.row,
|
||||
api: this.api,
|
||||
setting: "media-fields",
|
||||
filter: { ref: this.row.id, file__type: 1 },
|
||||
};
|
||||
obj = {
|
||||
component: "media/FileList",
|
||||
title: "Hồ sơ",
|
||||
width: "50%",
|
||||
height: "300px",
|
||||
vbind: vbind,
|
||||
};
|
||||
}
|
||||
this.$emit('open', {name: 'dataevent', data: {modal: obj}})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
this.$emit("open", { name: "dataevent", data: { modal: obj } });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,49 +1,88 @@
|
||||
<template>
|
||||
<div v-if="files">
|
||||
<div class="has-text-right" v-if="!hideopt && $getEditRights()">
|
||||
<FileUpload v-bind="{ type: ['file'], position }" @files="getFiles"></FileUpload>
|
||||
<div
|
||||
class="has-text-right"
|
||||
v-if="!hideopt && $getEditRights()"
|
||||
>
|
||||
<FileUpload
|
||||
v-bind="{ type: ['file'], position }"
|
||||
@files="getFiles"
|
||||
></FileUpload>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mt-3 has-text-grey fs-15" v-if="files.length===0 && info!==false">
|
||||
Chưa có tài liệu được tải lên
|
||||
</div>
|
||||
<DataView v-bind="vbind" v-else-if="vbind"></DataView>
|
||||
<div>
|
||||
<div
|
||||
class="mt-3 has-text-grey fs-15"
|
||||
v-if="files.length === 0 && info !== false"
|
||||
>
|
||||
Chưa có tài liệu được tải lên
|
||||
</div>
|
||||
<DataView
|
||||
v-bind="vbind"
|
||||
v-else-if="vbind"
|
||||
></DataView>
|
||||
</div>
|
||||
<Modal @close="showmodal=undefined" v-bind="showmodal" v-if="showmodal"></Modal>
|
||||
<Modal
|
||||
@close="showmodal = undefined"
|
||||
v-bind="showmodal"
|
||||
v-if="showmodal"
|
||||
></Modal>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['row', 'pagename', 'api', 'hideopt', 'setting', 'info', 'position'],
|
||||
props: ["row", "pagename", "api", "hideopt", "setting", "info", "position"],
|
||||
data() {
|
||||
return {
|
||||
showmodal: undefined,
|
||||
files: undefined,
|
||||
vbind: undefined
|
||||
}
|
||||
vbind: undefined,
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
this.files = await this.$getdata(this.api, {ref: this.row.id, file__type: 1})
|
||||
if(this.files.length>0) this.vbind = {api: this.api, setting: this.setting || 'media-fields', data: this.files}
|
||||
this.files = await this.$getdata(this.api, {
|
||||
ref: this.row.id,
|
||||
file__type: 1,
|
||||
});
|
||||
if (this.files.length > 0)
|
||||
this.vbind = {
|
||||
api: this.api,
|
||||
setting: this.setting || "media-fields",
|
||||
data: this.files,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
this.showmodal = {component: 'media/Imagebox', width: '70%', title: 'Chọn hình ảnh', height: '500px'}
|
||||
this.showmodal = {
|
||||
component: "media/Imagebox",
|
||||
width: "70%",
|
||||
title: "Chọn hình ảnh",
|
||||
height: "500px",
|
||||
};
|
||||
},
|
||||
async remove(v, i) {
|
||||
this.$delete(this.files, i)
|
||||
await this.$deleteapi(this.api, v.id)
|
||||
this.$delete(this.files, i);
|
||||
await this.$deleteapi(this.api, v.id);
|
||||
},
|
||||
async getFiles(files) {
|
||||
let arr = files.map(v=>{return {ref: this.row.id, file: v.id}})
|
||||
let found = this.$findapi(this.api)
|
||||
let rs = await this.$insertapi(this.api, arr, found.params.values)
|
||||
if(rs!=='error') {
|
||||
this.files = this.files.concat(rs)
|
||||
this.vbind = undefined
|
||||
setTimeout(()=>this.vbind = {api: this.api, setting: 'media-fields', data: this.files}, 100)
|
||||
let arr = files.map((v) => {
|
||||
return { ref: this.row.id, file: v.id };
|
||||
});
|
||||
let found = this.$findapi(this.api);
|
||||
let rs = await this.$insertapi(this.api, arr, found.params.values);
|
||||
if (rs !== "error") {
|
||||
this.files = this.files.concat(rs);
|
||||
this.vbind = undefined;
|
||||
setTimeout(
|
||||
() =>
|
||||
(this.vbind = {
|
||||
api: this.api,
|
||||
setting: "media-fields",
|
||||
data: this.files,
|
||||
}),
|
||||
100,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -3,35 +3,50 @@
|
||||
<div class="field">
|
||||
<label class="label">Tên<span class="has-text-danger"> * </span> </label>
|
||||
<p class="control">
|
||||
<input class="input" type="text" placeholder="Nhập số" v-model="record.name">
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder="Nhập số"
|
||||
v-model="record.name"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
<div class="field mt-5">
|
||||
<label class="label">Ghi chú<span class="has-text-danger"> * </span> </label>
|
||||
<p class="control">
|
||||
<textarea class="textarea" placeholder="" rows="3" v-model="record.caption"></textarea>
|
||||
<textarea
|
||||
class="textarea"
|
||||
placeholder=""
|
||||
rows="3"
|
||||
v-model="record.caption"
|
||||
></textarea>
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<button class="button is-primary" @click="save()">Lưu lại</button>
|
||||
<button
|
||||
class="button is-primary"
|
||||
@click="save()"
|
||||
>
|
||||
Lưu lại
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['row', 'pagename'],
|
||||
props: ["row", "pagename"],
|
||||
data() {
|
||||
return {
|
||||
record: undefined
|
||||
}
|
||||
record: undefined,
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
this.record = await this.$getdata('file', {id: this.row.file || this.row.id}, undefined, true)
|
||||
this.record = await this.$getdata("file", { id: this.row.file || this.row.id }, undefined, true);
|
||||
},
|
||||
methods: {
|
||||
async save() {
|
||||
let rs = await this.$updateapi('file', this.record)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
let rs = await this.$updateapi("file", this.record);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,47 +1,71 @@
|
||||
<template>
|
||||
<div>
|
||||
<FileUpload :type="['file']" @files="getFiles"></FileUpload>
|
||||
<DataView v-if="vbind" v-bind="vbind"></DataView>
|
||||
<FileUpload
|
||||
:type="['file']"
|
||||
@files="getFiles"
|
||||
></FileUpload>
|
||||
<DataView
|
||||
v-if="vbind"
|
||||
v-bind="vbind"
|
||||
></DataView>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['row', 'pagename', 'api', 'setting'],
|
||||
props: ["row", "pagename", "api", "setting"],
|
||||
data() {
|
||||
return {
|
||||
vbind: undefined,
|
||||
files: undefined,
|
||||
pagename1: this.$findpage()
|
||||
}
|
||||
pagename1: this.$findpage(),
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
this.pagedata = this.$getpage()
|
||||
this.files = await this.$getdata(this.api, {ref: this.row.id, file__type: 1})
|
||||
this.vbind = {pagename: this.pagename1, api: this.api, setting: this.setting, data: this.$copy(this.files)}
|
||||
this.pagedata = this.$getpage();
|
||||
this.files = await this.$getdata(this.api, {
|
||||
ref: this.row.id,
|
||||
file__type: 1,
|
||||
});
|
||||
this.vbind = {
|
||||
pagename: this.pagename1,
|
||||
api: this.api,
|
||||
setting: this.setting,
|
||||
data: this.$copy(this.files),
|
||||
};
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$clearpage(this.pagename1)
|
||||
this.$clearpage(this.pagename1);
|
||||
},
|
||||
computed: {
|
||||
pagedata: {
|
||||
get: function() {return this.$store.state[this.pagename1]},
|
||||
set: function(val) {this.$store.commit('updateStore', {name: this.pagename1, data: val})}
|
||||
}
|
||||
get: function () {
|
||||
return this.$store.state[this.pagename1];
|
||||
},
|
||||
set: function (val) {
|
||||
this.$store.commit("updateStore", { name: this.pagename1, data: val });
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async getFiles(files) {
|
||||
let arr = files.map(v=>{return {ref: this.row.id, file: v.id}})
|
||||
let found = this.$findapi(this.api)
|
||||
let rs = await this.$insertapi(this.api, arr, found.params.values)
|
||||
if(rs==='error') return
|
||||
this.files = this.files.concat(rs)
|
||||
this.$store.commit('updateState', {name: this.pagename1, key: 'update', data: {data: this.$copy(this.files)}})
|
||||
if(this.pagename) {
|
||||
let vapi = this.api.replace('file', '')
|
||||
let ele = await this.$getdata(vapi, {id: this.row.id}, undefined, true)
|
||||
this.$updatepage(this.pagename, ele)
|
||||
let arr = files.map((v) => {
|
||||
return { ref: this.row.id, file: v.id };
|
||||
});
|
||||
let found = this.$findapi(this.api);
|
||||
let rs = await this.$insertapi(this.api, arr, found.params.values);
|
||||
if (rs === "error") return;
|
||||
this.files = this.files.concat(rs);
|
||||
this.$store.commit("updateState", {
|
||||
name: this.pagename1,
|
||||
key: "update",
|
||||
data: { data: this.$copy(this.files) },
|
||||
});
|
||||
if (this.pagename) {
|
||||
let vapi = this.api.replace("file", "");
|
||||
let ele = await this.$getdata(vapi, { id: this.row.id }, undefined, true);
|
||||
this.$updatepage(this.pagename, ele);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,42 +1,69 @@
|
||||
<template>
|
||||
<p class="py-1 border-bottom" v-for="(v,i) in vfiles">
|
||||
<a class="mr-4" @click="open(v)">{{ v.name }}</a>
|
||||
<a class="mr-4" @click="download(v, i)">
|
||||
<SvgIcon v-bind="{name: 'download1.svg', type: 'dark', size: 16}"></SvgIcon>
|
||||
<p
|
||||
class="py-1 border-bottom"
|
||||
v-for="(v, i) in vfiles"
|
||||
>
|
||||
<a
|
||||
class="mr-4"
|
||||
@click="open(v)"
|
||||
>{{ v.name }}</a
|
||||
>
|
||||
<a
|
||||
class="mr-4"
|
||||
@click="download(v, i)"
|
||||
>
|
||||
<SvgIcon v-bind="{ name: 'download1.svg', type: 'dark', size: 16 }"></SvgIcon>
|
||||
</a>
|
||||
<a @click="remove(v, i)" v-if="show? show.delete : false">
|
||||
<SvgIcon v-bind="{name: 'bin1.svg', type: 'dark', size: 16}"></SvgIcon>
|
||||
<a
|
||||
@click="remove(v, i)"
|
||||
v-if="show ? show.delete : false"
|
||||
>
|
||||
<SvgIcon v-bind="{ name: 'bin1.svg', type: 'dark', size: 16 }"></SvgIcon>
|
||||
</a>
|
||||
</p>
|
||||
<Modal @close="showmodal=undefined" v-bind="showmodal" v-if="showmodal"></Modal>
|
||||
<Modal
|
||||
@close="showmodal = undefined"
|
||||
v-bind="showmodal"
|
||||
v-if="showmodal"
|
||||
></Modal>
|
||||
</template>
|
||||
<script setup>
|
||||
const { $copy, $getpath, $download } = useNuxtApp()
|
||||
const emit = defineEmits(['remove', 'close'])
|
||||
var props = defineProps({
|
||||
files: Object,
|
||||
show: Object
|
||||
})
|
||||
var showmodal = ref()
|
||||
var vfiles = ref($copy(props.files))
|
||||
function remove(v, i) {
|
||||
vfiles.value.splice(i, 1)
|
||||
emit('remove', {v: v, i: i})
|
||||
emit('modalevent', {name: 'removefile', data: {v: v, i: i}})
|
||||
if(vfiles.value.length===0) emit('close')
|
||||
}
|
||||
const { $copy, $getpath, $download } = useNuxtApp();
|
||||
const emit = defineEmits(["remove", "close"]);
|
||||
var props = defineProps({
|
||||
files: Object,
|
||||
show: Object,
|
||||
});
|
||||
var showmodal = ref();
|
||||
var vfiles = ref($copy(props.files));
|
||||
function remove(v, i) {
|
||||
vfiles.value.splice(i, 1);
|
||||
emit("remove", { v: v, i: i });
|
||||
emit("modalevent", { name: "removefile", data: { v: v, i: i } });
|
||||
if (vfiles.value.length === 0) emit("close");
|
||||
}
|
||||
function open(v) {
|
||||
if(v.name.indexOf('.png')>=0 || v.name.indexOf('.jpg')>=0 || v.name.indexOf('.jpeg')>=0) {
|
||||
showmodal.value = {title: v.file__file || v.file, component: 'media/ChipImage',
|
||||
vbind: {extend: false, file: v, image: `${$getpath()}static/files/${v.file__file || v.file}`}}
|
||||
return
|
||||
if (v.name.indexOf(".png") >= 0 || v.name.indexOf(".jpg") >= 0 || v.name.indexOf(".jpeg") >= 0) {
|
||||
showmodal.value = {
|
||||
title: v.file__file || v.file,
|
||||
component: "media/ChipImage",
|
||||
vbind: {
|
||||
extend: false,
|
||||
file: v,
|
||||
image: `${$getpath()}static/files/${v.file__file || v.file}`,
|
||||
},
|
||||
};
|
||||
return;
|
||||
}
|
||||
window.open(`${$getpath()}static/files/${v.file__file || v.file}`)
|
||||
window.open(`${$getpath()}static/files/${v.file__file || v.file}`);
|
||||
}
|
||||
function download(v) {
|
||||
window.open(`${$getpath()}static/files/${v.file}`, v.name)
|
||||
window.open(`${$getpath()}static/files/${v.file}`, v.name);
|
||||
}
|
||||
watch(() => props.files, (newVal, oldVal) => {
|
||||
vfiles.value = props.files
|
||||
})
|
||||
</script>
|
||||
watch(
|
||||
() => props.files,
|
||||
(newVal, oldVal) => {
|
||||
vfiles.value = props.files;
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="file is-primary" id="ignore" v-if="position === 'left'">
|
||||
<div
|
||||
class="file is-primary"
|
||||
id="ignore"
|
||||
v-if="position === 'left'"
|
||||
>
|
||||
<label class="file-label">
|
||||
<input
|
||||
class="file-input"
|
||||
@@ -12,15 +16,17 @@
|
||||
/>
|
||||
<span class="file-cta px-2">
|
||||
<span class="icon-text is-clickable">
|
||||
<SvgIcon
|
||||
v-bind="{ name: 'attach-file.svg', type: 'white', size: 22 }"
|
||||
></SvgIcon>
|
||||
<SvgIcon v-bind="{ name: 'attach-file.svg', type: 'white', size: 22 }"></SvgIcon>
|
||||
<!--<span class="has-text-white">File</span>-->
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="field is-grouped is-grouped-right" id="ignore" v-else>
|
||||
<div
|
||||
class="field is-grouped is-grouped-right"
|
||||
id="ignore"
|
||||
v-else
|
||||
>
|
||||
<div class="control">
|
||||
<div class="file is-primary">
|
||||
<label class="file-label">
|
||||
@@ -34,9 +40,7 @@
|
||||
/>
|
||||
<span class="file-cta is-primary px-1">
|
||||
<span class="icon-text is-clickable">
|
||||
<SvgIcon
|
||||
v-bind="{ name: 'attach-file.svg', type: 'white', size: 22 }"
|
||||
></SvgIcon>
|
||||
<SvgIcon v-bind="{ name: 'attach-file.svg', type: 'white', size: 22 }"></SvgIcon>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
@@ -75,10 +79,10 @@ export default {
|
||||
const imageFormat = ["png", "jpg", "jpeg", "bmp", "gif", "svg", "webp"];
|
||||
const videoFormat = ["wmv", "avi", "mp4", "flv", "mov", "mpg", "amv", "rm"];
|
||||
|
||||
if (ext === 'pdf') return 'pdf';
|
||||
if (imageFormat.includes(ext)) return 'image';
|
||||
if (videoFormat.includes(ext)) return 'video';
|
||||
return 'file';
|
||||
if (ext === "pdf") return "pdf";
|
||||
if (imageFormat.includes(ext)) return "image";
|
||||
if (videoFormat.includes(ext)) return "video";
|
||||
return "file";
|
||||
},
|
||||
doChange() {
|
||||
this.dataFiles = [];
|
||||
|
||||
@@ -1,41 +1,71 @@
|
||||
<template>
|
||||
<div>
|
||||
<table class="table" v-if="attrs">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="170px">Thông tin</th>
|
||||
<th>Diễn giải</th>
|
||||
<table
|
||||
class="table"
|
||||
v-if="attrs"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="170px">Thông tin</th>
|
||||
<th>Diễn giải</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="v in attrs">
|
||||
<td>{{ v.name }}</td>
|
||||
<td>
|
||||
<span class="icon-text">
|
||||
<span>{{ v.value }}</span>
|
||||
<span class="ml-5" v-if="!$empty(v.match)">
|
||||
<SvgIcon v-bind="{name: v.match? 'check2.svg' : 'error.svg', type: v.match? 'blue' : 'danger', size: 26}"></SvgIcon>
|
||||
</span>
|
||||
<span class="ml-5" v-if="!$empty(v.auth)">
|
||||
<SvgIcon v-bind="{name: v.auth? 'check2.svg' : 'error.svg', type: v.auth? 'blue' : 'danger', size: 26}"></SvgIcon>
|
||||
</span>
|
||||
<span class="ml-5" v-if="!$empty(v.expiry)">
|
||||
<SvgIcon v-bind="{name: v.expiry? 'error.svg' : 'check2.svg', type: v.expiry? 'danger' : 'blue', size: 26}"></SvgIcon>
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="v in attrs">
|
||||
<td>{{ v.name }}</td>
|
||||
<td>
|
||||
<span class="icon-text">
|
||||
<span>{{ v.value }}</span>
|
||||
<span
|
||||
class="ml-5"
|
||||
v-if="!$empty(v.match)"
|
||||
>
|
||||
<SvgIcon
|
||||
v-bind="{
|
||||
name: v.match ? 'check2.svg' : 'error.svg',
|
||||
type: v.match ? 'blue' : 'danger',
|
||||
size: 26,
|
||||
}"
|
||||
></SvgIcon>
|
||||
</span>
|
||||
<span
|
||||
class="ml-5"
|
||||
v-if="!$empty(v.auth)"
|
||||
>
|
||||
<SvgIcon
|
||||
v-bind="{
|
||||
name: v.auth ? 'check2.svg' : 'error.svg',
|
||||
type: v.auth ? 'blue' : 'danger',
|
||||
size: 26,
|
||||
}"
|
||||
></SvgIcon>
|
||||
</span>
|
||||
<span
|
||||
class="ml-5"
|
||||
v-if="!$empty(v.expiry)"
|
||||
>
|
||||
<SvgIcon
|
||||
v-bind="{
|
||||
name: v.expiry ? 'error.svg' : 'check2.svg',
|
||||
type: v.expiry ? 'danger' : 'blue',
|
||||
size: 26,
|
||||
}"
|
||||
></SvgIcon>
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['file'],
|
||||
props: ["file"],
|
||||
data() {
|
||||
return {
|
||||
attrs: this.file? this.file.file__verified_info || this.file.verified_info: undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
attrs: this.file ? this.file.file__verified_info || this.file.verified_info : undefined,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import FileActions from '@/components/media/FileActions.vue';
|
||||
import FileActions from "@/components/media/FileActions.vue";
|
||||
|
||||
const props = defineProps({
|
||||
image: Object,
|
||||
@@ -8,15 +8,9 @@ const props = defineProps({
|
||||
loadImages: Function,
|
||||
deleteImage: Function,
|
||||
viewImage: Function,
|
||||
})
|
||||
});
|
||||
|
||||
const {
|
||||
$buildFileUrl,
|
||||
$formatFileSize,
|
||||
$getpath,
|
||||
$patchapi,
|
||||
$snackbar,
|
||||
} = useNuxtApp();
|
||||
const { $buildFileUrl, $formatFileSize, $getpath, $patchapi, $snackbar } = useNuxtApp();
|
||||
|
||||
const url = $buildFileUrl(props.image.file__file);
|
||||
|
||||
@@ -51,7 +45,7 @@ async function saveEditImage() {
|
||||
console.error("Error updating image", error);
|
||||
$snackbar("Cập nhật ảnh thất bại", "Lỗi", "Error");
|
||||
}
|
||||
|
||||
|
||||
resetEditRefs();
|
||||
}
|
||||
|
||||
@@ -77,20 +71,19 @@ async function openDeleteImageConfirm(image) {
|
||||
if (confirmed) {
|
||||
await props.deleteImage(image);
|
||||
}
|
||||
|
||||
|
||||
pendingDeleteImage.value = null;
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div v-if="layoutMode === 'grid'" class="column is-one-third is-one-quarter-widescreen p-2">
|
||||
<div
|
||||
class="card image-card"
|
||||
style="
|
||||
outline: 1px solid hsl(0 0% 0% / 0.2);
|
||||
box-shadow: none;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
">
|
||||
<div
|
||||
v-if="layoutMode === 'grid'"
|
||||
class="column is-one-third is-one-quarter-widescreen p-2"
|
||||
>
|
||||
<div
|
||||
class="card image-card"
|
||||
style="outline: 1px solid hsl(0 0% 0% / 0.2); box-shadow: none; height: 100%; overflow: hidden"
|
||||
>
|
||||
<!-- Edit mode -->
|
||||
<div
|
||||
v-if="editingImage?.id === image.id"
|
||||
@@ -103,8 +96,7 @@ async function openDeleteImageConfirm(image) {
|
||||
class="input is-small"
|
||||
type="text"
|
||||
v-model="editingImageName"
|
||||
>
|
||||
</input>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
@@ -120,12 +112,12 @@ async function openDeleteImageConfirm(image) {
|
||||
<div class="field">
|
||||
<label class="label fs-14">Hashtag</label>
|
||||
<div class="control">
|
||||
<input
|
||||
<input
|
||||
class="input is-small"
|
||||
type="text"
|
||||
v-model="editingImageHashtag"
|
||||
>
|
||||
</input>
|
||||
/>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="buttons is-small mt-2">
|
||||
@@ -155,7 +147,7 @@ async function openDeleteImageConfirm(image) {
|
||||
:src="url"
|
||||
loading="lazy"
|
||||
:alt="image.file__caption || image.file__name || 'Hình ảnh'"
|
||||
style="object-fit: contain;"
|
||||
style="object-fit: contain"
|
||||
/>
|
||||
</figure>
|
||||
</div>
|
||||
@@ -172,13 +164,15 @@ async function openDeleteImageConfirm(image) {
|
||||
{{ image.file__caption }}
|
||||
</p>
|
||||
</div>
|
||||
<FileActions v-bind="{
|
||||
className: 'is-right mt-2',
|
||||
image,
|
||||
editImage,
|
||||
downloadImage,
|
||||
openDeleteImageConfirm
|
||||
}" />
|
||||
<FileActions
|
||||
v-bind="{
|
||||
className: 'is-right mt-2',
|
||||
image,
|
||||
editImage,
|
||||
downloadImage,
|
||||
openDeleteImageConfirm,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -260,36 +254,28 @@ async function openDeleteImageConfirm(image) {
|
||||
</p>
|
||||
</td>
|
||||
<td>
|
||||
<span class="is-size-7">{{
|
||||
image.file__caption
|
||||
}}</span>
|
||||
<span class="is-size-7">{{ image.file__caption }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="is-size-7">{{
|
||||
image.file__hashtag
|
||||
}}</span>
|
||||
<span class="is-size-7">{{ image.file__hashtag }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<p class="is-size-7 has-text-weight-semibold">
|
||||
{{ image.file__user__fullname }}
|
||||
</p>
|
||||
<p class="is-size-7 has-text-grey">
|
||||
{{
|
||||
image.create_time
|
||||
? new Date(image.create_time).toLocaleString(
|
||||
"vi-VN"
|
||||
)
|
||||
: "-"
|
||||
}}
|
||||
{{ image.create_time ? new Date(image.create_time).toLocaleString("vi-VN") : "-" }}
|
||||
</p>
|
||||
</td>
|
||||
<td>
|
||||
<FileActions v-bind="{
|
||||
image,
|
||||
editImage,
|
||||
downloadImage,
|
||||
openDeleteImageConfirm
|
||||
}" />
|
||||
<FileActions
|
||||
v-bind="{
|
||||
image,
|
||||
editImage,
|
||||
downloadImage,
|
||||
openDeleteImageConfirm,
|
||||
}"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
<template>
|
||||
<div v-if="files" class="is-fullwidth">
|
||||
<div class="has-text-right mb-4" v-if="!hideopt">
|
||||
<FileUpload :type="['image', 'pdf']" @files="getFiles"></FileUpload>
|
||||
<div
|
||||
v-if="files"
|
||||
class="is-fullwidth"
|
||||
>
|
||||
<div
|
||||
class="has-text-right mb-4"
|
||||
v-if="!hideopt"
|
||||
>
|
||||
<FileUpload
|
||||
:type="['image', 'pdf']"
|
||||
@files="getFiles"
|
||||
></FileUpload>
|
||||
</div>
|
||||
<div v-if="files.length > 0" class="is-flex is-gap-2 is-flex-wrap-wrap">
|
||||
<div
|
||||
v-if="files.length > 0"
|
||||
class="is-flex is-gap-2 is-flex-wrap-wrap"
|
||||
>
|
||||
<ChipImage
|
||||
v-for="(file, i) in files"
|
||||
style="width: 160px"
|
||||
@@ -18,10 +30,17 @@
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-3 has-text-grey" v-if="files.length === 0">
|
||||
<div
|
||||
class="mt-3 has-text-grey"
|
||||
v-if="files.length === 0"
|
||||
>
|
||||
{{ this.isVietnamese ? "Chưa có hình ảnh được tải lên" : "No images uploaded" }}
|
||||
</div>
|
||||
<Modal @close="showmodal = undefined" v-bind="showmodal" v-if="showmodal"></Modal>
|
||||
<Modal
|
||||
@close="showmodal = undefined"
|
||||
v-bind="showmodal"
|
||||
v-if="showmodal"
|
||||
></Modal>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
<div class="columns mx-0 mb-0 pb-0">
|
||||
<div class="column is-6">
|
||||
<div class="tags are-medium">
|
||||
<a v-for="v in hashtag" @click="refreshShow(v)">
|
||||
<span :class="`tag ${v.file__hashtag===current? 'is-primary' : ''}`">
|
||||
<a
|
||||
v-for="v in hashtag"
|
||||
@click="refreshShow(v)"
|
||||
>
|
||||
<span :class="`tag ${v.file__hashtag === current ? 'is-primary' : ''}`">
|
||||
{{ v.file__hashtag }}
|
||||
{{ `(${v.count})` }}
|
||||
</span>
|
||||
@@ -11,31 +14,37 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-6">
|
||||
<div class="is-flex is-align-items-center is-justify-content-flex-end">
|
||||
<div class="buttons has-addons are-small my-0">
|
||||
<button
|
||||
v-for="layout in Object.values(layoutModes)"
|
||||
:key="layout"
|
||||
:class="[
|
||||
'button px-5',
|
||||
{ 'is-primary': layout === layoutMode },
|
||||
]"
|
||||
@click="layoutMode = layout"
|
||||
<div class="is-flex is-align-items-center is-justify-content-flex-end">
|
||||
<div class="buttons has-addons are-small my-0">
|
||||
<button
|
||||
v-for="layout in Object.values(layoutModes)"
|
||||
:key="layout"
|
||||
:class="['button px-5', { 'is-primary': layout === layoutMode }]"
|
||||
@click="layoutMode = layout"
|
||||
>
|
||||
<SvgIcon
|
||||
v-bind="{
|
||||
name: `${layout}.svg`,
|
||||
type: layout === layoutMode ? 'white' : 'primary',
|
||||
size: 20,
|
||||
}"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-if="!dealer && $getEditRights('edit', { code: 'product', category: 'topmenu' })"
|
||||
class="ml-6 is-flex is-align-items-center"
|
||||
>
|
||||
<SvgIcon v-bind="{
|
||||
name: `${layout}.svg`,
|
||||
type: layout === layoutMode ? 'white' : 'primary',
|
||||
size: 20
|
||||
}" />
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="!dealer && $getEditRights('edit', { code: 'product', category: 'topmenu' })" class="ml-6 is-flex is-align-items-center">
|
||||
<div class="mr-5">
|
||||
<label class="checkbox mr-2">
|
||||
<input type="checkbox" v-model="convertToWebp" />
|
||||
<span class="ml-2">Convert to WebP</span>
|
||||
</label>
|
||||
<input v-if="convertToWebp"
|
||||
<div class="mr-5">
|
||||
<label class="checkbox mr-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="convertToWebp"
|
||||
/>
|
||||
<span class="ml-2">Convert to WebP</span>
|
||||
</label>
|
||||
<input
|
||||
v-if="convertToWebp"
|
||||
class="input is-small"
|
||||
type="number"
|
||||
v-model.number="webpQuality"
|
||||
@@ -44,20 +53,20 @@
|
||||
style="width: 60px"
|
||||
placeholder="80"
|
||||
/>
|
||||
</div>
|
||||
<FileUpload
|
||||
position="right"
|
||||
type="image"
|
||||
:convert="convertToWebp"
|
||||
:quality="webpQuality"
|
||||
@files="onUploaded"
|
||||
/>
|
||||
</div>
|
||||
<FileUpload
|
||||
position="right"
|
||||
type="image"
|
||||
:convert="convertToWebp"
|
||||
:quality="webpQuality"
|
||||
@files="onUploaded"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<component
|
||||
:is="layoutMode === layoutModes.GRID ? 'div' : 'table'"
|
||||
<component
|
||||
:is="layoutMode === layoutModes.GRID ? 'div' : 'table'"
|
||||
:class="layoutMode === layoutModes.GRID ? 'columns is-multiline px-2 mt-0' : 'table is-fullwidth is-bordered'"
|
||||
>
|
||||
<template v-if="layoutMode === layoutModes.LIST">
|
||||
@@ -69,40 +78,59 @@
|
||||
<th>Người tải lên</th>
|
||||
<th>
|
||||
<span class="icon-text">
|
||||
<span class="mr-5">Chức năng</span>
|
||||
<a class="mr-3" @click="excel()">
|
||||
<SvgIcon v-bind="{ name: 'excel.png', type: 'black', size: 20 }"></SvgIcon>
|
||||
</a>
|
||||
<a v-if="$getEditRights()" @click="importData()">
|
||||
<SvgIcon v-bind="{ name: 'upload.svg', type: 'black', size: 20 }"></SvgIcon>
|
||||
</a>
|
||||
<span class="mr-5">Chức năng</span>
|
||||
<a
|
||||
class="mr-3"
|
||||
@click="excel()"
|
||||
>
|
||||
<SvgIcon v-bind="{ name: 'excel.png', type: 'black', size: 20 }"></SvgIcon>
|
||||
</a>
|
||||
<a
|
||||
v-if="$getEditRights()"
|
||||
@click="importData()"
|
||||
>
|
||||
<SvgIcon v-bind="{ name: 'upload.svg', type: 'black', size: 20 }"></SvgIcon>
|
||||
</a>
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<ImageCard
|
||||
v-for="(image, index) in imgshow"
|
||||
:key="image.id"
|
||||
v-bind="{ image, index, layoutMode, loadImages, deleteImage, viewImage }"
|
||||
<ImageCard
|
||||
v-for="(image, index) in imgshow"
|
||||
:key="image.id"
|
||||
v-bind="{
|
||||
image,
|
||||
index,
|
||||
layoutMode,
|
||||
loadImages,
|
||||
deleteImage,
|
||||
viewImage,
|
||||
}"
|
||||
/>
|
||||
</tbody>
|
||||
</template>
|
||||
<ImageCard
|
||||
<ImageCard
|
||||
v-else
|
||||
v-for="(image, index) in imgshow"
|
||||
:key="image.id"
|
||||
v-for="(image, index) in imgshow"
|
||||
:key="image.id"
|
||||
v-bind="{ image, index, layoutMode, loadImages, deleteImage, viewImage }"
|
||||
/>
|
||||
</component>
|
||||
<!-- Modal xem ảnh lớn -->
|
||||
<div v-if="typeof activeImageIndex === 'number'" class="modal is-active">
|
||||
<div class="modal-background" @click="closeViewImage"></div>
|
||||
<div
|
||||
class="modal-content"
|
||||
<div
|
||||
v-if="typeof activeImageIndex === 'number'"
|
||||
class="modal is-active"
|
||||
>
|
||||
<div
|
||||
class="modal-background"
|
||||
@click="closeViewImage"
|
||||
></div>
|
||||
<div
|
||||
class="modal-content"
|
||||
style="width: 90vw; height: 90vh; border-radius: 8px; overflow: hidden"
|
||||
>
|
||||
<div
|
||||
<div
|
||||
class="is-flex"
|
||||
style="height: 100%; transition: all 0.3s ease-out"
|
||||
:style="`transform: translateX(${-100 * activeImageIndex}%)`"
|
||||
@@ -111,18 +139,18 @@
|
||||
v-for="image in imgshow"
|
||||
:key="image.id"
|
||||
class="has-background-black"
|
||||
style="flex: 0 0 100%; display: flex; justify-content: center; align-items: center;"
|
||||
style="flex: 0 0 100%; display: flex; justify-content: center; align-items: center"
|
||||
>
|
||||
<nuxt-img
|
||||
loading="lazy"
|
||||
:src="`${$getpath()}static/files/${image.file__file}`"
|
||||
:alt="image.file__name"
|
||||
style="object-fit: contain; height: 100%;"
|
||||
style="object-fit: contain; height: 100%"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gallery-arrows">
|
||||
<button
|
||||
<button
|
||||
@click="showPrevImage()"
|
||||
class="button is-rounded"
|
||||
style="left: 24px"
|
||||
@@ -149,97 +177,96 @@
|
||||
@click="closeViewImage"
|
||||
></button>
|
||||
</div>
|
||||
<Modal @dataevent="dataevent" @close="showmodal=undefined" v-bind="showmodal" v-if="showmodal" />
|
||||
<Modal
|
||||
@dataevent="dataevent"
|
||||
@close="showmodal = undefined"
|
||||
v-bind="showmodal"
|
||||
v-if="showmodal"
|
||||
/>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { debounce } from 'es-toolkit';
|
||||
import { debounce } from "es-toolkit";
|
||||
import { useNuxtApp } from "#app";
|
||||
import FileUpload from "@/components/media/FileUpload.vue";
|
||||
import ImageCard from '@/components/media/ImageCard.vue';
|
||||
import ImageCard from "@/components/media/ImageCard.vue";
|
||||
|
||||
// pass only projectId for project images, pass both for product images
|
||||
const props = defineProps({
|
||||
productId: String,
|
||||
projectId: String,
|
||||
})
|
||||
});
|
||||
|
||||
const { dealer } = useStore();
|
||||
const {
|
||||
$snackbar,
|
||||
$getdata,
|
||||
$insertapi,
|
||||
$deleteapi,
|
||||
$findapi,
|
||||
$getapi,
|
||||
$dayjs,
|
||||
$unique
|
||||
} = useNuxtApp();
|
||||
const { $snackbar, $getdata, $insertapi, $deleteapi, $findapi, $getapi, $dayjs, $unique } = useNuxtApp();
|
||||
|
||||
const layoutModes = { GRID: 'grid', LIST: 'list' };
|
||||
const layoutModes = { GRID: "grid", LIST: "list" };
|
||||
const layoutMode = ref(layoutModes.GRID);
|
||||
|
||||
const convertToWebp = ref(false);
|
||||
const webpQuality = ref(80);
|
||||
|
||||
const project = await $getdata('project', { id: props.projectId }, undefined, true);
|
||||
const project = await $getdata("project", { id: props.projectId }, undefined, true);
|
||||
const images = ref([]);
|
||||
const isForProduct = computed(() => props.productId !== undefined);
|
||||
const product = isForProduct.value ? await $getdata("product", { id: props.productId }, undefined, true) : undefined;
|
||||
const showmodal = ref()
|
||||
const imgshow = ref([])
|
||||
const hashtag = ref()
|
||||
const current = ref()
|
||||
const showmodal = ref();
|
||||
const imgshow = ref([]);
|
||||
const hashtag = ref();
|
||||
const current = ref();
|
||||
|
||||
async function loadImages() {
|
||||
const values = "id,file__hashtag,file__code,file,create_time,file__code,file__type,file__doc_type,file__name,file__file,file__size,file__caption,file__user__fullname,";
|
||||
|
||||
const values =
|
||||
"id,file__hashtag,file__code,file,create_time,file__code,file__type,file__doc_type,file__name,file__file,file__size,file__caption,file__user__fullname,";
|
||||
|
||||
const projectImages = await $getdata("projectfile", undefined, {
|
||||
filter: { project: project.id, file__type: 2 },
|
||||
values: values + 'project',
|
||||
sort: "-create_time"
|
||||
values: values + "project",
|
||||
sort: "-create_time",
|
||||
});
|
||||
|
||||
|
||||
if (isForProduct.value) {
|
||||
const productImages = await $getdata("productfile", undefined, {
|
||||
filter: { product: props.productId, file__type: 2 },
|
||||
values: values + 'product',
|
||||
sort: "-create_time"
|
||||
values: values + "product",
|
||||
sort: "-create_time",
|
||||
});
|
||||
|
||||
const sameTemplateImages = projectImages.filter(img => img.file__name.startsWith(product.template_name));
|
||||
const sameTemplateImages = projectImages.filter((img) => img.file__name.startsWith(product.template_name));
|
||||
images.value = [...productImages, ...sameTemplateImages];
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
images.value = projectImages;
|
||||
}
|
||||
imgshow.value = [...images.value]
|
||||
hashtag.value = $unique(images.value.filter(v=>v.file__hashtag), ["file__hashtag"])
|
||||
hashtag.value.map(v=>v.count = images.value.filter(x=>x.file__hashtag===v.file__hashtag).length)
|
||||
imgshow.value = [...images.value];
|
||||
hashtag.value = $unique(
|
||||
images.value.filter((v) => v.file__hashtag),
|
||||
["file__hashtag"],
|
||||
);
|
||||
hashtag.value.map((v) => (v.count = images.value.filter((x) => x.file__hashtag === v.file__hashtag).length));
|
||||
}
|
||||
|
||||
const handleKeyDown = debounce((e) => {
|
||||
if (e.key === 'Escape') closeViewImage();
|
||||
if (e.key === 'ArrowLeft') showPrevImage();
|
||||
if (e.key === 'ArrowRight') showNextImage();
|
||||
if (e.key === "Escape") closeViewImage();
|
||||
if (e.key === "ArrowLeft") showPrevImage();
|
||||
if (e.key === "ArrowRight") showNextImage();
|
||||
}, 100);
|
||||
|
||||
onMounted(() => {
|
||||
loadImages();
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
})
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
})
|
||||
window.removeEventListener("keydown", handleKeyDown);
|
||||
});
|
||||
|
||||
async function attachFiles(files) {
|
||||
if (!project) return 0;
|
||||
|
||||
|
||||
const payload = files.map((file) => ({
|
||||
file: file.id,
|
||||
project: isForProduct.value ? undefined : project.id,
|
||||
product: isForProduct.value ? product.id : undefined
|
||||
product: isForProduct.value ? product.id : undefined,
|
||||
}));
|
||||
|
||||
const result = await $insertapi(isForProduct.value ? "productfile" : "projectfile", payload);
|
||||
@@ -278,37 +305,47 @@ async function deleteImage(image) {
|
||||
}
|
||||
|
||||
async function excel() {
|
||||
const found = $findapi('exportcsv')
|
||||
found.params = {}
|
||||
let fields = [{name: 'code', label: 'code'}, {name: 'name', label: 'name'}, {name: 'caption', label: 'caption'}, {name: 'hashtag', label: 'hashtag'}]
|
||||
found.params.fields = JSON.stringify(fields)
|
||||
found.url = 'exportcsv/File/'
|
||||
found.params.filter = {code__in: images.value.map(v=>v.file__code)}
|
||||
const rs = await $getapi([found])
|
||||
if(rs === 'error') return $snackbar('Đã xảy ra lỗi. Vui lòng thử lại.')
|
||||
const url = window.URL.createObjectURL(new Blob([rs[0].data]))
|
||||
const link = document.createElement('a')
|
||||
const fileName = `${$dayjs(new Date()).format('YYYYMMDDhhmmss')}-data.csv`
|
||||
link.href = url
|
||||
link.setAttribute('download', fileName)
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
link.remove()
|
||||
const found = $findapi("exportcsv");
|
||||
found.params = {};
|
||||
let fields = [
|
||||
{ name: "code", label: "code" },
|
||||
{ name: "name", label: "name" },
|
||||
{ name: "caption", label: "caption" },
|
||||
{ name: "hashtag", label: "hashtag" },
|
||||
];
|
||||
found.params.fields = JSON.stringify(fields);
|
||||
found.url = "exportcsv/File/";
|
||||
found.params.filter = { code__in: images.value.map((v) => v.file__code) };
|
||||
const rs = await $getapi([found]);
|
||||
if (rs === "error") return $snackbar("Đã xảy ra lỗi. Vui lòng thử lại.");
|
||||
const url = window.URL.createObjectURL(new Blob([rs[0].data]));
|
||||
const link = document.createElement("a");
|
||||
const fileName = `${$dayjs(new Date()).format("YYYYMMDDhhmmss")}-data.csv`;
|
||||
link.href = url;
|
||||
link.setAttribute("download", fileName);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
}
|
||||
|
||||
function importData() {
|
||||
showmodal.value = {"title":"Nhập bút toán theo lô","vbind":{"code":"file-import"},
|
||||
"width":"90%","height":"500px","component":"parameter/ImportData"}
|
||||
showmodal.value = {
|
||||
title: "Nhập bút toán theo lô",
|
||||
vbind: { code: "file-import" },
|
||||
width: "90%",
|
||||
height: "500px",
|
||||
component: "parameter/ImportData",
|
||||
};
|
||||
}
|
||||
|
||||
function refreshShow(tag) {
|
||||
if(current.value===tag.file__hashtag) {
|
||||
imgshow.value = [...images.value]
|
||||
current.value = null
|
||||
return
|
||||
if (current.value === tag.file__hashtag) {
|
||||
imgshow.value = [...images.value];
|
||||
current.value = null;
|
||||
return;
|
||||
}
|
||||
imgshow.value = images.value.filter(v=>v.file__hashtag===tag.file__hashtag)
|
||||
current.value = tag.file__hashtag
|
||||
imgshow.value = images.value.filter((v) => v.file__hashtag === tag.file__hashtag);
|
||||
current.value = tag.file__hashtag;
|
||||
}
|
||||
|
||||
// State cho xem ảnh lớns
|
||||
@@ -349,8 +386,8 @@ function closeViewImage() {
|
||||
.gallery-count {
|
||||
position: absolute;
|
||||
bottom: 24px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: fit-content;
|
||||
margin-inline: auto;
|
||||
|
||||
@@ -360,4 +397,4 @@ function closeViewImage() {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,58 +1,95 @@
|
||||
<template>
|
||||
<div class="columns mx-0" v-if="picture">
|
||||
<div class="column is-narrow" style="border-right: 2px solid #D3D3D3;" v-if="viewport>1">
|
||||
<div class="pr-2" style="max-height: 700px; overflow-y: auto;">
|
||||
<div v-for="(v,i) in picture" class="pb-4">
|
||||
<figure class="image is-clickable" style="width:80px" @click="current=i">
|
||||
<img :src="v.image">
|
||||
</figure>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="columns mx-0"
|
||||
v-if="picture"
|
||||
>
|
||||
<div
|
||||
class="column is-narrow"
|
||||
style="border-right: 2px solid #d3d3d3"
|
||||
v-if="viewport > 1"
|
||||
>
|
||||
<div
|
||||
class="pr-2"
|
||||
style="max-height: 700px; overflow-y: auto"
|
||||
>
|
||||
<div
|
||||
v-for="(v, i) in picture"
|
||||
class="pb-4"
|
||||
>
|
||||
<figure
|
||||
class="image is-clickable"
|
||||
style="width: 80px"
|
||||
@click="current = i"
|
||||
>
|
||||
<img :src="v.image" />
|
||||
</figure>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<b-carousel :autoplay="false" :indicator="viewport===1? true : false" icon-size="is-medium" v-model="current">
|
||||
<b-carousel-item v-for="(item, i) in picture" :key="i">
|
||||
<ChipImage @remove="check(item, i)" v-bind="{extend: false, image: item.image, file: item.file, show: ['download', 'delete']}"></ChipImage>
|
||||
<b-carousel
|
||||
:autoplay="false"
|
||||
:indicator="viewport === 1 ? true : false"
|
||||
icon-size="is-medium"
|
||||
v-model="current"
|
||||
>
|
||||
<b-carousel-item
|
||||
v-for="(item, i) in picture"
|
||||
:key="i"
|
||||
>
|
||||
<ChipImage
|
||||
@remove="check(item, i)"
|
||||
v-bind="{
|
||||
extend: false,
|
||||
image: item.image,
|
||||
file: item.file,
|
||||
show: ['download', 'delete'],
|
||||
}"
|
||||
></ChipImage>
|
||||
</b-carousel-item>
|
||||
</b-carousel>
|
||||
</b-carousel>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['row', 'image', 'pagename', 'api'],
|
||||
props: ["row", "image", "pagename", "api"],
|
||||
data() {
|
||||
return {
|
||||
current: 0,
|
||||
picture: undefined,
|
||||
timer: undefined
|
||||
}
|
||||
timer: undefined,
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
let arr = []
|
||||
let files = await this.$getdata('file', {file__in: this.image})
|
||||
this.image.map(v=>{
|
||||
let found = this.$find(files, {file: v})
|
||||
arr.push({image: `${this.$getpath()}download/?name=${v}`, file: found})
|
||||
})
|
||||
this.picture = arr
|
||||
let arr = [];
|
||||
let files = await this.$getdata("file", { file__in: this.image });
|
||||
this.image.map((v) => {
|
||||
let found = this.$find(files, { file: v });
|
||||
arr.push({ image: `${this.$getpath()}download/?name=${v}`, file: found });
|
||||
});
|
||||
this.picture = arr;
|
||||
},
|
||||
computed: {
|
||||
viewport: {
|
||||
get: function() {return this.$store.state.viewport},
|
||||
set: function(val) {this.$store.commit("updateViewPort", {viewport: val})}
|
||||
}
|
||||
get: function () {
|
||||
return this.$store.state.viewport;
|
||||
},
|
||||
set: function (val) {
|
||||
this.$store.commit("updateViewPort", { viewport: val });
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
check(v, i) {
|
||||
if(!this.timer) this.timer = setTimeout(()=>this.remove(v,i), 200)
|
||||
if (!this.timer) this.timer = setTimeout(() => this.remove(v, i), 200);
|
||||
},
|
||||
async remove(v, i) {
|
||||
this.$delete(this.picture, i)
|
||||
await this.$deleteapi(this.api, v.file.id)
|
||||
this.timer = undefined
|
||||
if(this.picture.length===0) this.$emit('close')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
this.$delete(this.picture, i);
|
||||
await this.$deleteapi(this.api, v.file.id);
|
||||
this.timer = undefined;
|
||||
if (this.picture.length === 0) this.$emit("close");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,303 +1,447 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="is-hidden"><img id="image" :src="image"></div>
|
||||
<div class="is-hidden">
|
||||
<img
|
||||
id="image"
|
||||
:src="image"
|
||||
/>
|
||||
</div>
|
||||
<div class="field is-grouped is-grouped-multiline">
|
||||
<div class="control">
|
||||
<div class="file is-primary">
|
||||
<label class="file-label">
|
||||
<input class="file-input" type="file" :id="docid" multiple name="resume" @change="doChange">
|
||||
<a class="button is-primary is-rounded is-small" :class="loading? 'is-loading' : null">
|
||||
<SvgIcon v-bind="{name: 'add5.svg', type: 'white', size: 18}"></SvgIcon>
|
||||
<span class="fs-14 ml-1">Tải lên từ máy tính</span>
|
||||
</a>
|
||||
</label>
|
||||
<label class="file-label">
|
||||
<input
|
||||
class="file-input"
|
||||
type="file"
|
||||
:id="docid"
|
||||
multiple
|
||||
name="resume"
|
||||
@change="doChange"
|
||||
/>
|
||||
<a
|
||||
class="button is-primary is-rounded is-small"
|
||||
:class="loading ? 'is-loading' : null"
|
||||
>
|
||||
<SvgIcon v-bind="{ name: 'add5.svg', type: 'white', size: 18 }"></SvgIcon>
|
||||
<span class="fs-14 ml-1">Tải lên từ máy tính</span>
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="`control ${showUrl? '' : 'is-expanded'}`">
|
||||
<a class="button is-dark is-rounded is-small" @click="displayInput()">
|
||||
<SvgIcon v-bind="{name: 'add5.svg', type: 'white', size: 18}"></SvgIcon>
|
||||
<div :class="`control ${showUrl ? '' : 'is-expanded'}`">
|
||||
<a
|
||||
class="button is-dark is-rounded is-small"
|
||||
@click="displayInput()"
|
||||
>
|
||||
<SvgIcon v-bind="{ name: 'add5.svg', type: 'white', size: 18 }"></SvgIcon>
|
||||
<span class="fs-14 ml-1">Tải lên từ đường dẫn</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="control is-expanded" v-if="showUrl">
|
||||
<input class="input is-small is-rounded" id="url" v-model="url" style="width: 250px;" type="text" placeholder="Nhập đường dẫn vào đây">
|
||||
<a class="button is-primary is-small px-4 ml-3" @click="checkUrl()">
|
||||
<SvgIcon v-bind="{name: 'upload.svg', type: 'white', size: 22}"></SvgIcon>
|
||||
<div
|
||||
class="control is-expanded"
|
||||
v-if="showUrl"
|
||||
>
|
||||
<input
|
||||
class="input is-small is-rounded"
|
||||
id="url"
|
||||
v-model="url"
|
||||
style="width: 250px"
|
||||
type="text"
|
||||
placeholder="Nhập đường dẫn vào đây"
|
||||
/>
|
||||
<a
|
||||
class="button is-primary is-small px-4 ml-3"
|
||||
@click="checkUrl()"
|
||||
>
|
||||
<SvgIcon v-bind="{ name: 'upload.svg', type: 'white', size: 22 }"></SvgIcon>
|
||||
</a>
|
||||
<a class="ml-4" @click="showUrl=false">
|
||||
<SvgIcon v-bind="{name: 'close.svg', type: 'dark', size: 22}"></SvgIcon>
|
||||
<a
|
||||
class="ml-4"
|
||||
@click="showUrl = false"
|
||||
>
|
||||
<SvgIcon v-bind="{ name: 'close.svg', type: 'dark', size: 22 }"></SvgIcon>
|
||||
</a>
|
||||
</div>
|
||||
<div class="control">
|
||||
<input class="input is-small is-rounded" v-model="search" style="width: 250px;" type="text" placeholder="Tìm kiếm" @keyup="beginSearch">
|
||||
<input
|
||||
class="input is-small is-rounded"
|
||||
v-model="search"
|
||||
style="width: 250px"
|
||||
type="text"
|
||||
placeholder="Tìm kiếm"
|
||||
@keyup="beginSearch"
|
||||
/>
|
||||
</div>
|
||||
<div class="control">
|
||||
<span class="is-clickable" @click="mode='image'">
|
||||
<SvgIcon v-bind="{name: 'image3.svg', type: 'dark', size: 25}"></SvgIcon>
|
||||
<span
|
||||
class="is-clickable"
|
||||
@click="mode = 'image'"
|
||||
>
|
||||
<SvgIcon v-bind="{ name: 'image3.svg', type: 'dark', size: 25 }"></SvgIcon>
|
||||
</span>
|
||||
</div>
|
||||
<div class="control">
|
||||
<span class="is-clickable" @click="mode='list'">
|
||||
<SvgIcon v-bind="{name: 'list.png', type: 'dark', size: 25}"></SvgIcon>
|
||||
<span
|
||||
class="is-clickable"
|
||||
@click="mode = 'list'"
|
||||
>
|
||||
<SvgIcon v-bind="{ name: 'list.png', type: 'dark', size: 25 }"></SvgIcon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<DataView
|
||||
class="mt-3"
|
||||
v-bind="vbind"
|
||||
v-if="mode === 'list'"
|
||||
></DataView>
|
||||
<div
|
||||
class="tile is-ancestor mx-0 px-0 pt-3"
|
||||
v-else
|
||||
>
|
||||
<div class="tile is-vertical">
|
||||
<div
|
||||
class="tile is-parent"
|
||||
v-for="(v, i) in group"
|
||||
:key="i"
|
||||
>
|
||||
<article
|
||||
class="tile is-child"
|
||||
v-for="(k, j) in getData(i)"
|
||||
:key="j"
|
||||
@mouseover="focus = k"
|
||||
>
|
||||
<div
|
||||
class="image px-2 pb-2"
|
||||
v-if="k.file && type === 'image'"
|
||||
>
|
||||
<nuxt-img
|
||||
:src="`${path}download?name=${k.file}`"
|
||||
:id="'commentImage' + k.id"
|
||||
></nuxt-img>
|
||||
<div
|
||||
class="text-image"
|
||||
v-if="focus === k"
|
||||
>
|
||||
<a
|
||||
class="button is-primary is-small"
|
||||
@click="selectMedia(k)"
|
||||
>
|
||||
<SvgIcon v-bind="{ name: 'checked.svg', type: 'white', size: 22 }"></SvgIcon>
|
||||
</a>
|
||||
<a
|
||||
class="button is-primary is-small ml-2"
|
||||
@click="editImage(k)"
|
||||
>
|
||||
<SvgIcon v-bind="{ name: 'crop.svg', type: 'white', size: 22 }"></SvgIcon>
|
||||
</a>
|
||||
<a
|
||||
class="button is-primary is-small ml-2"
|
||||
@click="copyMedia(k, 'image')"
|
||||
>
|
||||
<SvgIcon v-bind="{ name: 'copy.svg', type: 'white', size: 22 }"></SvgIcon>
|
||||
</a>
|
||||
<a
|
||||
class="button is-danger is-small ml-2"
|
||||
@click="deleteMedia(k, 'file')"
|
||||
>
|
||||
<SvgIcon v-bind="{ name: 'bin1.svg', type: 'white', size: 22 }"></SvgIcon>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ml-2 mr-2"
|
||||
v-else-if="k.file && type === 'video'"
|
||||
>
|
||||
<vue-plyr>
|
||||
<video :src="path + 'static/videos/' + k.file">
|
||||
<source
|
||||
:src="path + 'static/videos/' + k.file"
|
||||
type="video/mp4"
|
||||
size="720"
|
||||
/>
|
||||
</video>
|
||||
<div
|
||||
class="mt-2"
|
||||
v-if="focus === k"
|
||||
>
|
||||
<a
|
||||
class="button is-primary"
|
||||
@click="selectMedia(k)"
|
||||
>
|
||||
<SvgIcon v-bind="{ name: 'check3.svg', type: 'primary', size: 22 }"></SvgIcon>
|
||||
</a>
|
||||
</div>
|
||||
</vue-plyr>
|
||||
</div>
|
||||
<div
|
||||
class="ml-2 mr-2"
|
||||
v-else-if="k.file && type === 'file'"
|
||||
>
|
||||
{{ k.file }}
|
||||
<div
|
||||
class="mt-2"
|
||||
v-if="focus === k"
|
||||
>
|
||||
<a
|
||||
class="button is-primary"
|
||||
@click="selectMedia(k)"
|
||||
>
|
||||
<SvgIcon v-bind="{ name: 'check3.svg', type: 'primary', size: 22 }"></SvgIcon>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Modal
|
||||
@close="showmodal = undefined"
|
||||
v-bind="showmodal"
|
||||
@image="updateImage"
|
||||
@files="getFiles"
|
||||
v-if="showmodal"
|
||||
/>
|
||||
</div>
|
||||
<DataView class="mt-3" v-bind="vbind" v-if="mode==='list'"></DataView>
|
||||
<div class="tile is-ancestor mx-0 px-0 pt-3" v-else>
|
||||
<div class="tile is-vertical">
|
||||
<div class="tile is-parent" v-for="(v,i) in group" :key="i">
|
||||
<article class="tile is-child" v-for="(k,j) in getData(i)" :key="j" @mouseover="focus=k">
|
||||
<div class="image px-2 pb-2" v-if="k.file && type==='image'">
|
||||
<nuxt-img :src="`${path}download?name=${k.file}`" :id="'commentImage' + k.id"></nuxt-img>
|
||||
<div class="text-image" v-if="focus===k">
|
||||
<a class="button is-primary is-small" @click="selectMedia(k)">
|
||||
<SvgIcon v-bind="{name: 'checked.svg', type: 'white', size: 22}"></SvgIcon>
|
||||
</a>
|
||||
<a class="button is-primary is-small ml-2" @click="editImage(k)">
|
||||
<SvgIcon v-bind="{name: 'crop.svg', type: 'white', size: 22}"></SvgIcon>
|
||||
</a>
|
||||
<a class="button is-primary is-small ml-2" @click="copyMedia(k, 'image')">
|
||||
<SvgIcon v-bind="{name: 'copy.svg', type: 'white', size: 22}"></SvgIcon>
|
||||
</a>
|
||||
<a class="button is-danger is-small ml-2" @click="deleteMedia(k, 'file')">
|
||||
<SvgIcon v-bind="{name: 'bin1.svg', type: 'white', size: 22}"></SvgIcon>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-2 mr-2" v-else-if="k.file && type==='video'">
|
||||
<vue-plyr>
|
||||
<video :src="path + 'static/videos/' + k.file">
|
||||
<source :src="path + 'static/videos/' + k.file" type="video/mp4" size="720">
|
||||
</video>
|
||||
<div class="mt-2" v-if="focus===k">
|
||||
<a class="button is-primary" @click="selectMedia(k)">
|
||||
<SvgIcon v-bind="{name: 'check3.svg', type: 'primary', size: 22}"></SvgIcon>
|
||||
</a>
|
||||
</div>
|
||||
</vue-plyr>
|
||||
</div>
|
||||
<div class="ml-2 mr-2" v-else-if="k.file && type==='file'">
|
||||
{{k.file}}
|
||||
<div class="mt-2" v-if="focus===k">
|
||||
<a class="button is-primary" @click="selectMedia(k)">
|
||||
<SvgIcon v-bind="{name: 'check3.svg', type: 'primary', size: 22}"></SvgIcon>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Modal @close="showmodal=undefined" v-bind="showmodal" @image="updateImage" @files="getFiles" v-if="showmodal"/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['source'],
|
||||
data() {
|
||||
return {
|
||||
file: undefined,
|
||||
image: undefined,
|
||||
showmodal: undefined,
|
||||
data: [],
|
||||
group: [],
|
||||
focus: undefined,
|
||||
type: 'image',
|
||||
selected: undefined,
|
||||
loading: false,
|
||||
path: this.$getpath(),
|
||||
showUrl: false,
|
||||
url: undefined,
|
||||
search: undefined,
|
||||
mode: 'image',
|
||||
timer: undefined,
|
||||
vbind: {api: 'file', setting: 'image-fields'},
|
||||
dataFiles: [],
|
||||
files: [],
|
||||
docid: this.$id()
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ["source"],
|
||||
data() {
|
||||
return {
|
||||
file: undefined,
|
||||
image: undefined,
|
||||
showmodal: undefined,
|
||||
data: [],
|
||||
group: [],
|
||||
focus: undefined,
|
||||
type: "image",
|
||||
selected: undefined,
|
||||
loading: false,
|
||||
path: this.$getpath(),
|
||||
showUrl: false,
|
||||
url: undefined,
|
||||
search: undefined,
|
||||
mode: "image",
|
||||
timer: undefined,
|
||||
vbind: { api: "file", setting: "image-fields" },
|
||||
dataFiles: [],
|
||||
files: [],
|
||||
docid: this.$id(),
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
let found = this.$findapi("file");
|
||||
found.params = {
|
||||
filter: { user: this.login.id },
|
||||
sort: "-create_time",
|
||||
type__code: this.type,
|
||||
};
|
||||
let result = await this.$getapi([found]);
|
||||
this.data = result[0].data.rows;
|
||||
},
|
||||
watch: {
|
||||
data: function () {
|
||||
this.group = [];
|
||||
for (let index = 0; index < this.data.length / (this.type === "video" ? 4 : 5); index++) {
|
||||
this.group.push(index);
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
let found = this.$findapi('file')
|
||||
found.params = {filter: {user: this.login.id}, sort: '-create_time', type__code: this.type}
|
||||
let result = await this.$getapi([found])
|
||||
this.data = result[0].data.rows
|
||||
},
|
||||
computed: {
|
||||
login: {
|
||||
get: function () {
|
||||
return this.$store.login;
|
||||
},
|
||||
set: function (val) {
|
||||
this.$store.commit("updateLogin", { login: val });
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
data: function() {
|
||||
this.group = []
|
||||
for (let index = 0; index < this.data.length/ (this.type==='video'? 4 : 5) ; index++) {
|
||||
this.group.push(index)
|
||||
}
|
||||
}
|
||||
media: {
|
||||
get: function () {
|
||||
return this.$store.media;
|
||||
},
|
||||
set: function (val) {
|
||||
this.$store.commit("updateMedia", { media: val });
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
login: {
|
||||
get: function() {return this.$store.login},
|
||||
set: function(val) {this.$store.commit("updateLogin", {login: val})}
|
||||
},
|
||||
media: {
|
||||
get: function() {return this.$store.media},
|
||||
set: function(val) {this.$store.commit("updateMedia", {media: val})}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
doChange() {
|
||||
this.dataFiles = []
|
||||
let fileList = document.getElementById(this.docid).files
|
||||
this.files = Array.from(fileList)
|
||||
if(this.files.length===0) return
|
||||
this.files.map(v=>{
|
||||
let file = this.$upload(v, this.vtype, this.$store.login.id)
|
||||
this.dataFiles.push(file)
|
||||
})
|
||||
this.showmodal = {component: 'media/UploadProgress', title: 'Upload files', width: '700px', height: '200px', vbind: {files: this.dataFiles}}
|
||||
this.clearFileList()
|
||||
},
|
||||
clearFileList() {
|
||||
const fileInput = document.getElementById(this.docid)
|
||||
const dt = new DataTransfer()
|
||||
fileInput.files = dt.files
|
||||
},
|
||||
getFiles(files) {
|
||||
this.data = files.concat(this.data)
|
||||
setTimeout(()=>this.showmodal=undefined, 3000)
|
||||
},
|
||||
beginSearch(e) {
|
||||
if(this.timer) clearTimeout(this.timer)
|
||||
this.timer = setTimeout(() => this.startSearch(e.target.value), 150)
|
||||
},
|
||||
async startSearch(value) {
|
||||
let filter = {user: this.login.id}
|
||||
if(!this.$empty(value)) filter.name__icontains = value.toLowerCase()
|
||||
this.data = await this.$getdata('file', filter)
|
||||
},
|
||||
displayInput() {
|
||||
this.showUrl = true
|
||||
this.url = undefined
|
||||
setTimeout(()=>document.getElementById('url').focus(), 100)
|
||||
},
|
||||
checkUrl() {
|
||||
if(this.$empty(this.url)) return this.$snackbar(`Đường dẫn không hợp lệ`, undefined, 'Error')
|
||||
let self = this
|
||||
this.loading = true
|
||||
this.$axios.get(this.url, {responseType:"blob" })
|
||||
.then(function(response) {
|
||||
var reader = new window.FileReader()
|
||||
},
|
||||
methods: {
|
||||
doChange() {
|
||||
this.dataFiles = [];
|
||||
let fileList = document.getElementById(this.docid).files;
|
||||
this.files = Array.from(fileList);
|
||||
if (this.files.length === 0) return;
|
||||
this.files.map((v) => {
|
||||
let file = this.$upload(v, this.vtype, this.$store.login.id);
|
||||
this.dataFiles.push(file);
|
||||
});
|
||||
this.showmodal = {
|
||||
component: "media/UploadProgress",
|
||||
title: "Upload files",
|
||||
width: "700px",
|
||||
height: "200px",
|
||||
vbind: { files: this.dataFiles },
|
||||
};
|
||||
this.clearFileList();
|
||||
},
|
||||
clearFileList() {
|
||||
const fileInput = document.getElementById(this.docid);
|
||||
const dt = new DataTransfer();
|
||||
fileInput.files = dt.files;
|
||||
},
|
||||
getFiles(files) {
|
||||
this.data = files.concat(this.data);
|
||||
setTimeout(() => (this.showmodal = undefined), 3000);
|
||||
},
|
||||
beginSearch(e) {
|
||||
if (this.timer) clearTimeout(this.timer);
|
||||
this.timer = setTimeout(() => this.startSearch(e.target.value), 150);
|
||||
},
|
||||
async startSearch(value) {
|
||||
let filter = { user: this.login.id };
|
||||
if (!this.$empty(value)) filter.name__icontains = value.toLowerCase();
|
||||
this.data = await this.$getdata("file", filter);
|
||||
},
|
||||
displayInput() {
|
||||
this.showUrl = true;
|
||||
this.url = undefined;
|
||||
setTimeout(() => document.getElementById("url").focus(), 100);
|
||||
},
|
||||
checkUrl() {
|
||||
if (this.$empty(this.url)) return this.$snackbar(`Đường dẫn không hợp lệ`, undefined, "Error");
|
||||
let self = this;
|
||||
this.loading = true;
|
||||
this.$axios
|
||||
.get(this.url, { responseType: "blob" })
|
||||
.then(function (response) {
|
||||
var reader = new window.FileReader();
|
||||
reader.onload = (e) => {
|
||||
self.image = e.target.result
|
||||
setTimeout(()=> self.doUpload(e.target.result), 100)
|
||||
}
|
||||
reader.readAsDataURL(response.data)
|
||||
self.loading = false
|
||||
self.image = e.target.result;
|
||||
setTimeout(() => self.doUpload(e.target.result), 100);
|
||||
};
|
||||
reader.readAsDataURL(response.data);
|
||||
self.loading = false;
|
||||
})
|
||||
.catch(e => {
|
||||
self.$buefy.toast.open({duration: 3000, message: `Đường dẫn không hợp lệ`, type: 'is-danger'})
|
||||
self.loading = false
|
||||
})
|
||||
},
|
||||
doUpload() {
|
||||
const image = document.getElementById('image')
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.width = image.width
|
||||
canvas.height = image.height
|
||||
const ctx = canvas.getContext('2d')
|
||||
ctx.drawImage(image, 0, 0)
|
||||
if(canvas) canvas.toBlob(blod=>this.saveAs(blod))
|
||||
},
|
||||
async saveAs(blod) {
|
||||
var form = new FormData();
|
||||
const fileName = this.$dayjs(new Date()).format("YYYYMMDDhhmmss") + '-' + this.$id() + '.png'
|
||||
form.append('filename', fileName)
|
||||
form.append('name', `${this.$id()}.png`)
|
||||
form.append('file', blod)
|
||||
form.append('type', 'image')
|
||||
form.append('size', 100)
|
||||
form.append('user', this.$store.login.id)
|
||||
let result = await this.$insertapi('upload', form)
|
||||
if(result==='error') return
|
||||
this.updateImage(result.rows[0])
|
||||
this.showUrl = false
|
||||
},
|
||||
async uploadImage(file) {
|
||||
this.loading = true
|
||||
let result = await this.$insertapi('upload', file.form)
|
||||
this.data.splice(0, 0, result.rows[0])
|
||||
this.loading = false
|
||||
},
|
||||
getData(i) {
|
||||
if(this.type==='video') {
|
||||
var list = this.data.slice(i*5, i*5+5)
|
||||
for (let index = 0; index < 5; index++) {
|
||||
if(list.length<index+1) list.push({file: undefined})
|
||||
}
|
||||
} else {
|
||||
list = this.data.slice(i*6, i*6+6)
|
||||
for (let index = 0; index < 6; index++) {
|
||||
if(list.length<index+1) list.push({file: undefined})
|
||||
}
|
||||
.catch((e) => {
|
||||
self.$buefy.toast.open({
|
||||
duration: 3000,
|
||||
message: `Đường dẫn không hợp lệ`,
|
||||
type: "is-danger",
|
||||
});
|
||||
self.loading = false;
|
||||
});
|
||||
},
|
||||
doUpload() {
|
||||
const image = document.getElementById("image");
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = image.width;
|
||||
canvas.height = image.height;
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx.drawImage(image, 0, 0);
|
||||
if (canvas) canvas.toBlob((blod) => this.saveAs(blod));
|
||||
},
|
||||
async saveAs(blod) {
|
||||
var form = new FormData();
|
||||
const fileName = this.$dayjs(new Date()).format("YYYYMMDDhhmmss") + "-" + this.$id() + ".png";
|
||||
form.append("filename", fileName);
|
||||
form.append("name", `${this.$id()}.png`);
|
||||
form.append("file", blod);
|
||||
form.append("type", "image");
|
||||
form.append("size", 100);
|
||||
form.append("user", this.$store.login.id);
|
||||
let result = await this.$insertapi("upload", form);
|
||||
if (result === "error") return;
|
||||
this.updateImage(result.rows[0]);
|
||||
this.showUrl = false;
|
||||
},
|
||||
async uploadImage(file) {
|
||||
this.loading = true;
|
||||
let result = await this.$insertapi("upload", file.form);
|
||||
this.data.splice(0, 0, result.rows[0]);
|
||||
this.loading = false;
|
||||
},
|
||||
getData(i) {
|
||||
if (this.type === "video") {
|
||||
var list = this.data.slice(i * 5, i * 5 + 5);
|
||||
for (let index = 0; index < 5; index++) {
|
||||
if (list.length < index + 1) list.push({ file: undefined });
|
||||
}
|
||||
return list
|
||||
},
|
||||
selectMedia(v) {
|
||||
let copy = this.media? this.$copy(this.media) : {}
|
||||
copy.type = 'image'
|
||||
copy.open = false
|
||||
copy.select = v
|
||||
this.media = copy
|
||||
let row = this.$copy(v)
|
||||
if(this.source) {
|
||||
let params = {name: v.file, type: 'file'}
|
||||
this.$axios.get(`${this.path}download/`, {params: params, responseType:"blob" })
|
||||
} else {
|
||||
list = this.data.slice(i * 6, i * 6 + 6);
|
||||
for (let index = 0; index < 6; index++) {
|
||||
if (list.length < index + 1) list.push({ file: undefined });
|
||||
}
|
||||
}
|
||||
return list;
|
||||
},
|
||||
selectMedia(v) {
|
||||
let copy = this.media ? this.$copy(this.media) : {};
|
||||
copy.type = "image";
|
||||
copy.open = false;
|
||||
copy.select = v;
|
||||
this.media = copy;
|
||||
let row = this.$copy(v);
|
||||
if (this.source) {
|
||||
let params = { name: v.file, type: "file" };
|
||||
this.$axios
|
||||
.get(`${this.path}download/`, {
|
||||
params: params,
|
||||
responseType: "blob",
|
||||
})
|
||||
.then(function (response) {
|
||||
var reader = new window.FileReader()
|
||||
var reader = new window.FileReader();
|
||||
reader.onload = (e) => {
|
||||
fetch(e.target.result)
|
||||
.then(res => res.blob())
|
||||
.then(blob => {
|
||||
const file = new File([blob], v.name,{ type: "image/png" })
|
||||
row.source = {file: file}
|
||||
})
|
||||
}
|
||||
reader.readAsDataURL(response.data)
|
||||
})
|
||||
}
|
||||
this.$emit('modalevent', {name: 'selectimage', data: row})
|
||||
},
|
||||
editImage(v) {
|
||||
this.loading = true
|
||||
this.selected = v
|
||||
let self = this
|
||||
let params = {name: v.file, type: 'file'}
|
||||
this.$axios.get(`${this.path}download/`, {params: params, responseType:"blob" })
|
||||
.then(function (response) {
|
||||
var reader = new window.FileReader()
|
||||
reader.onload = (e) => {
|
||||
self.image = e.target.result
|
||||
self.showmodal = {component: 'media/CropImage', width: '65%', title: 'Cắt hình ảnh', vbind: {selected: self.selected, image: self.image}}
|
||||
}
|
||||
reader.readAsDataURL(response.data)
|
||||
self.loading = false
|
||||
})
|
||||
},
|
||||
copyMedia(v) {
|
||||
this.$copyToClipboard(`${this.path}download/?name=${v.file}`)
|
||||
},
|
||||
deleteMedia(v, name) {
|
||||
let self = this
|
||||
var remove = async function() {
|
||||
let result = await self.$deleteapi(name, v.id)
|
||||
let idx = self.data.findIndex(x=>x.id===v.id)
|
||||
self.$delete(self.data, idx)
|
||||
}
|
||||
this.$buefy.dialog.confirm({
|
||||
message: 'Bạn muốn xóa file: ' + v.file,
|
||||
onConfirm: () => remove()})
|
||||
},
|
||||
updateImage(v) {
|
||||
this.data.splice(0, 0, v)
|
||||
this.showmodal = undefined
|
||||
.then((res) => res.blob())
|
||||
.then((blob) => {
|
||||
const file = new File([blob], v.name, { type: "image/png" });
|
||||
row.source = { file: file };
|
||||
});
|
||||
};
|
||||
reader.readAsDataURL(response.data);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
this.$emit("modalevent", { name: "selectimage", data: row });
|
||||
},
|
||||
editImage(v) {
|
||||
this.loading = true;
|
||||
this.selected = v;
|
||||
let self = this;
|
||||
let params = { name: v.file, type: "file" };
|
||||
this.$axios.get(`${this.path}download/`, { params: params, responseType: "blob" }).then(function (response) {
|
||||
var reader = new window.FileReader();
|
||||
reader.onload = (e) => {
|
||||
self.image = e.target.result;
|
||||
self.showmodal = {
|
||||
component: "media/CropImage",
|
||||
width: "65%",
|
||||
title: "Cắt hình ảnh",
|
||||
vbind: { selected: self.selected, image: self.image },
|
||||
};
|
||||
};
|
||||
reader.readAsDataURL(response.data);
|
||||
self.loading = false;
|
||||
});
|
||||
},
|
||||
copyMedia(v) {
|
||||
this.$copyToClipboard(`${this.path}download/?name=${v.file}`);
|
||||
},
|
||||
deleteMedia(v, name) {
|
||||
let self = this;
|
||||
var remove = async function () {
|
||||
let result = await self.$deleteapi(name, v.id);
|
||||
let idx = self.data.findIndex((x) => x.id === v.id);
|
||||
self.$delete(self.data, idx);
|
||||
};
|
||||
this.$buefy.dialog.confirm({
|
||||
message: "Bạn muốn xóa file: " + v.file,
|
||||
onConfirm: () => remove(),
|
||||
});
|
||||
},
|
||||
updateImage(v) {
|
||||
this.data.splice(0, 0, v);
|
||||
this.showmodal = undefined;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,54 +1,80 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="image-container" v-if="picture">
|
||||
<img :src="picture" style="max-height: 250px !important;">
|
||||
<div class="image-label">
|
||||
<span class="is-clickable" @click="remove()">
|
||||
<SvgIcon v-bind="{name: 'bin.svg', type: 'danger', size: 30}"></SvgIcon>
|
||||
</span>
|
||||
<div
|
||||
class="image-container"
|
||||
v-if="picture"
|
||||
>
|
||||
<img
|
||||
:src="picture"
|
||||
style="max-height: 250px !important"
|
||||
/>
|
||||
<div class="image-label">
|
||||
<span
|
||||
class="is-clickable"
|
||||
@click="remove()"
|
||||
>
|
||||
<SvgIcon v-bind="{ name: 'bin.svg', type: 'danger', size: 30 }"></SvgIcon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style="width: 130px; border-style: dashed; border-width: 1px"
|
||||
v-else
|
||||
>
|
||||
<a @click="openImage()"><SvgIcon v-bind="{ name: 'image.svg', type: 'grey', size: 120 }"></SvgIcon></a>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<a @click="openCamera()">
|
||||
<SvgIcon v-bind="{ name: 'camera.svg', type: 'dark', size: 28 }"></SvgIcon>
|
||||
</a>
|
||||
</div>
|
||||
<Modal
|
||||
@close="showmodal = undefined"
|
||||
v-bind="showmodal"
|
||||
v-if="showmodal"
|
||||
@selectimage="selectImage"
|
||||
></Modal>
|
||||
</div>
|
||||
</div>
|
||||
<div style="width: 130px; border-style: dashed; border-width: 1px;" v-else>
|
||||
<a @click="openImage()"><SvgIcon v-bind="{name: 'image.svg', type: 'grey', size: 120}"></SvgIcon></a>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<a @click="openCamera()">
|
||||
<SvgIcon v-bind="{name: 'camera.svg', type: 'dark', size: 28}"></SvgIcon>
|
||||
</a>
|
||||
</div>
|
||||
<Modal @close="showmodal=undefined" v-bind="showmodal" v-if="showmodal" @selectimage="selectImage"></Modal>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['file', 'image', 'show'],
|
||||
props: ["file", "image", "show"],
|
||||
data() {
|
||||
return {
|
||||
showmodal: undefined,
|
||||
display: this.show || [],
|
||||
picture: this.image || undefined,
|
||||
vfile: undefined
|
||||
}
|
||||
vfile: undefined,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
openImage() {
|
||||
this.showmodal = {component:'media/Imagebox', title: 'Thư viện hình ảnh', width: '90%', vbind: {source: true}}
|
||||
this.showmodal = {
|
||||
component: "media/Imagebox",
|
||||
title: "Thư viện hình ảnh",
|
||||
width: "90%",
|
||||
vbind: { source: true },
|
||||
};
|
||||
},
|
||||
selectImage(files) {
|
||||
this.showmodal = undefined
|
||||
let v = files
|
||||
this.picture = `${this.$path()}download/?name=${v.file__file || v.file}&type=file`
|
||||
v.image = this.$copy(this.picture)
|
||||
this.vfile = v
|
||||
this.$emit('picture', v)
|
||||
this.showmodal = undefined;
|
||||
let v = files;
|
||||
this.picture = `${this.$path()}download/?name=${v.file__file || v.file}&type=file`;
|
||||
v.image = this.$copy(this.picture);
|
||||
this.vfile = v;
|
||||
this.$emit("picture", v);
|
||||
},
|
||||
remove() {
|
||||
this.vfile = undefined
|
||||
this.picture = undefined
|
||||
this.vfile = undefined;
|
||||
this.picture = undefined;
|
||||
},
|
||||
openCamera() {
|
||||
this.showmodal = {component:'media/Camera', title: 'Chụp ảnh', width: '650px'}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
this.showmodal = {
|
||||
component: "media/Camera",
|
||||
title: "Chụp ảnh",
|
||||
width: "650px",
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<template>
|
||||
<div class="has-text-left">
|
||||
<p class="py-1 border-bottom" v-for="v in vfiles">
|
||||
<p
|
||||
class="py-1 border-bottom"
|
||||
v-for="v in vfiles"
|
||||
>
|
||||
<span class="icon-text">
|
||||
<span class="mr-5">{{ v.name }}</span>
|
||||
<SvgIcon
|
||||
@@ -12,10 +15,11 @@
|
||||
v-else-if="v.status === 'error'"
|
||||
></SvgIcon>
|
||||
</span>
|
||||
<span class="icon-text has-text-danger ml-6" v-if="v.error">
|
||||
<SvgIcon
|
||||
v-bind="{ name: 'error.svg', type: 'danger', size: 22 }"
|
||||
></SvgIcon>
|
||||
<span
|
||||
class="icon-text has-text-danger ml-6"
|
||||
v-if="v.error"
|
||||
>
|
||||
<SvgIcon v-bind="{ name: 'error.svg', type: 'danger', size: 22 }"></SvgIcon>
|
||||
<span class="ml-1">{{ v.text }}</span>
|
||||
</span>
|
||||
<button
|
||||
@@ -59,9 +63,7 @@ export default {
|
||||
}
|
||||
},
|
||||
checkDone() {
|
||||
let found = this.vfiles.find(
|
||||
(v) => !v.status || v.status === "uploading"
|
||||
);
|
||||
let found = this.vfiles.find((v) => !v.status || v.status === "uploading");
|
||||
if (!found) {
|
||||
this.$emit("files", this.data);
|
||||
this.$emit("modalevent", { name: "files", data: this.data });
|
||||
|
||||
Reference in New Issue
Block a user