This commit is contained in:
Viet An
2026-05-14 09:11:18 +07:00
parent 336c8c9036
commit 4d37397ee4
25 changed files with 450 additions and 209 deletions

View File

@@ -38,7 +38,7 @@
z-index: 999; z-index: 999;
bottom: 110%; bottom: 110%;
transition: opacity 0.3s; transition: opacity 0.3s;
padding: 6px 9px; padding: 0.4rem 0.6rem;
font-size: 13px; font-size: 13px;
pointer-events: none; pointer-events: none;
box-shadow: 2px 2px 1px rgba(0, 0, 0, 0.1); box-shadow: 2px 2px 1px rgba(0, 0, 0, 0.1);

View File

@@ -15,7 +15,7 @@
class="modal-card" class="modal-card"
:id="docid" :id="docid"
:style="{ :style="{
width: $store.viewport <= 2 ? 'calc(100% - 2rem)' : width || '60%', width: $store.viewport <= 1 ? 'calc(100% - 2rem)' : width || '60%',
}" }"
> >
<header <header
@@ -45,7 +45,7 @@
@close="closeModal" @close="closeModal"
/> />
</section> </section>
<footer class="modal-card-foot pt-0 px-4 pb-4"></footer> <footer class="modal-card-foot px-4 pb-4 pt-0"></footer>
</div> </div>
</div> </div>
</Teleport> </Teleport>
@@ -139,7 +139,11 @@ onUnmounted(() => {
}); });
</script> </script>
<style scoped> <style scoped>
footer:empty { footer {
background-color: var(--bulma-modal-card-body-background-color);
&:empty {
display: none; display: none;
}
} }
</style> </style>

View File

@@ -390,11 +390,14 @@ export default {
.field:not(:last-child) { .field:not(:last-child) {
margin-bottom: 0; margin-bottom: 0;
} }
.button.is-light { .button.is-success {
&.is-light {
--bulma-button-background-l: 89%; --bulma-button-background-l: 89%;
} }
.button:hover,
.button.is-hovered { &:hover,
&.is-hovered {
--bulma-button-background-l-delta: -10%; --bulma-button-background-l-delta: -10%;
}
} }
</style> </style>

View File

@@ -174,7 +174,7 @@
> >
<span class="icon"> <span class="icon">
<Icon <Icon
name="material-symbols:menu-rounded" name="material-symbols:view-column-outline-rounded"
:size="22" :size="22"
/> />
</span> </span>
@@ -223,18 +223,18 @@
</p> </p>
<p class="control"> <p class="control">
<button <button
class="button is-light is-primary" class="button is-primary"
@click="saveSetting()" @click="saveSetting()"
> >
<span class="icon"> <span class="icon">
<Icon <Icon
name="material-symbols:save-outline-rounded" name="material-symbols:save-rounded"
:size="22" :size="22"
/> />
</span> </span>
</button> </button>
<span <span
class="tooltiptext" class="tooltiptext has-background-primary has-text-white"
style="top: 110%; bottom: unset; min-width: max-content; left: -45px" style="top: 110%; bottom: unset; min-width: max-content; left: -45px"
>Lưu thiết lập</span >Lưu thiết lập</span
> >
@@ -254,7 +254,7 @@
:class="selectTab.code === v.code ? 'is-active' : 'has-text-primary'" :class="selectTab.code === v.code ? 'is-active' : 'has-text-primary'"
@click="changeTab(v)" @click="changeTab(v)"
> >
<a class="px-8 py-1.5 fs-14">{{ v.name }}</a> <a class="px-12 py-1.5 fs-14">{{ v.name }}</a>
</li> </li>
</ul> </ul>
</div> </div>
@@ -387,21 +387,17 @@
</span> </span>
<span>{{ v.name }}</span> <span>{{ v.name }}</span>
</button> </button>
</div>
</div>
</div>
</div>
<p
class="mt-3"
v-if="radioTemplate === 'option'"
>
<button <button
class="button is-primary is-light fs-14" v-if="radioTemplate === 'option'"
class="button is-primary is-light fs-14 ml-2"
@click="showSidebar()" @click="showSidebar()"
> >
{{ `${currentField.template ? "Sửa" : "Tạo"} định dạng` }} {{ currentField.template ? "Sửa" : "Tạo" }} định dạng
</button> </button>
</p> </div>
</div>
</div>
</div>
</div> </div>
<div v-else-if="currentTab === 'value'"> <div v-else-if="currentTab === 'value'">
<ScrollBox <ScrollBox
@@ -563,7 +559,7 @@ function editLabel() {
component: "datatable/EditLabel", component: "datatable/EditLabel",
title: "Điều chỉnh tiêu đề", title: "Điều chỉnh tiêu đề",
width: "500px", width: "500px",
height: "300px", height: "auto",
vbind: { label }, vbind: { label },
}; };
} }
@@ -621,7 +617,7 @@ const showSidebar = function () {
pagename: props.pagename, pagename: props.pagename,
}, },
width: "850px", width: "850px",
height: "700px", height: "600px",
title: title, title: title,
}; };
}; };

View File

@@ -1,14 +1,14 @@
<template> <template>
<div <div
class="field is-grouped is-grouped-multiline is-gap-4 px-2" class="field is-grouped is-grouped-multiline is-gap-4 px-2"
v-if="filters ? filters.length > 0 : false" v-if="filters?.length > 0"
> >
<div class="control"> <div class="control">
<a <a
class="button is-primary is-light" class="button is-primary is-light fs-14"
@click="updateData({ filters: [] })" @click="updateData({ filters: [] })"
> >
<span class="fs-14">Xóa lọc</span> Xóa lọc
</a> </a>
</div> </div>
<div class="control"> <div class="control">
@@ -103,6 +103,7 @@
:row="v" :row="v"
v-if="field.template" v-if="field.template"
@clickevent="clickEvent($event, v, field)" @clickevent="clickEvent($event, v, field)"
@dynamicCompEvent="onDynamicCompEvent"
/> />
<span v-else>{{ v[field.name] }}</span> <span v-else>{{ v[field.name] }}</span>
</td> </td>
@@ -121,7 +122,7 @@
@confirm="confirmRemove" @confirm="confirmRemove"
v-bind="showmodal" v-bind="showmodal"
v-if="showmodal" v-if="showmodal"
></Modal> />
</template> </template>
<script setup> <script setup>
import { createApp } from "vue/dist/vue.esm-bundler.js"; import { createApp } from "vue/dist/vue.esm-bundler.js";
@@ -136,6 +137,7 @@ const {
$deleterow, $deleterow,
$empty, $empty,
$find, $find,
$getdata,
$getEditRights, $getEditRights,
$formatNumber, $formatNumber,
$multiSort, $multiSort,
@@ -144,9 +146,10 @@ const {
$unique, $unique,
} = useNuxtApp(); } = useNuxtApp();
const store = useStore(); const store = useStore();
var props = defineProps({ const props = defineProps({
pagename: String, pagename: String,
}); });
function dynamicComponent(htmlString) { function dynamicComponent(htmlString) {
return defineComponent({ return defineComponent({
template: htmlString, template: htmlString,
@@ -165,10 +168,14 @@ var currentPage = 1;
var displayFields = ref([]); var displayFields = ref([]);
var displayData = []; var displayData = [];
var pagedata = store[props.pagename]; var pagedata = store[props.pagename];
var tablesetting = $copy(pagedata.tablesetting || store.tablesetting); let tablesetting = $copy(pagedata.tablesetting || store.tablesetting);
if (!Array.isArray(tablesetting)) { if (!Array.isArray(tablesetting)) {
tablesetting = Object.values(tablesetting); tablesetting = Object.values(tablesetting);
} }
// console.log("props.pagename", props.pagename);
// console.log("pagedata.tablesetting", pagedata.tablesetting);
// console.log("store.tablesetting", store.tablesetting);
// console.log("tablesetting", tablesetting);
// var tablesetting = $copy(store.tablesetting); // var tablesetting = $copy(store.tablesetting);
// const tablesettingObj = Object.fromEntries(tablesetting.map((v) => [v.code, v])); // const tablesettingObj = Object.fromEntries(tablesetting.map((v) => [v.code, v]));
var perPage = Number($find(tablesetting, { code: "per-page" }, "detail")) || 20; var perPage = Number($find(tablesetting, { code: "per-page" }, "detail")) || 20;
@@ -183,7 +190,7 @@ var data = $copy(pagedata.data);
var showmodal = ref(); var showmodal = ref();
watch( watch(
() => store[props.pagename], () => store[props.pagename],
(newVal, oldVal) => { () => {
updateChange(); updateChange();
}, },
); );
@@ -204,7 +211,7 @@ const updateShow = function (full_data) {
const allowedFns = { const allowedFns = {
"$getEditRights()": $getEditRights, "$getEditRights()": $getEditRights,
}; };
const arr = pagedata.fields.filter(({ show }) => { const shownFields = pagedata.fields.filter(({ show }) => {
if (typeof show === "boolean") return show; if (typeof show === "boolean") return show;
else { else {
// show is a string // show is a string
@@ -213,19 +220,20 @@ const updateShow = function (full_data) {
return allowedFns[show]?.() || false; return allowedFns[show]?.() || false;
} }
}); });
if (full_data === false) displayData = $copy(data);
else if (full_data === false) {
displayData = $copy( displayData = $copy(data);
data.filter((ele, index) => index >= (currentPage - 1) * perPage && index < currentPage * perPage), } else {
); displayData = data.filter((ele, index) => index >= (currentPage - 1) * perPage && index < currentPage * perPage);
displayData.map((v) => { }
arr.map((x) => (v[`${x.name}color`] = getStyle(x, v))); displayData.forEach((v) => {
shownFields.forEach((x) => (v[`${x.name}color`] = getStyle(x, v)));
}); });
arr.map((v) => { shownFields.forEach((v) => {
v.headerStyle = getSettingStyle("header", v); v.headerStyle = getSettingStyle("header", v);
v.dropStyle = getSettingStyle("dropdown", v); v.dropStyle = getSettingStyle("dropdown", v);
}); });
displayFields.value = arr; displayFields.value = shownFields;
showPagination(); showPagination();
}; };
function confirmRemove() { function confirmRemove() {
@@ -237,11 +245,11 @@ const clickEvent = function (event, row, field) {
if (name === "remove") { if (name === "remove") {
currentRow = row; currentRow = row;
showmodal.value = { showmodal.value = {
component: `dialog/Confirm`, component: "dialog/Confirm",
vbind: { content: "Bạn có muốn xóa bản ghi này không?", duration: 10 }, vbind: { content: "Bạn có muốn xóa bản ghi này không?", duration: 10 },
title: "Xác nhận", title: "Xác nhận",
width: "500px", width: "500px",
height: "100px", height: "auto",
}; };
} }
emit(name, row, field, data); emit(name, row, field, data);
@@ -269,7 +277,7 @@ const showField = async function (field) {
}; //$stripHtml(field.label) }; //$stripHtml(field.label)
}; };
const getStyle = function (field, record) { const getStyle = function (field, record) {
var stop = false; let stop = false;
let val = tablesetting.find((v) => v.code === "td-border") let val = tablesetting.find((v) => v.code === "td-border")
? tablesetting.find((v) => v.code === "td-border").detail ? tablesetting.find((v) => v.code === "td-border").detail
: "border: solid 1px rgb(44, 44, 44); "; : "border: solid 1px rgb(44, 44, 44); ";
@@ -342,6 +350,117 @@ const getStyle = function (field, record) {
return val; return val;
}; };
const getSettingStyle = function (name, field) { const getSettingStyle = function (name, field) {
if (name === "table") {
console.log("tablesetting", tablesetting);
}
if (!tablesetting || tablesetting.length === 0) {
// manual temp fix
tablesetting = [
{
id: 4,
code: "per-page",
name: "Số dòng trong 1 trang",
detail: "20",
},
{
id: 8,
code: "header-filter-color",
name: "Màu chữ khi áp dụng Filter",
detail: "#00cc66",
},
{
id: 10,
code: "background",
name: "Màu nền background",
detail: "#363636",
},
{
id: 11,
code: "table-background",
name: "Màu nền của bảng",
detail: "#ffffff",
},
{
id: 12,
code: "table-font-color",
name: "Mầu chữ trong bảng",
detail: "hsl(0, 0%, 14%)",
},
{
id: 13,
code: "table-font-size",
name: "Cỡ chữ trong bảng",
detail: "12",
},
{
id: 14,
code: "header-background",
name: "Màu nền tiêu đề",
detail: "var(--bulma-primary-soft)",
},
{
id: 15,
code: "header-font-color",
name: "Màu chữ tiêu đề",
detail: "var(--bulma-primary-40)",
},
{
id: 16,
code: "header-font-size",
name: "Cỡ chữ tiêu đề",
detail: "12",
},
{
id: 17,
code: "container-height",
name: "Chiều cao container",
detail: "38",
},
{
id: 18,
code: "header-arrow",
name: "Mũi tên trỏ xuống",
detail: "no",
},
{
id: 19,
code: "menu-width",
name: "Chiều rộng menu",
detail: "20.6",
},
{
id: 20,
code: "menu-min-height",
name: "Chiều cao menu (nhỏ nhất)",
detail: "32",
},
{
id: 21,
code: "menu-max-height",
name: "Chiều cao menu (lớn nhất)",
detail: "37",
},
{
id: 22,
code: "show-menu",
name: "Hiển thị menu",
detail: "yes",
},
{
id: 23,
code: "note",
name: "Ghi chú",
detail: "@",
},
{
id: 24,
code: "td-border",
name: "Đường viền",
detail: "border: 1px solid #dbdbdb;",
},
];
}
let value = ""; let value = "";
if (name === "container") { if (name === "container") {
value = "min-height:" + tablesetting.find((v) => v.code === "container-height").detail + "rem; "; value = "min-height:" + tablesetting.find((v) => v.code === "container-height").detail + "rem; ";
@@ -550,8 +669,15 @@ const doubleClick = function (field, v) {
currentField = field; currentField = field;
doSelect(v[field.name]); doSelect(v[field.name]);
}; };
var tableStyle = getSettingStyle("table"); const tableStyle = getSettingStyle("table");
setTimeout(() => updateShow(), 200); setTimeout(() => updateShow(), 200);
async function onDynamicCompEvent(e) {
console.log("DataTable received dynamicCompEvent", e);
if (e.type === "refresh") {
updateShow(); // doesn't get new data
}
}
</script> </script>
<style scoped> <style scoped>
:deep(.table tbody tr:hover td, .table tbody tr:hover th) { :deep(.table tbody tr:hover td, .table tbody tr:hover th) {

View File

@@ -219,20 +219,20 @@
</p> </p>
</template> </template>
<TableOption <TableOption
v-bind="{ pagename: pagename }" v-bind="{ pagename }"
v-else-if="sideBar === 'option'" v-else-if="sideBar === 'option'"
> >
</TableOption> </TableOption>
<CreateTemplate <CreateTemplate
v-else-if="sideBar === 'template'" v-else-if="sideBar === 'template'"
v-bind="{ pagename: pagename, field: openField }" v-bind="{ pagename, field: openField }"
> >
</CreateTemplate> </CreateTemplate>
</template> </template>
<script setup> <script setup>
// FilterOption: () => import("@/components/datatable/FilterOption"), // FilterOption: () => import("@/components/datatable/FilterOption"),
// TableOption: () => import("@/components/datatable/TableOption"), // TableOption: () => import("@/components/datatable/TableOption"),
//CreateTemplate: () => import("@/components/datatable/CreateTemplate") // CreateTemplate: () => import("@/components/datatable/CreateTemplate")
import CreateTemplate from "~/components/datatable/CreateTemplate"; import CreateTemplate from "~/components/datatable/CreateTemplate";
const { $id, $copy, $empty, $stripHtml } = useNuxtApp(); const { $id, $copy, $empty, $stripHtml } = useNuxtApp();
var props = defineProps({ var props = defineProps({
@@ -323,5 +323,4 @@ const doConditionFilter = function (v, type, id) {
$emit("modalevent", { name: "updatefields", data: copy }); $emit("modalevent", { name: "updatefields", data: copy });
}; };
initData(); initData();
console.log(sideBar);
</script> </script>

View File

@@ -14,22 +14,29 @@
</ul> </ul>
</div> </div>
<template v-if="selectType.code === 'formula'"> <template v-if="selectType.code === 'formula'">
<b-radio <div class="control is-flex is-gap-2">
:class="i === 1 ? 'ml-5' : null" <label
v-model="choice"
v-for="(v, i) in choices" v-for="(v, i) in choices"
:key="i" :key="i"
:native-value="v.code" class="radio"
> >
<span :class="v.code === choice ? 'fsb-16' : 'fs-16'">{{ v.name }}</span> <input
</b-radio> v-model="choice"
<div class="has-background-light mt-3 px-3 py-3"> :value="v.code"
@input="changeType(v)"
type="radio"
name="choice"
/>
{{ v.name }}
</label>
</div>
<div class="has-background-white-bis mt-3 p-3 rounded-md">
<div <div
class="tags are-medium mb-0" class="tags mb-0"
v-if="choice === 'function'" v-if="choice === 'function'"
> >
<span <span
:class="`tag ${func === v.code ? 'is-primary' : 'is-dark'} is-rounded is-clickable`" :class="`tag is-primary ${func !== v.code && 'is-light'} fs-13 is-rounded is-clickable`"
v-for="(v, i) in funcs" v-for="(v, i) in funcs"
:key="i" :key="i"
@click="changeFunc(v)" @click="changeFunc(v)"
@@ -285,7 +292,7 @@ var choices = [
{ code: "column", name: "Dùng cột dữ liệu" }, { code: "column", name: "Dùng cột dữ liệu" },
{ code: "function", name: "Dùng hàm số" }, { code: "function", name: "Dùng hàm số" },
]; ];
var choice = "column"; const choice = ref("column");
var funcs = [ var funcs = [
{ code: "sum", name: "Sum" }, { code: "sum", name: "Sum" },
{ code: "max", name: "Max" }, { code: "max", name: "Max" },
@@ -363,7 +370,7 @@ function checkFunc() {
} }
function checkValid() { function checkValid() {
errors.value = []; errors.value = [];
if (tags.length === 0 && choice === "column") { if (tags.length === 0 && choice.value === "column") {
errors.value.push({ errors.value.push({
name: "tags", name: "tags",
message: "Chưa chọn trường xây dựng công thức.", message: "Chưa chọn trường xây dựng công thức.",
@@ -382,7 +389,7 @@ function checkValid() {
} }
if (errors.value.length > 0) return false; if (errors.value.length > 0) return false;
//check formula in case use column //check formula in case use column
if (choice === "column") { if (choice.value === "column") {
let val = $copy(formula); let val = $copy(formula);
tags.forEach((v) => { tags.forEach((v) => {
let myRegExp = new RegExp(v.name, "g"); let myRegExp = new RegExp(v.name, "g");
@@ -410,7 +417,7 @@ function createField() {
if (!checkValid()) return; if (!checkValid()) return;
let field = $createField(name.trim(), label.trim(), "number", true); let field = $createField(name.trim(), label.trim(), "number", true);
field.formula = formula.trim().replaceAll(" ", ""); field.formula = formula.trim().replaceAll(" ", "");
if (choice === "function") { if (choice.value === "function") {
field.func = func; field.func = func;
field.vals = checkFunc(); field.vals = checkFunc();
} else field.tags = tags.map((v) => v.name); } else field.tags = tags.map((v) => v.name);

View File

@@ -2,10 +2,10 @@
<table class="table is-fullwidth"> <table class="table is-fullwidth">
<thead> <thead>
<tr class="fs-14"> <tr class="fs-14">
<th>#</th> <th class="is-narrow">#</th>
<th>Tên trường</th> <th>Tên trường</th>
<th>Tên cột</th> <th>Tên cột</th>
<th class="is-narrow">...</th> <th class="is-narrow has-text-right">Thao tác</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -13,52 +13,52 @@
class="fs-14" class="fs-14"
v-for="(v, i) in fields" v-for="(v, i) in fields"
> >
<td>{{ i }}</td> <td style="vertical-align: middle">{{ i }}</td>
<td> <td style="vertical-align: middle">
<a <a
class="has-text-primary" class="has-text-primary"
@click="openField(v, i)" @click="openField(v, i)"
>{{ v.name }}</a >{{ v.name }}</a
> >
</td> </td>
<td>{{ $stripHtml(v.label, 50) }}</td> <td style="vertical-align: middle">{{ $stripHtml(v.label, 50) }}</td>
<td> <td>
<div class="field has-addons"> <div class="field has-addons">
<p class="control"> <p class="control">
<button <button
class="button is-primary is-light" class="button is-primary is-light px-3.5 py-1.5"
@click="moveDown(v, i)" @click="moveDown(v, i)"
> >
<span class="icon"> <span class="icon">
<Icon <Icon
name="material-symbols:arrow-downward-rounded" name="material-symbols:arrow-downward-rounded"
:size="19" :size="18"
/> />
</span> </span>
</button> </button>
</p> </p>
<p class="control"> <p class="control">
<button <button
class="button is-primary is-light" class="button is-primary is-light px-3.5 py-1.5"
@click="moveUp(v, i)" @click="moveUp(v, i)"
> >
<span class="icon"> <span class="icon">
<Icon <Icon
name="material-symbols:arrow-upward-rounded" name="material-symbols:arrow-upward-rounded"
:size="19" :size="18"
/> />
</span> </span>
</button> </button>
</p> </p>
<p class="control"> <p class="control">
<button <button
class="button is-primary is-light" class="button is-primary is-light px-3.5 py-1.5"
@click="askConfirm(v, i)" @click="askConfirm(v, i)"
> >
<span class="icon"> <span class="icon">
<Icon <Icon
name="material-symbols:delete-outline-rounded" name="material-symbols:delete-outline-rounded"
:size="19" :size="18"
/> />
</span> </span>
</button> </button>

View File

@@ -8,7 +8,7 @@
<div <div
v-if="enableTime" v-if="enableTime"
class="cell is-col-span-7 is-flex is-align-items-center is-flex-wrap-wrap" class="cell is-col-span-7 is-flex is-align-items-center is-flex-wrap-wrap"
style="row-gap: 0.5rem; column-gap: 1rem" style="row-gap: 0; column-gap: 1rem"
> >
<Caption <Caption
:title="lang === 'vi' ? 'Thời gian' : 'Time'" :title="lang === 'vi' ? 'Thời gian' : 'Time'"
@@ -41,11 +41,14 @@
</div> </div>
<span <span
v-if="newDataAvailable" v-if="newDataAvailable"
class="has-text-danger is-italic is-size-6 ml-2" class="has-text-danger is-italic fs-14 ml-2"
> dữ liệu mới, vui lòng làm mới.</span > dữ liệu mới, vui lòng làm mới.</span
> >
</div> </div>
<div class="cell is-col-span-5 is-flex is-align-items-center is-gap-1 is-flex-wrap-wrap"> <div
class="cell is-col-span-5 is-flex is-align-items-center is-flex-wrap-wrap"
style="row-gap: 0; column-gap: 1rem"
>
<Caption <Caption
:title="lang === 'vi' ? `Tìm ${viewport === 1 ? '' : 'kiếm'}` : 'Search'" :title="lang === 'vi' ? `Tìm ${viewport === 1 ? '' : 'kiếm'}` : 'Search'"
type="has-text-orange" type="has-text-orange"
@@ -60,45 +63,50 @@
:placeholder="lang === 'vi' ? 'Nhập từ khóa...' : 'Enter keyword...'" :placeholder="lang === 'vi' ? 'Nhập từ khóa...' : 'Enter keyword...'"
/> />
<div class="field has-addons is-flex is-flex-wrap-wrap is-gap-0 is-align-items-center"> <div class="field has-addons is-flex is-flex-wrap-wrap is-gap-0 is-align-items-center">
<span <p
class="tooltip"
v-if="importdata && $getEditRights()" v-if="importdata && $getEditRights()"
class="control"
> >
<a <button
class="mr-2" class="button is-ghost has-text-orange fs-14"
@click="openImport()" @click="openImport()"
> >
<SvgIcon <span class="icon">
v-bind="{ <Icon
name: 'upload.svg', name="material-symbols:upload-rounded"
type: 'findata', :size="22"
size: 22, />
}" </span>
></SvgIcon> </button>
</a>
<span <span
class="tooltiptext" class="tooltiptext has-background-orange-soft has-text-orange-bold"
style="min-width: max-content" style="top: 110%; bottom: unset; min-width: max-content; left: -45px"
> >
{{ lang === "vi" ? "Nhập dữ liệu" : "Import data" }} Nhập dữ liệu
</span> </span>
</span> </p>
<span <p
class="tooltip"
v-if="enableAdd && $getEditRights()" v-if="enableAdd && $getEditRights()"
class="control"
> >
<a <button
class="mr-2" class="button is-ghost has-text-orange fs-14"
@click="$emit('add')" @click="$emit('add')"
> >
<SvgIcon v-bind="{ name: 'add1.png', type: 'findata', size: 22 }"></SvgIcon> <span class="icon">
</a> <Icon
<span name="material-symbols:add-rounded"
class="tooltiptext" :size="22"
style="min-width: max-content" />
>{{ lang === "vi" ? "Thêm mới" : "Add new" }}</span
>
</span> </span>
</button>
<span
class="tooltiptext has-background-orange-soft has-text-orange-bold"
style="top: 110%; bottom: unset; min-width: max-content; left: -45px"
>
Thêm mới
</span>
</p>
<p class="control"> <p class="control">
<button <button
class="button is-ghost has-text-orange fs-14" class="button is-ghost has-text-orange fs-14"
@@ -110,8 +118,13 @@
:size="22" :size="22"
/> />
</span> </span>
<span>Xuất excel</span>
</button> </button>
<span
class="tooltiptext has-background-orange-soft has-text-orange-bold"
style="top: 110%; bottom: unset; min-width: max-content; left: -45px"
>
Xuất Excel
</span>
</p> </p>
<p class="control"> <p class="control">
<button <button
@@ -124,8 +137,13 @@
:size="22" :size="22"
/> />
</span> </span>
<span>Làm mới</span>
</button> </button>
<span
class="tooltiptext has-background-orange-soft has-text-orange-bold"
style="top: 110%; bottom: unset; min-width: max-content; left: -45px"
>
Làm mới
</span>
</p> </p>
<Icon <Icon
v-if="loading" v-if="loading"

View File

@@ -5,37 +5,35 @@
defer defer
to=".modal-card:has(.confirm) .modal-card-foot" to=".modal-card:has(.confirm) .modal-card-foot"
> >
<div class="field is-grouped"> <div class="field is-grouped w-full is-align-items-center">
<div class="control is-expanded"> <div class="control is-expanded">
<div class="buttons"> <div class="buttons">
<button <button
class="button is-primary has-text-white" class="button is-primary has-text-white"
@click="confirm()" @click="confirm"
> >
Đồng ý Đồng ý
</button> </button>
<button <button
class="button is-white" class="button is-white"
@click="cancel()" @click="cancel"
> >
Hủy Hủy
</button> </button>
</div> </div>
</div> </div>
<div
class="control"
v-if="duration"
>
<CountDown <CountDown
v-if="duration"
:duration="duration" :duration="duration"
@close="cancel()" @close="cancel"
/> />
</div> </div>
</div>
</Teleport> </Teleport>
</div> </div>
</template> </template>
<script setup> <script setup>
import CountDown from "@/components/dialog/CountDown.vue";
const props = defineProps({ const props = defineProps({
content: String, content: String,
duration: Number, duration: Number,

View File

@@ -1,12 +1,14 @@
<template> <template>
<div id="countdown"> <div id="countdown">
<div id="countdown-number"></div> <div
ref="countdownNumber"
style="display: inline-block; line-height: 40px"
></div>
<svg> <svg>
<circle <circle
r="18" r="18"
cx="20" cx="20"
cy="20" cy="20"
color="red"
></circle> ></circle>
</svg> </svg>
</div> </div>
@@ -21,18 +23,19 @@ export default {
}; };
}, },
mounted() { mounted() {
var countdownNumberEl = document.getElementById("countdown-number"); this.$refs.countdownNumber.textContent = this.countdown;
countdownNumberEl.textContent = this.countdown;
this.timer = setInterval(() => this.startCount(), 1000); this.timer = setInterval(() => this.startCount(), 1000);
}, },
unmounted() {
clearInterval(this.timer);
},
beforeDestroy() { beforeDestroy() {
clearInterval(this.timer); clearInterval(this.timer);
}, },
methods: { methods: {
startCount() { startCount() {
this.countdown -= 1; this.countdown -= 1;
var countdownNumberEl = document.getElementById("countdown-number"); this.$refs.countdownNumber.textContent = this.countdown;
countdownNumberEl.textContent = this.countdown;
if (this.countdown === 0) { if (this.countdown === 0) {
clearInterval(this.timer); clearInterval(this.timer);
this.$emit("close"); this.$emit("close");
@@ -49,12 +52,7 @@ export default {
width: 40px; width: 40px;
text-align: center; text-align: center;
} }
#countdown-number { svg {
color: black;
display: inline-block;
line-height: 40px;
}
:deep(svg) {
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
@@ -62,12 +60,12 @@ export default {
height: 40px; height: 40px;
transform: rotateY(-180deg) rotateZ(-90deg); transform: rotateY(-180deg) rotateZ(-90deg);
} }
:deep(svg circle) { svg circle {
stroke-dasharray: 113px; stroke-dasharray: 113px;
stroke-dashoffset: 0px; stroke-dashoffset: 0px;
stroke-linecap: round; stroke-linecap: round;
stroke-width: 2px; stroke-width: 2px;
stroke: black; stroke: var(--bulma-primary);
fill: none; fill: none;
animation: countdown 10s linear infinite forwards; animation: countdown 10s linear infinite forwards;
} }

View File

@@ -11,7 +11,7 @@ watch(product, () => {
<template> <template>
<div class="fixed-grid has-12-cols"> <div class="fixed-grid has-12-cols">
<div class="grid"> <div class="grid">
<div class="cell is-col-span-6 is-col-start-4"> <div class="cell is-col-span-8 is-col-start-3">
<div class="field"> <div class="field">
<label class="label">Sản phẩm</label> <label class="label">Sản phẩm</label>
<SearchBox <SearchBox
@@ -47,6 +47,7 @@ watch(product, () => {
values: values:
'id,code,product,product__name,color,color__code,color__name,color__hex_code,ram,ram__code,ram__capacity,internal_storage,internal_storage__code,internal_storage__capacity,image,price,note,create_time,update_time', 'id,code,product,product__name,color,color__code,color__name,color__hex_code,ram,ram__code,ram__capacity,internal_storage,internal_storage__code,internal_storage__capacity,image,price,note,create_time,update_time',
filter: { product: product.id }, filter: { product: product.id },
sort: 'id',
}, },
}" }"
/> />

View File

@@ -6,7 +6,7 @@ const props = defineProps({
<template> <template>
<div <div
class="w-20 h-4" class="w-full h-4"
:style="{ backgroundColor: color, outline: '1px solid var(--bulma-grey-85)' }" :style="{ backgroundColor: color, outline: '1px solid var(--bulma-grey-85)' }"
></div> ></div>
</template> </template>

View File

@@ -16,7 +16,7 @@ const activeMenu = ref(menus[1]);
<template> <template>
<div class="fixed-grid has-12-cols"> <div class="fixed-grid has-12-cols">
<div class="grid is-gap-4"> <div class="grid is-gap-4">
<div class="cell is-col-span-2"> <div class="cell is-col-span-3">
<aside class="menu"> <aside class="menu">
<ul class="menu-list"> <ul class="menu-list">
<li <li
@@ -25,19 +25,36 @@ const activeMenu = ref(menus[1]);
> >
<a <a
@click="activeMenu = menu" @click="activeMenu = menu"
:class="{ :class="[
'fs-13',
{
'is-active': activeMenu.id === menu.id, 'is-active': activeMenu.id === menu.id,
}" },
]"
>{{ menu.name }}</a >{{ menu.name }}</a
> >
</li> </li>
</ul> </ul>
</aside> </aside>
</div> </div>
<div class="cell is-col-span-10"> <div class="cell is-col-span-9">
<AddProductForm v-if="activeMenu.id === 'add-product'" /> <AddProductForm v-if="activeMenu.id === 'add-product'" />
<AddProductVariant v-if="activeMenu.id === 'add-product-variant'" /> <AddProductVariant v-if="activeMenu.id === 'add-product-variant'" />
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<style scoped>
.menu-list a {
--bulma-menu-list-link-padding: 0.75em 1.25em;
&:not(.is-active) {
--bulma-menu-item-background-l: 95%;
}
&.is-active {
--bulma-menu-item-h: var(--bulma-menu-item-selected-h);
--bulma-menu-item-s: var(--bulma-menu-item-selected-s);
--bulma-menu-item-l: var(--bulma-menu-item-selected-l);
}
}
</style>

View File

@@ -1,5 +1,4 @@
<script setup> <script setup>
const { dealer } = useStore();
const { $buildFileUrl, $copyToClipboard, $getEditRights } = useNuxtApp(); const { $buildFileUrl, $copyToClipboard, $getEditRights } = useNuxtApp();
const props = defineProps({ const props = defineProps({
@@ -24,7 +23,7 @@ const url = $buildFileUrl(props.image.file__file);
</span> </span>
</button> </button>
<button <button
v-if="!dealer && $getEditRights()" v-if="$getEditRights()"
class="button is-small is-white" class="button is-small is-white"
@click="editImage(image)" @click="editImage(image)"
title="Sửa" title="Sửa"
@@ -43,7 +42,7 @@ const url = $buildFileUrl(props.image.file__file);
</span> </span>
</button> </button>
<button <button
v-if="!dealer && $getEditRights()" v-if="$getEditRights()"
class="button is-small is-white" class="button is-small is-white"
@click="openDeleteImageConfirm(image)" @click="openDeleteImageConfirm(image)"
title="Xóa" title="Xóa"

View File

@@ -1,7 +1,6 @@
<script setup> <script setup>
import FileUpload from "@/components/media/FileUpload.vue"; import FileUpload from "@/components/media/FileUpload.vue";
const { dealer } = useStore();
const { $buildFileUrl, $formatFileSize, $getdata, $insertapi, $patchapi, $snackbar } = useNuxtApp(); const { $buildFileUrl, $formatFileSize, $getdata, $insertapi, $patchapi, $snackbar } = useNuxtApp();
const project = await $getdata("project", undefined, undefined, true); const project = await $getdata("project", undefined, undefined, true);
@@ -117,7 +116,7 @@ function cancelEditDocument() {
<template> <template>
<div class="mb-3"> <div class="mb-3">
<FileUpload <FileUpload
v-if="!dealer && $getEditRights()" v-if="$getEditRights()"
position="right" position="right"
:type="['pdf', 'file']" :type="['pdf', 'file']"
@files="onUploadedProjectDocs" @files="onUploadedProjectDocs"
@@ -222,7 +221,7 @@ function cancelEditDocument() {
<td> <td>
<div class="buttons"> <div class="buttons">
<a <a
v-if="!dealer && $getEditRights()" v-if="$getEditRights()"
@click="editDocument(doc)" @click="editDocument(doc)"
title="Sửa" title="Sửa"
> >
@@ -252,7 +251,7 @@ function cancelEditDocument() {
></span> ></span>
</a> </a>
<a <a
v-if="!dealer && $getEditRights()" v-if="$getEditRights()"
@click="deleteDocument(doc)" @click="deleteDocument(doc)"
title="Xóa" title="Xóa"
> >

View File

@@ -176,11 +176,9 @@ async function handleFileUpload(uploadedFiles) {
@click.prevent="openModal" @click.prevent="openModal"
>Hợp đồng</a >Hợp đồng</a
> >
<!-- <span v-if="!store.dealer" class="mx-1 has-text-grey-light"></span> -->
<FileUpload <FileUpload
v-if=" v-if="
txndetail.phase === 7 && txndetail.phase === 7 &&
!store.dealer &&
$getEditRights('edit', { $getEditRights('edit', {
code: 'transaction', code: 'transaction',
category: 'topmenu', category: 'topmenu',

View File

@@ -5,8 +5,7 @@ const props = defineProps({
transaction__code: String, transaction__code: String,
}); });
const { $exportpdf, $getdata, $snackbar, $store } = useNuxtApp(); const { $exportpdf, $getdata, $snackbar } = useNuxtApp();
const { dealer } = $store;
const txn = ref(null); const txn = ref(null);
const txndetails = ref(null); const txndetails = ref(null);
const showModal = ref(null); const showModal = ref(null);
@@ -83,7 +82,7 @@ onMounted(async () => {
class="column is-narrow is-flex is-align-items-center is-gap-2" class="column is-narrow is-flex is-align-items-center is-gap-2"
> >
<button <button
v-if="txn?.phase === 4 && !dealer && $getEditRights('edit', { code: 'transaction', category: 'topmenu' })" v-if="txn?.phase === 4 && $getEditRights('edit', { code: 'transaction', category: 'topmenu' })"
@click="openChangeCustomerModal" @click="openChangeCustomerModal"
class="button is-link" class="button is-link"
> >

View File

@@ -36,16 +36,21 @@ const showmodal = ref(undefined);
function getViewport() { function getViewport() {
let viewport; let viewport;
const width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; const width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
// Follow Bulma's breakpoints: https://bulma.io/documentation/start/responsiveness/#breakpoints
if (width <= 768) if (width <= 768)
viewport = 1; // 'mobile' viewport = 1; // 'mobile'
else if (width >= 769 && width <= 1023) else if (width <= 1023)
viewport = 2; // 'tablet' viewport = 2; // 'tablet'
else if (width >= 1024 && width <= 1215) else if (width <= 1215)
viewport = 3; // 'desktop' viewport = 3; // 'desktop'
else if (width >= 1216 && width <= 1407) else if (width <= 1407)
viewport = 4; // 'widescreen' viewport = 4; // 'widescreen'
else if (width >= 1408) viewport = 5; // 'fullhd' else viewport = 5; // 'fullhd'
if (viewport !== $store.viewport) {
$store.commit("viewport", viewport); $store.commit("viewport", viewport);
}
} }
async function checkRedirect() { async function checkRedirect() {
if (route.query.username && route.query.token) { if (route.query.username && route.query.token) {
@@ -69,18 +74,6 @@ async function checkRedirect() {
async function checkLogin() { async function checkLogin() {
if ($store.login ? $store.login.token : false) { if ($store.login ? $store.login.token : false) {
$store.commit("rights", await $getdata("grouprights", { group: $store.login.type })); $store.commit("rights", await $getdata("grouprights", { group: $store.login.type }));
$store.commit(
"dealer",
await $getdata(
"dealer",
undefined,
{
filter: { user: $store.login.id },
values: "id,code,name,phone,email,create_time",
},
true,
),
);
let authtoken = await $getdata("token", { token: $store.login.token }, undefined, true); let authtoken = await $getdata("token", { token: $store.login.token }, undefined, true);
if (authtoken ? authtoken.expiry : true) return; /* $requestLogin(); */ if (authtoken ? authtoken.expiry : true) return; /* $requestLogin(); */
authorized.value = true; authorized.value = true;

View File

@@ -403,7 +403,7 @@ export default defineNuxtPlugin(() => {
}; };
//======================Export=============================== //======================Export===============================
const exportExcel = function (data, filename, fields) { const exportExcel = async function (data, filename, fields) {
var _filename = filename + ".xlsx"; var _filename = filename + ".xlsx";
let list = []; let list = [];
data.map((v) => { data.map((v) => {
@@ -414,8 +414,9 @@ export default defineNuxtPlugin(() => {
}); });
list.push(ele); list.push(ele);
}); });
var XLSX = require("xlsx");
//workBook class const XLSX = await import("xlsx");
// WorkBook class
function Workbook() { function Workbook() {
if (!(this instanceof Workbook)) return new Workbook(); if (!(this instanceof Workbook)) return new Workbook();
this.SheetNames = []; this.SheetNames = [];

View File

@@ -20,12 +20,17 @@ export default defineNuxtPlugin((nuxtApp) => {
const { $id, $empty, $store } = nuxtApp; const { $id, $empty, $store } = nuxtApp;
const dialog = function (content, title, type, duration, width, height, vbind) { const dialog = function (content, title, type, duration, width, height, vbind) {
content = typeof content === "string" ? content : JSON.stringify(content); content = typeof content === "string" ? content : JSON.stringify(content);
let vtitle = type === "Success" ? `<span class="has-text-primary">${title}</span>` : title; const vtitle =
if (type === "Error") vtitle = `<span class="has-text-danger">${title}</span>`; type === "Success"
? `<span class="has-text-primary">${title}</span>`
: type === "Error"
? `<span class="has-text-danger">${title}</span>`
: title;
let data = { let data = {
id: $id(), id: $id(),
component: `dialog/${type || "Info"}`, component: `dialog/${type || "Info"}`,
vbind: { content: content, duration: duration, vbind: vbind }, vbind: { content, duration, vbind },
title: vtitle, title: vtitle,
width: width || "600px", width: width || "600px",
height: height || "100px", height: height || "100px",

View File

@@ -1108,13 +1108,18 @@ export default defineNuxtPlugin((nuxtApp) => {
sort: "capacity", sort: "capacity",
}, },
}, },
{
name: "IMEI",
url: "data/IMEI/",
url_detail: "data-detail/IMEI/",
params: {},
},
]; ];
const { $copy, $clone, $updateSeriesFields, $snackbar, $store, $remove, $dialog } = nuxtApp; const { $copy, $clone, $updateSeriesFields, $snackbar, $store, $remove, $dialog } = nuxtApp;
const requestLogin = function () { const requestLogin = function () {
$store.commit("login", undefined); $store.commit("login", undefined);
$store.commit("layersetting", undefined); $store.commit("layersetting", undefined);
$store.commit("lastlegendfiltertab", "Giỏ hàng");
window.location.href = `https://${mode === "dev" ? "dev." : ""}login.utopia.com.vn/signin?module=${module}&link=${window.location.origin}`; window.location.href = `https://${mode === "dev" ? "dev." : ""}login.utopia.com.vn/signin?module=${module}&link=${window.location.origin}`;
}; };
@@ -1178,7 +1183,7 @@ export default defineNuxtPlugin((nuxtApp) => {
const insertapi = async function (name, data, values, notify) { const insertapi = async function (name, data, values, notify) {
try { try {
let found = findapi(name); let found = findapi(name);
let curpath = found.path ? paths.find((x) => x.name === found.path).url : path; const curpath = found.path ? paths.find((x) => x.name === found.path).url : path;
let rs; let rs;
if (!Array.isArray(data)) { if (!Array.isArray(data)) {
rs = await $fetch(`${curpath}${found.url}`, { rs = await $fetch(`${curpath}${found.url}`, {
@@ -1190,15 +1195,15 @@ export default defineNuxtPlugin((nuxtApp) => {
rs = await $fetch(`${curpath}import-data/${found.url.substring(5, found.url.length - 1)}/`, { rs = await $fetch(`${curpath}import-data/${found.url.substring(5, found.url.length - 1)}/`, {
method: "POST", method: "POST",
body: data, body: data,
params: { action: "import", values }, params: { values, action: "import" },
}); });
} }
// update store // update store
if (found.commit) { if (found.commit) {
if ($store[found.commit]) { if ($store[found.commit]) {
let copy = $copy($store[found.commit]); const copy = $copy($store[found.commit]);
let rows = Array.isArray(rs) ? rs : [rs]; const rows = Array.isArray(rs) ? rs : [rs];
rows.map((v) => { rows.forEach((v) => {
if (v.id && !v.error) { if (v.id && !v.error) {
let idx = copy.findIndex((x) => x.id === v.id); let idx = copy.findIndex((x) => x.id === v.id);
if (idx >= 0) copy[idx] = v; if (idx >= 0) copy[idx] = v;
@@ -1441,16 +1446,15 @@ export default defineNuxtPlugin((nuxtApp) => {
// delete data // delete data
const deleteapi = async function (name, id) { const deleteapi = async function (name, id) {
try { try {
var rs; const found = findapi(name);
let found = findapi(name); let rs;
if (!Array.isArray(id)) if (!Array.isArray(id)) {
rs = await $fetch(`${path}${found.url_detail}${id}`, { rs = await $fetch(`${path}${found.url_detail}${id}`, {
method: "delete", method: "delete",
}); });
else { } else {
let params = { action: "delete" };
rs = await $fetch(`${path}import-data/${found.url.substring(5, found.url.length - 1)}/`, id, { rs = await $fetch(`${path}import-data/${found.url.substring(5, found.url.length - 1)}/`, id, {
params, params: { action: "delete" },
}); });
} }
if (found.commit) { if (found.commit) {
@@ -1465,13 +1469,12 @@ export default defineNuxtPlugin((nuxtApp) => {
}); });
} }
$store.commit(found.name, copy); $store.commit(found.name, copy);
console.log("copy", copy);
} }
return id; return id;
} catch (err) { } catch (err) {
console.log(err); console.log(err);
if (err.response) { if (err.response) {
let content = `<span class="has-text-danger">Đã xảy ra lỗi, xóa dữ liệu không thành công</span>`; let content = `<span>Đã xảy ra lỗi, xóa dữ liệu không thành công</span>`;
if (err.response.data) if (err.response.data)
content += `<p class="mt-2 has-text-grey-dark"> content += `<p class="mt-2 has-text-grey-dark">
<sapn class="icon-text">Chi tiết<SvgIcon class="ml-1" v-bind="{name: 'right.svg', type: 'dark', size: 20}"></SvgIcon></span></p> <sapn class="icon-text">Chi tiết<SvgIcon class="ml-1" v-bind="{name: 'right.svg', type: 'dark', size: 20}"></SvgIcon></span></p>

View File

@@ -4,7 +4,6 @@ export const useStore = defineStore("maindev", {
state: () => ({ state: () => ({
viewport: undefined, viewport: undefined,
login: undefined, login: undefined,
dealer: undefined,
token: undefined, token: undefined,
common: undefined, common: undefined,
settings: [], settings: [],
@@ -18,15 +17,13 @@ export const useStore = defineStore("maindev", {
branch: {}, branch: {},
rights: [], rights: [],
product: [], product: [],
cart: [],
}), }),
actions: { actions: {
commit(name, data) { commit(name, data) {
// if (name === "common") {
// console.trace("commit", name, data); // console.trace("commit", name, data);
// }
this[name] = data; this[name] = data;
}, },
removeSnackbar() { removeSnackbar() {
this.snackbar = undefined; this.snackbar = undefined;
}, },
@@ -35,10 +32,6 @@ export const useStore = defineStore("maindev", {
this.product = products; this.product = products;
}, },
updateCart(carts) {
this.cart = carts;
},
updateSingleProduct(updatedProduct) { updateSingleProduct(updatedProduct) {
const index = this.product.findIndex((p) => p.id === updatedProduct.id); const index = this.product.findIndex((p) => p.id === updatedProduct.id);
if (index !== -1) { if (index !== -1) {
@@ -54,7 +47,7 @@ export const useStore = defineStore("maindev", {
}, },
persist: { persist: {
pick: ["token", "login", "lang", "dealer", "lastlegendfiltertab", "layersetting"], pick: ["token", "login", "lang"],
storage: piniaPluginPersistedstate.localStorage(), storage: piniaPluginPersistedstate.localStorage(),
}, },
}); });

87
package-lock.json generated
View File

@@ -38,7 +38,8 @@
"vue-pdf": "^4.3.0", "vue-pdf": "^4.3.0",
"vue-router": "latest", "vue-router": "latest",
"vue3-datepicker": "^0.4.0", "vue3-datepicker": "^0.4.0",
"vue3-quill": "^0.3.1" "vue3-quill": "^0.3.1",
"xlsx": "^0.18.5"
}, },
"devDependencies": { "devDependencies": {
"@iconify-json/material-symbols": "^1.2.69", "@iconify-json/material-symbols": "^1.2.69",
@@ -6010,6 +6011,14 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
} }
}, },
"node_modules/adler-32": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz",
"integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==",
"engines": {
"node": ">=0.8"
}
},
"node_modules/agent-base": { "node_modules/agent-base": {
"version": "7.1.4", "version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
@@ -7716,6 +7725,18 @@
"url": "https://github.com/sponsors/wooorm" "url": "https://github.com/sponsors/wooorm"
} }
}, },
"node_modules/cfb": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz",
"integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
"dependencies": {
"adler-32": "~1.3.0",
"crc-32": "~1.2.0"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/character-entities-html4": { "node_modules/character-entities-html4": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
@@ -7968,6 +7989,14 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/codepage": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz",
"integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==",
"engines": {
"node": ">=0.8"
}
},
"node_modules/collection-visit": { "node_modules/collection-visit": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
@@ -8347,7 +8376,6 @@
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
"crc32": "bin/crc32.njs" "crc32": "bin/crc32.njs"
@@ -10419,6 +10447,14 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/frac": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz",
"integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==",
"engines": {
"node": ">=0.8"
}
},
"node_modules/fraction.js": { "node_modules/fraction.js": {
"version": "5.3.4", "version": "5.3.4",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
@@ -17279,6 +17315,17 @@
"node": ">=20.16.0" "node": ">=20.16.0"
} }
}, },
"node_modules/ssf": {
"version": "0.11.2",
"resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz",
"integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
"dependencies": {
"frac": "~1.1.2"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/ssri": { "node_modules/ssri": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz",
@@ -20361,6 +20408,22 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/wmf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz",
"integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==",
"engines": {
"node": ">=0.8"
}
},
"node_modules/word": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz",
"integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==",
"engines": {
"node": ">=0.8"
}
},
"node_modules/word-wrap": { "node_modules/word-wrap": {
"version": "1.2.5", "version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
@@ -20545,6 +20608,26 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/xlsx": {
"version": "0.18.5",
"resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",
"integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
"dependencies": {
"adler-32": "~1.3.0",
"cfb": "~1.2.1",
"codepage": "~1.15.0",
"crc-32": "~1.2.1",
"ssf": "~0.11.2",
"wmf": "~1.0.1",
"word": "~0.3.0"
},
"bin": {
"xlsx": "bin/xlsx.njs"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/xmlbuilder": { "node_modules/xmlbuilder": {
"version": "10.1.1", "version": "10.1.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.1.tgz", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.1.tgz",

View File

@@ -43,7 +43,8 @@
"vue-pdf": "^4.3.0", "vue-pdf": "^4.3.0",
"vue-router": "latest", "vue-router": "latest",
"vue3-datepicker": "^0.4.0", "vue3-datepicker": "^0.4.0",
"vue3-quill": "^0.3.1" "vue3-quill": "^0.3.1",
"xlsx": "^0.18.5"
}, },
"overrides": { "overrides": {
"vue": "latest" "vue": "latest"