Files
web/app/components/media/CropImage.vue
2026-05-05 11:06:49 +07:00

140 lines
3.7 KiB
Vue

<template>
<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"
/>
</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 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
class="is-italic has-text-grey"
v-else
>
* Di chuyển khung để chọn hình ảnh phù hợp
</div>
</div>
</div>
</template>
<script>
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) };
},
},
methods: {
onChange({ coordinates, canvas }) {
this.coordinates = coordinates;
this.coordinates.ratio = ((this.coordinates.width * 1.0) / 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>