changes
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<span :class="`icon-text is-flex-wrap-nowrap font-semibold fs-${props.size || 16} ${props.type || ''}`">
|
||||
<span :class="`icon-text is-flex-wrap-nowrap font-semibold fs-${props.size || 15} ${props.type || ''}`">
|
||||
<span style="text-wrap: nowrap">{{ title }}</span>
|
||||
<span class="icon">
|
||||
<Icon name="material-symbols:arrow-forward-ios-rounded" />
|
||||
|
||||
@@ -259,11 +259,11 @@
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="currentTab === 'detail'">
|
||||
<div class="fs-14 mt-3 is-flex is-gap-0.5 is-align-items-center">
|
||||
<p class="font-semibold">Tên trường:</p>
|
||||
<p>{{ currentField.name }}</p>
|
||||
<p class="field fs-14 is-flex is-gap-0.5 is-align-items-center">
|
||||
<span class="font-semibold">Tên trường:</span>
|
||||
<span>{{ currentField.name }}</span>
|
||||
<button
|
||||
class="button is-white is-small"
|
||||
class="button is-small size-8 is-primary is-light is-rounded"
|
||||
@click="copyContent(currentField.name)"
|
||||
>
|
||||
<span class="icon">
|
||||
@@ -273,8 +273,10 @@
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<label class="label fs-14 mt-3">Mô tả<span class="has-text-danger"> *</span></label>
|
||||
</p>
|
||||
<div class="field">
|
||||
<label class="label fs-14">Mô tả<span class="has-text-danger"> *</span></label>
|
||||
<div class="control">
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
<input
|
||||
@@ -298,13 +300,15 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
v-if="errors.find((v) => v.name === 'label')"
|
||||
class="help is-danger"
|
||||
>
|
||||
{{ errors.find((v) => v.name === "label").msg }}
|
||||
</p>
|
||||
<div class="field mt-3">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label fs-14">Kiểu dữ liệu<span class="has-text-danger"> * </span></label>
|
||||
<div class="control fs-14">
|
||||
<button
|
||||
@@ -365,8 +369,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-horizontal mt-3">
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<div class="field">
|
||||
<label class="label fs-14">Định dạng nâng cao</label>
|
||||
<div class="control">
|
||||
@@ -398,7 +401,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="currentTab === 'value'">
|
||||
<ScrollBox
|
||||
v-bind="{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="array || !enableTime"
|
||||
v-if="timeRanges || !enableTime"
|
||||
class="has-text-primary fixed-grid has-12-cols"
|
||||
style="border-bottom: 2px solid var(--bulma-grey-80)"
|
||||
>
|
||||
@@ -8,61 +8,62 @@
|
||||
<div
|
||||
v-if="enableTime"
|
||||
class="cell is-col-span-7 is-flex is-align-items-center is-flex-wrap-wrap"
|
||||
style="row-gap: 0; column-gap: 1rem"
|
||||
style="gap: 0.5rem 1rem"
|
||||
>
|
||||
<Caption
|
||||
:title="lang === 'vi' ? 'Thời gian' : 'Time'"
|
||||
type="has-text-orange"
|
||||
/>
|
||||
<div
|
||||
v-for="v in array"
|
||||
v-for="v in timeRanges"
|
||||
:key="v.code"
|
||||
:class="['is-flex is-align-items-center', v.code !== current && 'is-clickable']"
|
||||
style="gap: 0.35rem"
|
||||
@click="v.code !== current && changeOption(v)"
|
||||
>
|
||||
<span
|
||||
v-if="v.code === current"
|
||||
class="icon-text is-flex-wrap-nowrap font-semibold has-text-orange"
|
||||
>
|
||||
<span class="icon">
|
||||
<Icon
|
||||
name="material-symbols:check-rounded"
|
||||
:size="22"
|
||||
/>
|
||||
</span>
|
||||
<span style="text-wrap: nowrap">{{ v.name }}</span>
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="icon-text font-semibold has-text-grey is-clickable"
|
||||
:class="v.code === current ? 'font-bold has-text-orange' : 'font-medium has-text-grey-50'"
|
||||
style="text-wrap: nowrap"
|
||||
@click="changeOption(v)"
|
||||
>
|
||||
{{ v.name }}
|
||||
</span>
|
||||
<span
|
||||
:class="[
|
||||
'tag rounded-md w-5 h-6 fs-13',
|
||||
v.code === current
|
||||
? 'font-bold has-text-orange has-background-orange-90'
|
||||
: 'font-medium has-text-grey-40 has-background-grey-90',
|
||||
]"
|
||||
>{{ v.count }}</span
|
||||
>
|
||||
</div>
|
||||
<span
|
||||
v-if="newDataAvailable"
|
||||
class="has-text-danger is-italic fs-14 ml-2"
|
||||
class="has-text-danger-50 px-3 py-1.5 has-background-danger-95 rounded-md is-italic fs-14"
|
||||
>Có dữ liệu mới, vui lòng làm mới.</span
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="cell is-col-span-5 is-flex is-align-items-center is-flex-wrap-wrap"
|
||||
style="row-gap: 0; column-gap: 1rem"
|
||||
style="gap: 0.25rem 0.5rem"
|
||||
>
|
||||
<Caption
|
||||
:title="lang === 'vi' ? `Tìm ${viewport === 1 ? '' : 'kiếm'}` : 'Search'"
|
||||
:title="lang === 'vi' ? `Tìm ${this.store.viewport > 2 ? 'kiếm' : ''}` : 'Search'"
|
||||
type="has-text-orange"
|
||||
/>
|
||||
<input
|
||||
class="input is-small is-orange"
|
||||
id="input"
|
||||
type="text"
|
||||
v-model="text"
|
||||
:style="`${viewport === 1 ? 'width:150px;' : ''} border: 1px solid var(--bulma-grey-80); max-width: 160px`"
|
||||
@keyup="startSearch"
|
||||
id="input"
|
||||
:placeholder="lang === 'vi' ? 'Nhập từ khóa...' : 'Enter keyword...'"
|
||||
class="input is-orange fs-12"
|
||||
:style="{
|
||||
maxWidth: '180px',
|
||||
width: this.store.viewport === 1 ? '150px' : 'auto',
|
||||
}"
|
||||
/>
|
||||
<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-wrap-wrap is-align-items-center">
|
||||
<p
|
||||
v-if="importdata && $getEditRights()"
|
||||
class="control"
|
||||
@@ -174,14 +175,14 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
options: [
|
||||
{ code: 0, name: this.lang === "vi" ? "Hôm nay" : "Today" },
|
||||
{ code: 0, name: this.store.lang === "vi" ? "Hôm nay" : "Today" },
|
||||
{ code: 1, name: "1D" },
|
||||
{ code: 7, name: "7D" },
|
||||
{ code: 30, name: "1M" },
|
||||
{ code: 90, name: "3M" },
|
||||
{ code: 180, name: "6M" },
|
||||
{ code: 360, name: "1Y" },
|
||||
{ code: 36000, name: this.lang === "vi" ? "Tất cả" : "All" },
|
||||
{ code: 36000, name: this.store.lang === "vi" ? "Tất cả" : "All" },
|
||||
],
|
||||
showmodal: undefined,
|
||||
current: 7,
|
||||
@@ -189,11 +190,10 @@ export default {
|
||||
timer: undefined,
|
||||
text: undefined,
|
||||
status: [{ code: 100, name: "Chọn" }],
|
||||
array: undefined,
|
||||
timeRanges: undefined,
|
||||
enableAdd: true,
|
||||
enableTime: true,
|
||||
choices: [], // Sẽ được cập nhật động từ pagedata
|
||||
viewport: this.store.viewport,
|
||||
pagedata: undefined,
|
||||
loading: false,
|
||||
pollingInterval: null,
|
||||
@@ -202,7 +202,7 @@ export default {
|
||||
},
|
||||
|
||||
watch: {
|
||||
pagename(newVal) {
|
||||
pagename() {
|
||||
this.updateSearchableFields();
|
||||
},
|
||||
},
|
||||
@@ -211,7 +211,7 @@ export default {
|
||||
// Cập nhật searchable fields ngay từ đầu
|
||||
this.updateSearchableFields();
|
||||
|
||||
if (this.viewport < 5) {
|
||||
if (this.store.viewport < 5) {
|
||||
this.options = [
|
||||
{ code: 0, name: "Hôm nay" },
|
||||
{ code: 1, name: "1D" },
|
||||
@@ -255,7 +255,7 @@ export default {
|
||||
|
||||
let f = {};
|
||||
this.options.map((v) => {
|
||||
f[`${v.code}`] = {
|
||||
f[v.code] = {
|
||||
type: "Count",
|
||||
field: "create_time__date",
|
||||
filter: v.filter,
|
||||
@@ -268,16 +268,16 @@ export default {
|
||||
try {
|
||||
let rs = await this.$getapi([found]);
|
||||
for (const [key, value] of Object.entries(rs[0].data.rows)) {
|
||||
let found = this.$find(this.options, { code: Number(key) });
|
||||
const found = this.$find(this.options, { code: Number(key) });
|
||||
if (found) {
|
||||
found.name = `${found.name} (${value})`;
|
||||
found.count = value;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
}
|
||||
|
||||
this.array = this.$copy(this.options);
|
||||
this.timeRanges = this.$copy(this.options);
|
||||
},
|
||||
|
||||
mounted() {
|
||||
@@ -380,7 +380,7 @@ export default {
|
||||
this.text = undefined;
|
||||
this.search = undefined;
|
||||
}
|
||||
this.$emit("option", this.$find(this.array, { code: this.current }));
|
||||
this.$emit("option", this.$find(this.timeRanges, { code: this.current }));
|
||||
},
|
||||
|
||||
doSearch() {
|
||||
|
||||
81
app/components/imports/AddIMEIForm.vue
Normal file
81
app/components/imports/AddIMEIForm.vue
Normal file
@@ -0,0 +1,81 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
variant: Object,
|
||||
});
|
||||
|
||||
const { $empty, $insertapi } = useNuxtApp();
|
||||
const emit = defineEmits(["created"]);
|
||||
const isLoading = ref(false);
|
||||
const body = ref({
|
||||
imei: null,
|
||||
variant: props.variant.id,
|
||||
});
|
||||
|
||||
async function submit() {
|
||||
isLoading.value = true;
|
||||
const res = await $insertapi("IMEI", body.value);
|
||||
isLoading.value = false;
|
||||
if (res !== "error") {
|
||||
emit("created");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form class="fixed-grid has-12-cols">
|
||||
<div class="grid">
|
||||
<div class="cell is-col-span-4">
|
||||
<div class="field">
|
||||
<label class="label">Variant</label>
|
||||
<SearchBox
|
||||
v-bind="{
|
||||
api: 'Product_Variant',
|
||||
field: 'code',
|
||||
column: ['code'],
|
||||
optionid: variant.id,
|
||||
disabled: true,
|
||||
first: true,
|
||||
placeholder: 'Variant',
|
||||
}"
|
||||
@option="body.variant = $event?.id ?? $event"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cell is-col-span-6">
|
||||
<div class="field">
|
||||
<label class="label">IMEI</label>
|
||||
<div class="control">
|
||||
<input
|
||||
v-model.trim="body.imei"
|
||||
type="text"
|
||||
class="input"
|
||||
placeholder="IMEI"
|
||||
maxlength="15"
|
||||
/>
|
||||
</div>
|
||||
<p class="help is-info">15 chữ số</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cell is-col-span-2">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<label class="label is-invisible">Submit</label>
|
||||
<button
|
||||
:class="['button is-primary is-fullwidth', isLoading && 'is-loading']"
|
||||
:disabled="$empty(body.imei) || body.imei.length !== 15"
|
||||
@click.prevent="submit"
|
||||
>
|
||||
<span class="icon">
|
||||
<Icon
|
||||
name="material-symbols:add-rounded"
|
||||
:size="18"
|
||||
/>
|
||||
</span>
|
||||
<span>Thêm</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
@@ -45,7 +45,7 @@ watch(product, () => {
|
||||
pagename: 'product-variants',
|
||||
params: {
|
||||
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,image__path,price,note,create_time,update_time',
|
||||
filter: { product: product.id },
|
||||
sort: 'id',
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup>
|
||||
import InputNumber from "@/components/common/InputNumber.vue";
|
||||
import FileUpload from "@/components/media/FileUpload.vue";
|
||||
import { omitBy } from "es-toolkit";
|
||||
|
||||
const props = defineProps({
|
||||
@@ -8,7 +9,7 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(["created"]);
|
||||
|
||||
const { $empty, $insertapi } = useNuxtApp();
|
||||
const { $buildFileUrl, $empty, $insertapi } = useNuxtApp();
|
||||
const isPending = ref(false);
|
||||
const body = ref({
|
||||
product: props.productId,
|
||||
@@ -16,13 +17,16 @@ const body = ref({
|
||||
internal_storage: null,
|
||||
ram: null,
|
||||
color: null,
|
||||
image: null,
|
||||
note: "",
|
||||
});
|
||||
|
||||
const uploadedImage = ref();
|
||||
|
||||
watch(
|
||||
() => props.productId,
|
||||
(newVal) => {
|
||||
body.value.product = props.productId;
|
||||
body.value.product = newVal;
|
||||
},
|
||||
);
|
||||
|
||||
@@ -34,8 +38,22 @@ function selected(field, data) {
|
||||
}
|
||||
}
|
||||
|
||||
function onFiles(files) {
|
||||
const file = files[0];
|
||||
uploadedImage.value = file;
|
||||
}
|
||||
|
||||
async function createProductVariant() {
|
||||
isPending.value = true;
|
||||
|
||||
// 1. insert row to Product_Image, get back id
|
||||
const productImage = await $insertapi("Product_Image", {
|
||||
name: uploadedImage.value.name,
|
||||
path: $buildFileUrl(uploadedImage.value.file),
|
||||
});
|
||||
body.value.image = productImage.id;
|
||||
|
||||
// 2. upload to Product_Variant table
|
||||
const trimmedBody = omitBy(body.value, $empty);
|
||||
const res = await $insertapi("Product_Variant", trimmedBody);
|
||||
isPending.value = false;
|
||||
@@ -132,7 +150,7 @@ async function createProductVariant() {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cell is-col-span-12">
|
||||
<div class="cell is-col-span-6">
|
||||
<div class="field">
|
||||
<label class="label">Ghi chú</label>
|
||||
<textarea
|
||||
@@ -140,10 +158,65 @@ async function createProductVariant() {
|
||||
class="textarea"
|
||||
name="note"
|
||||
placeholder="Ghi chú"
|
||||
rows="1"
|
||||
rows="5"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cell is-col-span-6">
|
||||
<div class="field">
|
||||
<label class="label">Hình ảnh</label>
|
||||
<div class="control is-flex is-gap-1">
|
||||
<div>
|
||||
<div
|
||||
v-if="uploadedImage"
|
||||
class="relative"
|
||||
style="border: 1px solid var(--bulma-grey-50)"
|
||||
>
|
||||
<figure class="image is-128x128">
|
||||
<img :src="$buildFileUrl(uploadedImage.file)" />
|
||||
</figure>
|
||||
<button
|
||||
type="button"
|
||||
class="button is-small is-danger is-light absolute"
|
||||
:style="{ top: '5px', right: '5px' }"
|
||||
@click="uploadedImage = undefined"
|
||||
>
|
||||
<span class="icon">
|
||||
<Icon
|
||||
name="material-symbols:delete-outline-rounded"
|
||||
:size="20"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="w-36 h-36 rounded-md is-flex is-align-items-center is-justify-content-center"
|
||||
style="border: 1px solid var(--bulma-grey-80)"
|
||||
>
|
||||
<Icon
|
||||
name="material-symbols:add-photo-alternate-outline-rounded"
|
||||
:size="40"
|
||||
class="has-text-grey-light"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<FileUpload
|
||||
:type="['image']"
|
||||
:multiple="false"
|
||||
@files="onFiles"
|
||||
>
|
||||
<template #icon>
|
||||
<Icon
|
||||
name="material-symbols:add-photo-alternate-outline-rounded"
|
||||
:size="20"
|
||||
/>
|
||||
</template>
|
||||
<span class="font-medium">Chọn</span>
|
||||
</FileUpload>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cell is-col-span-12">
|
||||
<button
|
||||
:class="['button is-primary', { 'is-loading': isPending }]"
|
||||
|
||||
50
app/components/imports/DeleteProductVariant.vue
Normal file
50
app/components/imports/DeleteProductVariant.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<script setup>
|
||||
import Modal from "@/components/Modal.vue";
|
||||
|
||||
const props = defineProps({
|
||||
variant: Object,
|
||||
});
|
||||
|
||||
const emit = defineEmits(["dynamicCompEvent"]);
|
||||
const { $deleteapi } = useNuxtApp();
|
||||
const showConfirmModal = ref(null);
|
||||
|
||||
function displayModal() {
|
||||
showConfirmModal.value = {
|
||||
component: "dialog/Confirm",
|
||||
title: "Xoá phiên bản",
|
||||
width: "500px",
|
||||
height: "auto",
|
||||
vbind: {
|
||||
content: `Bạn xác nhận xoá phiên bản <code>${props.variant.code}</code>?`,
|
||||
onModalevent: deleteVariant,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function deleteVariant() {
|
||||
const res = await $deleteapi("Product_Variant", props.variant.id);
|
||||
if (res !== "error") {
|
||||
// emit to parent, which is DataTable
|
||||
emit("dynamicCompEvent", { type: "refresh" });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a
|
||||
class="has-text-danger"
|
||||
@click="displayModal"
|
||||
>
|
||||
<span class="icon">
|
||||
<Icon
|
||||
name="material-symbols:delete-outline-rounded"
|
||||
:size="18"
|
||||
/>
|
||||
</span>
|
||||
<Modal
|
||||
v-bind="showConfirmModal"
|
||||
@close="showConfirmModal = null"
|
||||
/>
|
||||
</a>
|
||||
</template>
|
||||
32
app/components/imports/IMEIButton.vue
Normal file
32
app/components/imports/IMEIButton.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
variant: Object,
|
||||
});
|
||||
const showModal = ref(null);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
@click="
|
||||
showModal = {
|
||||
component: 'imports/IMEIs',
|
||||
title: 'IMEIs',
|
||||
width: '80%',
|
||||
height: '400px',
|
||||
vbind: { variant: props.variant },
|
||||
}
|
||||
"
|
||||
class="button is-small is-primary is-ghost"
|
||||
>
|
||||
<span class="icon">
|
||||
<Icon
|
||||
name="mdi:launch"
|
||||
:size="22"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
<Modal
|
||||
v-bind="showModal"
|
||||
@close="showModal = null"
|
||||
/>
|
||||
</template>
|
||||
40
app/components/imports/IMEIs.vue
Normal file
40
app/components/imports/IMEIs.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<script setup>
|
||||
import DataView from "@/components/datatable/DataView.vue";
|
||||
import AddIMEIForm from "@/components/imports/AddIMEIForm.vue";
|
||||
import ImportData from "@/components/parameter/ImportData.vue";
|
||||
|
||||
const props = defineProps({
|
||||
variant: Object,
|
||||
});
|
||||
const emit = defineEmits(["close"]);
|
||||
const key = ref(0);
|
||||
</script>
|
||||
<template>
|
||||
<div class="block">
|
||||
<DataView
|
||||
:key="key"
|
||||
v-bind="{
|
||||
api: 'IMEI',
|
||||
setting: 'imeis',
|
||||
pagename: 'imeis',
|
||||
params: {
|
||||
values: 'id,code,imei,variant,variant__code,create_time,update_time',
|
||||
filter: { variant: variant.id },
|
||||
sort: 'id',
|
||||
},
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
<div class="block">
|
||||
<AddIMEIForm
|
||||
:variant="variant"
|
||||
@created="key++"
|
||||
/>
|
||||
</div>
|
||||
<div class="block">
|
||||
<ImportData
|
||||
code="imeis"
|
||||
@close="key++"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
11
app/components/imports/ProductImage.vue
Normal file
11
app/components/imports/ProductImage.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
variant: Object,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<figure class="image is-32x32">
|
||||
<img :src="variant.image__path" />
|
||||
</figure>
|
||||
</template>
|
||||
@@ -1,4 +1,4 @@
|
||||
<!-- extract logic to a composable -->
|
||||
<!-- TODO: extract logic to a composable -->
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
@@ -14,7 +14,7 @@
|
||||
ref="file-input"
|
||||
type="file"
|
||||
:id="docid"
|
||||
multiple
|
||||
:multiple="multiple ?? true"
|
||||
name="resume"
|
||||
@change="doChange"
|
||||
/>
|
||||
@@ -47,6 +47,7 @@ const props = defineProps({
|
||||
type: Array,
|
||||
convert: String,
|
||||
quality: Number,
|
||||
multiple: Boolean,
|
||||
});
|
||||
|
||||
const emit = defineEmits(["files"]);
|
||||
@@ -85,24 +86,25 @@ function doChange() {
|
||||
const convertValue = props.convert ? "1" : "0";
|
||||
const qualityValue = props.convert && props.quality ? props.quality : null;
|
||||
|
||||
files.value.map((v) => {
|
||||
const ext = getFileExtension(v.name);
|
||||
for (const file of files.value) {
|
||||
const ext = getFileExtension(file.name);
|
||||
const type = getType(ext);
|
||||
|
||||
if (!vtype.includes(type)) {
|
||||
$snackbar(`Định dạng file "${ext}" không hợp lệ`);
|
||||
fileInput.value.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
const file = $upload(v, type, 1, convertValue, qualityValue);
|
||||
dataFiles.value.push(file);
|
||||
});
|
||||
const dataFile = $upload(file, type, 1, convertValue, qualityValue);
|
||||
dataFiles.value.push(dataFile);
|
||||
}
|
||||
|
||||
showmodal.value = {
|
||||
component: "media/UploadProgress",
|
||||
title: "Upload files",
|
||||
title: "Upload Files",
|
||||
width: "700px",
|
||||
height: "200px",
|
||||
height: "auto",
|
||||
vbind: { files: dataFiles.value },
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
<template>
|
||||
<div class="field is-grouped">
|
||||
<div
|
||||
class="control"
|
||||
id="ignore"
|
||||
>
|
||||
<div class="file is-primary m-0">
|
||||
<label class="file-label">
|
||||
<input
|
||||
class="file-input"
|
||||
@@ -12,86 +9,112 @@
|
||||
name="resume"
|
||||
@change="inputFile"
|
||||
/>
|
||||
<span class="file-cta px-2 has-background-primary">
|
||||
<span class="icon-text is-clickable">
|
||||
<SvgIcon v-bind="{ name: 'attach-file.svg', type: 'white', size: 22 }"></SvgIcon>
|
||||
<span class="has-text-white">Chọn file</span>
|
||||
<span
|
||||
class="file-cta"
|
||||
style="padding-left: 0.5rem"
|
||||
>
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<Icon
|
||||
name="material-symbols:attach-file"
|
||||
:size="18"
|
||||
/>
|
||||
</span>
|
||||
<span class="font-medium">Chọn file</span>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="control">
|
||||
<a
|
||||
class="button is-primary"
|
||||
@click="download()"
|
||||
<button
|
||||
class="button is-primary is-light"
|
||||
@click="download"
|
||||
>
|
||||
Tải template
|
||||
</a>
|
||||
<a
|
||||
class="button is-light ml-6"
|
||||
<span class="icon">
|
||||
<Icon
|
||||
name="material-symbols:download"
|
||||
:size="20"
|
||||
/>
|
||||
</span>
|
||||
<span>Tải template</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="datafile"
|
||||
@click="exportExcel()"
|
||||
>Xuất Excel</a
|
||||
class="button is-success is-light"
|
||||
@click="exportExcel"
|
||||
>
|
||||
</div>
|
||||
<span class="icon">
|
||||
<Icon
|
||||
name="mdi:microsoft-excel"
|
||||
:size="20"
|
||||
/>
|
||||
</span>
|
||||
<span>Xuất Excel</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="columns mb-0"
|
||||
class="fixed-grid has-12-cols"
|
||||
v-if="msgInfo.length > 0"
|
||||
>
|
||||
<div class="column is-7 mb-0">
|
||||
<div class="notification is-white py-2 mb-2">
|
||||
<div class="grid">
|
||||
<div class="cell is-col-span-9">
|
||||
<div class="notification fs-14">
|
||||
<button
|
||||
class="delete"
|
||||
@click="msgInfo = []"
|
||||
></button>
|
||||
<div style="max-height: 250px; overflow-y: auto">
|
||||
<article
|
||||
class="media py-0 my-0"
|
||||
v-for="(ele, key) in msgInfo"
|
||||
:key="key"
|
||||
<div
|
||||
class="content"
|
||||
style="max-height: 250px; overflow-y: auto"
|
||||
>
|
||||
<figure class="media-left">
|
||||
<i :class="classinfo.find((v) => v.type === ele.type).icon_class"> </i>
|
||||
</figure>
|
||||
<div class="media-content">
|
||||
<div class="content">
|
||||
<p :class="classinfo.find((v) => v.type == ele.type).text_class">
|
||||
{{ ele.message }}
|
||||
<p
|
||||
v-for="(ele, i) in msgInfo"
|
||||
:key="i"
|
||||
class="mb-2"
|
||||
>
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<Icon
|
||||
:name="classinfo.find((v) => v.type === ele.type).icon"
|
||||
:size="18"
|
||||
:class="classinfo.find((v) => v.type === ele.type).color"
|
||||
/>
|
||||
</span>
|
||||
<span v-html="ele.message"></span>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="result">
|
||||
<span class="has-text-primary fsb-18"
|
||||
>{{ `Thành công: ${result.success_count || 0}` }} / {{ total || "" }}</span
|
||||
>
|
||||
<span class="has-text-primary fsb-18">Thành công: {{ result.success_count || 0 }} / {{ total || "" }}</span>
|
||||
<span class="has-text-danger fsb-18 ml-5">{{ `Lỗi: ${result.error_count || 0}` }} / {{ total || "" }}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="column has-text-centered mb-0"
|
||||
v-if="ready"
|
||||
class="cell is-col-span-3"
|
||||
>
|
||||
<a
|
||||
:class="`button is-primary ${isloading ? 'is-loading' : ''}`"
|
||||
@click="insert()"
|
||||
<div class="buttons">
|
||||
<button
|
||||
v-if="countNew > 0"
|
||||
>Thêm mới ({{ countNew }}/{{ total }})</a
|
||||
:class="['button is-success', isloading && 'is-loading']"
|
||||
@click="insert"
|
||||
>
|
||||
<a
|
||||
:class="`button is-dark ${isloading ? 'is-loading' : ''} ml-2`"
|
||||
@click="update()"
|
||||
Thêm mới ({{ countNew }}/{{ total }})
|
||||
</button>
|
||||
<button
|
||||
v-if="countUdt > 0"
|
||||
>Cập nhật ({{ countUdt }}/{{ total }})</a
|
||||
:class="['button is-primary is-light', isloading && 'is-loading']"
|
||||
@click="update"
|
||||
>
|
||||
Cập nhật ({{ countUdt }}/{{ total }})
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DataTable
|
||||
v-bind="{ pagename: pagename }"
|
||||
v-if="pagedata"
|
||||
:pagename="pagename"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -103,23 +126,23 @@ export default {
|
||||
classinfo: [
|
||||
{
|
||||
type: "success",
|
||||
icon_class: "mdi mdi-check",
|
||||
text_class: "has-text-primary",
|
||||
icon: "material-symbols:check-rounded",
|
||||
color: "has-text-success",
|
||||
},
|
||||
{
|
||||
type: "error",
|
||||
icon_class: "mdi mdi-close has-text-danger",
|
||||
text_class: "has-text-danger",
|
||||
icon: "material-symbols:cancel-rounded",
|
||||
color: "has-text-danger",
|
||||
},
|
||||
{
|
||||
type: "warning",
|
||||
icon_class: "mdi mdi-alert has-text-warning",
|
||||
text_class: "has-text-warning",
|
||||
icon: "material-symbols:warning-rounded",
|
||||
color: "has-text-warning",
|
||||
},
|
||||
{
|
||||
type: "waiting",
|
||||
icon_class: "mdi mdi-timer-sand has-text-primary",
|
||||
text_class: "has-text-primary",
|
||||
icon: "material-symbols:hourglass-top-rounded",
|
||||
color: "has-text-primary",
|
||||
},
|
||||
],
|
||||
msgInfo: [],
|
||||
@@ -160,34 +183,36 @@ export default {
|
||||
download() {
|
||||
window.open(`${this.$getpath()}static/excelform/${this.setting.template}`, "_blank");
|
||||
},
|
||||
async inputFile(v) {
|
||||
async inputFile() {
|
||||
this.isloading = true;
|
||||
this.msgInfo = [];
|
||||
let fileList = document.getElementById("divfile").files;
|
||||
let files = Array.from(fileList);
|
||||
const files = document.getElementById("divfile").files;
|
||||
if (files.length === 0) return;
|
||||
var thefile = this.$upload(files[0], "file", 1);
|
||||
|
||||
const file = files.item(0);
|
||||
let thefile = this.$upload(file, "file", 1);
|
||||
|
||||
if (thefile.error) {
|
||||
this.$buefy.toast.open({ message: thefile.text, type: "is-danger" });
|
||||
this.$snackbar(thefile.text, undefined, "Error");
|
||||
return (this.isloading = false);
|
||||
}
|
||||
if (!(thefile.name.search(".xls") > 0 || thefile.name.search(".xlsx") > 0)) {
|
||||
let text = this.$find(
|
||||
const message = this.$find(
|
||||
this.$store.syspara,
|
||||
{ category: "inform", classify: "import", code: "file-type-invalid" },
|
||||
"value",
|
||||
);
|
||||
this.msgInfo.push({ message: text, type: "error" });
|
||||
this.msgInfo.push({ message, type: "error" });
|
||||
return (this.isloading = false);
|
||||
}
|
||||
let result = await this.$insertapi("upload", thefile.form, undefined, false);
|
||||
if (result === "error") {
|
||||
let text = this.$find(
|
||||
const message = this.$find(
|
||||
this.$store.syspara,
|
||||
{ category: "inform", classify: "import", code: "file-upload-fail" },
|
||||
"value",
|
||||
);
|
||||
this.msgInfo.push({ message: text, type: "error" });
|
||||
this.msgInfo.push({ message, type: "error" });
|
||||
} else {
|
||||
this.fileLog = result;
|
||||
this.fileInfo = thefile;
|
||||
@@ -199,19 +224,22 @@ export default {
|
||||
document.getElementById("divfile").value = "";
|
||||
},
|
||||
async fillData() {
|
||||
let found = this.$findapi("readexcel");
|
||||
found.params = { name: this.fileInfo.filename };
|
||||
let result = await this.$getapi([found]);
|
||||
const readExcelApi = this.$findapi("readexcel");
|
||||
readExcelApi.params = { name: this.fileInfo.filename };
|
||||
const result = await this.$getapi([readExcelApi]);
|
||||
this.datafile = JSON.parse(result[0].data);
|
||||
let fields = [];
|
||||
const fields = [];
|
||||
|
||||
this.datafile.schema.fields.forEach((ele) => {
|
||||
let field = this.$createField(ele.name, ele.name, "string", true);
|
||||
if (field.name !== "index") fields.push(field);
|
||||
});
|
||||
let copy = this.pagedata ? this.$copy(this.pagedata) : this.$getpage();
|
||||
let data = this.datafile.data;
|
||||
data.map((v) => (v.updater = this.$store.login.id));
|
||||
copy.update = { fields: fields, data: data };
|
||||
|
||||
const copy = this.pagedata ? this.$copy(this.pagedata) : this.$getpage();
|
||||
const data = this.datafile.data;
|
||||
// data.map((v) => (v.updater = this.$store.login.id));
|
||||
data.map((v) => (v.updater = 1));
|
||||
copy.update = { fields, data };
|
||||
copy.data = data;
|
||||
copy.fields = fields;
|
||||
this.pagedata = copy;
|
||||
@@ -223,17 +251,18 @@ export default {
|
||||
let found = fields.find((x) => x.name === v);
|
||||
if (!found) misslist.push(v);
|
||||
});
|
||||
|
||||
if (misslist.length > 0) {
|
||||
let text = `Thiếu các cột bắt buộc: ${misslist.join(", ")}`;
|
||||
this.msgInfo.push({ message: text, type: "error" });
|
||||
const message = `Thiếu các cột bắt buộc: ${misslist.join(", ")}`;
|
||||
this.msgInfo.push({ message, type: "error" });
|
||||
} else {
|
||||
this.validateFontend();
|
||||
this.validateFrontend();
|
||||
}
|
||||
},
|
||||
validateFontend() {
|
||||
validateFrontend() {
|
||||
let self = this;
|
||||
var checkValid = function (name, obj, data) {
|
||||
var error = false;
|
||||
const checkValid = function (name, obj, data) {
|
||||
let error = false;
|
||||
if (obj.empty === "no" || !self.$empty(obj.api)) {
|
||||
data.map((x) => {
|
||||
if (self.$empty(x[name])) {
|
||||
@@ -305,14 +334,14 @@ export default {
|
||||
message: "Dữ liệu có lỗi. Hãy kiểm tra lại",
|
||||
type: "error",
|
||||
});
|
||||
let field = this.$createField("error", "error", "string", true, true);
|
||||
const field = this.$createField("error", "error", "string", true, true);
|
||||
field.color = "red";
|
||||
let fields = this.$copy(this.pagedata.fields);
|
||||
fields = [field].concat(fields);
|
||||
let copy = this.$clone(this.pagedata);
|
||||
const copy = this.$clone(this.pagedata);
|
||||
copy.data = data;
|
||||
copy.fields = fields;
|
||||
copy.update = { data: data, fields: fields };
|
||||
copy.update = { data, fields };
|
||||
this.pagedata = copy;
|
||||
},
|
||||
|
||||
@@ -323,12 +352,12 @@ export default {
|
||||
type: "waiting",
|
||||
});
|
||||
|
||||
let keys = [];
|
||||
let related = [];
|
||||
const keys = [];
|
||||
const related = [];
|
||||
|
||||
// 1. Tạo dữ liệu gửi lên: Dùng chính tên cột Excel để chứa giá trị
|
||||
let mappedRows = rows.map((row) => {
|
||||
let newRow = { ...row };
|
||||
const mappedRows = rows.map((row) => {
|
||||
const newRow = { ...row };
|
||||
for (const [excelColName, config] of Object.entries(this.requireFields)) {
|
||||
if (excelColName in row) {
|
||||
newRow[excelColName] = row[excelColName];
|
||||
@@ -354,17 +383,19 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
let conn = this.$findapi(this.setting.api);
|
||||
let payload = {
|
||||
// this.setting.api is 'Product_Variant'
|
||||
const conn = this.$findapi(this.setting.api);
|
||||
const payload = {
|
||||
name: conn.url.replace("data/", "").replace(/\//g, ""),
|
||||
data: mappedRows,
|
||||
keys: keys,
|
||||
related: related,
|
||||
keys,
|
||||
related,
|
||||
};
|
||||
|
||||
try {
|
||||
let rs = await this.$insertapi("findkey", payload);
|
||||
if (rs && rs.data) {
|
||||
const rs = await this.$insertapi("findkey", payload, undefined, false);
|
||||
if (rs === "error") throw new Error();
|
||||
else {
|
||||
this.data = rs.data;
|
||||
this.total = this.data.length;
|
||||
|
||||
@@ -372,7 +403,7 @@ export default {
|
||||
this.checkUpdatePermission();
|
||||
|
||||
// 1. Lọc ra danh sách các lỗi
|
||||
let errors = this.data
|
||||
const errors = this.data
|
||||
.map((v, i) => (v.error ? { line: i + 1, msg: v.error } : null))
|
||||
.filter((v) => v !== null);
|
||||
|
||||
@@ -403,9 +434,10 @@ export default {
|
||||
this.showStatus();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
this.isloading = false;
|
||||
this.msgInfo.push({
|
||||
message: "Lỗi hệ thống khi kiểm tra",
|
||||
message: `Lỗi hệ thống khi kiểm tra: ${e.message}`,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
@@ -454,23 +486,23 @@ export default {
|
||||
},
|
||||
|
||||
showStatus() {
|
||||
let field = this.$createField("record_status", "Status", "string", true, true);
|
||||
const field = this.$createField("record_status", "Status", "string", true, true);
|
||||
field.color = "blue";
|
||||
let fields = this.$copy(this.pagedata.fields);
|
||||
const fields = this.$copy(this.pagedata.fields);
|
||||
fields.push(field);
|
||||
this.$store.commit("updateState", {
|
||||
name: this.pagename,
|
||||
key: "fields",
|
||||
data: fields,
|
||||
});
|
||||
let data = this.$copy(this.pagedata.data);
|
||||
data.map((v, i) => (v.record_status = this.data[i].id ? "Tồn tại" : "Mới"));
|
||||
const data = this.$copy(this.pagedata.data);
|
||||
data.forEach((v, i) => (v.record_status = this.data[i].id ? "Tồn tại" : "Mới"));
|
||||
this.$store.commit("updateState", {
|
||||
name: this.pagename,
|
||||
key: "data",
|
||||
data: data,
|
||||
data,
|
||||
});
|
||||
let copy = this.$clone(this.pagedata);
|
||||
const copy = this.$clone(this.pagedata);
|
||||
this.pagedata = undefined;
|
||||
setTimeout(() => (this.pagedata = copy), 10);
|
||||
},
|
||||
@@ -500,15 +532,15 @@ export default {
|
||||
async insert() {
|
||||
this.isloading = true;
|
||||
this.result = undefined;
|
||||
let conn = this.$findapi(this.setting.api);
|
||||
let obj = {
|
||||
const conn = this.$findapi(this.setting.api);
|
||||
const importLogPayload = {
|
||||
code: this.$id(),
|
||||
model: conn.url.replace("data/", "").replace("/", ""),
|
||||
file: this.fileInfo.name,
|
||||
fields: this.setting.detail,
|
||||
};
|
||||
await this.$insertapi("importlog", obj);
|
||||
var interval = setInterval(() => this.getResult(obj.code), 2000);
|
||||
await this.$insertapi("importlog", importLogPayload);
|
||||
const interval = setInterval(() => this.getResult(importLogPayload.code), 2000);
|
||||
|
||||
// Lọc bản ghi KHÔNG có id (bản ghi mới)
|
||||
let filter = this.data.filter((v) => !v.id && !v.error);
|
||||
@@ -525,14 +557,15 @@ export default {
|
||||
this.setting.call_api,
|
||||
{ data: filter, user: this.$store.login.id },
|
||||
undefined,
|
||||
obj.code,
|
||||
importLogPayload.code,
|
||||
);
|
||||
} else {
|
||||
result = await this.$insertapi(this.setting.api, filter, undefined, obj.code);
|
||||
result = await this.$insertapi(this.setting.api, filter, undefined, importLogPayload.code);
|
||||
}
|
||||
|
||||
this.isloading = false;
|
||||
clearInterval(interval);
|
||||
this.getResult(obj.code);
|
||||
this.getResult(importLogPayload.code);
|
||||
|
||||
// Xử lý result/response mới
|
||||
if (result === "error") {
|
||||
@@ -544,7 +577,7 @@ export default {
|
||||
}
|
||||
|
||||
let hasError = false;
|
||||
let errorMessages = [];
|
||||
const errorMessages = [];
|
||||
|
||||
// Kiểm tra entries có lỗi không
|
||||
if (result.entries && Array.isArray(result.entries)) {
|
||||
@@ -600,15 +633,15 @@ export default {
|
||||
async update() {
|
||||
this.isloading = true;
|
||||
this.result = undefined;
|
||||
let conn = this.$findapi(this.setting.api);
|
||||
let obj = {
|
||||
const conn = this.$findapi(this.setting.api);
|
||||
const importlogPayload = {
|
||||
code: this.$id(),
|
||||
model: conn.url.replace("data/", "").replace("/", ""),
|
||||
file: this.fileInfo.name,
|
||||
fields: this.setting.detail,
|
||||
};
|
||||
await this.$insertapi("importlog", obj);
|
||||
var interval = setInterval(() => this.getResult(obj.code), 2000);
|
||||
await this.$insertapi("importlog", importlogPayload);
|
||||
const interval = setInterval(() => this.getResult(importlogPayload.code), 2000);
|
||||
|
||||
// Lọc bản ghi CÓ id (bản ghi tồn tại) VÀ KHÔNG CÓ LỖI
|
||||
let filter = this.data.filter((v) => v.id && !v.error);
|
||||
@@ -619,17 +652,22 @@ export default {
|
||||
this.total = filter.length;
|
||||
|
||||
// Gửi lên API - Backend sẽ UPDATE
|
||||
let result = await this.$insertapi(this.setting.call_api || this.setting.api, filter, undefined, obj.code);
|
||||
const result = await this.$insertapi(
|
||||
this.setting.call_api || this.setting.api,
|
||||
filter,
|
||||
undefined,
|
||||
importlogPayload.code,
|
||||
);
|
||||
this.isloading = false;
|
||||
clearInterval(interval);
|
||||
this.getResult(obj.code);
|
||||
this.getResult(importlogPayload.code);
|
||||
|
||||
if (result === "error") {
|
||||
return this.msgInfo.push({ message: "Có lỗi xảy ra", type: "error" });
|
||||
}
|
||||
|
||||
let hasError = false;
|
||||
let errorMessages = [];
|
||||
const errorMessages = [];
|
||||
|
||||
// Kiểm tra có lỗi trong result không
|
||||
if (Array.isArray(result)) {
|
||||
@@ -656,16 +694,16 @@ export default {
|
||||
},
|
||||
|
||||
async getResult(logcode) {
|
||||
let found = this.$findapi("importlog");
|
||||
const found = this.$findapi("importlog");
|
||||
found.params.filter = { code: logcode };
|
||||
let rs = await this.$getapi([found]);
|
||||
const rs = await this.$getapi([found]);
|
||||
this.result = rs[0].data.rows[0];
|
||||
},
|
||||
|
||||
exportExcel() {
|
||||
let dataType = {};
|
||||
const dataType = {};
|
||||
this.pagedata.fields.map((v) => (dataType[v.label.indexOf(">") >= 0 ? v.name : v.label] = "String"));
|
||||
let data = this.pagedata.dataFilter || this.pagedata.data;
|
||||
const data = this.pagedata.dataFilter || this.pagedata.data;
|
||||
this.$exportExcel(
|
||||
data,
|
||||
"data-export",
|
||||
|
||||
@@ -110,10 +110,7 @@ export default {
|
||||
let rs1 = await this.$updateapi("user", user);
|
||||
if (rs1 !== "error") this.$emit("close");
|
||||
else {
|
||||
this.$buefy.toast.open({
|
||||
message: "Có lỗi xảy ra. Hãy thử lại một lần nữa",
|
||||
type: "is-danger",
|
||||
});
|
||||
this.$snackbar("Có lỗi xảy ra. Hãy thử lại một lần nữa", undefined, "Error");
|
||||
}
|
||||
},
|
||||
checkPassword() {
|
||||
|
||||
@@ -30,9 +30,10 @@ import { throttle } from "es-toolkit";
|
||||
|
||||
const route = useRoute();
|
||||
const { $getdata, $requestLogin, $store } = useNuxtApp();
|
||||
var authorized = ref(false);
|
||||
const authorized = ref(false);
|
||||
const snackbar = ref(undefined);
|
||||
const showmodal = ref(undefined);
|
||||
|
||||
function getViewport() {
|
||||
let viewport;
|
||||
const width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
|
||||
@@ -52,6 +53,7 @@ function getViewport() {
|
||||
$store.commit("viewport", viewport);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkRedirect() {
|
||||
if (route.query.username && route.query.token) {
|
||||
let row = await $getdata(
|
||||
@@ -71,6 +73,7 @@ async function checkRedirect() {
|
||||
} else if (!$store.login) return; /* $requestLogin(); */
|
||||
// await checkLogin();
|
||||
}
|
||||
|
||||
async function checkLogin() {
|
||||
if ($store.login ? $store.login.token : false) {
|
||||
$store.commit("rights", await $getdata("grouprights", { group: $store.login.type }));
|
||||
@@ -80,6 +83,7 @@ async function checkLogin() {
|
||||
}
|
||||
// else $requestLogin();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
checkRedirect();
|
||||
getViewport();
|
||||
@@ -95,7 +99,7 @@ watch(
|
||||
);
|
||||
watch(
|
||||
() => $store.showmodal,
|
||||
(newVal, oldVal) => {
|
||||
(newVal) => {
|
||||
showmodal.value = newVal;
|
||||
},
|
||||
);
|
||||
|
||||
@@ -107,7 +107,7 @@ export default defineNuxtPlugin((nuxtApp) => {
|
||||
};
|
||||
|
||||
const upload = function (file, type, user, convert, quality) {
|
||||
var fileFormat = [
|
||||
const fileFormat = [
|
||||
{
|
||||
type: "image",
|
||||
format: [".png", ".jpg", "jpeg", ".bmp", ".gif", ".svg", ".webp"],
|
||||
@@ -117,7 +117,7 @@ export default defineNuxtPlugin((nuxtApp) => {
|
||||
format: [".wmv", ".avi", ".mp4", ".flv", ".mov", ".mpg", ".amv", ".rm"],
|
||||
},
|
||||
];
|
||||
var valid = undefined;
|
||||
let valid = undefined;
|
||||
if (type === "image" || type === "video") {
|
||||
valid = false;
|
||||
let found = fileFormat.find((v) => v.type === type);
|
||||
|
||||
@@ -1114,6 +1114,12 @@ export default defineNuxtPlugin((nuxtApp) => {
|
||||
url_detail: "data-detail/IMEI/",
|
||||
params: {},
|
||||
},
|
||||
{
|
||||
name: "Product_Image",
|
||||
url: "data/Product_Image/",
|
||||
url_detail: "data-detail/Product_Image/",
|
||||
params: {},
|
||||
},
|
||||
];
|
||||
const { $copy, $clone, $updateSeriesFields, $snackbar, $store, $remove, $dialog } = nuxtApp;
|
||||
|
||||
|
||||
@@ -24,6 +24,10 @@ import AddColor from "@/components/imports/addons/AddColor.vue";
|
||||
import AddRAM from "@/components/imports/addons/AddRAM.vue";
|
||||
import AddInternalStorage from "@/components/imports/addons/AddInternalStorage.vue";
|
||||
import Color from "@/components/imports/Color.vue";
|
||||
import IMEIButton from "@/components/imports/IMEIButton.vue";
|
||||
import IMEIs from "@/components/imports/IMEIs.vue";
|
||||
import DeleteProductVariant from "@/components/imports/DeleteProductVariant.vue";
|
||||
import ProductImage from "@/components/imports/ProductImage.vue";
|
||||
import Returns from "@/components/imports/Returns.vue";
|
||||
import Exports from "@/components/exports/Exports.vue";
|
||||
import ExportsDamaged from "@/components/exports/ExportsDamaged.vue";
|
||||
@@ -36,66 +40,67 @@ import Customers from "@/components/report/Customers.vue";
|
||||
import Goods from "@/components/report/Goods.vue";
|
||||
import ReportCashBook from "@/components/report/CashBook.vue";
|
||||
import Finance from "@/components/report/Finance.vue";
|
||||
import Notebox from "~/components/common/Notebox.vue";
|
||||
import ProductCountbox from "~/components/common/ProductCountbox.vue";
|
||||
import SvgIcon from "~/components/SvgIcon.vue";
|
||||
import DataView from "~/components/datatable/DataView.vue";
|
||||
import PivotDataView from "~/components/datatable/PivotDataView.vue";
|
||||
import PickDay from "~/components/datepicker/PickDay.vue";
|
||||
import Datepicker from "~/components/datepicker/Datepicker.vue";
|
||||
import ImageGallery from "~/components/media/ImageGallery.vue";
|
||||
import FileGallery from "~/components/media/FileGallery.vue";
|
||||
import FileUpload from "~/components/media/FileUpload.vue";
|
||||
import FileShow from "~/components/media/FileShow.vue";
|
||||
import ChipImage from "~/components/media/ChipImage.vue";
|
||||
import Avatarbox from "~/components/common/Avatarbox.vue";
|
||||
import Email from "~/components/marketing/email/Email.vue";
|
||||
import ViewList from "~/components/common/ViewList.vue";
|
||||
import InternalEntry from "~/components/modal/InternalEntry.vue";
|
||||
import Notebox from "@/components/common/Notebox.vue";
|
||||
import ProductCountbox from "@/components/common/ProductCountbox.vue";
|
||||
import SvgIcon from "@/components/SvgIcon.vue";
|
||||
import DataView from "@/components/datatable/DataView.vue";
|
||||
import PivotDataView from "@/components/datatable/PivotDataView.vue";
|
||||
import PickDay from "@/components/datepicker/PickDay.vue";
|
||||
import Datepicker from "@/components/datepicker/Datepicker.vue";
|
||||
import ImageGallery from "@/components/media/ImageGallery.vue";
|
||||
import FileGallery from "@/components/media/FileGallery.vue";
|
||||
import FileUpload from "@/components/media/FileUpload.vue";
|
||||
import FileShow from "@/components/media/FileShow.vue";
|
||||
import ChipImage from "@/components/media/ChipImage.vue";
|
||||
import Avatarbox from "@/components/common/Avatarbox.vue";
|
||||
import Email from "@/components/marketing/email/Email.vue";
|
||||
import ViewList from "@/components/common/ViewList.vue";
|
||||
import InternalEntry from "@/components/modal/InternalEntry.vue";
|
||||
|
||||
import Configuration from "~/components/maintab/Configuration.vue";
|
||||
import DebtView from "~/components/accounting/DebtView.vue";
|
||||
import Configuration from "@/components/maintab/Configuration.vue";
|
||||
import DebtView from "@/components/accounting/DebtView.vue";
|
||||
|
||||
//format
|
||||
import FormatNumber from "~/components/datatable/format/FormatNumber.vue";
|
||||
import DataTable from "~/components/datatable/DataTable.vue";
|
||||
import DataModel from "~/components/datatable/DataModel.vue";
|
||||
import InputNumber from "~/components/common/InputNumber.vue";
|
||||
import ColorText from "~/components/datatable/format/ColorText.vue";
|
||||
import FormatNumber from "@/components/datatable/format/FormatNumber.vue";
|
||||
import FormatDate from "@/components/datatable/format/FormatDate.vue";
|
||||
import DataTable from "@/components/datatable/DataTable.vue";
|
||||
import DataModel from "@/components/datatable/DataModel.vue";
|
||||
import InputNumber from "@/components/common/InputNumber.vue";
|
||||
import ColorText from "@/components/datatable/format/ColorText.vue";
|
||||
|
||||
//menu
|
||||
import MenuAction from "~/components/menu/MenuAction.vue";
|
||||
import MenuApp from "~/components/menu/MenuApp.vue";
|
||||
import MenuCust from "~/components/menu/MenuCust.vue";
|
||||
import MenuPhone from "~/components/menu/MenuPhone.vue";
|
||||
import MenuParam from "~/components/menu/MenuParam.vue";
|
||||
import MenuAdd from "~/components/menu/MenuAdd.vue";
|
||||
import MenuCollab from "~/components/menu/MenuCollab.vue";
|
||||
import MenuNote from "~/components/menu/MenuNote.vue";
|
||||
import MenuPayment from "~/components/menu/MenuPayment.vue";
|
||||
import ScrollBox from "~/components/datatable/ScrollBox.vue";
|
||||
import Product from "~/components/product/Product.vue";
|
||||
import Reservation from "~/components/modal/Reservation.vue";
|
||||
import UserMainTab from "~/components/modal/UserMainTab.vue";
|
||||
import TransactionFiles from "~/components/transaction/TransactionFiles.vue";
|
||||
import PaymentSchedule from "~/components/application/PaymentSchedule.vue";
|
||||
import TransactionView from "~/components/transaction/TransactionView.vue";
|
||||
import ContractPaymentUpload from "~/components/application/ContractPaymentUpload.vue";
|
||||
import CountWithAdd from "~/components/common/CountWithAdd.vue";
|
||||
import CalculationView from "~/components/application/CalculationView.vue";
|
||||
import InternalAccount from "~/components/accounting/InternalAccount.vue";
|
||||
import MenuAccount from "~/components/menu/MenuAccount.vue";
|
||||
import PhaseAdvance from "~/components/application/PhaseAdvance.vue";
|
||||
import MenuAction from "@/components/menu/MenuAction.vue";
|
||||
import MenuApp from "@/components/menu/MenuApp.vue";
|
||||
import MenuCust from "@/components/menu/MenuCust.vue";
|
||||
import MenuPhone from "@/components/menu/MenuPhone.vue";
|
||||
import MenuParam from "@/components/menu/MenuParam.vue";
|
||||
import MenuAdd from "@/components/menu/MenuAdd.vue";
|
||||
import MenuCollab from "@/components/menu/MenuCollab.vue";
|
||||
import MenuNote from "@/components/menu/MenuNote.vue";
|
||||
import MenuPayment from "@/components/menu/MenuPayment.vue";
|
||||
import ScrollBox from "@/components/datatable/ScrollBox.vue";
|
||||
import Product from "@/components/product/Product.vue";
|
||||
import Reservation from "@/components/modal/Reservation.vue";
|
||||
import UserMainTab from "@/components/modal/UserMainTab.vue";
|
||||
import TransactionFiles from "@/components/transaction/TransactionFiles.vue";
|
||||
import PaymentSchedule from "@/components/application/PaymentSchedule.vue";
|
||||
import TransactionView from "@/components/transaction/TransactionView.vue";
|
||||
import ContractPaymentUpload from "@/components/application/ContractPaymentUpload.vue";
|
||||
import CountWithAdd from "@/components/common/CountWithAdd.vue";
|
||||
import CalculationView from "@/components/application/CalculationView.vue";
|
||||
import InternalAccount from "@/components/accounting/InternalAccount.vue";
|
||||
import MenuAccount from "@/components/menu/MenuAccount.vue";
|
||||
import PhaseAdvance from "@/components/application/PhaseAdvance.vue";
|
||||
import ImageLayout from "@/components/media/ImageLayout.vue";
|
||||
import ProjectDocuments from "~/components/product/ProjectDocuments.vue";
|
||||
import ProjectDocuments from "@/components/product/ProjectDocuments.vue";
|
||||
|
||||
import Cart from "~/components/product/Cart.vue";
|
||||
import CountdownTimer from "~/components/common/CountdownTimer.vue";
|
||||
import CustomerInfo2 from "~/components/customer/CustomerInfo2.vue";
|
||||
import MenuFile from "~/components/menu/MenuFile.vue";
|
||||
import DebtProduct from "~/components/accounting/DebtProduct.vue";
|
||||
import DebtCustomer from "~/components/accounting/DebtCustomer.vue";
|
||||
import Due from "~/components/debt/Due.vue";
|
||||
import Cart from "@/components/product/Cart.vue";
|
||||
import CountdownTimer from "@/components/common/CountdownTimer.vue";
|
||||
import CustomerInfo2 from "@/components/customer/CustomerInfo2.vue";
|
||||
import MenuFile from "@/components/menu/MenuFile.vue";
|
||||
import DebtProduct from "@/components/accounting/DebtProduct.vue";
|
||||
import DebtCustomer from "@/components/accounting/DebtCustomer.vue";
|
||||
import Due from "@/components/debt/Due.vue";
|
||||
import Overdue from "@/components/debt/Overdue.vue";
|
||||
|
||||
const components = {
|
||||
@@ -137,6 +142,7 @@ const components = {
|
||||
MenuPayment,
|
||||
DataModel,
|
||||
FormatNumber,
|
||||
FormatDate,
|
||||
MenuApp,
|
||||
MenuCust,
|
||||
MenuAdd,
|
||||
@@ -179,6 +185,10 @@ const components = {
|
||||
AddRAM,
|
||||
AddInternalStorage,
|
||||
Color,
|
||||
IMEIButton,
|
||||
IMEIs,
|
||||
DeleteProductVariant,
|
||||
ProductImage,
|
||||
Returns,
|
||||
Exports,
|
||||
ExportsDamaged,
|
||||
|
||||
Reference in New Issue
Block a user