changes
This commit is contained in:
@@ -282,7 +282,7 @@
|
||||
</button>
|
||||
<button
|
||||
class="button is-light"
|
||||
@click="$exportpdf(docid, record.code)"
|
||||
@click="exportPdf(docid, { filename: record.code })"
|
||||
>
|
||||
In thông tin
|
||||
</button>
|
||||
|
||||
30
app/components/imports/BarcodeScanner.vue
Normal file
30
app/components/imports/BarcodeScanner.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<script setup>
|
||||
import { Html5Qrcode } from "html5-qrcode";
|
||||
|
||||
const emit = defineEmits(["scanned"]);
|
||||
let scanner;
|
||||
|
||||
onMounted(async () => {
|
||||
scanner = new Html5Qrcode("scanner-region");
|
||||
await scanner.start(
|
||||
{ facingMode: "environment" }, // or 'user' for front cam
|
||||
{ fps: 10, qrbox: 450 },
|
||||
(decodedText) => {
|
||||
console.log("scanned", decodedText);
|
||||
emit("scanned", decodedText);
|
||||
scanner.stop(); // stop after first scan
|
||||
},
|
||||
(errorMsg) => {
|
||||
console.log("errorMsg", errorMsg);
|
||||
}, // ignore per-frame scan failures
|
||||
);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
scanner?.stop().catch(() => {});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="scanner-region"></div>
|
||||
</template>
|
||||
@@ -1,8 +1,16 @@
|
||||
<script setup>
|
||||
import BarcodeScanner from "~/components/imports/BarcodeScanner.vue";
|
||||
import Products from "~/components/imports/Products.vue";
|
||||
|
||||
function onScanned(code) {
|
||||
console.log("code", code);
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="fixed-grid has-12-cols">
|
||||
<!-- <ClientOnly>
|
||||
<BarcodeScanner @scanned="onScanned" />
|
||||
</ClientOnly> -->
|
||||
<div class="grid">
|
||||
<div class="cell is-col-span-12">
|
||||
<Products />
|
||||
|
||||
@@ -164,7 +164,7 @@
|
||||
</button>
|
||||
<button
|
||||
class="button is-light has-text-black"
|
||||
@click="$exportpdf(docid, record.code)"
|
||||
@click="exportPdf(docid, { filename: record.code })"
|
||||
>
|
||||
{{ findLang("print") }}
|
||||
</button>
|
||||
|
||||
@@ -232,61 +232,6 @@ export default defineNuxtPlugin((nuxtApp) => {
|
||||
return id;
|
||||
};
|
||||
|
||||
const exportpdf = function (docid, name, size, orientation = "portrait") {
|
||||
var element = document.getElementById(docid);
|
||||
let elms = element.querySelectorAll("[id='ignore']");
|
||||
for (var i = 0; i < elms.length; i++) {
|
||||
elms[i].style.setProperty("display", "none", "important");
|
||||
}
|
||||
// Xóa cache ảnh để lần export sau không bị lỗi
|
||||
element.querySelectorAll("img").forEach((img) => {
|
||||
img.src = `${img.src}`;
|
||||
});
|
||||
var opt = {
|
||||
margin: 0.4,
|
||||
filename: `${name || "file"}-${dayjs().format("YYYYMMDDHHmmss")}.pdf`,
|
||||
image: { type: "jpeg", quality: 1 },
|
||||
html2canvas: { scale: 3, useCORS: true },
|
||||
jsPDF: { unit: "in", format: size || "a4", orientation },
|
||||
|
||||
pagebreak: {
|
||||
mode: ["avoid-all", "css", "legacy"],
|
||||
before: ".page-break-before",
|
||||
after: ".page-break-after",
|
||||
avoid: ".avoid-page-break",
|
||||
},
|
||||
};
|
||||
setTimeout(() => html2pdf().set(opt).from(element).save(), 300);
|
||||
setTimeout(() => {
|
||||
for (var i = 0; i < elms.length; i++) {
|
||||
elms[i].style.removeProperty("display");
|
||||
}
|
||||
}, 1000);
|
||||
return opt.filename;
|
||||
};
|
||||
|
||||
const exportImage = function (docid, name) {
|
||||
var element = document.getElementById(docid);
|
||||
let elms = element.querySelectorAll("[id='ignore']");
|
||||
for (var i = 0; i < elms.length; i++) {
|
||||
elms[i].style.display = "none";
|
||||
}
|
||||
let filename = `${name}-${dayjs().format("YYYYMMDDHHmmss")}.png`;
|
||||
html2canvas(element).then((canvas) => {
|
||||
const link = document.createElement("a");
|
||||
link.href = canvas.toDataURL("image/png");
|
||||
link.download = filename;
|
||||
link.click();
|
||||
link.remove();
|
||||
});
|
||||
setTimeout(() => {
|
||||
for (var i = 0; i < elms.length; i++) {
|
||||
elms[i].style.display = "block";
|
||||
}
|
||||
}, 1000);
|
||||
return filename;
|
||||
};
|
||||
|
||||
const download = async function (url, fileName) {
|
||||
const name = dayjs(new Date()).format("YYYYMMDDhhmmss") + "-" + fileName;
|
||||
const response = await fetch(url);
|
||||
@@ -549,8 +494,6 @@ export default defineNuxtPlugin((nuxtApp) => {
|
||||
copyToClipboard,
|
||||
nonAccent,
|
||||
linkID,
|
||||
exportpdf,
|
||||
exportImage,
|
||||
vnmoney,
|
||||
createMeta,
|
||||
dayjs,
|
||||
|
||||
28
app/utils/exportImage.js
Normal file
28
app/utils/exportImage.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import dayjs from "dayjs";
|
||||
import html2canvas from "html2canvas-pro";
|
||||
|
||||
export default async function exportImage(docid, filename = "file") {
|
||||
const target = document.getElementById(docid);
|
||||
|
||||
const ignored = target.querySelectorAll(".ignore");
|
||||
ignored.forEach((el) => el.style.setProperty("display", "none", "important"));
|
||||
// Xóa cache ảnh để lần export sau không bị lỗi
|
||||
target.querySelectorAll("img").forEach((img) => (img.src = `${img.src}`));
|
||||
const restoreIcons = swapIconsForExport(target);
|
||||
|
||||
try {
|
||||
await new Promise((r) => requestAnimationFrame(r));
|
||||
|
||||
const canvas = await html2canvas(target);
|
||||
const link = document.createElement("a");
|
||||
link.href = canvas.toDataURL("image/png");
|
||||
link.download = `${filename}-${dayjs().format("YYYYMMDDHHmmss")}.png`;
|
||||
link.click();
|
||||
link.remove();
|
||||
} finally {
|
||||
restoreIcons();
|
||||
ignored.forEach((el) => el.style.removeProperty("display"));
|
||||
}
|
||||
|
||||
return filename;
|
||||
}
|
||||
35
app/utils/exportPdf.js
Normal file
35
app/utils/exportPdf.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import dayjs from "dayjs";
|
||||
import html2pdf from "html2pdf.js";
|
||||
|
||||
export default async function exportPdf(docid, { filename = "file", format = "a4", orientation = "portrait" } = {}) {
|
||||
const target = document.getElementById(docid);
|
||||
|
||||
const ignored = target.querySelectorAll(".ignore");
|
||||
ignored.forEach((el) => el.style.setProperty("display", "none", "important"));
|
||||
// Xóa cache ảnh để lần export sau không bị lỗi
|
||||
target.querySelectorAll("img").forEach((img) => (img.src = `${img.src}`));
|
||||
const restoreIcons = swapIconsForExport(target);
|
||||
|
||||
const opt = {
|
||||
margin: 3,
|
||||
filename: `${filename}-${dayjs().format("YYYYMMDDHHmmss")}.pdf`,
|
||||
jsPDF: { format, orientation, unit: "mm" },
|
||||
html2canvas: { scale: 3, useCORS: true },
|
||||
image: { type: "jpeg", quality: 1 },
|
||||
pagebreak: {
|
||||
mode: ["avoid-all", "css", "legacy"],
|
||||
before: ".page-break-before",
|
||||
after: ".page-break-after",
|
||||
avoid: ".avoid-page-break",
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
await new Promise((r) => requestAnimationFrame(r));
|
||||
await html2pdf().set(opt).from(target).save();
|
||||
} finally {
|
||||
restoreIcons();
|
||||
ignored.forEach((el) => el.style.removeProperty("display"));
|
||||
}
|
||||
return opt.filename;
|
||||
}
|
||||
52
app/utils/iconSwap.js
Normal file
52
app/utils/iconSwap.js
Normal file
@@ -0,0 +1,52 @@
|
||||
const ICONIFY_SELECTOR = '[class*="iconify"]';
|
||||
|
||||
/**
|
||||
* Replaces iconify CSS-mask spans with real <svg> (for html2pdf, html2canvas),
|
||||
* returns a restore function to swap them back.
|
||||
*/
|
||||
export function swapIconsForExport(root) {
|
||||
const spans = root.querySelectorAll(ICONIFY_SELECTOR);
|
||||
const restoreList = [];
|
||||
|
||||
spans.forEach((span) => {
|
||||
const svgMarkup = extractSvgFromMask(span);
|
||||
if (!svgMarkup) return; // not a mask-based icon, skip
|
||||
|
||||
const computed = getComputedStyle(span);
|
||||
const size = computed.fontSize || "1em";
|
||||
const color = computed.color;
|
||||
|
||||
const wrapper = document.createElement("span");
|
||||
wrapper.innerHTML = svgMarkup;
|
||||
const svgEl = wrapper.firstElementChild;
|
||||
|
||||
svgEl.setAttribute("width", size);
|
||||
svgEl.setAttribute("height", size);
|
||||
svgEl.style.display = "inline-block";
|
||||
svgEl.style.verticalAlign = "middle";
|
||||
// force fill to currentColor's resolved value
|
||||
svgEl.querySelectorAll("[fill]").forEach((node) => node.setAttribute("fill", color));
|
||||
|
||||
span.replaceWith(svgEl);
|
||||
restoreList.push({ svgEl, originalSpan: span });
|
||||
});
|
||||
|
||||
return function restoreIcons() {
|
||||
restoreList.forEach(({ svgEl, originalSpan }) => {
|
||||
svgEl.replaceWith(originalSpan);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function extractSvgFromMask(el) {
|
||||
const maskVar = getComputedStyle(el).getPropertyValue("--svg").trim();
|
||||
// maskVar looks like: url("data:image/svg+xml,...")
|
||||
const match = maskVar.match(/url\((['"]?)(.*?)\1\)/);
|
||||
if (!match) return null;
|
||||
|
||||
const dataUri = match[2];
|
||||
if (!dataUri.startsWith("data:image/svg+xml,")) return null;
|
||||
|
||||
const encoded = dataUri.replace("data:image/svg+xml,", "");
|
||||
return decodeURIComponent(encoded);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
|
||||
export default defineNuxtConfig({
|
||||
ssr: false,
|
||||
css: ["./my-bulma-project.css", "./app/assets/styles/main.scss"],
|
||||
future: {
|
||||
compatibilityVersion: 4,
|
||||
@@ -8,7 +9,6 @@ export default defineNuxtConfig({
|
||||
app: {
|
||||
head: {
|
||||
link: [{ rel: "icon", type: "image/x-icon", href: "/favicon.svg" }],
|
||||
script: [{ src: "/js/html2pdf.bundle.min.js" }, { src: "/js/html2canvas.min.js" }],
|
||||
htmlAttrs: {
|
||||
lang: "vi",
|
||||
class: "theme-light has-background-blue-100",
|
||||
@@ -42,6 +42,7 @@ export default defineNuxtConfig({
|
||||
"highcharts-vue", // CJS
|
||||
"highcharts/modules/export-data", // CJS
|
||||
"highcharts/modules/exporting", // CJS
|
||||
"html5-qrcode",
|
||||
"jsbarcode", // CJS
|
||||
"xlsx",
|
||||
],
|
||||
|
||||
1881
package-lock.json
generated
1881
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -24,10 +24,12 @@
|
||||
"es-toolkit": "^1.42.0",
|
||||
"highcharts": "^12.2.0",
|
||||
"highcharts-vue": "^2.0.1",
|
||||
"html2canvas-pro": "^2.2.0",
|
||||
"html2pdf.js": "^0.14.0",
|
||||
"html5-qrcode": "^2.3.8",
|
||||
"jsbarcode": "^3.12.3",
|
||||
"mammoth": "^1.9.1",
|
||||
"nuxt-qrcode": "^0.4.8",
|
||||
"pdfjs-dist": "^3.11.174",
|
||||
"pinia": "^3.0.3",
|
||||
"pinia-plugin-persistedstate": "^4.5.0",
|
||||
"sass": "^1.81.0",
|
||||
@@ -36,7 +38,7 @@
|
||||
"vue": "^3.5.34",
|
||||
"vue-advanced-cropper": "^2.8.9",
|
||||
"vue-router": "latest",
|
||||
"xlsx": "^0.18.5"
|
||||
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/material-symbols": "^1.2.69",
|
||||
|
||||
20
public/js/html2canvas.min.js
vendored
20
public/js/html2canvas.min.js
vendored
File diff suppressed because one or more lines are too long
3
public/js/html2pdf.bundle.min.js
vendored
3
public/js/html2pdf.bundle.min.js
vendored
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user