changes
This commit is contained in:
@@ -132,9 +132,9 @@ $class-types: (
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// ─── Numeric: w-0 → w-48, h-0 → h-48, size-0 → size-48 ───────────────────
|
// ─── Numeric: w-0 → w-96, h-0 → h-96, size-0 → size-96 ───────────────────
|
||||||
@each $prefix, $props in $class-types {
|
@each $prefix, $props in $class-types {
|
||||||
@for $i from 0 through 48 {
|
@for $i from 0 through 96 {
|
||||||
.#{$prefix}-#{$i} {
|
.#{$prefix}-#{$i} {
|
||||||
@include set-props($props, calc(var(--spacing) * #{$i}));
|
@include set-props($props, calc(var(--spacing) * #{$i}));
|
||||||
}
|
}
|
||||||
@@ -341,9 +341,9 @@ $inset-types: (
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// ─── Numeric: 0 → 48, using the same --spacing scale ──────────────────────
|
// ─── Numeric: 0 → 96, using the same --spacing scale ──────────────────────
|
||||||
@each $prefix, $props in $inset-types {
|
@each $prefix, $props in $inset-types {
|
||||||
@for $i from 0 through 48 {
|
@for $i from 0 through 96 {
|
||||||
.#{$prefix}-#{$i} {
|
.#{$prefix}-#{$i} {
|
||||||
@include set-props($props, calc(var(--spacing) * #{$i}));
|
@include set-props($props, calc(var(--spacing) * #{$i}));
|
||||||
}
|
}
|
||||||
@@ -375,3 +375,98 @@ $inset-keywords: (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Max/Min width & height ────────────────────────────────────────────────
|
||||||
|
$minmax-types: (
|
||||||
|
"max-w": (
|
||||||
|
max-width,
|
||||||
|
),
|
||||||
|
"min-w": (
|
||||||
|
min-width,
|
||||||
|
),
|
||||||
|
"max-h": (
|
||||||
|
max-height,
|
||||||
|
),
|
||||||
|
"min-h": (
|
||||||
|
min-height,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// ─── Numeric: 0 → 96, using --spacing scale ───────────────────────────────
|
||||||
|
@each $prefix, $props in $minmax-types {
|
||||||
|
@for $i from 0 through 96 {
|
||||||
|
.#{$prefix}-#{$i} {
|
||||||
|
@include set-props($props, calc(var(--spacing) * #{$i}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Fractions ─────────────────────────────────────────────────────────────
|
||||||
|
@each $prefix, $props in $minmax-types {
|
||||||
|
@each $name, $pair in $fractions {
|
||||||
|
$num: list.nth($pair, 1);
|
||||||
|
$den: list.nth($pair, 2);
|
||||||
|
.#{$prefix}-#{$name} {
|
||||||
|
@include set-props($props, calc(#{$num} / #{$den} * 100%));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Shared keywords ───────────────────────────────────────────────────────
|
||||||
|
@each $prefix, $props in $minmax-types {
|
||||||
|
@each $name, $value in $shared-keywords {
|
||||||
|
.#{$prefix}-#{$name} {
|
||||||
|
@include set-props($props, $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Viewport keywords ─────────────────────────────────────────────────────
|
||||||
|
@each $prefix, $props in $minmax-types {
|
||||||
|
@each $name, $value in $viewport-keywords {
|
||||||
|
.#{$prefix}-#{$name} {
|
||||||
|
@include set-props($props, $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Container sizes (max-w- / min-w- only) ───────────────────────────────
|
||||||
|
@each $name in $containers {
|
||||||
|
.max-w-#{$name} {
|
||||||
|
max-width: var(--container-#{$name});
|
||||||
|
}
|
||||||
|
.min-w-#{$name} {
|
||||||
|
min-width: var(--container-#{$name});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Screen ────────────────────────────────────────────────────────────────
|
||||||
|
.max-w-screen {
|
||||||
|
max-width: 100vw;
|
||||||
|
}
|
||||||
|
.min-w-screen {
|
||||||
|
min-width: 100vw;
|
||||||
|
}
|
||||||
|
.max-h-screen {
|
||||||
|
max-height: 100vh;
|
||||||
|
}
|
||||||
|
.min-h-screen {
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── none (max only) ───────────────────────────────────────────────────────
|
||||||
|
.max-w-none {
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
.max-h-none {
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Opacity ───────────────────────────────────────────────────────────────
|
||||||
|
$opacity-values: (0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100);
|
||||||
|
|
||||||
|
@each $val in $opacity-values {
|
||||||
|
.opacity-#{$val} {
|
||||||
|
opacity: calc($val / 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,33 +1,59 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div class="block rounded-md is-clipped">
|
||||||
<video
|
<video
|
||||||
ref="video"
|
ref="video"
|
||||||
id="video"
|
|
||||||
width="640"
|
|
||||||
height="480"
|
height="480"
|
||||||
autoplay
|
autoplay
|
||||||
|
class="w-full is-block"
|
||||||
></video>
|
></video>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2">
|
<div class="fixed-grid has-12-cols mb-0">
|
||||||
<button
|
<div class="grid">
|
||||||
class="button is-primary"
|
<div class="cell is-flex is-justify-content-center is-align-items-center">
|
||||||
id="snap"
|
<button
|
||||||
v-on:click="capture()"
|
@click="$emit('close')"
|
||||||
>
|
class="button is-light rounded-full"
|
||||||
Chụp ảnh
|
>
|
||||||
</button>
|
<span class="icon">
|
||||||
<a
|
<Icon
|
||||||
class="ml-6"
|
name="material-symbols:close-rounded"
|
||||||
@click="switchView()"
|
:size="20"
|
||||||
>
|
/>
|
||||||
<SvgIcon v-bind="{ name: 'camera_switch.svg', type: 'black', size: 40 }"></SvgIcon>
|
</span>
|
||||||
</a>
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="cell is-col-span-10 has-text-centered">
|
||||||
|
<button
|
||||||
|
@click="capture"
|
||||||
|
class="button is-primary is-medium rounded-full"
|
||||||
|
>
|
||||||
|
<span class="icon">
|
||||||
|
<Icon
|
||||||
|
name="material-symbols:photo-camera-outline-rounded"
|
||||||
|
:size="24"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="cell is-flex is-justify-content-center is-align-items-center">
|
||||||
|
<button
|
||||||
|
@click="flip"
|
||||||
|
class="button is-light is-primary rounded-full"
|
||||||
|
>
|
||||||
|
<span class="icon">
|
||||||
|
<Icon
|
||||||
|
name="material-symbols:flip-camera-ios-outline-rounded"
|
||||||
|
:size="20"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<canvas
|
<canvas
|
||||||
v-show="false"
|
v-show="false"
|
||||||
ref="canvas"
|
ref="canvas"
|
||||||
id="canvas"
|
|
||||||
width="640"
|
width="640"
|
||||||
height="480"
|
height="480"
|
||||||
></canvas>
|
></canvas>
|
||||||
@@ -35,14 +61,14 @@
|
|||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
data: function () {
|
data() {
|
||||||
return {
|
return {
|
||||||
video: {},
|
video: {},
|
||||||
canvas: {},
|
canvas: {},
|
||||||
current: "front",
|
current: "front",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted: function () {
|
mounted() {
|
||||||
this.openCamera();
|
this.openCamera();
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
@@ -54,46 +80,61 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
openCamera() {
|
openCamera() {
|
||||||
let f = this.current === "front" ? { facingMode: "user" } : { facingMode: { exact: "environment" } };
|
|
||||||
this.video = this.$refs.video;
|
this.video = this.$refs.video;
|
||||||
|
const facingConstraint = {
|
||||||
|
facingMode: this.current === "front" ? "user" : { exact: "environment" },
|
||||||
|
};
|
||||||
|
|
||||||
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
||||||
navigator.mediaDevices.getUserMedia({ video: f, audio: false }).then((stream) => {
|
navigator.mediaDevices
|
||||||
video.srcObject = stream;
|
.getUserMedia({ video: facingConstraint, audio: false })
|
||||||
this.video.play();
|
.then((stream) => {
|
||||||
});
|
this.video.srcObject = stream;
|
||||||
|
this.video.play();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
if (this.current === "back" && (err.name === "OverconstrainedError" || err.name === "NotFoundError")) {
|
||||||
|
this.current = "front";
|
||||||
|
this.$snackbar("Không tìm thấy camera sau", "Error");
|
||||||
|
this.openCamera(); // retry front cam
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
capture() {
|
capture() {
|
||||||
this.canvas = this.$refs.canvas;
|
this.canvas = this.$refs.canvas;
|
||||||
let scale = 600 / this.video.videoWidth;
|
const scale = 600 / this.video.videoWidth;
|
||||||
let w = this.video.videoWidth * scale;
|
const w = this.video.videoWidth * scale;
|
||||||
let h = this.video.videoHeight * scale;
|
const h = this.video.videoHeight * scale;
|
||||||
this.canvas.width = w;
|
this.canvas.width = w;
|
||||||
this.canvas.height = h;
|
this.canvas.height = h;
|
||||||
var context = this.canvas.getContext("2d").drawImage(this.video, 0, 0, w, h);
|
this.canvas.getContext("2d").drawImage(this.video, 0, 0, w, h);
|
||||||
this.canvas.toBlob((blod) => this.saveAs(blod));
|
this.canvas.toBlob((blob) => this.saveAs(blob));
|
||||||
},
|
},
|
||||||
async saveAs(blod) {
|
async saveAs(blob) {
|
||||||
var form = new FormData();
|
const form = new FormData();
|
||||||
let name = `${this.$id()}.png`;
|
const name = `${this.$id()}.png`;
|
||||||
this.fileName = `${this.$dayjs(new Date()).format("YYYYMMDDhhmmss")}-${name}`;
|
this.fileName = `${this.$dayjs(new Date()).format("YYYYMMDDhhmmss")}-${name}`;
|
||||||
|
|
||||||
form.append("filename", this.fileName);
|
form.append("filename", this.fileName);
|
||||||
form.append("name", name);
|
form.append("name", name);
|
||||||
form.append("file", blod);
|
form.append("file", blob);
|
||||||
form.append("type", "image");
|
form.append("type", "image");
|
||||||
form.append("size", 100);
|
form.append("size", 100);
|
||||||
form.append("user", this.$store.state.login.id);
|
form.append("user", this.$store.login.id || 1);
|
||||||
let result = await this.$insertapi("upload", { data: form });
|
|
||||||
|
const result = await this.$insertapi("upload", { data: form });
|
||||||
if (result === "error") return;
|
if (result === "error") return;
|
||||||
let row = result.rows[0];
|
|
||||||
const file = new File([blod], name, { type: "image/png" });
|
const row = result.rows[0];
|
||||||
row.source = { file: file };
|
const file = new File([blob], name, { type: "image/png" });
|
||||||
|
row.source = { file };
|
||||||
this.$emit("modalevent", { name: "selectimage", data: row });
|
this.$emit("modalevent", { name: "selectimage", data: row });
|
||||||
this.$emit("close");
|
this.$emit("close");
|
||||||
},
|
},
|
||||||
switchView() {
|
flip() {
|
||||||
this.current = this.current === "front" ? "back" : "front";
|
this.current = this.current === "front" ? "back" : "front";
|
||||||
var vidTrack = this.video.srcObject.getVideoTracks();
|
const vidTrack = this.video.srcObject.getVideoTracks();
|
||||||
vidTrack.forEach((track) => {
|
vidTrack.forEach((track) => {
|
||||||
track.stop();
|
track.stop();
|
||||||
track.enabled = false;
|
track.enabled = false;
|
||||||
|
|||||||
@@ -1,66 +1,97 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="tile is-ancestor py-5 px-3 mx-0"
|
|
||||||
v-if="image"
|
v-if="image"
|
||||||
|
class="fixed-grid has-12-cols"
|
||||||
>
|
>
|
||||||
<div class="tile is-1" />
|
<div class="grid is-gap-2">
|
||||||
<div class="tile is-7">
|
<div class="cell is-col-span-8">
|
||||||
<Cropper
|
<Cropper
|
||||||
ref="cropper"
|
ref="cropper"
|
||||||
:src="image"
|
:src="image"
|
||||||
@change="onChange"
|
:canvas="false"
|
||||||
:stencil-props="getRatio"
|
@change="onChange"
|
||||||
/>
|
:stencil-props="getRatio"
|
||||||
</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 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>
|
|
||||||
<div class="block mt-5">
|
|
||||||
<b-radio
|
|
||||||
v-model="radio"
|
|
||||||
native-value="replace"
|
|
||||||
>
|
|
||||||
Ghi đè
|
|
||||||
</b-radio>
|
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="cell is-col-span-4">
|
||||||
class="is-italic has-text-grey"
|
<div
|
||||||
v-else
|
v-if="avatar"
|
||||||
>
|
class="is-italic has-text-grey"
|
||||||
* Di chuyển khung để chọn hình ảnh phù hợp
|
>
|
||||||
|
* Di chuyển khung để chọn hình ảnh phù hợp
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<p class="label">Chọn tỷ lệ:</p>
|
||||||
|
<div class="fixed-grid has-12-cols">
|
||||||
|
<div class="grid is-gap-0.5">
|
||||||
|
<div
|
||||||
|
class="cell is-col-span-3"
|
||||||
|
v-for="ratio in ratios"
|
||||||
|
:key="ratio"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
:class="['button is-fullwidth is-small', curRatio === ratio ? 'is-primary' : 'is-light']"
|
||||||
|
@click="curRatio = ratio"
|
||||||
|
>
|
||||||
|
{{ ratio }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div
|
||||||
|
v-if="coordinates"
|
||||||
|
class="fixed-grid has-3-cols is-family-monospace"
|
||||||
|
>
|
||||||
|
<div class="grid">
|
||||||
|
<div class="cell p-2 rounded has-background-white-ter has-text-centered">
|
||||||
|
<p class="fs-11 is-uppercase has-text-grey">width</p>
|
||||||
|
<p class="font-semibold">{{ coordinates.width }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="cell p-2 rounded has-background-white-ter has-text-centered">
|
||||||
|
<p class="fs-11 is-uppercase has-text-grey">height</p>
|
||||||
|
<p class="font-semibold">{{ coordinates.height }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="cell p-2 rounded has-background-white-ter has-text-centered">
|
||||||
|
<p class="fs-11 is-uppercase has-text-grey">ratio</p>
|
||||||
|
<p class="font-semibold">{{ coordinates.ratio }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="block is-flex is-gap-1 is-flex-wrap-wrap">
|
||||||
|
<button
|
||||||
|
v-for="v in [
|
||||||
|
{ name: 'Ghi đè', value: 'replace' },
|
||||||
|
{ name: 'Tạo file mới', value: 'new' },
|
||||||
|
]"
|
||||||
|
:key="v.value"
|
||||||
|
@click="radio = v.value"
|
||||||
|
:class="['button fs-14', radio === v.value ? 'is-primary is-light' : 'is-white']"
|
||||||
|
>
|
||||||
|
<span class="icon">
|
||||||
|
<Icon
|
||||||
|
:name="
|
||||||
|
radio === v.value
|
||||||
|
? 'material-symbols:radio-button-checked-outline'
|
||||||
|
: 'material-symbols:radio-button-unchecked'
|
||||||
|
"
|
||||||
|
:size="18"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span>{{ v.name }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="block">
|
||||||
|
<button
|
||||||
|
class="button is-primary"
|
||||||
|
:class="loading && 'is-loading'"
|
||||||
|
@click="updateImage"
|
||||||
|
>
|
||||||
|
Lưu lại
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -68,6 +99,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { CircleStencil, Cropper } from "vue-advanced-cropper";
|
import { CircleStencil, Cropper } from "vue-advanced-cropper";
|
||||||
import "vue-advanced-cropper/dist/style.css";
|
import "vue-advanced-cropper/dist/style.css";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Cropper,
|
Cropper,
|
||||||
@@ -78,61 +110,67 @@ export default {
|
|||||||
return {
|
return {
|
||||||
coordinates: undefined,
|
coordinates: undefined,
|
||||||
ratios: [
|
ratios: [
|
||||||
{ k: "1/1" },
|
"1/1",
|
||||||
{ k: "5/4" },
|
"5/4",
|
||||||
{ k: "4/3" },
|
"4/3",
|
||||||
{ k: "3/2" },
|
"3/2",
|
||||||
{ k: "5/3" },
|
"5/3",
|
||||||
{ k: "16/9" },
|
"16/9",
|
||||||
{ k: "2/1" },
|
"2/1",
|
||||||
{ k: "3/1" },
|
"3/1",
|
||||||
{ k: "4/5" },
|
"4/5",
|
||||||
{ k: "3/4" },
|
"3/4",
|
||||||
{ k: "2/3" },
|
"2/3",
|
||||||
{ k: "3/5" },
|
"3/5",
|
||||||
{ k: "9/16" },
|
"9/16",
|
||||||
{ k: "1/2" },
|
"1/2",
|
||||||
{ k: "1/3" },
|
"1/3",
|
||||||
],
|
],
|
||||||
curRatio: { k: "1/1" },
|
curRatio: "1/1",
|
||||||
radio: this.avatar ? "new" : "replace",
|
radio: this.avatar ? "new" : "replace",
|
||||||
rectangle: true,
|
|
||||||
loading: false,
|
loading: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
getRatio() {
|
getRatio() {
|
||||||
return { aspectRatio: this.$calc(this.curRatio.k) };
|
return { aspectRatio: this.$calc(this.curRatio) };
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onChange({ coordinates, canvas }) {
|
onChange({ coordinates }) {
|
||||||
this.coordinates = coordinates;
|
this.coordinates = coordinates;
|
||||||
this.coordinates.ratio = ((this.coordinates.width * 1.0) / this.coordinates.height).toFixed(2);
|
this.coordinates.ratio = (this.coordinates.width / this.coordinates.height).toFixed(2);
|
||||||
},
|
},
|
||||||
updateImage() {
|
updateImage() {
|
||||||
const { canvas } = this.$refs.cropper.getResult();
|
const { canvas } = this.$refs.cropper.getResult();
|
||||||
if (canvas) canvas.toBlob((blod) => this.saveAs(blod));
|
if (canvas) canvas.toBlob((blob) => this.saveAs(blob));
|
||||||
},
|
},
|
||||||
async saveAs(blod) {
|
async saveAs(blob) {
|
||||||
this.loading = true;
|
try {
|
||||||
var form = new FormData();
|
this.loading = true;
|
||||||
let name =
|
const form = new FormData();
|
||||||
this.selected.file.indexOf("-") > 0
|
const name =
|
||||||
? this.selected.file.substring(15, this.selected.file.length)
|
this.selected.file.indexOf("-") > 0
|
||||||
: this.selected.file;
|
? this.selected.file.substring(15, this.selected.file.length)
|
||||||
this.fileName = this.$dayjs(new Date()).format("YYYYMMDDhhmmss") + "-" + name;
|
: this.selected.file;
|
||||||
if (this.radio === "replace") this.fileName = this.selected.file;
|
this.fileName = this.$dayjs(new Date()).format("YYYYMMDDhhmmss") + "-" + name;
|
||||||
form.append("filename", this.fileName);
|
if (this.radio === "replace") this.fileName = this.selected.file;
|
||||||
form.append("name", name);
|
|
||||||
form.append("file", blod);
|
form.append("filename", this.fileName);
|
||||||
form.append("type", "file");
|
form.append("name", name);
|
||||||
form.append("size", this.selected.size);
|
form.append("file", blob);
|
||||||
form.append("user", this.$store.state.login.id);
|
form.append("type", "file");
|
||||||
let result = await this.$insertapi("upload", { data: form });
|
form.append("size", this.selected.size);
|
||||||
this.loading = false;
|
form.append("user", this.$store.login.id);
|
||||||
if (result === "error") return;
|
|
||||||
this.$emit("modalevent", { name: "image", data: result.rows[0] });
|
const result = await this.$insertapi("upload", { data: form });
|
||||||
|
if (result === "error") return;
|
||||||
|
this.$emit("modalevent", { name: "image", data: result.rows[0] });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,31 +1,31 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="files">
|
<div v-if="files">
|
||||||
<div
|
<div
|
||||||
class="has-text-right"
|
|
||||||
v-if="!hideopt && $getEditRights()"
|
v-if="!hideopt && $getEditRights()"
|
||||||
|
class="has-text-right"
|
||||||
>
|
>
|
||||||
<FileUpload
|
<FileUpload
|
||||||
v-bind="{ type: ['file'], position }"
|
v-bind="{ type: ['file'], position }"
|
||||||
@files="getFiles"
|
@files="getFiles"
|
||||||
></FileUpload>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
|
v-if="files.length === 0 && info"
|
||||||
class="mt-3 has-text-grey fs-15"
|
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
|
Chưa có tài liệu được tải lên
|
||||||
</div>
|
</div>
|
||||||
<DataView
|
<DataView
|
||||||
v-bind="vbind"
|
|
||||||
v-else-if="vbind"
|
v-else-if="vbind"
|
||||||
></DataView>
|
v-bind="vbind"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Modal
|
<Modal
|
||||||
@close="showmodal = undefined"
|
|
||||||
v-bind="showmodal"
|
|
||||||
v-if="showmodal"
|
v-if="showmodal"
|
||||||
></Modal>
|
v-bind="showmodal"
|
||||||
|
@close="showmodal = undefined"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -10,144 +10,147 @@
|
|||||||
<div class="control">
|
<div class="control">
|
||||||
<div class="file is-primary">
|
<div class="file is-primary">
|
||||||
<label class="file-label">
|
<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
|
<input
|
||||||
class="file-input"
|
|
||||||
type="file"
|
type="file"
|
||||||
:id="docid"
|
@change="doChange"
|
||||||
multiple
|
multiple
|
||||||
name="resume"
|
name="resume"
|
||||||
@change="doChange"
|
class="file-input is-clickable"
|
||||||
|
:id="docid"
|
||||||
/>
|
/>
|
||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div :class="`control ${showUrl ? '' : 'is-expanded'}`">
|
<div :class="['control', !showUrl && 'is-expanded']">
|
||||||
<a
|
<button
|
||||||
class="button is-dark is-rounded is-small"
|
class="button is-small"
|
||||||
@click="displayInput()"
|
@click="displayInput"
|
||||||
>
|
>
|
||||||
<SvgIcon v-bind="{ name: 'add5.svg', type: 'white', size: 18 }"></SvgIcon>
|
<span class="icon">
|
||||||
<span class="fs-14 ml-1">Tải lên từ đường dẫn</span>
|
<Icon
|
||||||
</a>
|
name="material-symbols:link-rounded"
|
||||||
|
:size="18"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span>Tải lên từ đường dẫn</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="control is-expanded"
|
|
||||||
v-if="showUrl"
|
v-if="showUrl"
|
||||||
|
class="field has-addons is-flex-grow-1 mb-0"
|
||||||
>
|
>
|
||||||
<input
|
<div class="control">
|
||||||
class="input is-small is-rounded"
|
<input
|
||||||
id="url"
|
class="input is-small w-60"
|
||||||
v-model="url"
|
id="url"
|
||||||
style="width: 250px"
|
v-model.trim="url"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Nhập đường dẫn vào đây"
|
placeholder="Nhập đường dẫn"
|
||||||
/>
|
/>
|
||||||
<a
|
</div>
|
||||||
class="button is-primary is-small px-4 ml-3"
|
<div class="control">
|
||||||
@click="checkUrl()"
|
<button
|
||||||
>
|
class="button is-light is-small rounded-full"
|
||||||
<SvgIcon v-bind="{ name: 'upload.svg', type: 'white', size: 22 }"></SvgIcon>
|
@click="showUrl = false"
|
||||||
</a>
|
>
|
||||||
<a
|
<span class="icon">
|
||||||
class="ml-4"
|
<Icon
|
||||||
@click="showUrl = false"
|
name="material-symbols:close-rounded"
|
||||||
>
|
:size="18"
|
||||||
<SvgIcon v-bind="{ name: 'close.svg', type: 'dark', size: 22 }"></SvgIcon>
|
/>
|
||||||
</a>
|
</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>
|
||||||
<div class="control">
|
<div class="control has-icons-left">
|
||||||
<input
|
<input
|
||||||
class="input is-small is-rounded"
|
|
||||||
v-model="search"
|
v-model="search"
|
||||||
style="width: 250px"
|
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Tìm kiếm"
|
|
||||||
@keyup="beginSearch"
|
@keyup="beginSearch"
|
||||||
|
class="input is-small is-rounded w-2xs"
|
||||||
|
placeholder="Tìm kiếm"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div class="control">
|
|
||||||
<span
|
<span
|
||||||
class="is-clickable"
|
class="icon is-small is-left"
|
||||||
@click="mode = 'image'"
|
style="left: 2px"
|
||||||
>
|
>
|
||||||
<SvgIcon v-bind="{ name: 'image3.svg', type: 'dark', size: 25 }"></SvgIcon>
|
<Icon
|
||||||
|
name="material-symbols:search"
|
||||||
|
:size="18"
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="control">
|
<div class="tabs is-toggle m-0">
|
||||||
<span
|
<ul class="is-flex-grow-0 ml-auto">
|
||||||
class="is-clickable"
|
<li
|
||||||
@click="mode = 'list'"
|
v-for="viewMode in viewModes"
|
||||||
>
|
:key="viewMode.name"
|
||||||
<SvgIcon v-bind="{ name: 'list.png', type: 'dark', size: 25 }"></SvgIcon>
|
:class="[viewMode.name === mode && 'is-active']"
|
||||||
</span>
|
@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>
|
||||||
</div>
|
</div>
|
||||||
<DataView
|
<DataView
|
||||||
class="mt-3"
|
|
||||||
v-bind="vbind"
|
|
||||||
v-if="mode === 'list'"
|
v-if="mode === 'list'"
|
||||||
></DataView>
|
v-bind="vbind"
|
||||||
<div
|
class="mt-3"
|
||||||
class="tile is-ancestor mx-0 px-0 pt-3"
|
/>
|
||||||
v-else
|
<div v-else>
|
||||||
>
|
<div
|
||||||
<div class="tile is-vertical">
|
class="fixed-grid has-5-cols"
|
||||||
<div
|
v-for="(_, i) in group"
|
||||||
class="tile is-parent"
|
:key="i"
|
||||||
v-for="(v, i) in group"
|
>
|
||||||
:key="i"
|
<div class="grid">
|
||||||
>
|
<div
|
||||||
<article
|
|
||||||
class="tile is-child"
|
|
||||||
v-for="(k, j) in getData(i)"
|
v-for="(k, j) in getData(i)"
|
||||||
:key="j"
|
:key="j"
|
||||||
@mouseover="focus = k"
|
@mouseover="focus = k"
|
||||||
|
@mouseleave="focus = undefined"
|
||||||
|
class="cell relative rounded-md is-clipped"
|
||||||
>
|
>
|
||||||
<div
|
<figure
|
||||||
class="image px-2 pb-2"
|
|
||||||
v-if="k.file && type === 'image'"
|
v-if="k.file && type === 'image'"
|
||||||
|
class="image is-square mx-auto is-flex is-align-items-center"
|
||||||
>
|
>
|
||||||
<NuxtImg
|
<NuxtImg
|
||||||
:src="`${path}download?name=${k.file}`"
|
:src="`${path}download?name=${k.file}`"
|
||||||
:id="'commentImage' + k.id"
|
:id="'commentImage' + k.id"
|
||||||
/>
|
/>
|
||||||
<div
|
</figure>
|
||||||
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
|
<div
|
||||||
class="ml-2 mr-2"
|
class="ml-2 mr-2"
|
||||||
v-else-if="k.file && type === 'video'"
|
v-else-if="k.file && type === 'video'"
|
||||||
@@ -164,12 +167,17 @@
|
|||||||
class="mt-2"
|
class="mt-2"
|
||||||
v-if="focus === k"
|
v-if="focus === k"
|
||||||
>
|
>
|
||||||
<a
|
<button
|
||||||
class="button is-primary"
|
class="button is-primary"
|
||||||
@click="selectMedia(k)"
|
@click="selectMedia(k)"
|
||||||
>
|
>
|
||||||
<SvgIcon v-bind="{ name: 'check3.svg', type: 'primary', size: 22 }"></SvgIcon>
|
<span class="icon">
|
||||||
</a>
|
<Icon
|
||||||
|
name="material-symbols:check-rounded"
|
||||||
|
:size="18"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</vue-plyr>
|
</vue-plyr>
|
||||||
</div>
|
</div>
|
||||||
@@ -182,28 +190,88 @@
|
|||||||
class="mt-2"
|
class="mt-2"
|
||||||
v-if="focus === k"
|
v-if="focus === k"
|
||||||
>
|
>
|
||||||
<a
|
<button
|
||||||
class="button is-primary"
|
class="button is-primary"
|
||||||
@click="selectMedia(k)"
|
@click="selectMedia(k)"
|
||||||
>
|
>
|
||||||
<SvgIcon v-bind="{ name: 'check3.svg', type: 'primary', size: 22 }"></SvgIcon>
|
<span class="icon">
|
||||||
</a>
|
<Icon
|
||||||
|
name="material-symbols:check-rounded"
|
||||||
|
:size="18"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Modal
|
<Modal
|
||||||
@close="showmodal = undefined"
|
v-if="showmodal"
|
||||||
v-bind="showmodal"
|
v-bind="showmodal"
|
||||||
|
@close="showmodal = undefined"
|
||||||
@image="updateImage"
|
@image="updateImage"
|
||||||
@files="getFiles"
|
@files="getFiles"
|
||||||
v-if="showmodal"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
import DataView from "~/components/datatable/DataView.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ["source"],
|
props: ["source"],
|
||||||
data() {
|
data() {
|
||||||
@@ -222,6 +290,10 @@ export default {
|
|||||||
url: undefined,
|
url: undefined,
|
||||||
search: undefined,
|
search: undefined,
|
||||||
mode: "image",
|
mode: "image",
|
||||||
|
viewModes: [
|
||||||
|
{ name: "list", icon: "material-symbols:format-list-bulleted-rounded" },
|
||||||
|
{ name: "image", icon: "material-symbols:grid-view-outline-rounded" },
|
||||||
|
],
|
||||||
timer: undefined,
|
timer: undefined,
|
||||||
vbind: { api: "file", setting: "image-fields" },
|
vbind: { api: "file", setting: "image-fields" },
|
||||||
dataFiles: [],
|
dataFiles: [],
|
||||||
@@ -230,13 +302,15 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
async created() {
|
async created() {
|
||||||
let found = this.$findapi("file");
|
const fileApi = this.$findapi("file");
|
||||||
found.params = {
|
fileApi.params = {
|
||||||
filter: { user: this.login.id },
|
|
||||||
sort: "-create_time",
|
sort: "-create_time",
|
||||||
type__code: this.type,
|
filter: {
|
||||||
|
user: this.login.id,
|
||||||
|
type__code: this.type,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
let result = await this.$getapi([found]);
|
const result = await this.$getapi([fileApi]);
|
||||||
this.data = result[0].data.rows;
|
this.data = result[0].data.rows;
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -298,7 +372,7 @@ export default {
|
|||||||
this.timer = setTimeout(() => this.startSearch(e.target.value), 150);
|
this.timer = setTimeout(() => this.startSearch(e.target.value), 150);
|
||||||
},
|
},
|
||||||
async startSearch(value) {
|
async startSearch(value) {
|
||||||
const filter = { user: this.login.id };
|
const filter = { user: this.login.id, type__code: this.type };
|
||||||
if (!this.$empty(value)) filter.name__icontains = value.toLowerCase();
|
if (!this.$empty(value)) filter.name__icontains = value.toLowerCase();
|
||||||
this.data = await this.$getdata("file", { filter });
|
this.data = await this.$getdata("file", { filter });
|
||||||
},
|
},
|
||||||
@@ -307,29 +381,25 @@ export default {
|
|||||||
this.url = undefined;
|
this.url = undefined;
|
||||||
setTimeout(() => document.getElementById("url").focus(), 100);
|
setTimeout(() => document.getElementById("url").focus(), 100);
|
||||||
},
|
},
|
||||||
checkUrl() {
|
async checkUrl() {
|
||||||
if (this.$empty(this.url)) return this.$snackbar("Đường dẫn không hợp lệ", "Error");
|
if (this.$empty(this.url)) return this.$snackbar("Đường dẫn không hợp lệ", "Error");
|
||||||
let self = this;
|
|
||||||
this.loading = true;
|
try {
|
||||||
this.$axios
|
const self = this;
|
||||||
.get(this.url, { responseType: "blob" })
|
this.loading = true;
|
||||||
.then(function (response) {
|
const res = await $fetch(this.url);
|
||||||
var reader = new window.FileReader();
|
const reader = new window.FileReader();
|
||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
self.image = e.target.result;
|
self.image = e.target.result;
|
||||||
setTimeout(() => self.doUpload(e.target.result), 100);
|
setTimeout(() => self.doUpload(e.target.result), 100);
|
||||||
};
|
};
|
||||||
reader.readAsDataURL(response.data);
|
reader.readAsDataURL(res);
|
||||||
self.loading = false;
|
} catch (error) {
|
||||||
})
|
this.$snackbar("Đường dẫn không hợp lệ", "Error");
|
||||||
.catch((e) => {
|
console.error(error);
|
||||||
self.$buefy.toast.open({
|
} finally {
|
||||||
duration: 3000,
|
this.loading = false;
|
||||||
message: `Đường dẫn không hợp lệ`,
|
}
|
||||||
type: "is-danger",
|
|
||||||
});
|
|
||||||
self.loading = false;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
doUpload() {
|
doUpload() {
|
||||||
const image = document.getElementById("image");
|
const image = document.getElementById("image");
|
||||||
@@ -340,102 +410,103 @@ export default {
|
|||||||
ctx.drawImage(image, 0, 0);
|
ctx.drawImage(image, 0, 0);
|
||||||
if (canvas) canvas.toBlob((blod) => this.saveAs(blod));
|
if (canvas) canvas.toBlob((blod) => this.saveAs(blod));
|
||||||
},
|
},
|
||||||
async saveAs(blod) {
|
async saveAs(blob) {
|
||||||
var form = new FormData();
|
const form = new FormData();
|
||||||
const fileName = this.$dayjs(new Date()).format("YYYYMMDDhhmmss") + "-" + this.$id() + ".png";
|
const fileName = this.$dayjs(new Date()).format("YYYYMMDDhhmmss") + "-" + this.$id() + ".png";
|
||||||
|
|
||||||
form.append("filename", fileName);
|
form.append("filename", fileName);
|
||||||
form.append("name", `${this.$id()}.png`);
|
form.append("name", `${this.$id()}.png`);
|
||||||
form.append("file", blod);
|
form.append("file", blob);
|
||||||
form.append("type", "image");
|
form.append("type", "image");
|
||||||
form.append("size", 100);
|
form.append("size", 100);
|
||||||
form.append("user", this.$store.login.id);
|
form.append("user", this.$store.login.id);
|
||||||
let result = await this.$insertapi("upload", { data: form });
|
|
||||||
|
const result = await this.$insertapi("upload", { data: form });
|
||||||
if (result === "error") return;
|
if (result === "error") return;
|
||||||
this.updateImage(result.rows[0]);
|
this.updateImage(result.rows[0]);
|
||||||
this.showUrl = false;
|
this.showUrl = false;
|
||||||
},
|
},
|
||||||
async uploadImage(file) {
|
async uploadImage(file) {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
let result = await this.$insertapi("upload", { data: file.form });
|
const result = await this.$insertapi("upload", { data: file.form });
|
||||||
this.data.splice(0, 0, result.rows[0]);
|
this.data.splice(0, 0, result.rows[0]);
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
},
|
},
|
||||||
getData(i) {
|
getData(i) {
|
||||||
if (this.type === "video") {
|
const list = this.data.slice(i * 5, i * 5 + 5);
|
||||||
var list = this.data.slice(i * 5, i * 5 + 5);
|
for (let index = 0; index < 5; index++) {
|
||||||
for (let index = 0; index < 5; index++) {
|
if (list.length < index + 1) list.push({ file: undefined });
|
||||||
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 });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
},
|
},
|
||||||
selectMedia(v) {
|
async selectMedia(v) {
|
||||||
let copy = this.media ? this.$copy(this.media) : {};
|
const copy = this.media ? this.$copy(this.media) : {};
|
||||||
copy.type = "image";
|
copy.type = "image";
|
||||||
copy.open = false;
|
copy.open = false;
|
||||||
copy.select = v;
|
copy.select = v;
|
||||||
this.media = copy;
|
this.media = copy;
|
||||||
let row = this.$copy(v);
|
const row = this.$copy(v);
|
||||||
|
|
||||||
if (this.source) {
|
if (this.source) {
|
||||||
let params = { name: v.file, type: "file" };
|
const res = await $fetch(`${this.path}download/`, {
|
||||||
this.$axios
|
params: {
|
||||||
.get(`${this.path}download/`, {
|
name: v.file,
|
||||||
params: params,
|
type: "file",
|
||||||
responseType: "blob",
|
},
|
||||||
})
|
});
|
||||||
.then(function (response) {
|
|
||||||
var reader = new window.FileReader();
|
const reader = new window.FileReader();
|
||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
fetch(e.target.result)
|
fetch(e.target.result)
|
||||||
.then((res) => res.blob())
|
.then((res) => res.blob())
|
||||||
.then((blob) => {
|
.then((blob) => {
|
||||||
const file = new File([blob], v.name, { type: "image/png" });
|
const file = new File([blob], v.name, { type: "image/png" });
|
||||||
row.source = { file: file };
|
row.source = { file };
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
reader.readAsDataURL(response.data);
|
reader.readAsDataURL(res);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
this.$emit("modalevent", { name: "selectimage", data: row });
|
this.$emit("modalevent", { name: "selectimage", data: row });
|
||||||
},
|
},
|
||||||
editImage(v) {
|
async editImage(v) {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.selected = v;
|
this.selected = v;
|
||||||
let self = this;
|
const self = this;
|
||||||
let params = { name: v.file, type: "file" };
|
|
||||||
this.$axios.get(`${this.path}download/`, { params: params, responseType: "blob" }).then(function (response) {
|
const res = await $fetch(`${this.path}download/`, {
|
||||||
var reader = new window.FileReader();
|
params: {
|
||||||
reader.onload = (e) => {
|
name: v.file,
|
||||||
self.image = e.target.result;
|
type: "file",
|
||||||
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;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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) {
|
copyMedia(v) {
|
||||||
this.$copyToClipboard(`${this.path}download/?name=${v.file}`);
|
this.$copyToClipboard(`${this.path}download/?name=${v.file}`);
|
||||||
},
|
},
|
||||||
deleteMedia(v, name) {
|
deleteMedia(v, name) {
|
||||||
let self = this;
|
let self = this;
|
||||||
var remove = async function () {
|
const remove = async function () {
|
||||||
let result = await self.$deleteapi(name, v.id);
|
await self.$deleteapi(name, v.id);
|
||||||
let idx = self.data.findIndex((x) => x.id === v.id);
|
const idx = self.data.findIndex((x) => x.id === v.id);
|
||||||
self.$delete(self.data, idx);
|
self.$delete(self.data, idx);
|
||||||
};
|
};
|
||||||
this.$buefy.dialog.confirm({
|
this.$buefy.dialog.confirm({
|
||||||
message: "Bạn muốn xóa file: " + v.file,
|
message: "Bạn muốn xóa file: " + v.file,
|
||||||
onConfirm: () => remove(),
|
onConfirm: remove,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
updateImage(v) {
|
updateImage(v) {
|
||||||
@@ -445,3 +516,10 @@ export default {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</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>
|
||||||
|
|||||||
@@ -1,39 +1,55 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="is-flex is-gap-1">
|
||||||
<div
|
<div
|
||||||
class="image-container"
|
|
||||||
v-if="picture"
|
v-if="picture"
|
||||||
|
class="image is-128x128 relative"
|
||||||
>
|
>
|
||||||
<img
|
<NuxtImg :src="picture" />
|
||||||
:src="picture"
|
<div class="absolute top-2 right-2">
|
||||||
style="max-height: 250px !important"
|
<button
|
||||||
/>
|
class="button is-small is-light is-danger"
|
||||||
<div class="image-label">
|
@click="remove"
|
||||||
<span
|
|
||||||
class="is-clickable"
|
|
||||||
@click="remove()"
|
|
||||||
>
|
>
|
||||||
<SvgIcon v-bind="{ name: 'bin.svg', type: 'danger', size: 30 }"></SvgIcon>
|
<span class="icon">
|
||||||
</span>
|
<Icon
|
||||||
|
name="material-symbols:delete-outline-rounded"
|
||||||
|
:size="20"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style="width: 130px; border-style: dashed; border-width: 1px"
|
|
||||||
v-else
|
v-else
|
||||||
|
@click="openImage()"
|
||||||
|
class="size-35 rounded-md is-clickable is-flex is-justify-content-center is-align-items-center"
|
||||||
|
style="border: 1px dashed var(--bulma-grey-light)"
|
||||||
>
|
>
|
||||||
<a @click="openImage()"><SvgIcon v-bind="{ name: 'image.svg', type: 'grey', size: 120 }"></SvgIcon></a>
|
<Icon
|
||||||
|
name="material-symbols:add-photo-alternate-outline-rounded"
|
||||||
|
:size="50"
|
||||||
|
class="has-text-grey-light"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2">
|
<div>
|
||||||
<a @click="openCamera()">
|
<button
|
||||||
<SvgIcon v-bind="{ name: 'camera.svg', type: 'dark', size: 28 }"></SvgIcon>
|
class="button is-medium is-light is-primary"
|
||||||
</a>
|
@click="openCamera()"
|
||||||
|
>
|
||||||
|
<span class="icon">
|
||||||
|
<Icon
|
||||||
|
name="material-symbols:photo-camera-outline-rounded"
|
||||||
|
:size="24"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<Modal
|
<Modal
|
||||||
@close="showmodal = undefined"
|
@close="showmodal = undefined"
|
||||||
v-bind="showmodal"
|
v-bind="showmodal"
|
||||||
v-if="showmodal"
|
v-if="showmodal"
|
||||||
@selectimage="selectImage"
|
@selectimage="selectImage"
|
||||||
></Modal>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
@@ -58,8 +74,8 @@ export default {
|
|||||||
},
|
},
|
||||||
selectImage(files) {
|
selectImage(files) {
|
||||||
this.showmodal = undefined;
|
this.showmodal = undefined;
|
||||||
let v = files;
|
const v = files;
|
||||||
this.picture = `${this.$path()}download/?name=${v.file__file || v.file}&type=file`;
|
this.picture = `${this.$getpath()}download/?name=${v.file__file || v.file}&type=file`;
|
||||||
v.image = this.$copy(this.picture);
|
v.image = this.$copy(this.picture);
|
||||||
this.vfile = v;
|
this.vfile = v;
|
||||||
this.$emit("picture", v);
|
this.$emit("picture", v);
|
||||||
@@ -71,8 +87,8 @@ export default {
|
|||||||
openCamera() {
|
openCamera() {
|
||||||
this.showmodal = {
|
this.showmodal = {
|
||||||
component: "media/Camera",
|
component: "media/Camera",
|
||||||
title: "Chụp ảnh",
|
width: "700px",
|
||||||
width: "650px",
|
height: "auto",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ export default {
|
|||||||
if (files.length === 0) return;
|
if (files.length === 0) return;
|
||||||
|
|
||||||
const file = files.item(0);
|
const file = files.item(0);
|
||||||
let thefile = this.$upload(file, "file", 1);
|
const thefile = this.$upload(file, "file", 1);
|
||||||
|
|
||||||
if (thefile.error) {
|
if (thefile.error) {
|
||||||
this.$snackbar(thefile.text, "Error");
|
this.$snackbar(thefile.text, "Error");
|
||||||
@@ -214,7 +214,7 @@ export default {
|
|||||||
this.msgInfo.push({ message, type: "error" });
|
this.msgInfo.push({ message, type: "error" });
|
||||||
return (this.isloading = false);
|
return (this.isloading = false);
|
||||||
}
|
}
|
||||||
let result = await this.$insertapi("upload", { data: thefile.form, notify: false });
|
const result = await this.$insertapi("upload", { data: thefile.form, notify: false });
|
||||||
if (result === "error") {
|
if (result === "error") {
|
||||||
const message = this.$find(
|
const message = this.$find(
|
||||||
this.$store.syspara,
|
this.$store.syspara,
|
||||||
|
|||||||
@@ -140,26 +140,26 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|||||||
text: "Kích thước video phải dưới 1GB",
|
text: "Kích thước video phải dưới 1GB",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
let data = new FormData();
|
const form = new FormData();
|
||||||
let fileName = dayjs(new Date()).format("YYYYMMDDhhmmss") + "-" + file.name;
|
const filename = dayjs(new Date()).format("YYYYMMDDhhmmss") + "-" + file.name;
|
||||||
data.append("name", file.name);
|
form.append("name", file.name);
|
||||||
data.append("filename", fileName);
|
form.append("filename", filename);
|
||||||
data.append("file", file);
|
form.append("file", file);
|
||||||
data.append("type", type);
|
form.append("type", type);
|
||||||
data.append("size", file.size);
|
form.append("size", file.size);
|
||||||
data.append("user", user);
|
form.append("user", user);
|
||||||
data.append("convert", convert);
|
form.append("convert", convert);
|
||||||
// Thêm quality nếu convert được bật và quality được cung cấp
|
// Thêm quality nếu convert được bật và quality được cung cấp
|
||||||
if (convert && quality !== null && quality !== undefined) {
|
if (convert && quality !== null && quality !== undefined) {
|
||||||
data.append("quality", quality);
|
form.append("quality", quality);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
form: data,
|
form,
|
||||||
type: type,
|
type,
|
||||||
size: file.size,
|
file,
|
||||||
file: file,
|
filename,
|
||||||
name: file.name,
|
name: file.name,
|
||||||
filename: fileName,
|
size: file.size,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user