chore: install prettier
This commit is contained in:
@@ -2,9 +2,18 @@
|
||||
<div>
|
||||
<div class="fixed-grid has-3-cols">
|
||||
<div class="grid">
|
||||
<div class="cell" v-for="rule in recordRules" :key="rule.code">
|
||||
<div
|
||||
class="cell"
|
||||
v-for="rule in recordRules"
|
||||
:key="rule.code"
|
||||
>
|
||||
<label class="radio">
|
||||
<input type="radio" name="answer" :value="rule.code" v-model="recordCurrent" />
|
||||
<input
|
||||
type="radio"
|
||||
name="answer"
|
||||
:value="rule.code"
|
||||
v-model="recordCurrent"
|
||||
/>
|
||||
{{ isVietnamese ? rule.vi : rule.en }}
|
||||
</label>
|
||||
</div>
|
||||
@@ -27,14 +36,21 @@ const recordRules = ref([]);
|
||||
const recordCurrent = ref({});
|
||||
let current = {};
|
||||
|
||||
let foundCurrent = await $getdata(props.api, { category: "system", classify: "current", code: "rule" });
|
||||
let foundCurrent = await $getdata(props.api, {
|
||||
category: "system",
|
||||
classify: "current",
|
||||
code: "rule",
|
||||
});
|
||||
|
||||
if (foundCurrent !== "error" && foundCurrent.length > 0) {
|
||||
recordCurrent.value = foundCurrent[0].detail;
|
||||
current.value = foundCurrent[0];
|
||||
}
|
||||
|
||||
let foundRules = await $getdata(props.api, { category: "system", classify: "rules" });
|
||||
let foundRules = await $getdata(props.api, {
|
||||
category: "system",
|
||||
classify: "rules",
|
||||
});
|
||||
if (foundRules !== "error" && foundRules.length > 0) {
|
||||
recordRules.value = foundRules;
|
||||
}
|
||||
|
||||
@@ -1,52 +1,74 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-body">
|
||||
<div class="field is-narrow">
|
||||
<label class="label">Code <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control">
|
||||
<input class="input" type="text" placeholder="" v-model="record.code" id="code">
|
||||
</p>
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-body">
|
||||
<div class="field is-narrow">
|
||||
<label class="label">Code <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control">
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder=""
|
||||
v-model="record.code"
|
||||
id="code"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Name <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control">
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder=""
|
||||
v-model="record.name"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Name <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control">
|
||||
<input class="input" type="text" placeholder="" v-model="record.name">
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field mt-5">
|
||||
<div class="field mt-5">
|
||||
<label class="label">Index</label>
|
||||
<p class="control">
|
||||
<input class="input" type="text" placeholder="" v-model="record.index">
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder=""
|
||||
v-model="record.index"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<button class="button is-primary has-text-white" @click="update()">Save</button>
|
||||
<div class="mt-5">
|
||||
<button
|
||||
class="button is-primary has-text-white"
|
||||
@click="update()"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { onMounted } from 'vue'
|
||||
const { $copy, $resetNull, $insertrow, $updaterow, $snackbar} = useNuxtApp()
|
||||
var props = defineProps({
|
||||
api: String,
|
||||
pagename: String,
|
||||
row: Object,
|
||||
prefix: String
|
||||
})
|
||||
const emit = defineEmits(['close', 'modalevent'])
|
||||
var record = $copy( props.row || {})
|
||||
async function update() {
|
||||
let data = $resetNull(record)
|
||||
let rs = data.id? await $updaterow(props.api, data, undefined, props.pagename)
|
||||
: await $insertrow(props.api, data, undefined, props.pagename)
|
||||
if(rs==='error') return $snackbar(rs)
|
||||
emit('modalevent', {name: 'dataevent', data: rs})
|
||||
emit('close')
|
||||
}
|
||||
onMounted(()=> {
|
||||
document.getElementById('code').focus()
|
||||
})
|
||||
</script>
|
||||
import { onMounted } from "vue";
|
||||
const { $copy, $resetNull, $insertrow, $updaterow, $snackbar } = useNuxtApp();
|
||||
var props = defineProps({
|
||||
api: String,
|
||||
pagename: String,
|
||||
row: Object,
|
||||
prefix: String,
|
||||
});
|
||||
const emit = defineEmits(["close", "modalevent"]);
|
||||
var record = $copy(props.row || {});
|
||||
async function update() {
|
||||
let data = $resetNull(record);
|
||||
let rs = data.id
|
||||
? await $updaterow(props.api, data, undefined, props.pagename)
|
||||
: await $insertrow(props.api, data, undefined, props.pagename);
|
||||
if (rs === "error") return $snackbar(rs);
|
||||
emit("modalevent", { name: "dataevent", data: rs });
|
||||
emit("close");
|
||||
}
|
||||
onMounted(() => {
|
||||
document.getElementById("code").focus();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,59 +1,85 @@
|
||||
<template>
|
||||
<div class="columns is-multiline is-mobile mx-0">
|
||||
<div class="column is-1">
|
||||
<div class="field">
|
||||
<label class="label has-text-left">ID<span class="has-text-danger ml-1">*</span></label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" v-model="record.id" disabled>
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns is-multiline is-mobile mx-0">
|
||||
<div class="column is-1">
|
||||
<div class="field">
|
||||
<label class="label has-text-left">ID<span class="has-text-danger ml-1">*</span></label>
|
||||
<div class="control">
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
v-model="record.id"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label has-text-left">Category<span class="has-text-danger ml-1">*</span></label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" v-model="record.category">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label has-text-left">Category<span class="has-text-danger ml-1">*</span></label>
|
||||
<div class="control">
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
v-model="record.category"
|
||||
/>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label has-text-left">Classify<span class="has-text-danger ml-1">*</span></label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" v-model="record.classify">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label has-text-left">Classify<span class="has-text-danger ml-1">*</span></label>
|
||||
<div class="control">
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
v-model="record.classify"
|
||||
/>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label has-text-left">Code<span class="has-text-danger ml-1">*</span></label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" v-model="record.code">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-2">
|
||||
<div class="field">
|
||||
<label class="label has-text-left">Index</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" v-model="record.index">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label has-text-left">Code<span class="has-text-danger ml-1">*</span></label>
|
||||
<div class="control">
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
v-model="record.code"
|
||||
/>
|
||||
</div>
|
||||
<div class="column is-12">
|
||||
<label class="label has-text-left">VI<span class="has-text-danger ml-1">*</span></label>
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
<textarea class="textarea" placeholder="" rows="3" v-model="record.vi"></textarea>
|
||||
</div>
|
||||
<div class="control ml-5">
|
||||
<a @click="openEditor()">
|
||||
<SvgIcon v-bind="{name: 'pen1.svg', type: 'gray', size: 22}"></SvgIcon>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-2">
|
||||
<div class="field">
|
||||
<label class="label has-text-left">Index</label>
|
||||
<div class="control">
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
v-model="record.index"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-12">
|
||||
<label class="label has-text-left">VI<span class="has-text-danger ml-1">*</span></label>
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
<textarea
|
||||
class="textarea"
|
||||
placeholder=""
|
||||
rows="3"
|
||||
v-model="record.vi"
|
||||
></textarea>
|
||||
</div>
|
||||
<!-- <div class="column is-12">
|
||||
<div class="control ml-5">
|
||||
<a @click="openEditor()">
|
||||
<SvgIcon v-bind="{ name: 'pen1.svg', type: 'gray', size: 22 }"></SvgIcon>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="column is-12">
|
||||
<label class="label has-text-left">EN<span class="has-text-danger ml-1">*</span></label>
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
@@ -66,48 +92,70 @@
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="column is-6">
|
||||
<div class="field">
|
||||
<label class="label has-text-left">Image</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" v-model="record.image">
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-6">
|
||||
<div class="field">
|
||||
<label class="label has-text-left">Image</label>
|
||||
<div class="control">
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
v-model="record.image"
|
||||
/>
|
||||
</div>
|
||||
<div class="column is-6">
|
||||
<div class="field">
|
||||
<label class="label has-text-left">Link</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" v-model="record.link">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-6">
|
||||
<div class="field">
|
||||
<label class="label has-text-left">Link</label>
|
||||
<div class="control">
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
v-model="record.link"
|
||||
/>
|
||||
</div>
|
||||
<div class="column is-12">
|
||||
<label class="label has-text-left">Detail</label>
|
||||
<div class="field has-addons">
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-12">
|
||||
<label class="label has-text-left">Detail</label>
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
<input class="input" type="text" placeholder="" v-model="record.detail">
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder=""
|
||||
v-model="record.detail"
|
||||
/>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button" @click="editDetail('detail')">
|
||||
<button
|
||||
class="button"
|
||||
@click="editDetail('detail')"
|
||||
>
|
||||
<span>
|
||||
<SvgIcon v-bind="{name: 'pen1.svg', type: 'gray', size: 17}"></SvgIcon>
|
||||
<SvgIcon v-bind="{ name: 'pen1.svg', type: 'gray', size: 17 }"></SvgIcon>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button" @click="copyDetail('detail')">
|
||||
<button
|
||||
class="button"
|
||||
@click="copyDetail('detail')"
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button" @click="pasteDetail('detail')">
|
||||
<button
|
||||
class="button"
|
||||
@click="pasteDetail('detail')"
|
||||
>
|
||||
Paste
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="column is-6">
|
||||
</div>
|
||||
<!-- <div class="column is-6">
|
||||
<label class="label has-text-left">Detail EN</label>
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
@@ -132,57 +180,84 @@
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="column is-12 pt-5">
|
||||
<a class="button is-primary has-text-white" @click="updateData()">Save</a>
|
||||
<a class="button is-dark has-text-white ml-5" @click="updateData(true)" v-if="record.id">Create new</a>
|
||||
<div class="column is-12 pt-5">
|
||||
<a
|
||||
class="button is-primary has-text-white"
|
||||
@click="updateData()"
|
||||
>Save</a
|
||||
>
|
||||
<a
|
||||
class="button is-dark has-text-white ml-5"
|
||||
@click="updateData(true)"
|
||||
v-if="record.id"
|
||||
>Create new</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<Modal @close="showmodal=undefined" v-bind="showmodal" @texteditor="updateText" @update="updateAttr" v-if="showmodal"></Modal>
|
||||
<Modal
|
||||
@close="showmodal = undefined"
|
||||
v-bind="showmodal"
|
||||
@texteditor="updateText"
|
||||
@update="updateAttr"
|
||||
v-if="showmodal"
|
||||
></Modal>
|
||||
</template>
|
||||
<script setup>
|
||||
const emit = defineEmits([])
|
||||
var props = defineProps({
|
||||
pagename: String,
|
||||
row: Object,
|
||||
api: String
|
||||
})
|
||||
const { $copy, $resetNull, $insertrow, $updaterow, $copyToClipboard, $empty } = useNuxtApp()
|
||||
var record = ref(props.row? $copy(props.row) : {})
|
||||
var showmodal = ref()
|
||||
var vapi = props.api
|
||||
var current
|
||||
var updateText = function(content) {
|
||||
record.value.vi = content
|
||||
}
|
||||
var openEditor = function() {
|
||||
showmodal.value = {component: 'common/TextEditor', vbind: {content: record.value.vi}, title: 'Text editor', width: '40%', height: '150px'}
|
||||
}
|
||||
var editDetail = function(attr) {
|
||||
current = attr
|
||||
let detail = record.value[attr]? record.value[attr] : {}
|
||||
showmodal.value = {component: 'datatable/FieldAttribute', vbind: {field: detail, close: true},
|
||||
title: 'Change attributes', width: '40%', height: '150px'}
|
||||
}
|
||||
var updateAttr = function(detail) {
|
||||
record.value[current] = detail
|
||||
}
|
||||
function copyDetail(attr) {
|
||||
if($empty(record.value[attr])) return
|
||||
let val = typeof record.value[attr]=='string'? record.value[attr] : JSON.stringify(record.value[attr])
|
||||
$copyToClipboard(val)
|
||||
}
|
||||
async function pasteDetail(attr) {
|
||||
let text = await navigator.clipboard.readText()
|
||||
if($empty(text)) return
|
||||
record.value[attr] = JSON.parse(text)
|
||||
}
|
||||
var updateData = async function(isNew) {
|
||||
let ele = record.value
|
||||
if(ele.create_time===null) ele.create_time = new Date()
|
||||
ele = $resetNull(ele)
|
||||
if(isNew) delete ele.id
|
||||
let result = ele.id? await $updaterow(vapi, ele, undefined, props.pagename)
|
||||
: await $insertrow(vapi, ele, undefined, props.pagename)
|
||||
if(isNew) emit('close')
|
||||
}
|
||||
</script>
|
||||
const emit = defineEmits([]);
|
||||
var props = defineProps({
|
||||
pagename: String,
|
||||
row: Object,
|
||||
api: String,
|
||||
});
|
||||
const { $copy, $resetNull, $insertrow, $updaterow, $copyToClipboard, $empty } = useNuxtApp();
|
||||
var record = ref(props.row ? $copy(props.row) : {});
|
||||
var showmodal = ref();
|
||||
var vapi = props.api;
|
||||
var current;
|
||||
var updateText = function (content) {
|
||||
record.value.vi = content;
|
||||
};
|
||||
var openEditor = function () {
|
||||
showmodal.value = {
|
||||
component: "common/TextEditor",
|
||||
vbind: { content: record.value.vi },
|
||||
title: "Text editor",
|
||||
width: "40%",
|
||||
height: "150px",
|
||||
};
|
||||
};
|
||||
var editDetail = function (attr) {
|
||||
current = attr;
|
||||
let detail = record.value[attr] ? record.value[attr] : {};
|
||||
showmodal.value = {
|
||||
component: "datatable/FieldAttribute",
|
||||
vbind: { field: detail, close: true },
|
||||
title: "Change attributes",
|
||||
width: "40%",
|
||||
height: "150px",
|
||||
};
|
||||
};
|
||||
var updateAttr = function (detail) {
|
||||
record.value[current] = detail;
|
||||
};
|
||||
function copyDetail(attr) {
|
||||
if ($empty(record.value[attr])) return;
|
||||
let val = typeof record.value[attr] == "string" ? record.value[attr] : JSON.stringify(record.value[attr]);
|
||||
$copyToClipboard(val);
|
||||
}
|
||||
async function pasteDetail(attr) {
|
||||
let text = await navigator.clipboard.readText();
|
||||
if ($empty(text)) return;
|
||||
record.value[attr] = JSON.parse(text);
|
||||
}
|
||||
var updateData = async function (isNew) {
|
||||
let ele = record.value;
|
||||
if (ele.create_time === null) ele.create_time = new Date();
|
||||
ele = $resetNull(ele);
|
||||
if (isNew) delete ele.id;
|
||||
let result = ele.id
|
||||
? await $updaterow(vapi, ele, undefined, props.pagename)
|
||||
: await $insertrow(vapi, ele, undefined, props.pagename);
|
||||
if (isNew) emit("close");
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,73 +1,107 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-body">
|
||||
<div class="field is-narrow">
|
||||
<label class="label">Mã <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control">
|
||||
<input class="input" type="text" placeholder="" v-model="record.code" id="code">
|
||||
</p>
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-body">
|
||||
<div class="field is-narrow">
|
||||
<label class="label">Mã <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control">
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder=""
|
||||
v-model="record.code"
|
||||
id="code"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Tên <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control">
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder=""
|
||||
v-model="record.name"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Tên <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control">
|
||||
<input class="input" type="text" placeholder="" v-model="record.name">
|
||||
</p>
|
||||
<div class="field is-horizontal mt-5">
|
||||
<div class="field-body">
|
||||
<div class="field is-narrow">
|
||||
<label class="label">Điện thoại <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control">
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder=""
|
||||
v-model="record.phone"
|
||||
id="code"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Email <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control">
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder=""
|
||||
v-model="record.email"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-horizontal mt-5">
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<label class="label">Địa chỉ <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control">
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder=""
|
||||
v-model="record.address"
|
||||
id="code"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<button
|
||||
class="button is-primary has-text-white"
|
||||
@click="update()"
|
||||
>
|
||||
Lưu lại
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-horizontal mt-5">
|
||||
<div class="field-body">
|
||||
<div class="field is-narrow">
|
||||
<label class="label">Điện thoại <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control">
|
||||
<input class="input" type="text" placeholder="" v-model="record.phone" id="code">
|
||||
</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Email <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control">
|
||||
<input class="input" type="text" placeholder="" v-model="record.email">
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-horizontal mt-5">
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<label class="label">Địa chỉ <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control">
|
||||
<input class="input" type="text" placeholder="" v-model="record.address" id="code">
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<button class="button is-primary has-text-white" @click="update()">Lưu lại</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { onMounted } from 'vue'
|
||||
const { $copy, $resetNull, $insertrow, $updaterow, $snackbar, $updatepage} = useNuxtApp()
|
||||
var props = defineProps({
|
||||
api: String,
|
||||
pagename: String,
|
||||
row: Object,
|
||||
prefix: String
|
||||
})
|
||||
const emit = defineEmits(['close', 'modalevent'])
|
||||
var record = $copy( props.row || {})
|
||||
async function update() {
|
||||
let data = $resetNull(record)
|
||||
let rs = data.id? await $updaterow(props.api, data, undefined, props.pagename)
|
||||
: await $insertrow(props.api, data, undefined, props.pagename)
|
||||
$updatepage(props.pagename, rs)
|
||||
if(rs==='error') return $snackbar(rs)
|
||||
emit('modalevent', {name: 'dataevent', data: rs})
|
||||
emit('close')
|
||||
}
|
||||
onMounted(()=> {
|
||||
document.getElementById('code').focus()
|
||||
})
|
||||
</script>
|
||||
import { onMounted } from "vue";
|
||||
const { $copy, $resetNull, $insertrow, $updaterow, $snackbar, $updatepage } = useNuxtApp();
|
||||
var props = defineProps({
|
||||
api: String,
|
||||
pagename: String,
|
||||
row: Object,
|
||||
prefix: String,
|
||||
});
|
||||
const emit = defineEmits(["close", "modalevent"]);
|
||||
var record = $copy(props.row || {});
|
||||
async function update() {
|
||||
let data = $resetNull(record);
|
||||
let rs = data.id
|
||||
? await $updaterow(props.api, data, undefined, props.pagename)
|
||||
: await $insertrow(props.api, data, undefined, props.pagename);
|
||||
$updatepage(props.pagename, rs);
|
||||
if (rs === "error") return $snackbar(rs);
|
||||
emit("modalevent", { name: "dataevent", data: rs });
|
||||
emit("close");
|
||||
}
|
||||
onMounted(() => {
|
||||
document.getElementById("code").focus();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -13,8 +13,18 @@
|
||||
không?
|
||||
</p>
|
||||
<div class="action mt-3">
|
||||
<button class="button is-light" @click="handleCancel">Hủy</button>
|
||||
<button class="button is-primary" @click="handleDeleteCart">Đồng ý</button>
|
||||
<button
|
||||
class="button is-light"
|
||||
@click="handleCancel"
|
||||
>
|
||||
Hủy
|
||||
</button>
|
||||
<button
|
||||
class="button is-primary"
|
||||
@click="handleDeleteCart"
|
||||
>
|
||||
Đồng ý
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,13 +3,22 @@
|
||||
<div class="container is-fluid px-4">
|
||||
<div>
|
||||
<p>
|
||||
Bạn có chắc chắn muốn xóa lịch công nợ thời gian: {{ detail.time }} ngày - mẫu: [{{
|
||||
detail.name?.toUpperCase()
|
||||
}}] không?
|
||||
Bạn có chắc chắn muốn xóa lịch công nợ thời gian:
|
||||
{{ detail.time }} ngày - mẫu: [{{ detail.name?.toUpperCase() }}] không?
|
||||
</p>
|
||||
<div class="action mt-3">
|
||||
<button class="button is-light" @click="handleCancel">Hủy</button>
|
||||
<button class="button is-primary" @click="handleDeleteCart">Đồng ý</button>
|
||||
<button
|
||||
class="button is-light"
|
||||
@click="handleCancel"
|
||||
>
|
||||
Hủy
|
||||
</button>
|
||||
<button
|
||||
class="button is-primary"
|
||||
@click="handleDeleteCart"
|
||||
>
|
||||
Đồng ý
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,13 +5,26 @@
|
||||
<div class="field is-narrow">
|
||||
<label class="label">Mã chiết khấu <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control">
|
||||
<input class="input" type="text" placeholder="" v-model="record.code" id="code" autocomplete="off" />
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder=""
|
||||
v-model="record.code"
|
||||
id="code"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Tên chiết khấu<span class="has-text-danger"> * </span> </label>
|
||||
<p class="control">
|
||||
<input class="input" type="text" placeholder="" v-model="record.name" autocomplete="off" />
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder=""
|
||||
v-model="record.name"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -20,13 +33,25 @@
|
||||
<div class="column">
|
||||
<label class="label">Giá trị chiết khấu <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control">
|
||||
<input class="input" type="text" placeholder="" v-model="record.value" autocomplete="off" />
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder=""
|
||||
v-model="record.value"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
<div class="column">
|
||||
<label class="label">Loại chiết khấu <span class="has-text-danger"> * </span> </label>
|
||||
<SearchBox
|
||||
v-bind="{ api: 'valuetype', field: 'name', column: ['name'], first: true, optionid: record.type }"
|
||||
v-bind="{
|
||||
api: 'valuetype',
|
||||
field: 'name',
|
||||
column: ['name'],
|
||||
first: true,
|
||||
optionid: record.type,
|
||||
}"
|
||||
:disabled="record.type"
|
||||
@option="documentSelected('type', $event)"
|
||||
></SearchBox>
|
||||
@@ -34,7 +59,13 @@
|
||||
<div class="column">
|
||||
<label class="label">Phương thức chiết khấu<span class="has-text-danger"> * </span> </label>
|
||||
<SearchBox
|
||||
v-bind="{ api: 'discountmethod', field: 'name', column: ['name'], first: true, optionid: record.method }"
|
||||
v-bind="{
|
||||
api: 'discountmethod',
|
||||
field: 'name',
|
||||
column: ['name'],
|
||||
first: true,
|
||||
optionid: record.method,
|
||||
}"
|
||||
:disabled="record.method"
|
||||
@option="documentSelected('method', $event)"
|
||||
></SearchBox>
|
||||
@@ -42,7 +73,10 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-5">
|
||||
<button class="button is-primary has-text-white" @click="update()">
|
||||
<button
|
||||
class="button is-primary has-text-white"
|
||||
@click="update()"
|
||||
>
|
||||
{{ record.id ? "Cập nhật" : "Tạo mới" }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -59,7 +93,6 @@ var props = defineProps({
|
||||
});
|
||||
const emit = defineEmits(["close", "modalevent"]);
|
||||
|
||||
|
||||
var record = $copy(props.row || {});
|
||||
console.log("record", record);
|
||||
function documentSelected(attr, obj, v) {
|
||||
|
||||
@@ -3,23 +3,37 @@
|
||||
<div class="mt-5 columns">
|
||||
<div class="column is-2">
|
||||
<label class="label"
|
||||
>{{ isVietnamese ? "Thời gian (ngày)" : "Duration (Days)" }} <span class="has-text-danger"> * </span>
|
||||
>{{ isVietnamese ? "Thời gian (ngày)" : "Duration (Days)" }}
|
||||
<span class="has-text-danger"> * </span>
|
||||
</label>
|
||||
<p class="control">
|
||||
<input class="input" type="number" placeholder="15,30,60" v-model="record.time" autocomplete="off" />
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
placeholder="15,30,60"
|
||||
v-model="record.time"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
<div class="column is-2">
|
||||
<label class="label"
|
||||
>{{ isVietnamese ? "So sánh" : "Lookup Field" }} <span class="has-text-danger"> * </span>
|
||||
>{{ isVietnamese ? "So sánh" : "Lookup Field" }}
|
||||
<span class="has-text-danger"> * </span>
|
||||
</label>
|
||||
<p class="control">
|
||||
<input class="input" placeholder="gt,lt,gte,lte" v-model="record.lookup" autocomplete="off" />
|
||||
<input
|
||||
class="input"
|
||||
placeholder="gt,lt,gte,lte"
|
||||
v-model="record.lookup"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
<div class="column">
|
||||
<label class="label"
|
||||
>{{ isVietnamese ? "Mẫu email" : "Email Template" }} <span class="has-text-danger"> * </span>
|
||||
>{{ isVietnamese ? "Mẫu email" : "Email Template" }}
|
||||
<span class="has-text-danger"> * </span>
|
||||
</label>
|
||||
<SearchBox
|
||||
v-bind="{
|
||||
@@ -34,7 +48,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<button class="button is-primary has-text-white" @click="update()">
|
||||
<button
|
||||
class="button is-primary has-text-white"
|
||||
@click="update()"
|
||||
>
|
||||
{{ dataTemp.id ? "Cập nhật" : "Tạo mới" }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -69,9 +86,9 @@ async function update() {
|
||||
detail,
|
||||
};
|
||||
|
||||
console.log('detail', detail)
|
||||
console.log('dataTemp', dataTemp)
|
||||
console.log('dataUpdate', dataUpdate)
|
||||
console.log("detail", detail);
|
||||
console.log("dataTemp", dataTemp);
|
||||
console.log("dataUpdate", dataUpdate);
|
||||
rs = await $updaterow(props.api, dataUpdate, undefined, props.pagename);
|
||||
} else {
|
||||
const dataInsert = {
|
||||
|
||||
@@ -5,13 +5,26 @@
|
||||
<div class="field is-narrow">
|
||||
<label class="label">Mã quà tặng <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control">
|
||||
<input class="input" type="text" placeholder="" v-model="record.code" id="code" autocomplete="off" />
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder=""
|
||||
v-model="record.code"
|
||||
id="code"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Tên quà tặng<span class="has-text-danger"> * </span> </label>
|
||||
<p class="control">
|
||||
<input class="input" type="text" placeholder="" v-model="record.name" autocomplete="off" />
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder=""
|
||||
v-model="record.name"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -20,13 +33,22 @@
|
||||
<div class="column">
|
||||
<label class="label">Chi tiết quà tặng<span class="has-text-danger"> * </span> </label>
|
||||
<p class="control">
|
||||
<input class="input" type="text" placeholder="" v-model="record.detail" autocomplete="off"/>
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder=""
|
||||
v-model="record.detail"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-5">
|
||||
<button class="button is-primary has-text-white" @click="update()">
|
||||
<button
|
||||
class="button is-primary has-text-white"
|
||||
@click="update()"
|
||||
>
|
||||
{{ record.id ? "Cập nhật" : "Tạo mới" }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
<template>
|
||||
<div class="field is-grouped">
|
||||
<div class="control" id="ignore">
|
||||
<div
|
||||
class="control"
|
||||
id="ignore"
|
||||
>
|
||||
<label class="file-label">
|
||||
<input class="file-input" type="file" id="divfile" name="resume" @change="inputFile" />
|
||||
<input
|
||||
class="file-input"
|
||||
type="file"
|
||||
id="divfile"
|
||||
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>
|
||||
@@ -12,59 +21,112 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="control">
|
||||
<a class="button is-primary" @click="download()"> Tải template </a>
|
||||
<a class="button is-light ml-6" v-if="datafile" @click="exportExcel()">Xuất Excel</a>
|
||||
<a
|
||||
class="button is-primary"
|
||||
@click="download()"
|
||||
>
|
||||
Tải template
|
||||
</a>
|
||||
<a
|
||||
class="button is-light ml-6"
|
||||
v-if="datafile"
|
||||
@click="exportExcel()"
|
||||
>Xuất Excel</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns mb-0" v-if="msgInfo.length > 0">
|
||||
<div
|
||||
class="columns mb-0"
|
||||
v-if="msgInfo.length > 0"
|
||||
>
|
||||
<div class="column is-7 mb-0">
|
||||
<div class="notification is-white py-2 mb-2">
|
||||
<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">
|
||||
<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"
|
||||
>
|
||||
<figure class="media-left">
|
||||
<i :class="classinfo.find(v => v.type === ele.type).icon_class"> </i>
|
||||
<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>
|
||||
<p :class="classinfo.find((v) => v.type == ele.type).text_class">
|
||||
{{ ele.message }}
|
||||
</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-danger fsb-18 ml-5">{{ `Lỗi: ${result.error_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">
|
||||
<a :class="`button is-primary ${isloading ? 'is-loading' : ''}`" @click="insert()" v-if="countNew > 0">Thêm mới
|
||||
({{ countNew }}/{{ total }})</a>
|
||||
<a :class="`button is-dark ${isloading ? 'is-loading' : ''} ml-2`" @click="update()" v-if="countUdt > 0">Cập nhật
|
||||
({{ countUdt }}/{{ total }})</a>
|
||||
<div
|
||||
class="column has-text-centered mb-0"
|
||||
v-if="ready"
|
||||
>
|
||||
<a
|
||||
:class="`button is-primary ${isloading ? 'is-loading' : ''}`"
|
||||
@click="insert()"
|
||||
v-if="countNew > 0"
|
||||
>Thêm mới ({{ countNew }}/{{ total }})</a
|
||||
>
|
||||
<a
|
||||
:class="`button is-dark ${isloading ? 'is-loading' : ''} ml-2`"
|
||||
@click="update()"
|
||||
v-if="countUdt > 0"
|
||||
>Cập nhật ({{ countUdt }}/{{ total }})</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<DataTable v-bind="{ pagename: pagename }" v-if="pagedata" />
|
||||
<DataTable
|
||||
v-bind="{ pagename: pagename }"
|
||||
v-if="pagedata"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['code'],
|
||||
props: ["code"],
|
||||
data() {
|
||||
return {
|
||||
classinfo: [
|
||||
{ type: 'success', icon_class: 'mdi mdi-check', text_class: 'has-text-primary' },
|
||||
{ type: 'error', icon_class: 'mdi mdi-close has-text-danger', text_class: 'has-text-danger' },
|
||||
{ type: 'warning', icon_class: 'mdi mdi-alert has-text-warning', text_class: 'has-text-warning' },
|
||||
{ type: 'waiting', icon_class: 'mdi mdi-timer-sand has-text-primary', text_class: 'has-text-primary' }
|
||||
{
|
||||
type: "success",
|
||||
icon_class: "mdi mdi-check",
|
||||
text_class: "has-text-primary",
|
||||
},
|
||||
{
|
||||
type: "error",
|
||||
icon_class: "mdi mdi-close has-text-danger",
|
||||
text_class: "has-text-danger",
|
||||
},
|
||||
{
|
||||
type: "warning",
|
||||
icon_class: "mdi mdi-alert has-text-warning",
|
||||
text_class: "has-text-warning",
|
||||
},
|
||||
{
|
||||
type: "waiting",
|
||||
icon_class: "mdi mdi-timer-sand has-text-primary",
|
||||
text_class: "has-text-primary",
|
||||
},
|
||||
],
|
||||
msgInfo: [],
|
||||
isloading: false,
|
||||
datafile: undefined,
|
||||
requireFields: [],
|
||||
pagename: 'importdata',
|
||||
pagename: "importdata",
|
||||
file: undefined,
|
||||
ready: false,
|
||||
data: [],
|
||||
@@ -74,174 +136,198 @@ export default {
|
||||
total: undefined,
|
||||
fileInfo: undefined,
|
||||
fileLog: undefined,
|
||||
result: undefined
|
||||
}
|
||||
result: undefined,
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
this.setting = await this.$getdata('importsetting', { code: this.code }, undefined, true)
|
||||
this.requireFields = this.setting.detail
|
||||
this.setting = await this.$getdata("importsetting", { code: this.code }, undefined, true);
|
||||
this.requireFields = this.setting.detail;
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.pagedata = undefined
|
||||
this.pagedata = undefined;
|
||||
},
|
||||
computed: {
|
||||
pagedata: {
|
||||
get: function () { return this.$store[this.pagename] },
|
||||
set: function (val) { this.$store.commit(this.pagename, val) }
|
||||
}
|
||||
get: function () {
|
||||
return this.$store[this.pagename];
|
||||
},
|
||||
set: function (val) {
|
||||
this.$store.commit(this.pagename, val);
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
download() {
|
||||
window.open(`${this.$getpath()}static/excelform/${this.setting.template}`, '_blank')
|
||||
window.open(`${this.$getpath()}static/excelform/${this.setting.template}`, "_blank");
|
||||
},
|
||||
async inputFile(v) {
|
||||
this.isloading = true
|
||||
this.msgInfo = []
|
||||
let fileList = document.getElementById('divfile').files
|
||||
let files = Array.from(fileList)
|
||||
if (files.length === 0) return
|
||||
var thefile = this.$upload(files[0], 'file', 1)
|
||||
this.isloading = true;
|
||||
this.msgInfo = [];
|
||||
let fileList = document.getElementById("divfile").files;
|
||||
let files = Array.from(fileList);
|
||||
if (files.length === 0) return;
|
||||
var thefile = this.$upload(files[0], "file", 1);
|
||||
if (thefile.error) {
|
||||
this.$buefy.toast.open({ message: thefile.text, type: 'is-danger' })
|
||||
return this.isloading = false
|
||||
this.$buefy.toast.open({ message: thefile.text, type: "is-danger" });
|
||||
return (this.isloading = false);
|
||||
}
|
||||
if (!(thefile.name.search('.xls') > 0 || thefile.name.search('.xlsx') > 0)) {
|
||||
let text = this.$find(this.$store.syspara, { category: 'inform', classify: 'import', code: 'file-type-invalid' }, 'value')
|
||||
this.msgInfo.push({ message: text, type: 'error' })
|
||||
return this.isloading = false
|
||||
if (!(thefile.name.search(".xls") > 0 || thefile.name.search(".xlsx") > 0)) {
|
||||
let text = this.$find(
|
||||
this.$store.syspara,
|
||||
{ category: "inform", classify: "import", code: "file-type-invalid" },
|
||||
"value",
|
||||
);
|
||||
this.msgInfo.push({ message: text, type: "error" });
|
||||
return (this.isloading = false);
|
||||
}
|
||||
let result = await this.$insertapi('upload', thefile.form, undefined, false)
|
||||
if (result === 'error') {
|
||||
let text = this.$find(this.$store.syspara, { category: 'inform', classify: 'import', code: 'file-upload-fail' }, 'value')
|
||||
this.msgInfo.push({ message: text, type: 'error' })
|
||||
let result = await this.$insertapi("upload", thefile.form, undefined, false);
|
||||
if (result === "error") {
|
||||
let text = this.$find(
|
||||
this.$store.syspara,
|
||||
{ category: "inform", classify: "import", code: "file-upload-fail" },
|
||||
"value",
|
||||
);
|
||||
this.msgInfo.push({ message: text, type: "error" });
|
||||
} else {
|
||||
this.fileLog = result
|
||||
this.fileInfo = thefile
|
||||
this.fillData()
|
||||
this.fileLog = result;
|
||||
this.fileInfo = thefile;
|
||||
this.fillData();
|
||||
}
|
||||
this.isloading = false
|
||||
this.isloading = false;
|
||||
|
||||
// Clear file
|
||||
document.getElementById("divfile").value = ''
|
||||
document.getElementById("divfile").value = "";
|
||||
},
|
||||
async fillData() {
|
||||
let found = this.$findapi('readexcel')
|
||||
found.params = { name: this.fileInfo.filename }
|
||||
let result = await this.$getapi([found])
|
||||
this.datafile = JSON.parse(result[0].data)
|
||||
let 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 }
|
||||
copy.data = data
|
||||
copy.fields = fields
|
||||
this.pagedata = copy
|
||||
this.checkRequireFields(fields)
|
||||
let found = this.$findapi("readexcel");
|
||||
found.params = { name: this.fileInfo.filename };
|
||||
let result = await this.$getapi([found]);
|
||||
this.datafile = JSON.parse(result[0].data);
|
||||
let 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 };
|
||||
copy.data = data;
|
||||
copy.fields = fields;
|
||||
this.pagedata = copy;
|
||||
this.checkRequireFields(fields);
|
||||
},
|
||||
checkRequireFields(fields) {
|
||||
let misslist = []
|
||||
Object.keys(this.requireFields).map(v => {
|
||||
let found = fields.find(x => x.name === v)
|
||||
if (!found) misslist.push(v)
|
||||
})
|
||||
let misslist = [];
|
||||
Object.keys(this.requireFields).map((v) => {
|
||||
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' })
|
||||
let text = `Thiếu các cột bắt buộc: ${misslist.join(", ")}`;
|
||||
this.msgInfo.push({ message: text, type: "error" });
|
||||
} else {
|
||||
this.validateFontend()
|
||||
this.validateFontend();
|
||||
}
|
||||
},
|
||||
validateFontend() {
|
||||
let self = this
|
||||
let self = this;
|
||||
var checkValid = function (name, obj, data) {
|
||||
var error = false
|
||||
if (obj.empty === 'no' || !self.$empty(obj.api)) {
|
||||
data.map(x => {
|
||||
var error = false;
|
||||
if (obj.empty === "no" || !self.$empty(obj.api)) {
|
||||
data.map((x) => {
|
||||
if (self.$empty(x[name])) {
|
||||
x['error'] = `${name} không được bỏ trống`
|
||||
error = true
|
||||
x["error"] = `${name} không được bỏ trống`;
|
||||
error = true;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
if (!error && obj.type === 'number') {
|
||||
data.map(x => {
|
||||
if (self.$empty(x[name])) x[name] = null
|
||||
if (!error && obj.type === "number") {
|
||||
data.map((x) => {
|
||||
if (self.$empty(x[name])) x[name] = null;
|
||||
else {
|
||||
if (self.$isNumber(x[name])) x[name] = self.$formatNumber(x[name])
|
||||
if (self.$isNumber(x[name])) x[name] = self.$formatNumber(x[name]);
|
||||
else {
|
||||
x['error'] = `${name} không phải là số`
|
||||
error = true
|
||||
x["error"] = `${name} không phải là số`;
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
if (!error && obj.type === 'date') {
|
||||
data.map(x => {
|
||||
if (self.$empty(x[name])) x[name] = null
|
||||
if (!error && obj.type === "date") {
|
||||
data.map((x) => {
|
||||
if (self.$empty(x[name])) x[name] = null;
|
||||
else {
|
||||
if (self.$dayjs(x[name], 'YYYY/MM/DD').isValid()) x[name] = self.$dayjs(x[name]).format('YYYY-MM-DD')
|
||||
if (self.$dayjs(x[name], "YYYY/MM/DD").isValid()) x[name] = self.$dayjs(x[name]).format("YYYY-MM-DD");
|
||||
else {
|
||||
x['error'] = `${name} không phải ngày hợp lệ`
|
||||
error = true
|
||||
x["error"] = `${name} không phải ngày hợp lệ`;
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
if (!error && obj.type === 'string') {
|
||||
data.map(x => x[name] = self.$empty(x[name]) ? null : x[name])
|
||||
if (!error && obj.type === "string") {
|
||||
data.map((x) => (x[name] = self.$empty(x[name]) ? null : x[name]));
|
||||
}
|
||||
return [error, data]
|
||||
}
|
||||
let err = false
|
||||
let data = this.$copy(this.pagedata.data)
|
||||
Object.keys(this.requireFields).map(v => {
|
||||
let obj = this.requireFields[v]
|
||||
let result = checkValid(v, obj, data)
|
||||
data = result[1]
|
||||
if (result[0] === true) err = true
|
||||
})
|
||||
if (err) this.showError(data)
|
||||
return [error, data];
|
||||
};
|
||||
let err = false;
|
||||
let data = this.$copy(this.pagedata.data);
|
||||
Object.keys(this.requireFields).map((v) => {
|
||||
let obj = this.requireFields[v];
|
||||
let result = checkValid(v, obj, data);
|
||||
data = result[1];
|
||||
if (result[0] === true) err = true;
|
||||
});
|
||||
if (err) this.showError(data);
|
||||
else {
|
||||
if (data.length > 5000) {
|
||||
this.msgInfo.push({ message: `Tổng số bản ghi là ${data.length}. Chỉ hiển thị 5000 trong bảng dữ liệu`, type: 'warning' })
|
||||
this.msgInfo.push({
|
||||
message: `Tổng số bản ghi là ${data.length}. Chỉ hiển thị 5000 trong bảng dữ liệu`,
|
||||
type: "warning",
|
||||
});
|
||||
} else {
|
||||
this.msgInfo.push({ message: `Tổng số bản ghi là ${data.length}`, type: 'success' })
|
||||
this.msgInfo.push({
|
||||
message: `Tổng số bản ghi là ${data.length}`,
|
||||
type: "success",
|
||||
});
|
||||
}
|
||||
let copy = this.$copy(this.pagedata)
|
||||
copy.update = { data: data.length > 5000 ? data.slice(0, 5000) : data }
|
||||
this.pagedata = copy
|
||||
this.data = data
|
||||
this.findKey(data)
|
||||
let copy = this.$copy(this.pagedata);
|
||||
copy.update = { data: data.length > 5000 ? data.slice(0, 5000) : data };
|
||||
this.pagedata = copy;
|
||||
this.data = data;
|
||||
this.findKey(data);
|
||||
}
|
||||
},
|
||||
showError(data) {
|
||||
data = data.filter(v => v.error)
|
||||
this.msgInfo.push({ 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)
|
||||
field.color = 'red'
|
||||
let fields = this.$copy(this.pagedata.fields)
|
||||
fields = [field].concat(fields)
|
||||
let copy = this.$clone(this.pagedata)
|
||||
copy.data = data
|
||||
copy.fields = fields
|
||||
copy.update = { data: data, fields: fields }
|
||||
this.pagedata = copy
|
||||
data = data.filter((v) => v.error);
|
||||
this.msgInfo.push({
|
||||
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);
|
||||
field.color = "red";
|
||||
let fields = this.$copy(this.pagedata.fields);
|
||||
fields = [field].concat(fields);
|
||||
let copy = this.$clone(this.pagedata);
|
||||
copy.data = data;
|
||||
copy.fields = fields;
|
||||
copy.update = { data: data, fields: fields };
|
||||
this.pagedata = copy;
|
||||
},
|
||||
|
||||
|
||||
// ✅ METHOD CHÍNH: TÌM KEY VÀ KIỂM TRA UPDATE PERMISSION
|
||||
async findKey(rows) {
|
||||
this.msgInfo.push({ message: 'Bắt đầu kiểm tra dữ liệu', type: 'waiting' });
|
||||
this.msgInfo.push({
|
||||
message: "Bắt đầu kiểm tra dữ liệu",
|
||||
type: "waiting",
|
||||
});
|
||||
|
||||
let keys = [];
|
||||
let 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 mappedRows = rows.map((row) => {
|
||||
let newRow = { ...row };
|
||||
for (const [excelColName, config] of Object.entries(this.requireFields)) {
|
||||
if (excelColName in row) {
|
||||
@@ -253,31 +339,31 @@ export default {
|
||||
|
||||
// 2. Cấu hình Mapping cho Backend
|
||||
for (const [excelColName, value] of Object.entries(this.requireFields)) {
|
||||
if (value.key === 'yes') {
|
||||
if (value.key === "yes") {
|
||||
keys.push({
|
||||
key: excelColName,
|
||||
value: value
|
||||
value: value,
|
||||
});
|
||||
}
|
||||
// Nếu là trường Lookup
|
||||
if (value.api) {
|
||||
related.push({
|
||||
key: excelColName,
|
||||
value: value
|
||||
value: value,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let conn = this.$findapi(this.setting.api);
|
||||
let payload = {
|
||||
name: conn.url.replace('data/', '').replace(/\//g, ''),
|
||||
name: conn.url.replace("data/", "").replace(/\//g, ""),
|
||||
data: mappedRows,
|
||||
keys: keys,
|
||||
related: related
|
||||
related: related,
|
||||
};
|
||||
|
||||
try {
|
||||
let rs = await this.$insertapi('findkey', payload);
|
||||
let rs = await this.$insertapi("findkey", payload);
|
||||
if (rs && rs.data) {
|
||||
this.data = rs.data;
|
||||
this.total = this.data.length;
|
||||
@@ -288,29 +374,29 @@ export default {
|
||||
// 1. Lọc ra danh sách các lỗi
|
||||
let errors = this.data
|
||||
.map((v, i) => (v.error ? { line: i + 1, msg: v.error } : null))
|
||||
.filter(v => v !== null);
|
||||
.filter((v) => v !== null);
|
||||
|
||||
this.countNew = this.data.filter(v => !v.id && !v.error).length;
|
||||
this.countUdt = this.data.filter(v => v.id && !v.error).length;
|
||||
this.countNew = this.data.filter((v) => !v.id && !v.error).length;
|
||||
this.countUdt = this.data.filter((v) => v.id && !v.error).length;
|
||||
this.ready = true;
|
||||
|
||||
// 2. Nếu có lỗi, hiển thị chi tiết từng dòng vào msgInfo
|
||||
if (errors.length > 0) {
|
||||
errors.forEach(err => {
|
||||
errors.forEach((err) => {
|
||||
this.msgInfo.push({
|
||||
message: `Dòng ${err.line}: ${err.msg}`,
|
||||
type: 'error'
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
|
||||
this.msgInfo.push({
|
||||
message: `Phát hiện tổng cộng ${errors.length} lỗi. Vui lòng sửa file và upload lại.`,
|
||||
type: 'warning'
|
||||
type: "warning",
|
||||
});
|
||||
} else {
|
||||
this.msgInfo.push({
|
||||
message: `Kiểm tra hoàn tất: Toàn bộ ${this.total} dòng dữ liệu hợp lệ.`,
|
||||
type: 'success'
|
||||
type: "success",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -318,21 +404,24 @@ export default {
|
||||
}
|
||||
} catch (e) {
|
||||
this.isloading = false;
|
||||
this.msgInfo.push({ message: 'Lỗi hệ thống khi kiểm tra', type: 'error' });
|
||||
this.msgInfo.push({
|
||||
message: "Lỗi hệ thống khi kiểm tra",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
checkUpdatePermission() {
|
||||
// Tìm các trường key có update = "no"
|
||||
let noUpdateFields = [];
|
||||
|
||||
|
||||
Object.entries(this.requireFields).forEach(([excelColName, config]) => {
|
||||
// Nếu là trường key và update = "no"
|
||||
// Mặc định nếu không khai báo update thì coi như "yes"
|
||||
if (config.key === 'yes' && config.update === 'no') {
|
||||
if (config.key === "yes" && config.update === "no") {
|
||||
noUpdateFields.push({
|
||||
name: excelColName,
|
||||
field: config.field || excelColName
|
||||
field: config.field || excelColName,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -348,8 +437,8 @@ export default {
|
||||
if (row.id && !row.error) {
|
||||
// Lấy danh sách các trường key bị trùng
|
||||
let duplicatedFields = [];
|
||||
|
||||
noUpdateFields.forEach(field => {
|
||||
|
||||
noUpdateFields.forEach((field) => {
|
||||
// Kiểm tra xem trường này có giá trị không
|
||||
if (row[field.name]) {
|
||||
duplicatedFields.push(field.name);
|
||||
@@ -358,29 +447,37 @@ export default {
|
||||
|
||||
// Nếu có trường nào bị trùng, đánh dấu lỗi
|
||||
if (duplicatedFields.length > 0) {
|
||||
row.error = `Bản ghi đã tồn tại với ${duplicatedFields.join(', ')}. Không được phép cập nhật.`;
|
||||
row.error = `Bản ghi đã tồn tại với ${duplicatedFields.join(", ")}. Không được phép cập nhật.`;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
showStatus() {
|
||||
let field = this.$createField('record_status', 'Status', 'string', true, true)
|
||||
field.color = 'blue'
|
||||
let 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')
|
||||
this.$store.commit('updateState', { name: this.pagename, key: 'data', data: data })
|
||||
let copy = this.$clone(this.pagedata)
|
||||
this.pagedata = undefined
|
||||
setTimeout(() => this.pagedata = copy, 10)
|
||||
let field = this.$createField("record_status", "Status", "string", true, true);
|
||||
field.color = "blue";
|
||||
let 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"));
|
||||
this.$store.commit("updateState", {
|
||||
name: this.pagename,
|
||||
key: "data",
|
||||
data: data,
|
||||
});
|
||||
let copy = this.$clone(this.pagedata);
|
||||
this.pagedata = undefined;
|
||||
setTimeout(() => (this.pagedata = copy), 10);
|
||||
},
|
||||
|
||||
//Helper: Xóa trường lookup (giữ lại id của mapping)
|
||||
cleanupLookupFields(data) {
|
||||
return data.map(row => {
|
||||
return data.map((row) => {
|
||||
let newRow = {};
|
||||
|
||||
Object.entries(this.requireFields).forEach(([excelColName, config]) => {
|
||||
@@ -390,8 +487,8 @@ export default {
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(row).forEach(key => {
|
||||
if (key !== 'index' && !this.requireFields[key]) {
|
||||
Object.keys(row).forEach((key) => {
|
||||
if (key !== "index" && !this.requireFields[key]) {
|
||||
newRow[key] = row[key];
|
||||
}
|
||||
});
|
||||
@@ -401,149 +498,181 @@ export default {
|
||||
},
|
||||
|
||||
async insert() {
|
||||
this.isloading = true
|
||||
this.result = undefined
|
||||
let conn = this.$findapi(this.setting.api)
|
||||
let obj = { 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)
|
||||
this.isloading = true;
|
||||
this.result = undefined;
|
||||
let conn = this.$findapi(this.setting.api);
|
||||
let obj = {
|
||||
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);
|
||||
|
||||
//Lọc bản ghi KHÔNG có id (bản ghi mới)
|
||||
let filter = this.data.filter(v => !v.id && !v.error)
|
||||
let filter = this.data.filter((v) => !v.id && !v.error);
|
||||
|
||||
//Xóa trường lookup trước khi gửi
|
||||
filter = this.cleanupLookupFields(filter)
|
||||
filter = filter.map(v => this.$resetNull(v))
|
||||
this.total = filter.length
|
||||
filter = this.cleanupLookupFields(filter);
|
||||
filter = filter.map((v) => this.$resetNull(v));
|
||||
this.total = filter.length;
|
||||
|
||||
//Gửi lên API - Backend sẽ INSERT
|
||||
let result
|
||||
let result;
|
||||
if (this.setting.call_api) {
|
||||
result = await this.$insertapi(this.setting.call_api, { data: filter, user: this.$store.login.id }, undefined, obj.code)
|
||||
result = await this.$insertapi(
|
||||
this.setting.call_api,
|
||||
{ data: filter, user: this.$store.login.id },
|
||||
undefined,
|
||||
obj.code,
|
||||
);
|
||||
} else {
|
||||
result = await this.$insertapi(this.setting.api, filter, undefined, obj.code)
|
||||
result = await this.$insertapi(this.setting.api, filter, undefined, obj.code);
|
||||
}
|
||||
this.isloading = false
|
||||
clearInterval(interval)
|
||||
this.getResult(obj.code)
|
||||
this.isloading = false;
|
||||
clearInterval(interval);
|
||||
this.getResult(obj.code);
|
||||
|
||||
// Xử lý result/response mới
|
||||
if (result === 'error') {
|
||||
this.msgInfo.push({ message: 'Có lỗi hệ thống khi thực hiện', type: 'error' })
|
||||
return
|
||||
if (result === "error") {
|
||||
this.msgInfo.push({
|
||||
message: "Có lỗi hệ thống khi thực hiện",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let hasError = false
|
||||
let errorMessages = []
|
||||
let hasError = false;
|
||||
let errorMessages = [];
|
||||
|
||||
// Kiểm tra entries có lỗi không
|
||||
if (result.entries && Array.isArray(result.entries)) {
|
||||
result.entries.forEach(entry => {
|
||||
result.entries.forEach((entry) => {
|
||||
if (entry.error) {
|
||||
hasError = true
|
||||
errorMessages.push(entry.error)
|
||||
hasError = true;
|
||||
errorMessages.push(entry.error);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Nếu có message chứa từ khóa lỗi (dự phòng)
|
||||
if (result.message && (result.message.toLowerCase().includes('lỗi') || result.message.toLowerCase().includes('không tồn tại'))) {
|
||||
hasError = true
|
||||
errorMessages.push(result.message)
|
||||
if (
|
||||
result.message &&
|
||||
(result.message.toLowerCase().includes("lỗi") || result.message.toLowerCase().includes("không tồn tại"))
|
||||
) {
|
||||
hasError = true;
|
||||
errorMessages.push(result.message);
|
||||
}
|
||||
|
||||
if (hasError) {
|
||||
// Hiển thị lỗi chi tiết
|
||||
errorMessages.forEach(msg => {
|
||||
this.msgInfo.push({ message: msg, type: 'error' })
|
||||
})
|
||||
errorMessages.forEach((msg) => {
|
||||
this.msgInfo.push({ message: msg, type: "error" });
|
||||
});
|
||||
this.msgInfo.push({
|
||||
message: 'Có lỗi trong quá trình xử lý. Vui lòng kiểm tra và thử lại.',
|
||||
type: 'warning'
|
||||
})
|
||||
message: "Có lỗi trong quá trình xử lý. Vui lòng kiểm tra và thử lại.",
|
||||
type: "warning",
|
||||
});
|
||||
} else {
|
||||
// Thành công thật sự
|
||||
const successMsg = this.countNew
|
||||
? `Thêm mới thành công: ${this.countNew} bản ghi`
|
||||
: `Cập nhật thành công: ${this.countUdt} bản ghi`
|
||||
: `Cập nhật thành công: ${this.countUdt} bản ghi`;
|
||||
|
||||
this.msgInfo.push({ message: successMsg, type: 'success' })
|
||||
this.msgInfo.push({ message: result.message || 'Hoàn tất.', type: 'success' })
|
||||
this.msgInfo.push({ message: successMsg, type: "success" });
|
||||
this.msgInfo.push({
|
||||
message: result.message || "Hoàn tất.",
|
||||
type: "success",
|
||||
});
|
||||
|
||||
// Chỉ emit close khi KHÔNG có lỗi
|
||||
setTimeout(() => {
|
||||
this.$emit('close')
|
||||
}, 1500)
|
||||
this.$emit("close");
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
// Luôn cập nhật count về undefined để nút biến mất
|
||||
this.countNew = undefined
|
||||
this.countUdt = undefined
|
||||
this.countNew = undefined;
|
||||
this.countUdt = undefined;
|
||||
},
|
||||
|
||||
async update() {
|
||||
this.isloading = true
|
||||
this.result = undefined
|
||||
let conn = this.$findapi(this.setting.api)
|
||||
let obj = { 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)
|
||||
this.isloading = true;
|
||||
this.result = undefined;
|
||||
let conn = this.$findapi(this.setting.api);
|
||||
let obj = {
|
||||
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);
|
||||
|
||||
//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)
|
||||
let filter = this.data.filter((v) => v.id && !v.error);
|
||||
|
||||
//Xóa trường lookup trước khi gửi
|
||||
filter = this.cleanupLookupFields(filter)
|
||||
filter = filter.map(v => this.$resetNull(v))
|
||||
this.total = filter.length
|
||||
filter = this.cleanupLookupFields(filter);
|
||||
filter = filter.map((v) => this.$resetNull(v));
|
||||
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)
|
||||
this.isloading = false
|
||||
clearInterval(interval)
|
||||
this.getResult(obj.code)
|
||||
let result = await this.$insertapi(this.setting.call_api || this.setting.api, filter, undefined, obj.code);
|
||||
this.isloading = false;
|
||||
clearInterval(interval);
|
||||
this.getResult(obj.code);
|
||||
|
||||
if (result === 'error') {
|
||||
return this.msgInfo.push({ message: 'Có lỗi xảy ra', type: 'error' })
|
||||
if (result === "error") {
|
||||
return this.msgInfo.push({ message: "Có lỗi xảy ra", type: "error" });
|
||||
}
|
||||
|
||||
let hasError = false
|
||||
let errorMessages = []
|
||||
let hasError = false;
|
||||
let errorMessages = [];
|
||||
|
||||
// Kiểm tra có lỗi trong result không
|
||||
if (Array.isArray(result)) {
|
||||
result.forEach(item => {
|
||||
result.forEach((item) => {
|
||||
if (item.error) {
|
||||
hasError = true
|
||||
errorMessages.push(item.error || JSON.stringify(item.note))
|
||||
hasError = true;
|
||||
errorMessages.push(item.error || JSON.stringify(item.note));
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
if (hasError) {
|
||||
errorMessages.forEach(msg => {
|
||||
this.msgInfo.push({ message: msg, type: 'error' })
|
||||
})
|
||||
errorMessages.forEach((msg) => {
|
||||
this.msgInfo.push({ message: msg, type: "error" });
|
||||
});
|
||||
} else {
|
||||
this.msgInfo.push({ message: `Cập nhật thành công: ${this.countUdt} bản ghi`, type: 'success' })
|
||||
this.countUdt = undefined
|
||||
this.$emit('modalevent', { name: 'reload' })
|
||||
this.msgInfo.push({
|
||||
message: `Cập nhật thành công: ${this.countUdt} bản ghi`,
|
||||
type: "success",
|
||||
});
|
||||
this.countUdt = undefined;
|
||||
this.$emit("modalevent", { name: "reload" });
|
||||
}
|
||||
},
|
||||
|
||||
async getResult(logcode) {
|
||||
let found = this.$findapi('importlog')
|
||||
found.params.filter = { code: logcode }
|
||||
let rs = await this.$getapi([found])
|
||||
this.result = rs[0].data.rows[0]
|
||||
let found = this.$findapi("importlog");
|
||||
found.params.filter = { code: logcode };
|
||||
let rs = await this.$getapi([found]);
|
||||
this.result = rs[0].data.rows[0];
|
||||
},
|
||||
|
||||
exportExcel() {
|
||||
let dataType = {}
|
||||
this.pagedata.fields.map(v => dataType[v.label.indexOf('>') >= 0 ? v.name : v.label] = 'String')
|
||||
let data = this.pagedata.dataFilter || this.pagedata.data
|
||||
this.$exportExcel(data, 'data-export', this.pagedata.fields.map(v => v.name), dataType)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
let dataType = {};
|
||||
this.pagedata.fields.map((v) => (dataType[v.label.indexOf(">") >= 0 ? v.name : v.label] = "String"));
|
||||
let data = this.pagedata.dataFilter || this.pagedata.data;
|
||||
this.$exportExcel(
|
||||
data,
|
||||
"data-export",
|
||||
this.pagedata.fields.map((v) => v.name),
|
||||
dataType,
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -2,45 +2,78 @@
|
||||
<div class="columns">
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label">Code</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" placeholder="" v-model="current.code">
|
||||
<label class="label">Code</label>
|
||||
<div class="control">
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder=""
|
||||
v-model="current.code"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field mt-4">
|
||||
<label class="label">Name</label>
|
||||
<div class="control">
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder=""
|
||||
v-model="current.name"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field mt-4">
|
||||
<label class="label">Api</label>
|
||||
<div class="control">
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder=""
|
||||
v-model="current.api"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 py-2 border-bottom">
|
||||
<a @click="updateFromModel()">Thêm cài đặt cột từ Model</a>
|
||||
</div>
|
||||
<div class="py-2 border-bottom">
|
||||
<a @click="createUserSetting()">Tạo thiết lập (user setting)</a>
|
||||
</div>
|
||||
<div class="buttons mt-5">
|
||||
<button
|
||||
class="button is-primary"
|
||||
@click="saveData()"
|
||||
>
|
||||
Lưu lại
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-5">
|
||||
<DataTable
|
||||
v-bind="{ pagename: pagename1 }"
|
||||
@delete="remove"
|
||||
@open="showAttr()"
|
||||
v-if="pagedata1"
|
||||
/>
|
||||
</div>
|
||||
<div class="column is-4">
|
||||
<DataTable
|
||||
v-bind="{ pagename: pagename2 }"
|
||||
v-if="pagedata2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field mt-4">
|
||||
<label class="label">Name</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" placeholder="" v-model="current.name">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field mt-4">
|
||||
<label class="label">Api</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" placeholder="" v-model="current.api">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 py-2 border-bottom">
|
||||
<a @click="updateFromModel()">Thêm cài đặt cột từ Model</a>
|
||||
</div>
|
||||
<div class="py-2 border-bottom">
|
||||
<a @click="createUserSetting()">Tạo thiết lập (user setting)</a>
|
||||
</div>
|
||||
<div class="buttons mt-5">
|
||||
<button class="button is-primary" @click="saveData()">Lưu lại</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-5">
|
||||
<DataTable v-bind="{pagename: pagename1}" @delete="remove" @open="showAttr()" v-if="pagedata1" />
|
||||
</div>
|
||||
<div class="column is-4">
|
||||
<DataTable v-bind="{pagename: pagename2}" v-if="pagedata2" />
|
||||
</div>
|
||||
</div>
|
||||
<Modal @close="showmodal=undefined" @update="updateAttr" v-bind="showmodal" v-if="showmodal" />
|
||||
<Modal
|
||||
@close="showmodal = undefined"
|
||||
@update="updateAttr"
|
||||
v-bind="showmodal"
|
||||
v-if="showmodal"
|
||||
/>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['row'],
|
||||
props: ["row"],
|
||||
data() {
|
||||
return {
|
||||
data: [],
|
||||
@@ -48,106 +81,142 @@ export default {
|
||||
showmodal: undefined,
|
||||
report: undefined,
|
||||
setting: {},
|
||||
pagename: 'page0',
|
||||
pagename1: 'page1',
|
||||
pagename2: 'page2',
|
||||
pagename3: 'page3'
|
||||
}
|
||||
pagename: "page0",
|
||||
pagename1: "page1",
|
||||
pagename2: "page2",
|
||||
pagename3: "page3",
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
let conn1 = this.$findapi('usersetting')
|
||||
conn1.params.filter = {name__in: ['import-setting', 'model-fields']}
|
||||
let rs = await this.$getapi([conn1])
|
||||
let obj = this.$find(rs, {name: 'usersetting'})
|
||||
let found = this.$find(obj.data.rows, {name: 'import-setting'})
|
||||
this.pagedata1 = this.$getpage()
|
||||
this.$setpage(this.pagename1, found)
|
||||
this.pagedata2 = this.$getpage()
|
||||
found = this.$find(obj.data.rows, {name: 'model-fields'})
|
||||
this.$setpage(this.pagename2, found)
|
||||
this.doClick(this.row)
|
||||
let conn1 = this.$findapi("usersetting");
|
||||
conn1.params.filter = { name__in: ["import-setting", "model-fields"] };
|
||||
let rs = await this.$getapi([conn1]);
|
||||
let obj = this.$find(rs, { name: "usersetting" });
|
||||
let found = this.$find(obj.data.rows, { name: "import-setting" });
|
||||
this.pagedata1 = this.$getpage();
|
||||
this.$setpage(this.pagename1, found);
|
||||
this.pagedata2 = this.$getpage();
|
||||
found = this.$find(obj.data.rows, { name: "model-fields" });
|
||||
this.$setpage(this.pagename2, found);
|
||||
this.doClick(this.row);
|
||||
},
|
||||
computed: {
|
||||
pagedata1: {
|
||||
get: function() {return this.$store[this.pagename1]},
|
||||
set: function(val) {this.$store.commit(this.pagename1, val)}
|
||||
get: function () {
|
||||
return this.$store[this.pagename1];
|
||||
},
|
||||
set: function (val) {
|
||||
this.$store.commit(this.pagename1, val);
|
||||
},
|
||||
},
|
||||
pagedata2: {
|
||||
get: function() {return this.$store[this.pagename2]},
|
||||
set: function(val) {this.$store.commit(this.pagename2, val)}
|
||||
get: function () {
|
||||
return this.$store[this.pagename2];
|
||||
},
|
||||
set: function (val) {
|
||||
this.$store.commit(this.pagename2, val);
|
||||
},
|
||||
},
|
||||
pagedata3: {
|
||||
get: function() {return this.$store[this.pagename3]},
|
||||
set: function(val) {this.$store.commit(this.pagename3, val)}
|
||||
}
|
||||
get: function () {
|
||||
return this.$store[this.pagename3];
|
||||
},
|
||||
set: function (val) {
|
||||
this.$store.commit(this.pagename3, val);
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateAttr(v) {
|
||||
this.current.detail = v
|
||||
this.showSetting()
|
||||
this.showmodal = undefined
|
||||
this.current.detail = v;
|
||||
this.showSetting();
|
||||
this.showmodal = undefined;
|
||||
},
|
||||
createUserSetting() {
|
||||
this.pagedata3 = this.$getpage()
|
||||
let fields = []
|
||||
let arr = ['AutoField', 'ForeignKey', 'FloatField', 'IntegerField']
|
||||
this.pagedata2.data.map(v=>{
|
||||
let field = this.$createField(v.name, v.name, arr.findIndex(x=>x==v.datatype)>=0? 'number' : 'string', true, true)
|
||||
fields.push(field)
|
||||
})
|
||||
this.$store.commit(this.pagename3, {update: {'fields': fields}})
|
||||
this.showmodal = {component: 'menu/MenuSave', title: 'Lưu thiết lập', width: '600px', height: '300px',
|
||||
vbind: {pagename: this.pagename3, classify: 3}}
|
||||
this.pagedata3 = this.$getpage();
|
||||
let fields = [];
|
||||
let arr = ["AutoField", "ForeignKey", "FloatField", "IntegerField"];
|
||||
this.pagedata2.data.map((v) => {
|
||||
let field = this.$createField(
|
||||
v.name,
|
||||
v.name,
|
||||
arr.findIndex((x) => x == v.datatype) >= 0 ? "number" : "string",
|
||||
true,
|
||||
true,
|
||||
);
|
||||
fields.push(field);
|
||||
});
|
||||
this.$store.commit(this.pagename3, { update: { fields: fields } });
|
||||
this.showmodal = {
|
||||
component: "menu/MenuSave",
|
||||
title: "Lưu thiết lập",
|
||||
width: "600px",
|
||||
height: "300px",
|
||||
vbind: { pagename: this.pagename3, classify: 3 },
|
||||
};
|
||||
},
|
||||
remove(v) {
|
||||
let copy = this.$copy(this.current.detail)
|
||||
delete copy[v.key]
|
||||
this.current.detail = copy
|
||||
this.doClick(this.current)
|
||||
let copy = this.$copy(this.current.detail);
|
||||
delete copy[v.key];
|
||||
this.current.detail = copy;
|
||||
this.doClick(this.current);
|
||||
},
|
||||
async insertData() {
|
||||
let copy = this.$copy(this.current)
|
||||
copy.id = undefined
|
||||
let rs = await this.$insertrow('importsetting', copy, undefined, this.pagename)
|
||||
let copy = this.$copy(this.current);
|
||||
copy.id = undefined;
|
||||
let rs = await this.$insertrow("importsetting", copy, undefined, this.pagename);
|
||||
},
|
||||
async saveData() {
|
||||
if(!this.row) return this.insertData()
|
||||
let rs = await this.$updateapi('importsetting', this.current, undefined, this.pagename)
|
||||
if (!this.row) return this.insertData();
|
||||
let rs = await this.$updateapi("importsetting", this.current, undefined, this.pagename);
|
||||
},
|
||||
updateFromModel() {
|
||||
let filter = this.$filter(this.pagedata2.data, {datatype: ['FloatField', 'IntegerField']})
|
||||
let copy = this.$copy(this.current.detail)
|
||||
filter.map(v=>{
|
||||
copy[v.name] = {type: 'number', empty: v.null? 'yes' : 'no'}
|
||||
})
|
||||
this.current.detail = copy
|
||||
this.showSetting()
|
||||
let filter = this.$filter(this.pagedata2.data, {
|
||||
datatype: ["FloatField", "IntegerField"],
|
||||
});
|
||||
let copy = this.$copy(this.current.detail);
|
||||
filter.map((v) => {
|
||||
copy[v.name] = { type: "number", empty: v.null ? "yes" : "no" };
|
||||
});
|
||||
this.current.detail = copy;
|
||||
this.showSetting();
|
||||
},
|
||||
showAttr() {
|
||||
this.showmodal = {component: 'datatable/FieldAttribute', width: '40%', height: '300px', title: 'Danh sách thuộc tính', vbind: {field: this.current.detail}}
|
||||
this.showmodal = {
|
||||
component: "datatable/FieldAttribute",
|
||||
width: "40%",
|
||||
height: "300px",
|
||||
title: "Danh sách thuộc tính",
|
||||
vbind: { field: this.current.detail },
|
||||
};
|
||||
},
|
||||
async doClick(v) {
|
||||
this.current = this.$copy(v)
|
||||
this.showSetting()
|
||||
let found = this.$findapi(v.api)
|
||||
let conn = this.$findapi('modelfields')
|
||||
conn.url += found.url.replace('data/', '')
|
||||
let rs = await this.$getapi([conn])
|
||||
let copy = this.$copy(this.pagedata2)
|
||||
copy.update = {data: rs[0].data}
|
||||
this.pagedata2 = copy
|
||||
this.current = this.$copy(v);
|
||||
this.showSetting();
|
||||
let found = this.$findapi(v.api);
|
||||
let conn = this.$findapi("modelfields");
|
||||
conn.url += found.url.replace("data/", "");
|
||||
let rs = await this.$getapi([conn]);
|
||||
let copy = this.$copy(this.pagedata2);
|
||||
copy.update = { data: rs[0].data };
|
||||
this.pagedata2 = copy;
|
||||
},
|
||||
showSetting() {
|
||||
let arr = []
|
||||
let i = 0
|
||||
for(let key in this.current.detail) {
|
||||
i += 1
|
||||
arr.push({id: i, key: key, value: this.current.detail[key], text: JSON.stringify(this.current.detail[key])})
|
||||
let arr = [];
|
||||
let i = 0;
|
||||
for (let key in this.current.detail) {
|
||||
i += 1;
|
||||
arr.push({
|
||||
id: i,
|
||||
key: key,
|
||||
value: this.current.detail[key],
|
||||
text: JSON.stringify(this.current.detail[key]),
|
||||
});
|
||||
}
|
||||
let copy = this.$copy(this.pagedata1)
|
||||
copy.update = {data: arr}
|
||||
this.pagedata1 = copy
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
let copy = this.$copy(this.pagedata1);
|
||||
copy.update = { data: arr };
|
||||
this.pagedata1 = copy;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
<div class="field-body">
|
||||
<!-- Mã giỏ hàng -->
|
||||
<div class="field is-narrow">
|
||||
<label class="label">
|
||||
Mã <span class="has-text-danger">*</span>
|
||||
</label>
|
||||
<label class="label"> Mã <span class="has-text-danger">*</span> </label>
|
||||
<div class="control">
|
||||
<input
|
||||
class="input"
|
||||
@@ -20,9 +18,7 @@
|
||||
|
||||
<!-- Tên giỏ hàng -->
|
||||
<div class="field">
|
||||
<label class="label">
|
||||
Tên giỏ hàng <span class="has-text-danger">*</span>
|
||||
</label>
|
||||
<label class="label"> Tên giỏ hàng <span class="has-text-danger">*</span> </label>
|
||||
<div class="control">
|
||||
<input
|
||||
class="input"
|
||||
@@ -34,10 +30,11 @@
|
||||
</div>
|
||||
|
||||
<!-- Index -->
|
||||
<div class="field" style="width: 120px;">
|
||||
<label class="label">
|
||||
Index <span class="has-text-danger">*</span>
|
||||
</label>
|
||||
<div
|
||||
class="field"
|
||||
style="width: 120px"
|
||||
>
|
||||
<label class="label"> Index <span class="has-text-danger">*</span> </label>
|
||||
<div class="control">
|
||||
<input
|
||||
class="input"
|
||||
@@ -59,10 +56,10 @@
|
||||
api: 'dealer',
|
||||
field: 'code',
|
||||
column: 'code',
|
||||
optionid: record.dealer || null,
|
||||
optionid: record.dealer || null,
|
||||
first: true,
|
||||
viewaddon: viewAddon,
|
||||
addon: newAddon
|
||||
addon: newAddon,
|
||||
}"
|
||||
@option="dealerSelected"
|
||||
/>
|
||||
@@ -72,8 +69,11 @@
|
||||
<!-- Nút lưu -->
|
||||
<div class="field is-grouped is-grouped-right mt-6">
|
||||
<div class="control">
|
||||
<button class="button is-primary has-text-white" @click="update">
|
||||
{{ record.id ? 'Cập nhật' : 'Thêm mới' }}
|
||||
<button
|
||||
class="button is-primary has-text-white"
|
||||
@click="update"
|
||||
>
|
||||
{{ record.id ? "Cập nhật" : "Thêm mới" }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -81,95 +81,88 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, nextTick } from 'vue'
|
||||
const {
|
||||
$copy,
|
||||
$resetNull,
|
||||
$snackbar,
|
||||
$getdata,
|
||||
$insertapi,
|
||||
$updateapi
|
||||
} = useNuxtApp()
|
||||
import { ref, onMounted, nextTick } from "vue";
|
||||
const { $copy, $resetNull, $snackbar, $getdata, $insertapi, $updateapi } = useNuxtApp();
|
||||
|
||||
// Chỉ nhận id (không nhận row)
|
||||
const props = defineProps({
|
||||
prefix: String,
|
||||
id: [Number, String, null]
|
||||
})
|
||||
id: [Number, String, null],
|
||||
});
|
||||
|
||||
const emit = defineEmits(['close', 'modalevent'])
|
||||
const emit = defineEmits(["close", "modalevent"]);
|
||||
|
||||
const record = ref({
|
||||
code: '',
|
||||
name: '',
|
||||
code: "",
|
||||
name: "",
|
||||
index: 0,
|
||||
dealer: null
|
||||
})
|
||||
dealer: null,
|
||||
});
|
||||
|
||||
const viewAddon = {}
|
||||
const newAddon = {}
|
||||
const viewAddon = {};
|
||||
const newAddon = {};
|
||||
|
||||
function dealerSelected(obj) {
|
||||
record.value.dealer = obj ? obj.id : null
|
||||
record.value.dealer = obj ? obj.id : null;
|
||||
}
|
||||
|
||||
async function loadCart() {
|
||||
if (!props.id) {
|
||||
record.value = {
|
||||
code: '',
|
||||
name: '',
|
||||
code: "",
|
||||
name: "",
|
||||
index: 0,
|
||||
dealer: null
|
||||
}
|
||||
return
|
||||
dealer: null,
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const row = await $getdata('cart', { id: props.id }, null, true)
|
||||
const row = await $getdata("cart", { id: props.id }, null, true);
|
||||
|
||||
if (row) {
|
||||
record.value = $copy(row)
|
||||
record.value = $copy(row);
|
||||
} else {
|
||||
$snackbar('Không tìm thấy giỏ hàng này')
|
||||
emit('close')
|
||||
$snackbar("Không tìm thấy giỏ hàng này");
|
||||
emit("close");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Lỗi khi tải dữ liệu giỏ hàng:', error)
|
||||
$snackbar('Lỗi tải dữ liệu')
|
||||
emit('close')
|
||||
console.error("Lỗi khi tải dữ liệu giỏ hàng:", error);
|
||||
$snackbar("Lỗi tải dữ liệu");
|
||||
emit("close");
|
||||
}
|
||||
}
|
||||
|
||||
// Lưu dữ liệu
|
||||
async function update() {
|
||||
const data = $resetNull(record.value)
|
||||
const data = $resetNull(record.value);
|
||||
|
||||
try {
|
||||
let rs
|
||||
let rs;
|
||||
if (data.id) {
|
||||
rs = await $updateapi('cart', data)
|
||||
rs = await $updateapi("cart", data);
|
||||
} else {
|
||||
rs = await $insertapi('cart', data)
|
||||
rs = await $insertapi("cart", data);
|
||||
}
|
||||
|
||||
if (rs === 'error') {
|
||||
$snackbar('Lỗi khi lưu dữ liệu')
|
||||
return
|
||||
if (rs === "error") {
|
||||
$snackbar("Lỗi khi lưu dữ liệu");
|
||||
return;
|
||||
}
|
||||
|
||||
$snackbar('Lưu thành công!')
|
||||
emit('modalevent', { name: 'dataevent', data: rs })
|
||||
emit('close')
|
||||
$snackbar("Lưu thành công!");
|
||||
emit("modalevent", { name: "dataevent", data: rs });
|
||||
emit("close");
|
||||
} catch (err) {
|
||||
$snackbar('Có lỗi xảy ra khi lưu')
|
||||
console.error(err)
|
||||
$snackbar("Có lỗi xảy ra khi lưu");
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
// Khởi tạo khi mở modal
|
||||
onMounted(async () => {
|
||||
await loadCart()
|
||||
await nextTick()
|
||||
document.getElementById('code')?.focus()
|
||||
})
|
||||
</script>
|
||||
await loadCart();
|
||||
await nextTick();
|
||||
document.getElementById("code")?.focus();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -3,15 +3,23 @@
|
||||
<div class="mt-5 columns">
|
||||
<div class="column">
|
||||
<label class="label"
|
||||
>{{ isVietnamese ? "Thời gian (ngày)" : "Duration (Days)" }} <span class="has-text-danger"> * </span>
|
||||
>{{ isVietnamese ? "Thời gian (ngày)" : "Duration (Days)" }}
|
||||
<span class="has-text-danger"> * </span>
|
||||
</label>
|
||||
<p class="control">
|
||||
<input class="input" type="number" placeholder="" v-model="record.time" autocomplete="off" />
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
placeholder=""
|
||||
v-model="record.time"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
<div class="column">
|
||||
<label class="label"
|
||||
>{{ isVietnamese ? "Mẫu email" : "Email Template" }} <span class="has-text-danger"> * </span>
|
||||
>{{ isVietnamese ? "Mẫu email" : "Email Template" }}
|
||||
<span class="has-text-danger"> * </span>
|
||||
</label>
|
||||
<SearchBox
|
||||
v-bind="{
|
||||
@@ -26,7 +34,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<button class="button is-primary has-text-white" @click="update()">
|
||||
<button
|
||||
class="button is-primary has-text-white"
|
||||
@click="update()"
|
||||
>
|
||||
{{ dataTemp.id ? "Cập nhật" : "Tạo mới" }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,12 @@
|
||||
<div class="field">
|
||||
<label class="label">Thứ tự</label>
|
||||
<div class="control">
|
||||
<input class="input" type="number" v-model.number="record.index" placeholder="Thứ tự" />
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
v-model.number="record.index"
|
||||
placeholder="Thứ tự"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -19,9 +24,20 @@
|
||||
<div class="field">
|
||||
<label class="label">Mã <span class="has-text-danger">*</span></label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" v-model="record.code" id="code" placeholder="Mã CS" />
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
v-model="record.code"
|
||||
id="code"
|
||||
placeholder="Mã CS"
|
||||
/>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors.code">{{ errors.code }}</p>
|
||||
<p
|
||||
class="help is-danger"
|
||||
v-if="errors.code"
|
||||
>
|
||||
{{ errors.code }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -29,9 +45,19 @@
|
||||
<div class="field">
|
||||
<label class="label">Tên chính sách <span class="has-text-danger">*</span></label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" v-model="record.name" placeholder="Tên" />
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
v-model="record.name"
|
||||
placeholder="Tên"
|
||||
/>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors.name">{{ errors.name }}</p>
|
||||
<p
|
||||
class="help is-danger"
|
||||
v-if="errors.name"
|
||||
>
|
||||
{{ errors.name }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -39,13 +65,21 @@
|
||||
<div class="field">
|
||||
<label class="label">Đặt cọc <span class="has-text-danger">*</span></label>
|
||||
<div class="control">
|
||||
<InputNumber v-bind="{
|
||||
record: record,
|
||||
attr: 'deposit',
|
||||
defaultValue: true,
|
||||
}" @number="selected('deposit', $event)" />
|
||||
<InputNumber
|
||||
v-bind="{
|
||||
record: record,
|
||||
attr: 'deposit',
|
||||
defaultValue: true,
|
||||
}"
|
||||
@number="selected('deposit', $event)"
|
||||
/>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors.deposit">{{ errors.deposit }}</p>
|
||||
<p
|
||||
class="help is-danger"
|
||||
v-if="errors.deposit"
|
||||
>
|
||||
{{ errors.deposit }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -53,23 +87,38 @@
|
||||
<div class="field">
|
||||
<label class="label">PT thanh toán <span class="has-text-danger">*</span></label>
|
||||
<div class="control">
|
||||
<SearchBox v-bind="{
|
||||
api: 'paymentmethod',
|
||||
field: 'name',
|
||||
column: ['name'],
|
||||
first: true,
|
||||
optionid: record.method
|
||||
}" @option="selected('method', $event)" />
|
||||
<SearchBox
|
||||
v-bind="{
|
||||
api: 'paymentmethod',
|
||||
field: 'name',
|
||||
column: ['name'],
|
||||
first: true,
|
||||
optionid: record.method,
|
||||
}"
|
||||
@option="selected('method', $event)"
|
||||
/>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors.method">{{ errors.method }}</p>
|
||||
<p
|
||||
class="help is-danger"
|
||||
v-if="errors.method"
|
||||
>
|
||||
{{ errors.method }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column is-3">
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label">% Phân bổ HĐ</label>
|
||||
<div class="control">
|
||||
<input class="input" type="number" v-model.number="record.contract_allocation_percentage" placeholder="%" min="0" max="100" />
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
v-model.number="record.contract_allocation_percentage"
|
||||
placeholder="%"
|
||||
min="0"
|
||||
max="100"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -81,16 +130,24 @@
|
||||
<span class="has-text-danger">*</span>
|
||||
</label>
|
||||
<div class="control">
|
||||
<div class="is-flex is-align-items-center is-clickable is-gap-2" @click="record.enable = !record.enable">
|
||||
<SvgIcon v-bind="{
|
||||
name: record.enable ? 'checked.svg' : 'uncheck.svg',
|
||||
type: record.enable ? 'primary' : 'twitter',
|
||||
size: 22
|
||||
}" />
|
||||
<div
|
||||
class="is-flex is-align-items-center is-clickable is-gap-2"
|
||||
@click="record.enable = !record.enable"
|
||||
>
|
||||
<SvgIcon
|
||||
v-bind="{
|
||||
name: record.enable ? 'checked.svg' : 'uncheck.svg',
|
||||
type: record.enable ? 'primary' : 'twitter',
|
||||
size: 22,
|
||||
}"
|
||||
/>
|
||||
<span>Cho phép</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors.enable">
|
||||
<p
|
||||
class="help is-danger"
|
||||
v-if="errors.enable"
|
||||
>
|
||||
{{ errors.enable }}
|
||||
</p>
|
||||
</div>
|
||||
@@ -102,19 +159,36 @@
|
||||
<Caption v-bind="{ title: 'Kế hoạch thanh toán', type: 'has-text-warning' }"></Caption>
|
||||
</div>
|
||||
|
||||
<div class="p-3" v-for="(plan, i) in paymentPlans" :key="i" style="position: relative;">
|
||||
<a v-if="paymentPlans.length > 1" class="has-text-danger is-size-7"
|
||||
style="position: absolute; top: 0.5rem; right: 0.75rem; cursor: pointer;" @click="removePlan(plan, i)">
|
||||
<div
|
||||
class="p-3"
|
||||
v-for="(plan, i) in paymentPlans"
|
||||
:key="i"
|
||||
style="position: relative"
|
||||
>
|
||||
<a
|
||||
v-if="paymentPlans.length > 1"
|
||||
class="has-text-danger is-size-7"
|
||||
style="position: absolute; top: 0.5rem; right: 0.75rem; cursor: pointer"
|
||||
@click="removePlan(plan, i)"
|
||||
>
|
||||
Xóa
|
||||
</a>
|
||||
<div class="columns is-multiline is-mobile mb-0" style="border-top: 1px solid #dbdbdb;">
|
||||
<div
|
||||
class="columns is-multiline is-mobile mb-0"
|
||||
style="border-top: 1px solid #dbdbdb"
|
||||
>
|
||||
<!-- Đợt -->
|
||||
<div class="column is-3-desktop is-2-mobile pb-0">
|
||||
<div class="field">
|
||||
<label class="label">Đợt</label>
|
||||
<div class="control">
|
||||
<input class="input has-text-centered has-text-weight-bold" type="text" :value="i + 1" disabled
|
||||
style="background-color: #f5f5f5;" />
|
||||
<input
|
||||
class="input has-text-centered has-text-weight-bold"
|
||||
type="text"
|
||||
:value="i + 1"
|
||||
disabled
|
||||
style="background-color: #f5f5f5"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -124,8 +198,14 @@
|
||||
<div class="field">
|
||||
<label class="label">Giá trị <span class="has-text-danger">*</span></label>
|
||||
<div class="control">
|
||||
<input class="input" type="number" v-model.number="plan.value" placeholder="Giá trị" min="0"
|
||||
step="0.01" />
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
v-model.number="plan.value"
|
||||
placeholder="Giá trị"
|
||||
min="0"
|
||||
step="0.01"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -135,13 +215,16 @@
|
||||
<div class="field">
|
||||
<label class="label">Loại <span class="has-text-danger">*</span></label>
|
||||
<div class="control">
|
||||
<SearchBox v-bind="{
|
||||
api: 'valuetype',
|
||||
field: 'name',
|
||||
column: ['name'],
|
||||
first: true,
|
||||
optionid: plan.type
|
||||
}" @option="planSelected('type', $event, plan)" />
|
||||
<SearchBox
|
||||
v-bind="{
|
||||
api: 'valuetype',
|
||||
field: 'name',
|
||||
column: ['name'],
|
||||
first: true,
|
||||
optionid: plan.type,
|
||||
}"
|
||||
@option="planSelected('type', $event, plan)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -151,7 +234,13 @@
|
||||
<div class="field">
|
||||
<label class="label">Số ngày</label>
|
||||
<div class="control">
|
||||
<input class="input" type="number" v-model.number="plan.days" placeholder="Ngày" min="0" />
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
v-model.number="plan.days"
|
||||
placeholder="Ngày"
|
||||
min="0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -161,7 +250,12 @@
|
||||
<div class="field">
|
||||
<label class="label">Ghi chú TT</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" v-model="plan.payment_note" placeholder="Ghi chú thanh toán" />
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
v-model="plan.payment_note"
|
||||
placeholder="Ghi chú thanh toán"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -171,7 +265,12 @@
|
||||
<div class="field mb-0">
|
||||
<label class="label">Ghi chú hạn</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" v-model="plan.due_note" placeholder="Ghi chú hạn thanh toán" />
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
v-model="plan.due_note"
|
||||
placeholder="Ghi chú hạn thanh toán"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -179,23 +278,39 @@
|
||||
</div>
|
||||
|
||||
<!-- Nút thêm -->
|
||||
<button class="button is-pulled-right is-info is-light mb-4" @click="addPlan">
|
||||
<button
|
||||
class="button is-pulled-right is-info is-light mb-4"
|
||||
@click="addPlan"
|
||||
>
|
||||
<span class="icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="12" width="12" viewBox="0 0 448 512">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="12"
|
||||
width="12"
|
||||
viewBox="0 0 448 512"
|
||||
>
|
||||
<path
|
||||
d="M256 80c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 144L48 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l144 0 0 144c0 17.7 14.3 32 32 32s32-14.3 32-32l0-144 144 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-144 0 0-144z" />
|
||||
d="M256 80c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 144L48 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l144 0 0 144c0 17.7 14.3 32 32 32s32-14.3 32-32l0-144 144 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-144 0 0-144z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<!-- Action buttons -->
|
||||
<div class="buttons">
|
||||
<button :class="`button has-text-white is-primary ${isSubmitting ? 'is-loading' : ''}`" @click="update()"
|
||||
:disabled="isSubmitting">
|
||||
<button
|
||||
:class="`button has-text-white is-primary ${isSubmitting ? 'is-loading' : ''}`"
|
||||
@click="update()"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
<span>Lưu</span>
|
||||
</button>
|
||||
|
||||
<button class="button has-text-white is-danger" @click="emit('close')" :disabled="isSubmitting">
|
||||
<button
|
||||
class="button has-text-white is-danger"
|
||||
@click="emit('close')"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
<span>Hủy</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -262,7 +377,7 @@ async function update() {
|
||||
isSubmitting.value = true;
|
||||
|
||||
let policyData = $resetNull(record.value);
|
||||
if (policyData.method && typeof policyData.method === 'object') {
|
||||
if (policyData.method && typeof policyData.method === "object") {
|
||||
policyData.method = policyData.method.id;
|
||||
}
|
||||
|
||||
@@ -274,7 +389,7 @@ async function update() {
|
||||
throw new Error("Failed to save policy");
|
||||
}
|
||||
|
||||
const plans = paymentPlans.value.filter(v => {
|
||||
const plans = paymentPlans.value.filter((v) => {
|
||||
return !$empty(v.value) && !$empty(v.type);
|
||||
});
|
||||
|
||||
@@ -288,7 +403,9 @@ async function update() {
|
||||
|
||||
if (plans.length > 0) {
|
||||
if (props.row?.id) {
|
||||
const oldPlans = await $getdata("paymentplan", { policy: policyResult.id });
|
||||
const oldPlans = await $getdata("paymentplan", {
|
||||
policy: policyResult.id,
|
||||
});
|
||||
|
||||
if (oldPlans && oldPlans.length > 0) {
|
||||
for (const oldPlan of oldPlans) {
|
||||
@@ -304,7 +421,6 @@ async function update() {
|
||||
|
||||
emit("modalevent", { name: "dataevent", data: policyResult });
|
||||
emit("close");
|
||||
|
||||
} catch (error) {
|
||||
$snackbar("Lưu dữ liệu thất bại");
|
||||
} finally {
|
||||
@@ -321,14 +437,14 @@ function selected(attr, obj) {
|
||||
}
|
||||
|
||||
function planSelected(attr, obj, plan) {
|
||||
if (attr === 'type') {
|
||||
if (obj && typeof obj === 'object') {
|
||||
if (attr === "type") {
|
||||
if (obj && typeof obj === "object") {
|
||||
plan.type = obj.id;
|
||||
plan._type = obj;
|
||||
} else {
|
||||
plan.type = obj;
|
||||
}
|
||||
} else if (attr === 'value') {
|
||||
} else if (attr === "value") {
|
||||
plan.value = Number(obj);
|
||||
} else {
|
||||
plan[attr] = obj;
|
||||
|
||||
@@ -1,87 +1,120 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="field is-horizontal mt-6 pt-4">
|
||||
<div class="field-body">
|
||||
<div class="field is-narrow">
|
||||
<label class="label">Code <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control">
|
||||
<input class="input" type="text" placeholder="" v-model="record.code" id="code">
|
||||
</p>
|
||||
<div class="field is-horizontal mt-6 pt-4">
|
||||
<div class="field-body">
|
||||
<div class="field is-narrow">
|
||||
<label class="label">Code <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control">
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder=""
|
||||
v-model="record.code"
|
||||
id="code"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
<div class="field is-narrow">
|
||||
<label class="label">Name <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control">
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder=""
|
||||
v-model="record.name"
|
||||
id="code"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-narrow">
|
||||
<label class="label">Name <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control">
|
||||
<input class="input" type="text" placeholder="" v-model="record.name" id="code">
|
||||
</p>
|
||||
<div class="columns mx-0">
|
||||
<div class="column is-8">
|
||||
<div class="mt-5 mb-3">
|
||||
<Caption v-bind="{ title: 'Required documents', type: 'has-text-primary' }"></Caption>
|
||||
</div>
|
||||
<div
|
||||
class="field is-grouped"
|
||||
v-for="(v, i) in array"
|
||||
>
|
||||
<div class="control">
|
||||
<SearchBox
|
||||
v-bind="{
|
||||
api: 'documenttype',
|
||||
field: 'name',
|
||||
column: ['name'],
|
||||
first: true,
|
||||
optionid: v.doctype,
|
||||
position: 'is-top-left',
|
||||
}"
|
||||
@option="documentSelected('_doctype', $event, v)"
|
||||
></SearchBox>
|
||||
</div>
|
||||
<div class="control pl-5">
|
||||
<a
|
||||
class="mr-4"
|
||||
@click="add()"
|
||||
>
|
||||
<SvgIcon v-bind="{ name: 'add1.png', type: 'dark', size: 20 }"></SvgIcon>
|
||||
</a>
|
||||
<a @click="remove(v, i)">
|
||||
<SvgIcon v-bind="{ name: 'bin1.svg', type: 'dark', size: 20 }"></SvgIcon>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<button
|
||||
class="button is-primary has-text-white"
|
||||
@click="update()"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns mx-0">
|
||||
<div class="column is-8">
|
||||
<div class="mt-5 mb-3">
|
||||
<Caption v-bind="{title: 'Required documents', type: 'has-text-primary'}"></Caption>
|
||||
</div>
|
||||
<div class="field is-grouped" v-for="(v,i) in array">
|
||||
<div class="control">
|
||||
<SearchBox v-bind="{api:'documenttype', field:'name', column:['name'], first:true, optionid:v.doctype, position: 'is-top-left'}"
|
||||
@option="documentSelected('_doctype', $event, v)"></SearchBox>
|
||||
</div>
|
||||
<div class="control pl-5">
|
||||
<a class="mr-4" @click="add()">
|
||||
<SvgIcon v-bind="{name: 'add1.png', type: 'dark', size: 20}"></SvgIcon>
|
||||
</a>
|
||||
<a @click="remove(v, i)">
|
||||
<SvgIcon v-bind="{name: 'bin1.svg', type: 'dark', size: 20}"></SvgIcon>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<button class="button is-primary has-text-white" @click="update()">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { onMounted } from 'vue'
|
||||
const { $getdata, $copy, $resetNull, $insertrow, $updaterow, $snackbar, $remove, $deleteapi} = useNuxtApp()
|
||||
var props = defineProps({
|
||||
api: String,
|
||||
pagename: String,
|
||||
row: Object,
|
||||
prefix: String
|
||||
})
|
||||
const emit = defineEmits(['close', 'modalevent'])
|
||||
var array = ref([{}])
|
||||
var record = $copy( props.row || {})
|
||||
if(props.row) {
|
||||
let arr = await $getdata('phasedoctype', {phase: props.row.id})
|
||||
if(arr.length>0) array.value = arr
|
||||
}
|
||||
async function update() {
|
||||
let data = $resetNull(record)
|
||||
let rs = data.id? await $updaterow(props.api, data, undefined, props.pagename)
|
||||
: await $insertrow(props.api, data, undefined, props.pagename)
|
||||
if(rs==='error') return $snackbar(rs)
|
||||
let arr = array.value.filter(v=>v.doctype)
|
||||
arr.map(v=>v.phase = rs.id)
|
||||
await $insertrow('phasedoctype', arr)
|
||||
emit('modalevent', {name: 'dataevent', data: rs})
|
||||
emit('close')
|
||||
}
|
||||
function documentSelected(attr, obj, v) {
|
||||
v[attr] = obj
|
||||
if(obj) v.doctype = obj.id
|
||||
}
|
||||
function add() {
|
||||
array.value.push({})
|
||||
}
|
||||
async function remove(v, i) {
|
||||
if(v.id) await $deleteapi('phasedoctype', v.id)
|
||||
$remove(array.value, i)
|
||||
if(array.value.length===0) array.value = [{}]
|
||||
}
|
||||
onMounted(()=> {
|
||||
document.getElementById('code').focus()
|
||||
})
|
||||
</script>
|
||||
import { onMounted } from "vue";
|
||||
const { $getdata, $copy, $resetNull, $insertrow, $updaterow, $snackbar, $remove, $deleteapi } = useNuxtApp();
|
||||
var props = defineProps({
|
||||
api: String,
|
||||
pagename: String,
|
||||
row: Object,
|
||||
prefix: String,
|
||||
});
|
||||
const emit = defineEmits(["close", "modalevent"]);
|
||||
var array = ref([{}]);
|
||||
var record = $copy(props.row || {});
|
||||
if (props.row) {
|
||||
let arr = await $getdata("phasedoctype", { phase: props.row.id });
|
||||
if (arr.length > 0) array.value = arr;
|
||||
}
|
||||
async function update() {
|
||||
let data = $resetNull(record);
|
||||
let rs = data.id
|
||||
? await $updaterow(props.api, data, undefined, props.pagename)
|
||||
: await $insertrow(props.api, data, undefined, props.pagename);
|
||||
if (rs === "error") return $snackbar(rs);
|
||||
let arr = array.value.filter((v) => v.doctype);
|
||||
arr.map((v) => (v.phase = rs.id));
|
||||
await $insertrow("phasedoctype", arr);
|
||||
emit("modalevent", { name: "dataevent", data: rs });
|
||||
emit("close");
|
||||
}
|
||||
function documentSelected(attr, obj, v) {
|
||||
v[attr] = obj;
|
||||
if (obj) v.doctype = obj.id;
|
||||
}
|
||||
function add() {
|
||||
array.value.push({});
|
||||
}
|
||||
async function remove(v, i) {
|
||||
if (v.id) await $deleteapi("phasedoctype", v.id);
|
||||
$remove(array.value, i);
|
||||
if (array.value.length === 0) array.value = [{}];
|
||||
}
|
||||
onMounted(() => {
|
||||
document.getElementById("code").focus();
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user