chore: install prettier
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user