526 lines
15 KiB
Vue
526 lines
15 KiB
Vue
<template>
|
|
<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">
|
|
<button :class="['button is-primary is-small', loading && 'is-loading']">
|
|
<span class="icon">
|
|
<Icon
|
|
name="material-symbols:upload-rounded"
|
|
:size="18"
|
|
/>
|
|
</span>
|
|
<span>Tải lên từ máy tính</span>
|
|
</button>
|
|
<input
|
|
type="file"
|
|
@change="doChange"
|
|
multiple
|
|
name="resume"
|
|
class="file-input is-clickable"
|
|
:id="docid"
|
|
/>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div :class="['control', !showUrl && 'is-expanded']">
|
|
<button
|
|
class="button is-small"
|
|
@click="displayInput"
|
|
>
|
|
<span class="icon">
|
|
<Icon
|
|
name="material-symbols:link-rounded"
|
|
:size="18"
|
|
/>
|
|
</span>
|
|
<span>Tải lên từ đường dẫn</span>
|
|
</button>
|
|
</div>
|
|
<div
|
|
v-if="showUrl"
|
|
class="field has-addons is-flex-grow-1 mb-0"
|
|
>
|
|
<div class="control">
|
|
<input
|
|
class="input is-small w-60"
|
|
id="url"
|
|
v-model.trim="url"
|
|
type="text"
|
|
placeholder="Nhập đường dẫn"
|
|
/>
|
|
</div>
|
|
<div class="control">
|
|
<button
|
|
class="button is-light is-small rounded-full"
|
|
@click="showUrl = false"
|
|
>
|
|
<span class="icon">
|
|
<Icon
|
|
name="material-symbols:close-rounded"
|
|
:size="18"
|
|
/>
|
|
</span>
|
|
</button>
|
|
</div>
|
|
<div class="control">
|
|
<button
|
|
class="button is-primary is-small"
|
|
:disabled="!url"
|
|
@click="checkUrl"
|
|
>
|
|
<span class="icon">
|
|
<Icon
|
|
name="material-symbols:arrow-forward-rounded"
|
|
:size="20"
|
|
/>
|
|
</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="control has-icons-left">
|
|
<input
|
|
v-model="search"
|
|
type="text"
|
|
@keyup="beginSearch"
|
|
class="input is-small is-rounded w-2xs"
|
|
placeholder="Tìm kiếm"
|
|
/>
|
|
<span
|
|
class="icon is-small is-left"
|
|
style="left: 2px"
|
|
>
|
|
<Icon
|
|
name="material-symbols:search"
|
|
:size="18"
|
|
/>
|
|
</span>
|
|
</div>
|
|
<div class="tabs is-toggle m-0">
|
|
<ul class="is-flex-grow-0 ml-auto">
|
|
<li
|
|
v-for="viewMode in viewModes"
|
|
:key="viewMode.name"
|
|
:class="[viewMode.name === mode && 'is-active']"
|
|
@click="mode = viewMode.name"
|
|
>
|
|
<a class="px-3 py-1">
|
|
<span class="icon m-0">
|
|
<Icon
|
|
:name="viewMode.icon"
|
|
:size="18"
|
|
/>
|
|
</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<DataView
|
|
v-if="mode === 'list'"
|
|
v-bind="vbind"
|
|
class="mt-3"
|
|
/>
|
|
<div v-else>
|
|
<div
|
|
class="fixed-grid has-5-cols"
|
|
v-for="(_, i) in group"
|
|
:key="i"
|
|
>
|
|
<div class="grid">
|
|
<div
|
|
v-for="(k, j) in getData(i)"
|
|
:key="j"
|
|
@mouseover="focus = k"
|
|
@mouseleave="focus = undefined"
|
|
class="cell relative rounded-md is-clipped"
|
|
>
|
|
<figure
|
|
v-if="k.file && type === 'image'"
|
|
class="image is-square mx-auto is-flex is-align-items-center"
|
|
>
|
|
<NuxtImg
|
|
:src="`${path}download?name=${k.file}`"
|
|
:id="'commentImage' + k.id"
|
|
/>
|
|
</figure>
|
|
<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"
|
|
>
|
|
<button
|
|
class="button is-primary"
|
|
@click="selectMedia(k)"
|
|
>
|
|
<span class="icon">
|
|
<Icon
|
|
name="material-symbols:check-rounded"
|
|
:size="18"
|
|
/>
|
|
</span>
|
|
</button>
|
|
</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"
|
|
>
|
|
<button
|
|
class="button is-primary"
|
|
@click="selectMedia(k)"
|
|
>
|
|
<span class="icon">
|
|
<Icon
|
|
name="material-symbols:check-rounded"
|
|
:size="18"
|
|
/>
|
|
</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div
|
|
v-if="focus === k"
|
|
class="absolute top-0 size-full"
|
|
>
|
|
<div class="size-full has-background-black opacity-15"></div>
|
|
<div class="buttons has-addons absolute left-0 right-0 bottom-2 mx-auto w-fit">
|
|
<button
|
|
class="button is-small is-white"
|
|
@click="selectMedia(k)"
|
|
>
|
|
<span class="icon">
|
|
<Icon
|
|
name="material-symbols:check-rounded"
|
|
:size="18"
|
|
/>
|
|
</span>
|
|
</button>
|
|
<button
|
|
class="button is-small is-white"
|
|
@click="editImage(k)"
|
|
>
|
|
<span class="icon">
|
|
<Icon
|
|
name="material-symbols:crop-free-rounded"
|
|
:size="18"
|
|
/>
|
|
</span>
|
|
</button>
|
|
<button
|
|
class="button is-small is-white"
|
|
@click="copyMedia(k)"
|
|
>
|
|
<span class="icon">
|
|
<Icon
|
|
name="material-symbols:content-copy-outline-rounded"
|
|
:size="18"
|
|
/>
|
|
</span>
|
|
</button>
|
|
<button
|
|
class="button is-small is-white"
|
|
@click="deleteMedia(k, 'file')"
|
|
>
|
|
<span class="icon">
|
|
<Icon
|
|
name="material-symbols:delete-outline-rounded"
|
|
:size="18"
|
|
class="has-text-danger"
|
|
/>
|
|
</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<Modal
|
|
v-if="showmodal"
|
|
v-bind="showmodal"
|
|
@close="showmodal = undefined"
|
|
@image="updateImage"
|
|
@files="getFiles"
|
|
/>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
import DataView from "~/components/datatable/DataView.vue";
|
|
|
|
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",
|
|
viewModes: [
|
|
{ name: "list", icon: "material-symbols:format-list-bulleted-rounded" },
|
|
{ name: "image", icon: "material-symbols:grid-view-outline-rounded" },
|
|
],
|
|
timer: undefined,
|
|
vbind: { api: "file", setting: "image-fields", pagename: "image-fields" },
|
|
dataFiles: [],
|
|
files: [],
|
|
docid: this.$id(),
|
|
};
|
|
},
|
|
async created() {
|
|
const fileApi = this.$findapi("file");
|
|
fileApi.params = {
|
|
sort: "-create_time",
|
|
filter: {
|
|
user: this.login.id,
|
|
type__code: this.type,
|
|
},
|
|
};
|
|
const result = await this.$getapi([fileApi]);
|
|
this.data = result[0].data.rows;
|
|
},
|
|
watch: {
|
|
data() {
|
|
this.group = [];
|
|
for (let index = 0; index < this.data.length / (this.type === "video" ? 4 : 5); index++) {
|
|
this.group.push(index);
|
|
}
|
|
},
|
|
},
|
|
computed: {
|
|
login: {
|
|
get() {
|
|
return this.$store.login;
|
|
},
|
|
set(val) {
|
|
this.$store.commit("updateLogin", { login: val });
|
|
},
|
|
},
|
|
media: {
|
|
get() {
|
|
return this.$store.media;
|
|
},
|
|
set(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) {
|
|
const filter = { user: this.login.id, type__code: this.type };
|
|
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);
|
|
},
|
|
async checkUrl() {
|
|
if (this.$empty(this.url)) return this.$snackbar("Đường dẫn không hợp lệ", "Error");
|
|
|
|
try {
|
|
const self = this;
|
|
this.loading = true;
|
|
const res = await $fetch(this.url);
|
|
const reader = new window.FileReader();
|
|
reader.onload = (e) => {
|
|
self.image = e.target.result;
|
|
setTimeout(() => self.doUpload(e.target.result), 100);
|
|
};
|
|
reader.readAsDataURL(res);
|
|
} catch (error) {
|
|
this.$snackbar("Đường dẫn không hợp lệ", "Error");
|
|
console.error(error);
|
|
} finally {
|
|
this.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(blob) {
|
|
const 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", blob);
|
|
form.append("type", "image");
|
|
form.append("size", 100);
|
|
form.append("user", this.$store.login.id);
|
|
|
|
const result = await this.$insertapi("upload", { data: form });
|
|
if (result === "error") return;
|
|
this.updateImage(result.rows[0]);
|
|
this.showUrl = false;
|
|
},
|
|
async uploadImage(file) {
|
|
this.loading = true;
|
|
const result = await this.$insertapi("upload", { data: file.form });
|
|
this.data.splice(0, 0, result.rows[0]);
|
|
this.loading = false;
|
|
},
|
|
getData(i) {
|
|
const 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;
|
|
},
|
|
async selectMedia(v) {
|
|
const copy = this.media ? this.$copy(this.media) : {};
|
|
copy.type = "image";
|
|
copy.open = false;
|
|
copy.select = v;
|
|
this.media = copy;
|
|
const row = this.$copy(v);
|
|
|
|
if (this.source) {
|
|
const res = await $fetch(`${this.path}download/`, {
|
|
params: {
|
|
name: v.file,
|
|
type: "file",
|
|
},
|
|
});
|
|
|
|
const 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 };
|
|
});
|
|
};
|
|
reader.readAsDataURL(res);
|
|
}
|
|
this.$emit("modalevent", { name: "selectimage", data: row });
|
|
},
|
|
async editImage(v) {
|
|
this.loading = true;
|
|
this.selected = v;
|
|
const self = this;
|
|
|
|
const res = await $fetch(`${this.path}download/`, {
|
|
params: {
|
|
name: v.file,
|
|
type: "file",
|
|
},
|
|
});
|
|
|
|
const reader = new window.FileReader();
|
|
reader.onload = (e) => {
|
|
self.image = e.target.result;
|
|
self.showmodal = {
|
|
component: "media/CropImage",
|
|
title: "Cắt hình ảnh",
|
|
width: "90%",
|
|
height: "auto",
|
|
vbind: { selected: self.selected, image: self.image },
|
|
};
|
|
};
|
|
reader.readAsDataURL(res);
|
|
self.loading = false;
|
|
},
|
|
copyMedia(v) {
|
|
this.$copyToClipboard(`${this.path}download/?name=${v.file}`);
|
|
},
|
|
deleteMedia(v, name) {
|
|
let self = this;
|
|
const remove = async function () {
|
|
await self.$deleteapi(name, v.id);
|
|
const 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>
|
|
<style scoped>
|
|
.tabs {
|
|
--bulma-tabs-toggle-link-active-background-color: var(--bulma-link-90);
|
|
--bulma-tabs-toggle-link-active-border-color: var(--bulma-link-90);
|
|
--bulma-tabs-toggle-link-active-color: var(--bulma-link-40);
|
|
}
|
|
</style>
|