717 lines
22 KiB
Vue
717 lines
22 KiB
Vue
<template>
|
|
<div class="field is-grouped">
|
|
<div class="file is-primary m-0">
|
|
<label class="file-label">
|
|
<input
|
|
class="file-input"
|
|
type="file"
|
|
id="divfile"
|
|
name="resume"
|
|
@change="inputFile"
|
|
/>
|
|
<span
|
|
class="file-cta"
|
|
style="padding-left: 0.5rem"
|
|
>
|
|
<span class="icon-text">
|
|
<span class="icon">
|
|
<Icon
|
|
name="material-symbols:attach-file"
|
|
:size="18"
|
|
/>
|
|
</span>
|
|
<span class="font-medium">Chọn file</span>
|
|
</span>
|
|
</span>
|
|
</label>
|
|
</div>
|
|
<button
|
|
class="button is-primary is-light"
|
|
@click="download"
|
|
>
|
|
<span class="icon">
|
|
<Icon
|
|
name="material-symbols:download"
|
|
:size="20"
|
|
/>
|
|
</span>
|
|
<span>Tải template</span>
|
|
</button>
|
|
<button
|
|
v-if="datafile"
|
|
class="button is-success is-light"
|
|
@click="exportExcel"
|
|
>
|
|
<span class="icon">
|
|
<Icon
|
|
name="mdi:microsoft-excel"
|
|
:size="20"
|
|
/>
|
|
</span>
|
|
<span>Xuất Excel</span>
|
|
</button>
|
|
</div>
|
|
<div
|
|
class="fixed-grid has-12-cols"
|
|
v-if="msgInfo.length > 0"
|
|
>
|
|
<div class="grid">
|
|
<div class="cell is-col-span-9">
|
|
<div class="notification fs-14">
|
|
<button
|
|
class="delete"
|
|
@click="msgInfo = []"
|
|
></button>
|
|
<div
|
|
class="content"
|
|
style="max-height: 250px; overflow-y: auto"
|
|
>
|
|
<p
|
|
v-for="(ele, i) in msgInfo"
|
|
:key="i"
|
|
class="mb-2"
|
|
>
|
|
<span class="icon-text">
|
|
<span class="icon">
|
|
<Icon
|
|
:name="classinfo.find((v) => v.type === ele.type).icon"
|
|
:size="18"
|
|
:class="classinfo.find((v) => v.type === ele.type).color"
|
|
/>
|
|
</span>
|
|
<span v-html="ele.message"></span>
|
|
</span>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<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>
|
|
</p>
|
|
</div>
|
|
<div
|
|
v-if="ready"
|
|
class="cell is-col-span-3"
|
|
>
|
|
<div class="buttons">
|
|
<button
|
|
v-if="countNew > 0"
|
|
:class="['button is-success', isloading && 'is-loading']"
|
|
@click="insert"
|
|
>
|
|
Thêm mới ({{ countNew }}/{{ total }})
|
|
</button>
|
|
<button
|
|
v-if="countUdt > 0"
|
|
:class="['button is-primary is-light', isloading && 'is-loading']"
|
|
@click="update"
|
|
>
|
|
Cập nhật ({{ countUdt }}/{{ total }})
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<DataTable
|
|
v-if="pagedata"
|
|
:pagename="pagename"
|
|
/>
|
|
</template>
|
|
|
|
<script>
|
|
export default {
|
|
props: ["code"],
|
|
data() {
|
|
return {
|
|
classinfo: [
|
|
{
|
|
type: "success",
|
|
icon: "material-symbols:check-rounded",
|
|
color: "has-text-success",
|
|
},
|
|
{
|
|
type: "error",
|
|
icon: "material-symbols:cancel-rounded",
|
|
color: "has-text-danger",
|
|
},
|
|
{
|
|
type: "warning",
|
|
icon: "material-symbols:warning-rounded",
|
|
color: "has-text-warning",
|
|
},
|
|
{
|
|
type: "waiting",
|
|
icon: "material-symbols:hourglass-top-rounded",
|
|
color: "has-text-primary",
|
|
},
|
|
],
|
|
msgInfo: [],
|
|
isloading: false,
|
|
datafile: undefined,
|
|
requireFields: [],
|
|
pagename: "importdata",
|
|
file: undefined,
|
|
ready: false,
|
|
data: [],
|
|
setting: undefined,
|
|
countUdt: undefined,
|
|
countNew: undefined,
|
|
total: undefined,
|
|
fileInfo: undefined,
|
|
fileLog: undefined,
|
|
result: undefined,
|
|
};
|
|
},
|
|
async created() {
|
|
this.setting = await this.$getdata("importsetting", { code: this.code }, undefined, true);
|
|
this.requireFields = this.setting.detail;
|
|
},
|
|
beforeUnmount() {
|
|
this.pagedata = undefined;
|
|
},
|
|
computed: {
|
|
pagedata: {
|
|
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");
|
|
},
|
|
async inputFile() {
|
|
this.isloading = true;
|
|
this.msgInfo = [];
|
|
const files = document.getElementById("divfile").files;
|
|
if (files.length === 0) return;
|
|
|
|
const file = files.item(0);
|
|
let thefile = this.$upload(file, "file", 1);
|
|
|
|
if (thefile.error) {
|
|
this.$snackbar(thefile.text, undefined, "Error");
|
|
return (this.isloading = false);
|
|
}
|
|
if (!(thefile.name.search(".xls") > 0 || thefile.name.search(".xlsx") > 0)) {
|
|
const message = this.$find(
|
|
this.$store.syspara,
|
|
{ category: "inform", classify: "import", code: "file-type-invalid" },
|
|
"value",
|
|
);
|
|
this.msgInfo.push({ message, type: "error" });
|
|
return (this.isloading = false);
|
|
}
|
|
let result = await this.$insertapi("upload", thefile.form, undefined, false);
|
|
if (result === "error") {
|
|
const message = this.$find(
|
|
this.$store.syspara,
|
|
{ category: "inform", classify: "import", code: "file-upload-fail" },
|
|
"value",
|
|
);
|
|
this.msgInfo.push({ message, type: "error" });
|
|
} else {
|
|
this.fileLog = result;
|
|
this.fileInfo = thefile;
|
|
this.fillData();
|
|
}
|
|
this.isloading = false;
|
|
|
|
// Clear file
|
|
document.getElementById("divfile").value = "";
|
|
},
|
|
async fillData() {
|
|
const readExcelApi = this.$findapi("readexcel");
|
|
readExcelApi.params = { name: this.fileInfo.filename };
|
|
const result = await this.$getapi([readExcelApi]);
|
|
this.datafile = JSON.parse(result[0].data);
|
|
const fields = [];
|
|
|
|
this.datafile.schema.fields.forEach((ele) => {
|
|
let field = this.$createField(ele.name, ele.name, "string", true);
|
|
if (field.name !== "index") fields.push(field);
|
|
});
|
|
|
|
const copy = this.pagedata ? this.$copy(this.pagedata) : this.$getpage();
|
|
const data = this.datafile.data;
|
|
// data.map((v) => (v.updater = this.$store.login.id));
|
|
data.map((v) => (v.updater = 1));
|
|
copy.update = { fields, data };
|
|
copy.data = data;
|
|
copy.fields = fields;
|
|
this.pagedata = copy;
|
|
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);
|
|
});
|
|
|
|
if (misslist.length > 0) {
|
|
const message = `Thiếu các cột bắt buộc: ${misslist.join(", ")}`;
|
|
this.msgInfo.push({ message, type: "error" });
|
|
} else {
|
|
this.validateFrontend();
|
|
}
|
|
},
|
|
validateFrontend() {
|
|
let self = this;
|
|
const checkValid = function (name, obj, data) {
|
|
let error = false;
|
|
if (obj.empty === "no" || !self.$empty(obj.api)) {
|
|
data.map((x) => {
|
|
if (self.$empty(x[name])) {
|
|
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;
|
|
else {
|
|
if (self.$isNumber(x[name])) x[name] = self.$formatNumber(x[name]);
|
|
else {
|
|
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;
|
|
else {
|
|
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;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
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);
|
|
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",
|
|
});
|
|
} else {
|
|
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);
|
|
}
|
|
},
|
|
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",
|
|
});
|
|
const field = this.$createField("error", "error", "string", true, true);
|
|
field.color = "red";
|
|
let fields = this.$copy(this.pagedata.fields);
|
|
fields = [field].concat(fields);
|
|
const copy = this.$clone(this.pagedata);
|
|
copy.data = data;
|
|
copy.fields = fields;
|
|
copy.update = { data, 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",
|
|
});
|
|
|
|
const keys = [];
|
|
const related = [];
|
|
|
|
// 1. Tạo dữ liệu gửi lên: Dùng chính tên cột Excel để chứa giá trị
|
|
const mappedRows = rows.map((row) => {
|
|
const newRow = { ...row };
|
|
for (const [excelColName, config] of Object.entries(this.requireFields)) {
|
|
if (excelColName in row) {
|
|
newRow[excelColName] = row[excelColName];
|
|
}
|
|
}
|
|
return newRow;
|
|
});
|
|
|
|
// 2. Cấu hình Mapping cho Backend
|
|
for (const [excelColName, value] of Object.entries(this.requireFields)) {
|
|
if (value.key === "yes") {
|
|
keys.push({
|
|
key: excelColName,
|
|
value: value,
|
|
});
|
|
}
|
|
// Nếu là trường Lookup
|
|
if (value.api) {
|
|
related.push({
|
|
key: excelColName,
|
|
value: value,
|
|
});
|
|
}
|
|
}
|
|
|
|
// this.setting.api is 'Product_Variant'
|
|
const conn = this.$findapi(this.setting.api);
|
|
const payload = {
|
|
name: conn.url.replace("data/", "").replace(/\//g, ""),
|
|
data: mappedRows,
|
|
keys,
|
|
related,
|
|
};
|
|
|
|
try {
|
|
const rs = await this.$insertapi("findkey", payload, undefined, false);
|
|
if (rs === "error") throw new Error();
|
|
else {
|
|
this.data = rs.data;
|
|
this.total = this.data.length;
|
|
|
|
// ✅ KIỂM TRA CẤU HÌNH UPDATE
|
|
this.checkUpdatePermission();
|
|
|
|
// 1. Lọc ra danh sách các lỗi
|
|
const errors = this.data
|
|
.map((v, i) => (v.error ? { line: i + 1, msg: v.error } : 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.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) => {
|
|
this.msgInfo.push({
|
|
message: `Dòng ${err.line}: ${err.msg}`,
|
|
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",
|
|
});
|
|
} 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",
|
|
});
|
|
}
|
|
|
|
this.showStatus();
|
|
}
|
|
} catch (e) {
|
|
console.error(e);
|
|
this.isloading = false;
|
|
this.msgInfo.push({
|
|
message: `Lỗi hệ thống khi kiểm tra: ${e.message}`,
|
|
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") {
|
|
noUpdateFields.push({
|
|
name: excelColName,
|
|
field: config.field || excelColName,
|
|
});
|
|
}
|
|
});
|
|
|
|
// Nếu không có trường nào cấm update thì bỏ qua
|
|
if (noUpdateFields.length === 0) {
|
|
return;
|
|
}
|
|
|
|
// Kiểm tra từng bản ghi
|
|
this.data.forEach((row, index) => {
|
|
// Nếu bản ghi đã tồn tại (có id) và chưa có lỗi
|
|
if (row.id && !row.error) {
|
|
// Lấy danh sách các trường key bị trùng
|
|
let duplicatedFields = [];
|
|
|
|
noUpdateFields.forEach((field) => {
|
|
// Kiểm tra xem trường này có giá trị không
|
|
if (row[field.name]) {
|
|
duplicatedFields.push(field.name);
|
|
}
|
|
});
|
|
|
|
// 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.`;
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
showStatus() {
|
|
const field = this.$createField("record_status", "Status", "string", true, true);
|
|
field.color = "blue";
|
|
const fields = this.$copy(this.pagedata.fields);
|
|
fields.push(field);
|
|
this.$store.commit("updateState", {
|
|
name: this.pagename,
|
|
key: "fields",
|
|
data: fields,
|
|
});
|
|
const data = this.$copy(this.pagedata.data);
|
|
data.forEach((v, i) => (v.record_status = this.data[i].id ? "Tồn tại" : "Mới"));
|
|
this.$store.commit("updateState", {
|
|
name: this.pagename,
|
|
key: "data",
|
|
data,
|
|
});
|
|
const 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) => {
|
|
let newRow = {};
|
|
|
|
Object.entries(this.requireFields).forEach(([excelColName, config]) => {
|
|
let dbFieldName = config.field || config.field;
|
|
if (dbFieldName && row[excelColName] !== undefined) {
|
|
newRow[dbFieldName] = row[excelColName];
|
|
}
|
|
});
|
|
|
|
Object.keys(row).forEach((key) => {
|
|
if (key !== "index" && !this.requireFields[key]) {
|
|
newRow[key] = row[key];
|
|
}
|
|
});
|
|
|
|
return newRow;
|
|
});
|
|
},
|
|
|
|
async insert() {
|
|
this.isloading = true;
|
|
this.result = undefined;
|
|
const conn = this.$findapi(this.setting.api);
|
|
const importLogPayload = {
|
|
code: this.$id(),
|
|
model: conn.url.replace("data/", "").replace("/", ""),
|
|
file: this.fileInfo.name,
|
|
fields: this.setting.detail,
|
|
};
|
|
await this.$insertapi("importlog", importLogPayload);
|
|
const interval = setInterval(() => this.getResult(importLogPayload.code), 2000);
|
|
|
|
// Lọc bản ghi KHÔNG có id (bản ghi mới)
|
|
let filter = this.data.filter((v) => !v.id && !v.error);
|
|
|
|
// 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;
|
|
|
|
// Gửi lên API - Backend sẽ INSERT
|
|
let result;
|
|
if (this.setting.call_api) {
|
|
result = await this.$insertapi(
|
|
this.setting.call_api,
|
|
{ data: filter, user: this.$store.login.id },
|
|
undefined,
|
|
importLogPayload.code,
|
|
);
|
|
} else {
|
|
result = await this.$insertapi(this.setting.api, filter, undefined, importLogPayload.code);
|
|
}
|
|
|
|
this.isloading = false;
|
|
clearInterval(interval);
|
|
this.getResult(importLogPayload.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;
|
|
}
|
|
|
|
let hasError = false;
|
|
const errorMessages = [];
|
|
|
|
// Kiểm tra entries có lỗi không
|
|
if (result.entries && Array.isArray(result.entries)) {
|
|
result.entries.forEach((entry) => {
|
|
if (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 (hasError) {
|
|
// Hiển thị lỗi chi tiết
|
|
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",
|
|
});
|
|
} 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`;
|
|
|
|
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);
|
|
}
|
|
|
|
// Luôn cập nhật count về undefined để nút biến mất
|
|
this.countNew = undefined;
|
|
this.countUdt = undefined;
|
|
},
|
|
|
|
async update() {
|
|
this.isloading = true;
|
|
this.result = undefined;
|
|
const conn = this.$findapi(this.setting.api);
|
|
const importlogPayload = {
|
|
code: this.$id(),
|
|
model: conn.url.replace("data/", "").replace("/", ""),
|
|
file: this.fileInfo.name,
|
|
fields: this.setting.detail,
|
|
};
|
|
await this.$insertapi("importlog", importlogPayload);
|
|
const interval = setInterval(() => this.getResult(importlogPayload.code), 2000);
|
|
|
|
// Lọc bản ghi CÓ id (bản ghi tồn tại) VÀ KHÔNG CÓ LỖI
|
|
let filter = this.data.filter((v) => v.id && !v.error);
|
|
|
|
// 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;
|
|
|
|
// Gửi lên API - Backend sẽ UPDATE
|
|
const result = await this.$insertapi(
|
|
this.setting.call_api || this.setting.api,
|
|
filter,
|
|
undefined,
|
|
importlogPayload.code,
|
|
);
|
|
this.isloading = false;
|
|
clearInterval(interval);
|
|
this.getResult(importlogPayload.code);
|
|
|
|
if (result === "error") {
|
|
return this.msgInfo.push({ message: "Có lỗi xảy ra", type: "error" });
|
|
}
|
|
|
|
let hasError = false;
|
|
const errorMessages = [];
|
|
|
|
// Kiểm tra có lỗi trong result không
|
|
if (Array.isArray(result)) {
|
|
result.forEach((item) => {
|
|
if (item.error) {
|
|
hasError = true;
|
|
errorMessages.push(item.error || JSON.stringify(item.note));
|
|
}
|
|
});
|
|
}
|
|
|
|
if (hasError) {
|
|
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" });
|
|
}
|
|
},
|
|
|
|
async getResult(logcode) {
|
|
const found = this.$findapi("importlog");
|
|
found.params.filter = { code: logcode };
|
|
const rs = await this.$getapi([found]);
|
|
this.result = rs[0].data.rows[0];
|
|
},
|
|
|
|
exportExcel() {
|
|
const dataType = {};
|
|
this.pagedata.fields.map((v) => (dataType[v.label.indexOf(">") >= 0 ? v.name : v.label] = "String"));
|
|
const data = this.pagedata.dataFilter || this.pagedata.data;
|
|
this.$exportExcel(
|
|
data,
|
|
"data-export",
|
|
this.pagedata.fields.map((v) => v.name),
|
|
dataType,
|
|
);
|
|
},
|
|
},
|
|
};
|
|
</script>
|