@fui-org/fui-cli 2.1.4 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -7
- package/dist/fui-qrb1j9jz.js +397 -0
- package/dist/fui.js +1 -1
- package/package.json +1 -1
- package/skills/fui/SKILL.md +143 -38
- package/skills/fui/agents/fui-builder.md +62 -0
- package/skills/fui/agents/fui-tester.md +77 -0
- package/skills/{fui-skill → fui}/assets/projectdefaultstyle.css +489 -489
- package/skills/{fui-skill → fui}/examples/component.vue +162 -162
- package/skills/{fui-skill → fui}/references/INDEX.md +5 -4
- package/skills/{fui-skill → fui}/references/advanced-techniques.md +170 -168
- package/skills/{fui-skill → fui}/references/component-design.md +2 -2
- package/skills/{fui-skill → fui}/references/component-quickref.md +78 -78
- package/skills/{fui-skill → fui}/references/component-table.md +287 -287
- package/skills/{fui-skill → fui}/references/components-dialog.md +191 -191
- package/skills/{fui-skill → fui}/references/components-display.md +141 -141
- package/skills/{fui-skill → fui}/references/components-echart.md +316 -316
- package/skills/{fui-skill → fui}/references/components-input.md +335 -335
- package/skills/{fui-skill → fui}/references/controls-patterns.md +2 -2
- package/skills/{fui-skill → fui}/references/controls-styling-vocabulary.md +173 -173
- package/skills/{fui-skill → fui}/references/db-workflow.md +9 -9
- package/skills/{fui-skill → fui}/references/default-function.md +456 -456
- package/skills/{fui-skill → fui}/references/design-modes.md +57 -57
- package/skills/{fui-skill → fui}/references/echart-templates.md +489 -489
- package/skills/{fui-skill → fui}/references/fastproject.md +99 -99
- package/skills/{fui-skill → fui}/references/fullstack-workflow.md +314 -313
- package/skills/{fui-skill → fui}/references/module-data-patterns.md +117 -117
- package/skills/{fui-skill → fui}/references/module-json-anatomy.md +132 -132
- package/skills/{fui-skill → fui}/references/module-structure.md +144 -144
- package/skills/{fui-skill → fui}/references/new-session.md +1 -2
- package/skills/{fui-skill → fui}/references/pdfmake.md +60 -60
- package/skills/{fui-skill → fui}/references/permission-system.md +1 -1
- package/skills/{fui-skill → fui}/references/platform-architecture.md +269 -269
- package/skills/{fui-skill → fui}/references/project-config.md +303 -303
- package/skills/{fui-skill → fui}/references/project-provisioning.md +4 -4
- package/skills/{fui-skill → fui}/references/script-map.md +262 -262
- package/skills/{fui-skill → fui}/references/sql-clr-functions.md +225 -225
- package/skills/{fui-skill → fui}/references/system-design.md +2 -2
- package/skills/{fui-skill → fui}/references/tapi-permission-patterns.md +156 -156
- package/skills/{fui-skill → fui}/references/tapi-reference.md +474 -474
- package/skills/fui/references/team-run.md +141 -0
- package/skills/fui/references/tools-registry.md +211 -0
- package/skills/{fui-skill → fui}/references/ui-crosswindow-patterns.md +321 -321
- package/skills/{fui-skill → fui}/references/ui-dialog-patterns.md +255 -255
- package/skills/{fui-skill → fui}/references/ui-layout-patterns.md +176 -176
- package/skills/{fui-skill → fui}/references/ui-patterns.md +303 -303
- package/skills/{fui-skill → fui}/references/ui-screenshot-review.md +95 -95
- package/skills/{fui-skill → fui}/references/ui-table-cell-patterns.md +318 -318
- package/skills/{fui-skill → fui}/references/verification.md +11 -11
- package/skills/{fui-skill → fui}/references/watcher-patterns.md +163 -163
- package/skills/{fui-skill → fui}/references/websocket-realtime.md +271 -271
- package/skills/{fui-skill → fui}/scripts/component-3.0.js +2715 -2715
- package/skills/{fui-skill → fui}/scripts/component.js +3147 -3147
- package/skills/{fui-skill → fui}/scripts/componentTable-3.0.js +1038 -1038
- package/skills/{fui-skill → fui}/scripts/componentTable.js +891 -891
- package/skills/{fui-skill → fui}/scripts/defaultfunction-3.0.js +866 -866
- package/skills/{fui-skill → fui}/scripts/defaultfunction.js +1051 -1051
- package/skills/{fui-skill → fui}/scripts/fastproject-3.0.js +870 -870
- package/skills/{fui-skill → fui}/scripts/fastproject.js +828 -828
- package/dist/fui-h4z7zhk2.js +0 -381
- package/skills/fui-skill/SKILL.md +0 -139
- package/skills/fui-skill/references/tools-registry.md +0 -84
- /package/skills/{fui-skill → fui}/assets/projectdefaultstyle-3.0.css +0 -0
- /package/skills/{fui-skill → fui}/design-md/airbnb/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/airbnb/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/airtable/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/airtable/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/apple/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/apple/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/asu/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/asu/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/binance/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/binance/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/bmw/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/bmw/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/bmw-m/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/bmw-m/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/bugatti/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/bugatti/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/cal/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/cal/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/claude/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/claude/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/clay/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/clay/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/clickhouse/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/clickhouse/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/cohere/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/cohere/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/coinbase/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/coinbase/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/composio/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/composio/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/cursor/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/cursor/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/elevenlabs/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/elevenlabs/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/expo/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/expo/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/ferrari/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/ferrari/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/figma/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/figma/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/framer/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/framer/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/fui/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/hashicorp/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/hashicorp/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/ibm/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/ibm/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/intercom/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/intercom/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/kraken/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/kraken/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/lamborghini/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/lamborghini/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/linear.app/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/linear.app/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/lovable/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/lovable/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/mastercard/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/mastercard/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/meta/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/meta/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/minimax/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/minimax/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/mintlify/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/mintlify/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/miro/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/miro/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/mistral.ai/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/mistral.ai/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/mongodb/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/mongodb/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/nike/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/nike/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/notion/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/notion/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/nvidia/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/nvidia/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/ollama/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/ollama/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/opencode.ai/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/opencode.ai/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/pinterest/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/pinterest/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/playstation/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/playstation/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/posthog/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/posthog/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/raycast/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/raycast/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/renault/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/renault/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/replicate/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/replicate/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/resend/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/resend/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/revolut/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/revolut/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/runwayml/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/runwayml/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/sanity/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/sanity/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/sentry/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/sentry/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/shopify/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/shopify/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/slack/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/spacex/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/spacex/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/spotify/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/spotify/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/starbucks/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/starbucks/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/stripe/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/stripe/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/supabase/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/supabase/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/superhuman/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/superhuman/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/tesla/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/tesla/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/theverge/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/theverge/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/together.ai/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/together.ai/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/uber/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/uber/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/vercel/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/vercel/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/vodafone/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/vodafone/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/voltagent/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/voltagent/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/warp/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/warp/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/webflow/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/webflow/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/wired/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/wired/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/wise/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/wise/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/x.ai/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/x.ai/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/zapier/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/zapier/README.md +0 -0
- /package/skills/{fui-skill → fui}/examples/f-table-patterns.json +0 -0
- /package/skills/{fui-skill → fui}/examples/module-patterns.json +0 -0
- /package/skills/{fui-skill → fui}/examples/project-patterns.json +0 -0
- /package/skills/{fui-skill → fui}/references/coding-standards.md +0 -0
- /package/skills/{fui-skill → fui}/references/db-table-design.md +0 -0
- /package/skills/{fui-skill → fui}/references/fsheet.md +0 -0
- /package/skills/{fui-skill → fui}/references/tapi-file-api.md +0 -0
- /package/skills/{fui-skill → fui}/references/ui-templates.md +0 -0
- /package/skills/{fui-skill → fui}/scripts/fechart.js +0 -0
- /package/skills/{fui-skill → fui}/scripts/fsheet.js +0 -0
|
@@ -1,1051 +1,1051 @@
|
|
|
1
|
-
$isMobile = navigator.userAgent.match(/mobile|touch|android|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|ip(ad|hone|od)|iris|kindle|lge |maemo|meego.+mobile|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino|playbook|silk/i) != null;
|
|
2
|
-
$isPhone = navigator.userAgent.match(/mobile|touch|android|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|ip(hone|od)|iris|kindle|lge |maemo|meego.+mobile|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (phone)|xda|xiino|playbook|silk/i) != null;
|
|
3
|
-
$isApple = navigator.userAgent.match(/ip(ad|hone|od)/i) != null;
|
|
4
|
-
//===========================================================================
|
|
5
|
-
var Loader = function () { }
|
|
6
|
-
Loader.prototype = {
|
|
7
|
-
require: function (scripts, callback) {
|
|
8
|
-
this.loadCount = 0;
|
|
9
|
-
this.totalRequired = scripts.length;
|
|
10
|
-
this.callback = callback;
|
|
11
|
-
|
|
12
|
-
for (var i = 0; i < scripts.length; i++) {
|
|
13
|
-
this.writeScript(scripts[i]);
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
loaded: function (evt) {
|
|
17
|
-
this.loadCount++;
|
|
18
|
-
|
|
19
|
-
if (this.loadCount == this.totalRequired && typeof this.callback == 'function') this.callback.call();
|
|
20
|
-
},
|
|
21
|
-
writeScript: function (src) {
|
|
22
|
-
var self = this;
|
|
23
|
-
var s = document.createElement('script');
|
|
24
|
-
s.type = "text/javascript";
|
|
25
|
-
s.async = false;
|
|
26
|
-
s.src = src;
|
|
27
|
-
s.addEventListener('load', function (e) { self.loaded(e); }, false);
|
|
28
|
-
var head = document.getElementsByTagName('head')[0];
|
|
29
|
-
head.appendChild(s);
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
//===================================================================================
|
|
33
|
-
//===================================================================================
|
|
34
|
-
//===========================================================================================
|
|
35
|
-
function extractHostname(url) {
|
|
36
|
-
var hostname;
|
|
37
|
-
//find & remove protocol (http, ftp, etc.) and get hostname
|
|
38
|
-
|
|
39
|
-
if (url.indexOf("//") > -1) {
|
|
40
|
-
hostname = url.split('/')[2];
|
|
41
|
-
} else {
|
|
42
|
-
hostname = url.split('/')[0];
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
//find & remove port number
|
|
46
|
-
hostname = hostname.split(':')[0];
|
|
47
|
-
//find & remove "?"
|
|
48
|
-
hostname = hostname.split('?')[0];
|
|
49
|
-
|
|
50
|
-
return hostname;
|
|
51
|
-
}
|
|
52
|
-
//===========================================================================================
|
|
53
|
-
const getDomainWithoutSubdomain = url => {
|
|
54
|
-
const urlParts = new URL(url).hostname.split('.')
|
|
55
|
-
|
|
56
|
-
return urlParts
|
|
57
|
-
.slice(0)
|
|
58
|
-
.slice(-(urlParts.length === 4 ? 3 : 2))
|
|
59
|
-
.join('.')
|
|
60
|
-
}
|
|
61
|
-
//===========================================================================================
|
|
62
|
-
function loadScripts(scriptsArray, callbackFunc) {
|
|
63
|
-
var scriptsArray = scriptsArray || new Array();
|
|
64
|
-
var callbackFunc = callbackFunc || function () { };
|
|
65
|
-
|
|
66
|
-
if (scriptsArray.length == 0) {
|
|
67
|
-
callbackFunc();
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
var loadScript = new Loader();
|
|
72
|
-
loadScript.require(scriptsArray, callbackFunc);
|
|
73
|
-
}
|
|
74
|
-
//===================================================================================
|
|
75
|
-
function pushRouter(router) {
|
|
76
|
-
var curPath = window.location.pathname;
|
|
77
|
-
if (curPath != router || curPath == '/') {
|
|
78
|
-
window.history.pushState({ id: router }, document.title, router);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
//===================================================================================
|
|
82
|
-
function CALL(obj, includeData) {
|
|
83
|
-
runAction(obj, includeData);
|
|
84
|
-
}
|
|
85
|
-
//===================================================================================
|
|
86
|
-
function windowSendMessage(window, cmd, data) {
|
|
87
|
-
//console.log("windowSendMessage", cmd, data);
|
|
88
|
-
if (_.isObject(window)) {
|
|
89
|
-
window.postMessage({ cmd: cmd, value: JSON.parse(JSON.stringify(data)) }, '*');
|
|
90
|
-
} else
|
|
91
|
-
if (window == "#PARENT") {
|
|
92
|
-
parent.postMessage({ cmd: cmd, value: JSON.parse(JSON.stringify(data)) }, '*');
|
|
93
|
-
} else {
|
|
94
|
-
var win = document.getElementById(window.slice(1, window.length));
|
|
95
|
-
if (win) win.contentWindow.postMessage({ cmd: cmd, value: JSON.parse(JSON.stringify(data)) }, '*');
|
|
96
|
-
else console.log("send DATA: Window ID not found")
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
//===========================================================================================
|
|
100
|
-
function appCommand(cmdData) {
|
|
101
|
-
if (window.ReactNativeWebView) window.ReactNativeWebView.postMessage(cmdData);
|
|
102
|
-
else if (window.webkit) window.webkit.messageHandlers["scriptHandler"].postMessage(cmdData);
|
|
103
|
-
else if (window.scriptHandler) window.scriptHandler.postMessage(cmdData);
|
|
104
|
-
}
|
|
105
|
-
//===========================================================================================
|
|
106
|
-
function buildHeader(obj, mapCol) {
|
|
107
|
-
var datatable = {};
|
|
108
|
-
var header = [];
|
|
109
|
-
|
|
110
|
-
if (mapCol) {
|
|
111
|
-
_.forEach(obj, function (o) {
|
|
112
|
-
header.push({ "text": o[mapCol.text], "value": o[mapCol.value], "divider": true });
|
|
113
|
-
});
|
|
114
|
-
} else {
|
|
115
|
-
if (obj && _.isArray(obj)) datatable = obj[0];
|
|
116
|
-
else if (obj && _.isArray(obj.data)) datatable = obj.data[0];
|
|
117
|
-
|
|
118
|
-
if (datatable) {
|
|
119
|
-
_.forEach(datatable, function (value, key) {
|
|
120
|
-
if (_.isNumber(value)) header.push({ "el": "t-num", "text": key, "value": key, "divider": true });
|
|
121
|
-
else header.push({ "text": key, "value": key, "divider": true });
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
console.log("Build Header: ", header)
|
|
127
|
-
return header;
|
|
128
|
-
}
|
|
129
|
-
//===========================================================================================
|
|
130
|
-
function fillData(obj) {
|
|
131
|
-
if (!obj.key && !obj.src) { // FILL ALL DATA
|
|
132
|
-
var mapD = {};
|
|
133
|
-
_.forEach(obj.map, function (value, key) {
|
|
134
|
-
mapD[key] = bindData(value);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
_.forEach(obj.des, function (desObj) {
|
|
138
|
-
_.forEach(mapD, function (value, key) {
|
|
139
|
-
desObj[key] = value;
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
return
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
var GroupKeySRC = _.groupBy(obj.src, obj.key);
|
|
146
|
-
_.forEach(obj.map, function (value, key) {
|
|
147
|
-
_.forEach(obj.des, function (desObj) {
|
|
148
|
-
var val = GroupKeySRC[desObj[obj.key]];
|
|
149
|
-
if (_.isString(value)) {
|
|
150
|
-
if (_.isArray(val)) val = val[0][value];
|
|
151
|
-
else val = null;
|
|
152
|
-
}
|
|
153
|
-
else if (_.isPlainObject(value)) {
|
|
154
|
-
if (_.isArray(val)) val = val[0];
|
|
155
|
-
else val = null;
|
|
156
|
-
}
|
|
157
|
-
desObj[key] = val;
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
//===========================================================================================
|
|
162
|
-
function groupBy(arrayObj, groupField, sumArr) { // vi du: groupBy(dsSinhVien, 'Khoa', ['SL Nghỉ Học','SL Tốt Nghiệp'])
|
|
163
|
-
var gArr = _.groupBy(arrayObj, groupField);
|
|
164
|
-
var returnArr = [];
|
|
165
|
-
var sumObj = {};
|
|
166
|
-
if (!_.isArray(sumArr)) sumArr = [sumArr];
|
|
167
|
-
//---------------------------------
|
|
168
|
-
_.forEach(gArr, function (fieldValue, fieldKey) {
|
|
169
|
-
var n = 0;
|
|
170
|
-
_.forEach(sumArr, function (v) {
|
|
171
|
-
sumObj[v] = 0;
|
|
172
|
-
});
|
|
173
|
-
_.forEach(fieldValue, function (value) {
|
|
174
|
-
n++;
|
|
175
|
-
_.forEach(sumArr, function (v) {
|
|
176
|
-
sumObj[v] = sumObj[v] + value[v];
|
|
177
|
-
});
|
|
178
|
-
});
|
|
179
|
-
var o = {};
|
|
180
|
-
o[groupField] = fieldKey;
|
|
181
|
-
o["count"] = n;
|
|
182
|
-
o = $.extend(o, sumObj);
|
|
183
|
-
returnArr.push(o);
|
|
184
|
-
});
|
|
185
|
-
return returnArr;
|
|
186
|
-
}
|
|
187
|
-
//===========================================================================================
|
|
188
|
-
function chartDataBuild(obj) {
|
|
189
|
-
if (obj.dataChart) {
|
|
190
|
-
var chartData = [];
|
|
191
|
-
var dataInput = obj.dataChart.data;
|
|
192
|
-
|
|
193
|
-
if (_.isUndefined(dataInput)) dataInput = obj.dataChart;
|
|
194
|
-
|
|
195
|
-
if (_.isArray(dataInput[0])) {
|
|
196
|
-
var chartName = JSON.parse(obj.dataChart.chartName);
|
|
197
|
-
_.forEach(dataInput, function (d, k) {
|
|
198
|
-
chartData.push({
|
|
199
|
-
name: chartName[k],
|
|
200
|
-
data: _.map(dataInput[k], function (n) {
|
|
201
|
-
return [n.label, n.value];
|
|
202
|
-
})
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
|
-
} else {
|
|
206
|
-
chartData.push({
|
|
207
|
-
name: (obj.dataChart.chartName ? obj.dataChart.chartName : null),
|
|
208
|
-
data: _.map(dataInput, function (n) {
|
|
209
|
-
return [n.label, n.value];
|
|
210
|
-
})
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
//---------------------
|
|
214
|
-
if (obj.allInOne) return [{ name: "", data: chartData }];
|
|
215
|
-
else return chartData;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
//===========================================================================================
|
|
219
|
-
function chartQABuild(obj) {
|
|
220
|
-
if (obj.dataChart) {
|
|
221
|
-
var chartData = [];
|
|
222
|
-
|
|
223
|
-
_.forEach(obj.dataChart[0], function (v, i) { // Duyet tung cau hoi
|
|
224
|
-
if (v.QType == 'TextArea') {
|
|
225
|
-
|
|
226
|
-
} else {
|
|
227
|
-
chartData.push({
|
|
228
|
-
name: v.QText,
|
|
229
|
-
data: _.map(_.filter(obj.dataChart[1], ['QID', v.QID]), function (n) {
|
|
230
|
-
return [n.label, n.val];
|
|
231
|
-
})
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
});
|
|
235
|
-
return chartData;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
//===========================================================================================
|
|
239
|
-
function confirm(obj) {
|
|
240
|
-
$.confirm({
|
|
241
|
-
icon: (obj.icon ? obj.icon : 'mdi mdi-help-circle-outline'),
|
|
242
|
-
title: obj.title,
|
|
243
|
-
content: (obj.message ? obj.message : ''),
|
|
244
|
-
type: (obj.type ? obj.type : 'blue'),
|
|
245
|
-
columnClass: 'col-xs-12 col-sm-8 col-md-6 col-lg-4',
|
|
246
|
-
animateFromElement: false,
|
|
247
|
-
buttons: {
|
|
248
|
-
ok: {
|
|
249
|
-
text: ' OK ',
|
|
250
|
-
btnClass: 'btn-blue',
|
|
251
|
-
keys: ['enter'],
|
|
252
|
-
action: obj.action
|
|
253
|
-
},
|
|
254
|
-
cancel: (obj.cancel ? obj.cancel : function () { })
|
|
255
|
-
}
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
//===========================================================================================
|
|
259
|
-
function showMessage(obj) {
|
|
260
|
-
$.dialog({
|
|
261
|
-
icon: (obj.icon ? obj.icon : 'mdi mdi-information-variant-circle-outline'),
|
|
262
|
-
title: (obj.title ? obj.title : ''),
|
|
263
|
-
backgroundDismiss: true,
|
|
264
|
-
content: (obj.message ? obj.message : ''),
|
|
265
|
-
type: (obj.type ? obj.type : 'blue'),
|
|
266
|
-
columnClass: 'col-xs-12 col-sm-8 col-md-6 col-lg-4',
|
|
267
|
-
animateFromElement: false,
|
|
268
|
-
closeIcon: true,
|
|
269
|
-
buttons: {
|
|
270
|
-
ok: {
|
|
271
|
-
text: ' Close ',
|
|
272
|
-
btnClass: 'btn-' + (obj.type ? obj.type : 'blue')
|
|
273
|
-
}
|
|
274
|
-
},
|
|
275
|
-
onClose: function () {
|
|
276
|
-
if (obj.onclose) {
|
|
277
|
-
runAction(obj.onclose, { $row: obj.$row, item: obj.item });
|
|
278
|
-
}
|
|
279
|
-
},
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
//===========================================================================================
|
|
283
|
-
function rightTest(rightObject) {
|
|
284
|
-
var returnVal = true
|
|
285
|
-
_.forEach(rightObject, function (rV, rK) {
|
|
286
|
-
if (!returnVal) return;
|
|
287
|
-
var A1 = [];
|
|
288
|
-
var A2 = [];
|
|
289
|
-
|
|
290
|
-
if (_.isArray(rV)) A1 = rV;
|
|
291
|
-
else A1 = [rV];
|
|
292
|
-
|
|
293
|
-
if (_.isArray(vueData.user[rK])) A2 = vueData.user[rK];
|
|
294
|
-
else A2 = [vueData.user[rK]];
|
|
295
|
-
|
|
296
|
-
if (_.intersection(A1, A2).length <= 0) returnVal = false;
|
|
297
|
-
});
|
|
298
|
-
return returnVal;
|
|
299
|
-
}
|
|
300
|
-
//===========================================================================================
|
|
301
|
-
function openWindow(obj) {
|
|
302
|
-
if (window === top) {
|
|
303
|
-
setTimeout(function () {
|
|
304
|
-
if (obj.id != undefined && vueData[obj.id] != undefined) {
|
|
305
|
-
console.log("Cửa sổ bị trùng tên: ", obj.id)
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
if (!vueData.v_windowVM) vueData.v_windowVM = {};
|
|
310
|
-
if (_.isString(obj)) obj = { url: obj };
|
|
311
|
-
|
|
312
|
-
if (!obj.id) obj.id = "Window" + generateID();
|
|
313
|
-
obj.open = true;
|
|
314
|
-
vueData[obj.id] = obj;
|
|
315
|
-
vueData.v_windowVM[obj.id] = new Vue({
|
|
316
|
-
vuetify: new Vuetify(),
|
|
317
|
-
render: Vue.compile("<f-window id=\"" + obj.id + "\"></f-window>").render,
|
|
318
|
-
}).$mount();
|
|
319
|
-
}, 10);
|
|
320
|
-
}
|
|
321
|
-
else {
|
|
322
|
-
windowSendMessage('#PARENT', 'openWindow', obj);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
//===========================================================================================
|
|
326
|
-
function redirect(obj, query) {
|
|
327
|
-
let Q = '';
|
|
328
|
-
if (obj && obj.url) Q = obj.url;
|
|
329
|
-
else Q = obj;
|
|
330
|
-
|
|
331
|
-
if ((query || (obj && obj.query)) && vueData.p_params != " ") {
|
|
332
|
-
if (Q.indexOf("?") < 0) Q = Q + vueData.p_params;
|
|
333
|
-
else Q = Q + vueData.p_params.replace("?", "");
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
location.assign(Q);
|
|
337
|
-
}
|
|
338
|
-
//===========================================================================================
|
|
339
|
-
function reload() {
|
|
340
|
-
location.assign(window.location.href);
|
|
341
|
-
}
|
|
342
|
-
//===========================================================================================
|
|
343
|
-
function findInArray(obj) {
|
|
344
|
-
if (_.isString(obj.value)) obj.value = JSON.parse(obj.value);
|
|
345
|
-
return _.find(obj.array, obj.value);
|
|
346
|
-
}
|
|
347
|
-
//===========================================================================================
|
|
348
|
-
function fixURL(url) {
|
|
349
|
-
if (url.indexOf('http') == 0) return url;
|
|
350
|
-
else return vueData.v_Set.apiDomain + url
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
//===========================================================================================
|
|
354
|
-
function stringAttrToJson(str, removeVueEvent) {
|
|
355
|
-
var jObj = {}
|
|
356
|
-
|
|
357
|
-
if (typeof str == 'undefined') str = "";
|
|
358
|
-
|
|
359
|
-
if (_.isPlainObject(str)) return { ...str };
|
|
360
|
-
|
|
361
|
-
$(`<div ${str} >`).each(function () {
|
|
362
|
-
$.each(this.attributes, function (i, a) {
|
|
363
|
-
if (removeVueEvent && (typeof a.name == "string" && (a.name.substring(0, 1) == ":" || a.name.substring(0, 1) == "@" || a.name.substring(0, 2) == "v-"))) return;
|
|
364
|
-
jObj[a.name] = a.value;
|
|
365
|
-
})
|
|
366
|
-
})
|
|
367
|
-
return jObj
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
//===========================================================================================
|
|
371
|
-
function json_data_parse(obj, level) {
|
|
372
|
-
//--- convert to json data array --------
|
|
373
|
-
if (level > 3) return;
|
|
374
|
-
|
|
375
|
-
level++;
|
|
376
|
-
if (_.isArray(obj) && obj.length > 0) {
|
|
377
|
-
//console.log("json_data_parse", level)
|
|
378
|
-
if (_.isArray(obj[0])) {
|
|
379
|
-
_.forEach(obj, function (value) {
|
|
380
|
-
json_data_parse(value, level);
|
|
381
|
-
});
|
|
382
|
-
} else if (_.isPlainObject(obj[0])) {
|
|
383
|
-
var R = obj[0];
|
|
384
|
-
var nameOfJson = [];
|
|
385
|
-
_.forEach(R, function (value, key) {
|
|
386
|
-
if (key.substring(0, 10) == 'json_data:') nameOfJson.push(key);
|
|
387
|
-
});
|
|
388
|
-
_.forEach(obj, function (row) {
|
|
389
|
-
_.forEach(nameOfJson, function (key) {
|
|
390
|
-
row[key.replace('json_data:', '')] = JSON.parse(row[key]);
|
|
391
|
-
delete row[key];
|
|
392
|
-
});
|
|
393
|
-
});
|
|
394
|
-
}
|
|
395
|
-
} else if (_.isPlainObject(obj)) {
|
|
396
|
-
_.forEach(obj, function (value, key) {
|
|
397
|
-
if (key.substring(0, 10) == 'json_data:') {
|
|
398
|
-
obj[key.replace('json_data:', '')] = JSON.parse(value);
|
|
399
|
-
delete obj[key];
|
|
400
|
-
} else {
|
|
401
|
-
if (_.isObject(value)) json_data_parse(value, level);
|
|
402
|
-
}
|
|
403
|
-
});
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
//===========================================================================================
|
|
407
|
-
function ajaxCALL(URL, DATA, callBack, errorCallBack, header) {
|
|
408
|
-
let loadingID = generateID();
|
|
409
|
-
vueData.v_LoadingOverlay.push(loadingID);
|
|
410
|
-
vueData.v_Loading = true;
|
|
411
|
-
|
|
412
|
-
$.ajax({
|
|
413
|
-
type: "POST",
|
|
414
|
-
headers: (header ? header : {
|
|
415
|
-
authorization: $awt
|
|
416
|
-
}),
|
|
417
|
-
url: fixURL(URL),
|
|
418
|
-
contentType: "application/json; charset=utf-8",
|
|
419
|
-
dataType: "json",
|
|
420
|
-
crossDomain: true,
|
|
421
|
-
data: JSON.stringify(DATA),
|
|
422
|
-
success: function (d) {
|
|
423
|
-
json_data_parse(d, 0);
|
|
424
|
-
if (callBack) callBack(d);
|
|
425
|
-
},
|
|
426
|
-
error: function (xhr, ajaxOptions, thrownError) {
|
|
427
|
-
console.log("API:", URL, "IN:", DATA);
|
|
428
|
-
console.log("Error XHR:", xhr);
|
|
429
|
-
|
|
430
|
-
if (errorCallBack) {
|
|
431
|
-
errorCallBack(xhr);
|
|
432
|
-
return
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
showMessage({
|
|
436
|
-
icon: 'mdi mdi-alert-circle-outline',
|
|
437
|
-
title: '',
|
|
438
|
-
message: (xhr.responseJSON ? xhr.responseJSON.Message : (xhr.responseText ? xhr.responseText : "")),
|
|
439
|
-
type: 'red',
|
|
440
|
-
});
|
|
441
|
-
},
|
|
442
|
-
complete: function (data) {
|
|
443
|
-
vueData.v_LoadingOverlay = _.without(vueData.v_LoadingOverlay, loadingID);
|
|
444
|
-
if (vueData.v_LoadingOverlay.length == 0) vueData.v_Loading = false;
|
|
445
|
-
}
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
//===========================================================================================
|
|
449
|
-
function capacityText(numb) {
|
|
450
|
-
if (numb < 1048576) return Math.round(numb / 1024 * 100 / 100) + ' Kb';
|
|
451
|
-
else return Math.round(numb / 1048576 * 100) / 100 + ' Mb';
|
|
452
|
-
}
|
|
453
|
-
//===========================================================================================
|
|
454
|
-
function generateID() {
|
|
455
|
-
var d = new Date();
|
|
456
|
-
var k = d.getTime();
|
|
457
|
-
var str = k.toString(16).slice(1)
|
|
458
|
-
var UUID = 'idxxxxxxxx'.replace(/[xy]/g, function (c) {
|
|
459
|
-
var r = Math.random() * 16 | 0;
|
|
460
|
-
v = c == 'x' ? r : (r & 3 | 8);
|
|
461
|
-
return v.toString(16);
|
|
462
|
-
});
|
|
463
|
-
var newString = UUID.replace(/[z]/, str)
|
|
464
|
-
return newString;
|
|
465
|
-
}
|
|
466
|
-
//===========================================================================================
|
|
467
|
-
function printPDF(obj) {
|
|
468
|
-
pdfMake.fonts = {
|
|
469
|
-
Roboto: {
|
|
470
|
-
normal: 'Roboto-Regular.ttf',
|
|
471
|
-
bold: 'Roboto-Medium.ttf',
|
|
472
|
-
italics: 'Roboto-Italic.ttf',
|
|
473
|
-
bolditalics: 'Roboto-MediumItalic.ttf'
|
|
474
|
-
},
|
|
475
|
-
TimesNewRoman: {
|
|
476
|
-
normal: 'times.ttf',
|
|
477
|
-
bold: 'timesbd.ttf',
|
|
478
|
-
italics: 'timesi.ttf',
|
|
479
|
-
bolditalics: 'timesbi.ttf'
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
if (obj.download) {
|
|
484
|
-
pdfMake.createPdf(obj.data).download('fp_pdf_' + generateID() + '.pdf');
|
|
485
|
-
} else if (obj.viewer) {
|
|
486
|
-
pdfMake.createPdf(obj.data).getDataUrl(function (outDoc) {
|
|
487
|
-
$(obj.viewer).attr("src", outDoc);
|
|
488
|
-
});
|
|
489
|
-
} else {
|
|
490
|
-
pdfMake.createPdf(obj.data).getBase64(function (outDoc) {
|
|
491
|
-
printJS({ printable: outDoc, type: 'pdf', base64: true });
|
|
492
|
-
});
|
|
493
|
-
}
|
|
494
|
-
//-------------------------------------------------------
|
|
495
|
-
if (_.isFunction(obj.callBack)) obj.callBack();
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
//===========================================================================================
|
|
499
|
-
function colorLib(color) {
|
|
500
|
-
// Hàm hỗ trợ phân tích và chuyển đổi màu
|
|
501
|
-
function parseColor(input) {
|
|
502
|
-
let r, g, b, a = 1;
|
|
503
|
-
|
|
504
|
-
// Xử lý HEX (3 hoặc 6 ký tự)
|
|
505
|
-
if (typeof input === 'string' && input.startsWith('#')) {
|
|
506
|
-
let hex = input.replace('#', '');
|
|
507
|
-
if (hex.length === 3) {
|
|
508
|
-
hex = hex.split('').map(c => c + c).join('');
|
|
509
|
-
}
|
|
510
|
-
r = parseInt(hex.slice(0, 2), 16);
|
|
511
|
-
g = parseInt(hex.slice(2, 4), 16);
|
|
512
|
-
b = parseInt(hex.slice(4, 6), 16);
|
|
513
|
-
return { r, g, b, a };
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// Xử lý RGB/RGBA
|
|
517
|
-
if (typeof input === 'string' && input.startsWith('rgb')) {
|
|
518
|
-
const match = input.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
|
|
519
|
-
if (match) {
|
|
520
|
-
r = parseInt(match[1]);
|
|
521
|
-
g = parseInt(match[2]);
|
|
522
|
-
b = parseInt(match[3]);
|
|
523
|
-
a = match[4] ? parseFloat(match[4]) : 1;
|
|
524
|
-
return { r, g, b, a };
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
// Xử lý HSL
|
|
529
|
-
if (typeof input === 'string' && input.startsWith('hsl')) {
|
|
530
|
-
const match = input.match(/hsla?\((\d+),\s*(\d+)%,\s*(\d+)%(?:,\s*([\d.]+))?\)/);
|
|
531
|
-
if (match) {
|
|
532
|
-
const h = parseInt(match[1]) / 360;
|
|
533
|
-
const s = parseInt(match[2]) / 100;
|
|
534
|
-
const l = parseInt(match[3]) / 100;
|
|
535
|
-
a = match[4] ? parseFloat(match[4]) : 1;
|
|
536
|
-
return hslToRgb(h, s, l, a);
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
// Mặc định trả về màu đen nếu không phân tích được
|
|
541
|
-
return { r: 0, g: 0, b: 0, a: 1 };
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
// Chuyển HSL sang RGB
|
|
545
|
-
function hslToRgb(h, s, l, a) {
|
|
546
|
-
let r, g, b;
|
|
547
|
-
if (s === 0) {
|
|
548
|
-
r = g = b = l; // Grayscale
|
|
549
|
-
} else {
|
|
550
|
-
const hue2rgb = (p, q, t) => {
|
|
551
|
-
if (t < 0) t += 1;
|
|
552
|
-
if (t > 1) t -= 1;
|
|
553
|
-
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
|
554
|
-
if (t < 1 / 2) return q;
|
|
555
|
-
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
|
556
|
-
return p;
|
|
557
|
-
};
|
|
558
|
-
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
559
|
-
const p = 2 * l - q;
|
|
560
|
-
r = hue2rgb(p, q, h + 1 / 3);
|
|
561
|
-
g = hue2rgb(p, q, h);
|
|
562
|
-
b = hue2rgb(p, q, h - 1 / 3);
|
|
563
|
-
}
|
|
564
|
-
return {
|
|
565
|
-
r: Math.round(r * 255),
|
|
566
|
-
g: Math.round(g * 255),
|
|
567
|
-
b: Math.round(b * 255),
|
|
568
|
-
a
|
|
569
|
-
};
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
// Chuyển RGB sang HSL
|
|
573
|
-
function rgbToHsl(r, g, b) {
|
|
574
|
-
r /= 255;
|
|
575
|
-
g /= 255;
|
|
576
|
-
b /= 255;
|
|
577
|
-
const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
|
578
|
-
let h, s, l = (max + min) / 2;
|
|
579
|
-
|
|
580
|
-
if (max === min) {
|
|
581
|
-
h = s = 0; // Grayscale
|
|
582
|
-
} else {
|
|
583
|
-
const d = max - min;
|
|
584
|
-
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
585
|
-
switch (max) {
|
|
586
|
-
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
|
|
587
|
-
case g: h = (b - r) / d + 2; break;
|
|
588
|
-
case b: h = (r - g) / d + 4; break;
|
|
589
|
-
}
|
|
590
|
-
h /= 6;
|
|
591
|
-
}
|
|
592
|
-
return { h, s, l };
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
// Hàm chuyển đổi số thành chuỗi hex 2 chữ số
|
|
596
|
-
function toHex(value) {
|
|
597
|
-
const hex = Math.round(value).toString(16);
|
|
598
|
-
return hex.length === 1 ? '0' + hex : hex;
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
// Phân tích màu đầu vào
|
|
602
|
-
const { r, g, b, a } = parseColor(color);
|
|
603
|
-
|
|
604
|
-
// Đối tượng trả về với các phương thức
|
|
605
|
-
return {
|
|
606
|
-
// Trả về chuỗi HEX
|
|
607
|
-
hexString() {
|
|
608
|
-
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
609
|
-
},
|
|
610
|
-
|
|
611
|
-
// Trả về chuỗi RGB hoặc RGBA
|
|
612
|
-
rgbString() {
|
|
613
|
-
return a < 1 ? `rgba(${r}, ${g}, ${b}, ${a})` : `rgb(${r}, ${g}, ${b})`;
|
|
614
|
-
},
|
|
615
|
-
|
|
616
|
-
// Trả về chuỗi HSL hoặc HSLA
|
|
617
|
-
hslString() {
|
|
618
|
-
const { h, s, l } = rgbToHsl(r, g, b);
|
|
619
|
-
return a < 1
|
|
620
|
-
? `hsla(${Math.round(h * 360)}, ${Math.round(s * 100)}%, ${Math.round(l * 100)}%, ${a})`
|
|
621
|
-
: `hsl(${Math.round(h * 360)}, ${Math.round(s * 100)}%, ${Math.round(l * 100)}%)`;
|
|
622
|
-
},
|
|
623
|
-
|
|
624
|
-
// Làm sáng màu (factor: 0 đến 1)
|
|
625
|
-
lighten(factor) {
|
|
626
|
-
const { h, s, l } = rgbToHsl(r, g, b);
|
|
627
|
-
const newL = Math.min(1, l + l * factor);
|
|
628
|
-
return colorLib(hslToRgb(h, s, newL, a).rgbString());
|
|
629
|
-
},
|
|
630
|
-
|
|
631
|
-
// Làm tối màu (factor: 0 đến 1)
|
|
632
|
-
darken(factor) {
|
|
633
|
-
const { h, s, l } = rgbToHsl(r, g, b);
|
|
634
|
-
const newL = Math.max(0, l - l * factor);
|
|
635
|
-
return colorLib(hslToRgb(h, s, newL, a).rgbString());
|
|
636
|
-
},
|
|
637
|
-
|
|
638
|
-
// Đặt độ trong suốt
|
|
639
|
-
alpha(value) {
|
|
640
|
-
return colorLib(`rgba(${r}, ${g}, ${b}, ${value})`);
|
|
641
|
-
}
|
|
642
|
-
};
|
|
643
|
-
}
|
|
644
|
-
//===========================================================================================
|
|
645
|
-
function transparentize(value, opacity) {
|
|
646
|
-
var alpha = opacity === undefined ? 0.5 : 1 - opacity;
|
|
647
|
-
return colorLib(value).alpha(alpha).rgbString();
|
|
648
|
-
}
|
|
649
|
-
//===========================================================================================
|
|
650
|
-
function jsonToExcel(Obj) {
|
|
651
|
-
if (typeof ExcelJS == 'undefined') {
|
|
652
|
-
var loadScript = new Loader();
|
|
653
|
-
loadScript.require(["/include/excel/exceljs.min.js"], function () {
|
|
654
|
-
writeExcelFile(Obj);
|
|
655
|
-
});
|
|
656
|
-
} else writeExcelFile(Obj);
|
|
657
|
-
function writeExcelFile(inputObj) {
|
|
658
|
-
var props = {
|
|
659
|
-
data: null, // Json to download (for single sheet - backward compatibility)
|
|
660
|
-
filename: "dataFP.xlsx", // filename to export
|
|
661
|
-
worksheet: "SheetFP", // sheet name for single sheet
|
|
662
|
-
sheets: null, // New property for multiple sheets
|
|
663
|
-
// optional: [{header, key, type?: 'number'|'date'|'html', numFmt?, width?, sum?: true|numFmt}]
|
|
664
|
-
// Có columns thì cột/tiêu đề/định dạng theo đúng danh sách này (dùng cho f-table excel="UI");
|
|
665
|
-
// không có thì giữ hành vi cũ: cột = key của dòng đầu, giá trị thô. Mỗi sheet có thể có columns riêng.
|
|
666
|
-
columns: null,
|
|
667
|
-
onProgress: null, // optional: function({percent, processedRows, totalRows, phase, sheetName}) - phase: 'building'|'finalizing'|'done'|'error'
|
|
668
|
-
showProgress: true // tự hiện thanh % cố định giữa màn hình khi xuất; truyền false để tắt UI này
|
|
669
|
-
}
|
|
670
|
-
Object.assign(props, inputObj);
|
|
671
|
-
// Gom về một danh sách sheet chung cho cả 2 chế độ cũ (single) và mới (multi) để dùng chung logic tiến trình/chunk
|
|
672
|
-
var sheetList;
|
|
673
|
-
if (props.sheets && Array.isArray(props.sheets)) {
|
|
674
|
-
if (!props.sheets.length) { showMessage({ title: "Sheets data is empty" }); return; }
|
|
675
|
-
sheetList = props.sheets.filter(function (s) { return s.data && s.data.length; });
|
|
676
|
-
if (!sheetList.length) { showMessage({ title: "Sheets data is empty" }); return; }
|
|
677
|
-
} else {
|
|
678
|
-
if (!props.data || !props.data.length) { showMessage({ title: "Data Null" }); return; }
|
|
679
|
-
sheetList = [{ name: props.worksheet, data: props.data, columns: props.columns }];
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
var totalRows = sheetList.reduce(function (sum, s) { return sum + s.data.length; }, 0);
|
|
683
|
-
var processedRows = 0;
|
|
684
|
-
// Chunk động: mỗi đợt ghi ~2% tổng số dòng, nên số lần nhường luồng UI luôn ~50 lần dù
|
|
685
|
-
// data ít hay nhiều (không còn đánh đổi mượt-vs-nhanh như chunk cố định) — data nhỏ thì
|
|
686
|
-
// chặn dưới 50 dòng/đợt để khỏi nhường luồng vô ích cho vài chục dòng.
|
|
687
|
-
var CHUNK_SIZE = Math.max(50, Math.ceil(totalRows * 0.02));
|
|
688
|
-
var progressUI = props.showProgress ? createProgressUI() : null;
|
|
689
|
-
|
|
690
|
-
function report(phase, sheetName) {
|
|
691
|
-
// Giai đoạn ghi dòng ("building") chiếm tối đa 95%; ExcelJS không có hook tiến trình cho bước
|
|
692
|
-
// nén file (writeBuffer) nên "finalizing" chỉ báo mốc cố định 98%, không nội suy %.
|
|
693
|
-
var percent = totalRows ? Math.min(95, Math.floor(processedRows / totalRows * 95)) : 0;
|
|
694
|
-
if (phase === 'finalizing') percent = 98;
|
|
695
|
-
if (phase === 'done') percent = 100;
|
|
696
|
-
if (typeof props.onProgress === 'function') {
|
|
697
|
-
try { props.onProgress({ percent: percent, processedRows: processedRows, totalRows: totalRows, phase: phase, sheetName: sheetName }); }
|
|
698
|
-
catch (e) { console.error('jsonToExcel onProgress error', e); }
|
|
699
|
-
}
|
|
700
|
-
if (progressUI) {
|
|
701
|
-
progressUI.update({ percent: percent, phase: phase });
|
|
702
|
-
if (phase === 'done' || phase === 'error') progressUI.destroy();
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
var workbook = new ExcelJS.Workbook();
|
|
707
|
-
|
|
708
|
-
function buildSheet(sheetIndex) {
|
|
709
|
-
if (sheetIndex >= sheetList.length) { finalize(); return; }
|
|
710
|
-
var sheetCfg = sheetList[sheetIndex];
|
|
711
|
-
var worksheet = workbook.addWorksheet(sheetCfg.name || 'Sheet');
|
|
712
|
-
var cols = Array.isArray(sheetCfg.columns) && sheetCfg.columns.length ? sheetCfg.columns : null;
|
|
713
|
-
// Create header (chỉ cần đọc key của dòng đầu, tương đương logic cũ nhưng không lặp check index===0 mỗi vòng)
|
|
714
|
-
if (cols) setupColumns(worksheet, cols, sheetCfg.data);
|
|
715
|
-
else worksheet.columns = Object.keys(sheetCfg.data[0]).map(function (k) { return { header: k, key: k }; });
|
|
716
|
-
|
|
717
|
-
var rows = sheetCfg.data;
|
|
718
|
-
var i = 0;
|
|
719
|
-
function nextChunk() {
|
|
720
|
-
try {
|
|
721
|
-
var end = Math.min(i + CHUNK_SIZE, rows.length);
|
|
722
|
-
for (var r = i; r < end; r++) { worksheet.addRow(cols ? columnRowValues(cols, rows[r]) : rows[r]); }
|
|
723
|
-
if (cols && end === rows.length) appendSumRow(worksheet, cols, rows);
|
|
724
|
-
processedRows += (end - i);
|
|
725
|
-
i = end;
|
|
726
|
-
report('building', sheetCfg.name);
|
|
727
|
-
} catch (err) {
|
|
728
|
-
console.error('jsonToExcel build error', err);
|
|
729
|
-
report('error', sheetCfg.name);
|
|
730
|
-
showMessage({ title: 'Xuất Excel thất bại' });
|
|
731
|
-
return;
|
|
732
|
-
}
|
|
733
|
-
if (i < rows.length) {
|
|
734
|
-
// Nhường luồng UI giữa các đợt (thay vì ghi hết trong 1 vòng lặp đồng bộ) để tránh
|
|
735
|
-
// trang bị "Not Responding" khi data lớn, đồng thời cho phép % cập nhật lên màn hình.
|
|
736
|
-
(window.requestAnimationFrame || function (cb) { setTimeout(cb, 0); })(nextChunk);
|
|
737
|
-
} else {
|
|
738
|
-
buildSheet(sheetIndex + 1);
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
nextChunk();
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
function finalize() {
|
|
745
|
-
report('finalizing');
|
|
746
|
-
workbook.xlsx.writeBuffer().then(function (data) {
|
|
747
|
-
var blobType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
|
|
748
|
-
var blob = new Blob([data], { type: blobType });
|
|
749
|
-
var a = document.createElement('a');
|
|
750
|
-
document.body.appendChild(a);
|
|
751
|
-
a.style = 'display: none';
|
|
752
|
-
var url = window.URL.createObjectURL(blob);
|
|
753
|
-
a.href = url;
|
|
754
|
-
a.download = props.filename;
|
|
755
|
-
a.click();
|
|
756
|
-
window.URL.revokeObjectURL(url);
|
|
757
|
-
a.remove();
|
|
758
|
-
report('done');
|
|
759
|
-
}).catch(function (err) {
|
|
760
|
-
console.error('jsonToExcel writeBuffer error', err);
|
|
761
|
-
report('error');
|
|
762
|
-
showMessage({ title: 'Xuất Excel thất bại' });
|
|
763
|
-
});
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
buildSheet(0);
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
//---------------------------------------------------- chế độ `columns`
|
|
770
|
-
function readColumnValue(row, key) {
|
|
771
|
-
if (row === null || row === undefined) return null;
|
|
772
|
-
if (row[key] !== undefined) return row[key];
|
|
773
|
-
return _.get(row, key, null);
|
|
774
|
-
}
|
|
775
|
-
// ExcelJS ghi Date theo UTC → dựng lại mốc UTC từ giờ ĐỊA PHƯƠNG, nếu không ngày 00:00 ở UTC+7
|
|
776
|
-
// sẽ lùi thành 17:00 hôm trước trong Excel. "yyyy-mm-dd" trần thì new Date() hiểu là UTC nên tách riêng.
|
|
777
|
-
function toExcelDate(v) {
|
|
778
|
-
if (v === null || v === undefined || v === '') return null;
|
|
779
|
-
var m = typeof v === 'string' && /^(\d{4})-(\d{2})-(\d{2})$/.exec(v);
|
|
780
|
-
if (m) return new Date(Date.UTC(+m[1], +m[2] - 1, +m[3]));
|
|
781
|
-
var d = v instanceof Date ? v : new Date(v);
|
|
782
|
-
if (isNaN(d.getTime())) return v;
|
|
783
|
-
return new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds()));
|
|
784
|
-
}
|
|
785
|
-
function htmlToText(v) {
|
|
786
|
-
var s = String(v);
|
|
787
|
-
if (s.indexOf('<') < 0 && s.indexOf('&') < 0) return s;
|
|
788
|
-
// DOMParser không chạy script/handler — innerHTML trên element sống thì <img onerror> VẪN chạy.
|
|
789
|
-
return new DOMParser().parseFromString(s, 'text/html').body.textContent;
|
|
790
|
-
}
|
|
791
|
-
function convertCellValue(col, v) {
|
|
792
|
-
if (v === undefined) return null;
|
|
793
|
-
if (col.type === 'date') return toExcelDate(v);
|
|
794
|
-
if (v === null) return null;
|
|
795
|
-
if (col.type === 'number') return (typeof v === 'string' && v.trim() !== '' && !isNaN(v)) ? Number(v) : v;
|
|
796
|
-
if (col.type === 'html') return htmlToText(v);
|
|
797
|
-
// object/array đưa thẳng vào ExcelJS sẽ bị hiểu nhầm là rich text/hyperlink/formula
|
|
798
|
-
if (typeof v === 'object' && !(v instanceof Date)) return JSON.stringify(v);
|
|
799
|
-
return v;
|
|
800
|
-
}
|
|
801
|
-
function columnRowValues(cols, row) {
|
|
802
|
-
var out = new Array(cols.length);
|
|
803
|
-
for (var c = 0; c < cols.length; c++) out[c] = convertCellValue(cols[c], readColumnValue(row, cols[c].key));
|
|
804
|
-
return out;
|
|
805
|
-
}
|
|
806
|
-
function estimateWidth(col, rows) {
|
|
807
|
-
var w = String(col.header != null ? col.header : col.key).length;
|
|
808
|
-
if (col.type === 'date') w = Math.max(w, String(col.numFmt || 'dd/mm/yyyy').replace(/"/g, '').length);
|
|
809
|
-
else {
|
|
810
|
-
for (var i = 0, n = Math.min(rows.length, 100); i < n; i++) {
|
|
811
|
-
var v = readColumnValue(rows[i], col.key);
|
|
812
|
-
if (v !== null && v !== undefined) w = Math.max(w, String(v).length + (typeof v === 'number' ? 3 : 0));
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
return Math.min(60, Math.max(8, w + 2));
|
|
816
|
-
}
|
|
817
|
-
function setupColumns(worksheet, cols, rows) {
|
|
818
|
-
// key nội bộ theo vị trí: hai cột trùng tiêu đề/trùng field vẫn không đè nhau (dòng ghi bằng mảng)
|
|
819
|
-
worksheet.columns = cols.map(function (c, idx) {
|
|
820
|
-
var def = { header: c.header != null ? String(c.header) : String(c.key), key: '__c' + idx, width: c.width || estimateWidth(c, rows) };
|
|
821
|
-
if (c.numFmt) def.style = { numFmt: c.numFmt };
|
|
822
|
-
return def;
|
|
823
|
-
});
|
|
824
|
-
worksheet.getRow(1).font = { bold: true };
|
|
825
|
-
worksheet.views = [{ state: 'frozen', ySplit: 1 }];
|
|
826
|
-
}
|
|
827
|
-
// Hàng tổng = công thức SUM trên chính các dòng đã xuất (kèm kết quả tính sẵn để trình xem không
|
|
828
|
-
// tính công thức vẫn thấy số) — sửa số trong Excel thì tổng tự cập nhật.
|
|
829
|
-
function appendSumRow(worksheet, cols, rows) {
|
|
830
|
-
if (!rows.length || !cols.some(function (c) { return c.sum; })) return;
|
|
831
|
-
var row = worksheet.addRow(cols.map(function (c, idx) {
|
|
832
|
-
if (!c.sum) return null;
|
|
833
|
-
var total = 0;
|
|
834
|
-
for (var i = 0; i < rows.length; i++) {
|
|
835
|
-
var n = Number(readColumnValue(rows[i], c.key));
|
|
836
|
-
if (!isNaN(n)) total += n;
|
|
837
|
-
}
|
|
838
|
-
var letter = worksheet.getColumn(idx + 1).letter;
|
|
839
|
-
return { formula: 'SUM(' + letter + '2:' + letter + (rows.length + 1) + ')', result: total };
|
|
840
|
-
}));
|
|
841
|
-
row.font = { bold: true };
|
|
842
|
-
cols.forEach(function (c, idx) {
|
|
843
|
-
if (typeof c.sum === 'string') row.getCell(idx + 1).numFmt = c.sum;
|
|
844
|
-
});
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
// Thanh % mặc định, tự tạo/tự dọn bằng DOM thuần (CSS thủ công, không dùng v-dialog/framework nào)
|
|
848
|
-
// — fix cố định giữa màn hình, không phụ thuộc Vue nên dùng chung được cho cả V2 và V3.
|
|
849
|
-
function createProgressUI() {
|
|
850
|
-
var STYLE_ID = 'fui-json-to-excel-progress-style';
|
|
851
|
-
// Luôn ghi đè nội dung CSS mới nhất vào thẻ <style>, kể cả khi tag đã tồn tại từ lần
|
|
852
|
-
// export trước trong cùng phiên trang (SPA không reload) — nếu chỉ tạo-1-lần-rồi-bỏ-qua
|
|
853
|
-
// như trước, CSS cũ bị kẹt lại vĩnh viễn trong DOM, sửa file JS trên server sau đó
|
|
854
|
-
// không có tác dụng cho tới khi người dùng F5 lại cả trang.
|
|
855
|
-
var style = document.getElementById(STYLE_ID);
|
|
856
|
-
if (!style) {
|
|
857
|
-
style = document.createElement('style');
|
|
858
|
-
style.id = STYLE_ID;
|
|
859
|
-
document.head.appendChild(style);
|
|
860
|
-
}
|
|
861
|
-
style.innerHTML = '.fui-xlsx-progress{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:350px;' +
|
|
862
|
-
'box-sizing:border-box;z-index:99999;background:#fff;border-radius:8px;box-shadow:0 12px 40px rgba(0,0,0,.35);' +
|
|
863
|
-
'padding: 30px 30px 40px 30px;font:14px/1.4 inherit;transition:opacity .3s}' +
|
|
864
|
-
'.fui-xlsx-progress .fui-xlsx-label{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;color:#333}' +
|
|
865
|
-
'.fui-xlsx-progress .fui-xlsx-label span:first-child{font-size:15px;font-weight:600}' +
|
|
866
|
-
'.fui-xlsx-progress .fui-xlsx-percent{font-size:22px;font-weight:700;color:#1976d2}' +
|
|
867
|
-
'.fui-xlsx-progress .fui-xlsx-track{height:8px;border-radius:4px;background:#e0e0e0;overflow:hidden}' +
|
|
868
|
-
'.fui-xlsx-progress .fui-xlsx-bar{height:100%;width:0;background:#1976d2;transition:width .2s ease-out}';
|
|
869
|
-
var el = document.createElement('div');
|
|
870
|
-
el.className = 'fui-xlsx-progress';
|
|
871
|
-
el.innerHTML = '<div class="fui-xlsx-label"><span>Đang xuất Excel...</span><span class="fui-xlsx-percent">0%</span></div>' +
|
|
872
|
-
'<div class="fui-xlsx-track"><div class="fui-xlsx-bar"></div></div>';
|
|
873
|
-
document.body.appendChild(el);
|
|
874
|
-
var percentEl = el.querySelector('.fui-xlsx-percent');
|
|
875
|
-
var barEl = el.querySelector('.fui-xlsx-bar');
|
|
876
|
-
var labelEl = el.querySelector('.fui-xlsx-label span');
|
|
877
|
-
var destroyed = false;
|
|
878
|
-
return {
|
|
879
|
-
update: function (info) {
|
|
880
|
-
if (destroyed) return;
|
|
881
|
-
percentEl.textContent = info.percent + '%';
|
|
882
|
-
barEl.style.width = info.percent + '%';
|
|
883
|
-
if (info.phase === 'finalizing') labelEl.textContent = 'Đang tạo file...';
|
|
884
|
-
if (info.phase === 'done') labelEl.textContent = 'Hoàn tất';
|
|
885
|
-
if (info.phase === 'error') labelEl.textContent = 'Xuất thất bại';
|
|
886
|
-
},
|
|
887
|
-
destroy: function () {
|
|
888
|
-
if (destroyed) return;
|
|
889
|
-
destroyed = true;
|
|
890
|
-
el.style.opacity = '0';
|
|
891
|
-
setTimeout(function () { if (el.parentNode) el.parentNode.removeChild(el); }, 500);
|
|
892
|
-
}
|
|
893
|
-
};
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
//===========================================================================================
|
|
897
|
-
function copyToClipboard(textToCopy) {
|
|
898
|
-
if (navigator.clipboard && window.isSecureContext) {
|
|
899
|
-
console.log("Navigator copy: ", textToCopy);
|
|
900
|
-
return navigator.clipboard.writeText(textToCopy);
|
|
901
|
-
} else {
|
|
902
|
-
let textArea = document.createElement("div");
|
|
903
|
-
textArea.innerText = textToCopy;
|
|
904
|
-
textArea.style.position = "fixed";
|
|
905
|
-
textArea.style.left = "0px";
|
|
906
|
-
textArea.style.top = "-999999px";
|
|
907
|
-
document.body.appendChild(textArea);
|
|
908
|
-
|
|
909
|
-
var range = document.createRange();
|
|
910
|
-
range.selectNode(textArea);
|
|
911
|
-
window.getSelection().removeAllRanges();
|
|
912
|
-
window.getSelection().addRange(range);
|
|
913
|
-
return new Promise((res, rej) => {
|
|
914
|
-
document.execCommand('copy') ? res() : rej();
|
|
915
|
-
console.log("Command copy: ", textToCopy);
|
|
916
|
-
textArea.remove();
|
|
917
|
-
});
|
|
918
|
-
}
|
|
919
|
-
};
|
|
920
|
-
//===========================================================================================
|
|
921
|
-
function fileNameClear(fname) {
|
|
922
|
-
if (fname) return fname.replace(/[\!\@\#\$\^\&\%\*\(\)\+\=\-\[\]\\\/\{\}\|\:\<\>\?\,\.\`\"\'\~\;]/gi, '');
|
|
923
|
-
return fname;
|
|
924
|
-
}
|
|
925
|
-
//===========================================================================================
|
|
926
|
-
function hashCode(s) {
|
|
927
|
-
var h = 0,
|
|
928
|
-
l = s.length,
|
|
929
|
-
i = 0;
|
|
930
|
-
if (l > 0)
|
|
931
|
-
while (i < l)
|
|
932
|
-
h = (h << 5) - h + s.charCodeAt(i++) | 0;
|
|
933
|
-
return h;
|
|
934
|
-
};
|
|
935
|
-
//===========================================================================================
|
|
936
|
-
function getCookie(cname) {
|
|
937
|
-
var name = cname + "=";
|
|
938
|
-
var ca = document.cookie.split(';');
|
|
939
|
-
for (var i = 0; i < ca.length; i++) {
|
|
940
|
-
var c = ca[i];
|
|
941
|
-
while (c.charAt(0) == ' ') {
|
|
942
|
-
c = c.substring(1);
|
|
943
|
-
}
|
|
944
|
-
if (c.indexOf(name) == 0) {
|
|
945
|
-
return c.substring(name.length, c.length);
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
return "";
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
function setCookie(name, value, days, domain) {
|
|
952
|
-
var expires = "";
|
|
953
|
-
var domainString = "";
|
|
954
|
-
if (days) {
|
|
955
|
-
var date = new Date();
|
|
956
|
-
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
|
957
|
-
expires = "; expires=" + date.toUTCString();
|
|
958
|
-
}
|
|
959
|
-
if (domain) {
|
|
960
|
-
domainString = "; domain=" + domain
|
|
961
|
-
}
|
|
962
|
-
document.cookie = name + "=" + (value || "") + expires + domainString + ";path=/";
|
|
963
|
-
}
|
|
964
|
-
//===================================================================================
|
|
965
|
-
function logout(cookiesName, domain, url) {
|
|
966
|
-
if (!cookiesName) cookiesName = 'awt';
|
|
967
|
-
if (typeof domain == "undefined" && vueData.v_Set.authDomain) domain = vueData.v_Set.authDomain;
|
|
968
|
-
|
|
969
|
-
setCookie(cookiesName, '', 0, domain);
|
|
970
|
-
|
|
971
|
-
if (!url) location.assign(location.href);
|
|
972
|
-
else location.assign(url);
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
//===========================================================================================
|
|
976
|
-
// WEBSOCKET
|
|
977
|
-
//===========================================================================================
|
|
978
|
-
function webSocketJoinGroup(groupObj, timeout, callBackFunc) {
|
|
979
|
-
groupObj = _.cloneDeep(groupObj)
|
|
980
|
-
_.forEach(groupObj, function (v, k) {
|
|
981
|
-
if (!vueData.webSocket.hasOwnProperty(k)) {
|
|
982
|
-
Vue.set(vueData.webSocket, k, v);
|
|
983
|
-
}
|
|
984
|
-
});
|
|
985
|
-
|
|
986
|
-
setTimeout(function () {
|
|
987
|
-
try {
|
|
988
|
-
$.connection.hub.start().done(function () {
|
|
989
|
-
_.forEach(groupObj, function (v, k) {
|
|
990
|
-
$.connection.chatHub.server.joinGroup(k);
|
|
991
|
-
});
|
|
992
|
-
if (_.isFunction(callBackFunc)) callBackFunc();
|
|
993
|
-
}).fail(function (e) {
|
|
994
|
-
});
|
|
995
|
-
}
|
|
996
|
-
catch (err) {
|
|
997
|
-
|
|
998
|
-
}
|
|
999
|
-
}, (timeout ? timeout : 0));
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
function webSocketConnection(wsURL, WS, wsState) {
|
|
1003
|
-
$.connection.hub.url = wsURL;
|
|
1004
|
-
|
|
1005
|
-
//=====================================================================
|
|
1006
|
-
$.connection.hub.stateChanged(function (change) {
|
|
1007
|
-
wsState.code = change.newState;
|
|
1008
|
-
if (change.newState === $.signalR.connectionState.reconnecting) {
|
|
1009
|
-
console.log("Websocket Reconnecting")
|
|
1010
|
-
wsState.text = "Reconnecting";
|
|
1011
|
-
}
|
|
1012
|
-
else if (change.newState === $.signalR.connectionState.connected) {
|
|
1013
|
-
console.log("Websocket Connected")
|
|
1014
|
-
wsState.text = "Connected";
|
|
1015
|
-
}
|
|
1016
|
-
else if (change.newState === $.signalR.connectionState.disconnected) {
|
|
1017
|
-
console.log("Websocket Disconnected")
|
|
1018
|
-
wsState.text = "Disconnected";
|
|
1019
|
-
|
|
1020
|
-
webSocketJoinGroup(WS, 5000);
|
|
1021
|
-
}
|
|
1022
|
-
});
|
|
1023
|
-
|
|
1024
|
-
$.connection.chatHub.client.onError = function (Mess) {
|
|
1025
|
-
console.log('Server error: ', Mess);
|
|
1026
|
-
};
|
|
1027
|
-
|
|
1028
|
-
$.connection.chatHub.client.onJoinGroup = function (mess) {
|
|
1029
|
-
console.log("Group", mess)
|
|
1030
|
-
};
|
|
1031
|
-
|
|
1032
|
-
$.connection.chatHub.client.messageReceived = function (group, message) {
|
|
1033
|
-
console.log("Message Received: ", group, message)
|
|
1034
|
-
if (_.isArray(WS[group])) WS[group].push(message);
|
|
1035
|
-
else WS[group] = message;
|
|
1036
|
-
};
|
|
1037
|
-
|
|
1038
|
-
$.connection.chatHub.client.messageSendLog = function (group, message) {
|
|
1039
|
-
console.log("Message Sent: ", group, message)
|
|
1040
|
-
};
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
function webSocket_Send(obj) {
|
|
1044
|
-
if (_.isPlainObject(obj.data)) $.connection.chatHub.server.messageSendObject(obj.group, obj.data);
|
|
1045
|
-
else if (_.isArray(obj.data)) $.connection.chatHub.server.messageSendArray(obj.group, obj.data);
|
|
1046
|
-
else $.connection.chatHub.server.messageSend(obj.group, obj.data);
|
|
1047
|
-
}
|
|
1048
|
-
//===========================================================================================
|
|
1049
|
-
//===========================================================================================
|
|
1050
|
-
//===========================================================================================
|
|
1051
|
-
//===========================================================================================
|
|
1
|
+
$isMobile = navigator.userAgent.match(/mobile|touch|android|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|ip(ad|hone|od)|iris|kindle|lge |maemo|meego.+mobile|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino|playbook|silk/i) != null;
|
|
2
|
+
$isPhone = navigator.userAgent.match(/mobile|touch|android|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|ip(hone|od)|iris|kindle|lge |maemo|meego.+mobile|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (phone)|xda|xiino|playbook|silk/i) != null;
|
|
3
|
+
$isApple = navigator.userAgent.match(/ip(ad|hone|od)/i) != null;
|
|
4
|
+
//===========================================================================
|
|
5
|
+
var Loader = function () { }
|
|
6
|
+
Loader.prototype = {
|
|
7
|
+
require: function (scripts, callback) {
|
|
8
|
+
this.loadCount = 0;
|
|
9
|
+
this.totalRequired = scripts.length;
|
|
10
|
+
this.callback = callback;
|
|
11
|
+
|
|
12
|
+
for (var i = 0; i < scripts.length; i++) {
|
|
13
|
+
this.writeScript(scripts[i]);
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
loaded: function (evt) {
|
|
17
|
+
this.loadCount++;
|
|
18
|
+
|
|
19
|
+
if (this.loadCount == this.totalRequired && typeof this.callback == 'function') this.callback.call();
|
|
20
|
+
},
|
|
21
|
+
writeScript: function (src) {
|
|
22
|
+
var self = this;
|
|
23
|
+
var s = document.createElement('script');
|
|
24
|
+
s.type = "text/javascript";
|
|
25
|
+
s.async = false;
|
|
26
|
+
s.src = src;
|
|
27
|
+
s.addEventListener('load', function (e) { self.loaded(e); }, false);
|
|
28
|
+
var head = document.getElementsByTagName('head')[0];
|
|
29
|
+
head.appendChild(s);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
//===================================================================================
|
|
33
|
+
//===================================================================================
|
|
34
|
+
//===========================================================================================
|
|
35
|
+
function extractHostname(url) {
|
|
36
|
+
var hostname;
|
|
37
|
+
//find & remove protocol (http, ftp, etc.) and get hostname
|
|
38
|
+
|
|
39
|
+
if (url.indexOf("//") > -1) {
|
|
40
|
+
hostname = url.split('/')[2];
|
|
41
|
+
} else {
|
|
42
|
+
hostname = url.split('/')[0];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
//find & remove port number
|
|
46
|
+
hostname = hostname.split(':')[0];
|
|
47
|
+
//find & remove "?"
|
|
48
|
+
hostname = hostname.split('?')[0];
|
|
49
|
+
|
|
50
|
+
return hostname;
|
|
51
|
+
}
|
|
52
|
+
//===========================================================================================
|
|
53
|
+
const getDomainWithoutSubdomain = url => {
|
|
54
|
+
const urlParts = new URL(url).hostname.split('.')
|
|
55
|
+
|
|
56
|
+
return urlParts
|
|
57
|
+
.slice(0)
|
|
58
|
+
.slice(-(urlParts.length === 4 ? 3 : 2))
|
|
59
|
+
.join('.')
|
|
60
|
+
}
|
|
61
|
+
//===========================================================================================
|
|
62
|
+
function loadScripts(scriptsArray, callbackFunc) {
|
|
63
|
+
var scriptsArray = scriptsArray || new Array();
|
|
64
|
+
var callbackFunc = callbackFunc || function () { };
|
|
65
|
+
|
|
66
|
+
if (scriptsArray.length == 0) {
|
|
67
|
+
callbackFunc();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
var loadScript = new Loader();
|
|
72
|
+
loadScript.require(scriptsArray, callbackFunc);
|
|
73
|
+
}
|
|
74
|
+
//===================================================================================
|
|
75
|
+
function pushRouter(router) {
|
|
76
|
+
var curPath = window.location.pathname;
|
|
77
|
+
if (curPath != router || curPath == '/') {
|
|
78
|
+
window.history.pushState({ id: router }, document.title, router);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//===================================================================================
|
|
82
|
+
function CALL(obj, includeData) {
|
|
83
|
+
runAction(obj, includeData);
|
|
84
|
+
}
|
|
85
|
+
//===================================================================================
|
|
86
|
+
function windowSendMessage(window, cmd, data) {
|
|
87
|
+
//console.log("windowSendMessage", cmd, data);
|
|
88
|
+
if (_.isObject(window)) {
|
|
89
|
+
window.postMessage({ cmd: cmd, value: JSON.parse(JSON.stringify(data)) }, '*');
|
|
90
|
+
} else
|
|
91
|
+
if (window == "#PARENT") {
|
|
92
|
+
parent.postMessage({ cmd: cmd, value: JSON.parse(JSON.stringify(data)) }, '*');
|
|
93
|
+
} else {
|
|
94
|
+
var win = document.getElementById(window.slice(1, window.length));
|
|
95
|
+
if (win) win.contentWindow.postMessage({ cmd: cmd, value: JSON.parse(JSON.stringify(data)) }, '*');
|
|
96
|
+
else console.log("send DATA: Window ID not found")
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//===========================================================================================
|
|
100
|
+
function appCommand(cmdData) {
|
|
101
|
+
if (window.ReactNativeWebView) window.ReactNativeWebView.postMessage(cmdData);
|
|
102
|
+
else if (window.webkit) window.webkit.messageHandlers["scriptHandler"].postMessage(cmdData);
|
|
103
|
+
else if (window.scriptHandler) window.scriptHandler.postMessage(cmdData);
|
|
104
|
+
}
|
|
105
|
+
//===========================================================================================
|
|
106
|
+
function buildHeader(obj, mapCol) {
|
|
107
|
+
var datatable = {};
|
|
108
|
+
var header = [];
|
|
109
|
+
|
|
110
|
+
if (mapCol) {
|
|
111
|
+
_.forEach(obj, function (o) {
|
|
112
|
+
header.push({ "text": o[mapCol.text], "value": o[mapCol.value], "divider": true });
|
|
113
|
+
});
|
|
114
|
+
} else {
|
|
115
|
+
if (obj && _.isArray(obj)) datatable = obj[0];
|
|
116
|
+
else if (obj && _.isArray(obj.data)) datatable = obj.data[0];
|
|
117
|
+
|
|
118
|
+
if (datatable) {
|
|
119
|
+
_.forEach(datatable, function (value, key) {
|
|
120
|
+
if (_.isNumber(value)) header.push({ "el": "t-num", "text": key, "value": key, "divider": true });
|
|
121
|
+
else header.push({ "text": key, "value": key, "divider": true });
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
console.log("Build Header: ", header)
|
|
127
|
+
return header;
|
|
128
|
+
}
|
|
129
|
+
//===========================================================================================
|
|
130
|
+
function fillData(obj) {
|
|
131
|
+
if (!obj.key && !obj.src) { // FILL ALL DATA
|
|
132
|
+
var mapD = {};
|
|
133
|
+
_.forEach(obj.map, function (value, key) {
|
|
134
|
+
mapD[key] = bindData(value);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
_.forEach(obj.des, function (desObj) {
|
|
138
|
+
_.forEach(mapD, function (value, key) {
|
|
139
|
+
desObj[key] = value;
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
var GroupKeySRC = _.groupBy(obj.src, obj.key);
|
|
146
|
+
_.forEach(obj.map, function (value, key) {
|
|
147
|
+
_.forEach(obj.des, function (desObj) {
|
|
148
|
+
var val = GroupKeySRC[desObj[obj.key]];
|
|
149
|
+
if (_.isString(value)) {
|
|
150
|
+
if (_.isArray(val)) val = val[0][value];
|
|
151
|
+
else val = null;
|
|
152
|
+
}
|
|
153
|
+
else if (_.isPlainObject(value)) {
|
|
154
|
+
if (_.isArray(val)) val = val[0];
|
|
155
|
+
else val = null;
|
|
156
|
+
}
|
|
157
|
+
desObj[key] = val;
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
//===========================================================================================
|
|
162
|
+
function groupBy(arrayObj, groupField, sumArr) { // vi du: groupBy(dsSinhVien, 'Khoa', ['SL Nghỉ Học','SL Tốt Nghiệp'])
|
|
163
|
+
var gArr = _.groupBy(arrayObj, groupField);
|
|
164
|
+
var returnArr = [];
|
|
165
|
+
var sumObj = {};
|
|
166
|
+
if (!_.isArray(sumArr)) sumArr = [sumArr];
|
|
167
|
+
//---------------------------------
|
|
168
|
+
_.forEach(gArr, function (fieldValue, fieldKey) {
|
|
169
|
+
var n = 0;
|
|
170
|
+
_.forEach(sumArr, function (v) {
|
|
171
|
+
sumObj[v] = 0;
|
|
172
|
+
});
|
|
173
|
+
_.forEach(fieldValue, function (value) {
|
|
174
|
+
n++;
|
|
175
|
+
_.forEach(sumArr, function (v) {
|
|
176
|
+
sumObj[v] = sumObj[v] + value[v];
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
var o = {};
|
|
180
|
+
o[groupField] = fieldKey;
|
|
181
|
+
o["count"] = n;
|
|
182
|
+
o = $.extend(o, sumObj);
|
|
183
|
+
returnArr.push(o);
|
|
184
|
+
});
|
|
185
|
+
return returnArr;
|
|
186
|
+
}
|
|
187
|
+
//===========================================================================================
|
|
188
|
+
function chartDataBuild(obj) {
|
|
189
|
+
if (obj.dataChart) {
|
|
190
|
+
var chartData = [];
|
|
191
|
+
var dataInput = obj.dataChart.data;
|
|
192
|
+
|
|
193
|
+
if (_.isUndefined(dataInput)) dataInput = obj.dataChart;
|
|
194
|
+
|
|
195
|
+
if (_.isArray(dataInput[0])) {
|
|
196
|
+
var chartName = JSON.parse(obj.dataChart.chartName);
|
|
197
|
+
_.forEach(dataInput, function (d, k) {
|
|
198
|
+
chartData.push({
|
|
199
|
+
name: chartName[k],
|
|
200
|
+
data: _.map(dataInput[k], function (n) {
|
|
201
|
+
return [n.label, n.value];
|
|
202
|
+
})
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
} else {
|
|
206
|
+
chartData.push({
|
|
207
|
+
name: (obj.dataChart.chartName ? obj.dataChart.chartName : null),
|
|
208
|
+
data: _.map(dataInput, function (n) {
|
|
209
|
+
return [n.label, n.value];
|
|
210
|
+
})
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
//---------------------
|
|
214
|
+
if (obj.allInOne) return [{ name: "", data: chartData }];
|
|
215
|
+
else return chartData;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
//===========================================================================================
|
|
219
|
+
function chartQABuild(obj) {
|
|
220
|
+
if (obj.dataChart) {
|
|
221
|
+
var chartData = [];
|
|
222
|
+
|
|
223
|
+
_.forEach(obj.dataChart[0], function (v, i) { // Duyet tung cau hoi
|
|
224
|
+
if (v.QType == 'TextArea') {
|
|
225
|
+
|
|
226
|
+
} else {
|
|
227
|
+
chartData.push({
|
|
228
|
+
name: v.QText,
|
|
229
|
+
data: _.map(_.filter(obj.dataChart[1], ['QID', v.QID]), function (n) {
|
|
230
|
+
return [n.label, n.val];
|
|
231
|
+
})
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
return chartData;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
//===========================================================================================
|
|
239
|
+
function confirm(obj) {
|
|
240
|
+
$.confirm({
|
|
241
|
+
icon: (obj.icon ? obj.icon : 'mdi mdi-help-circle-outline'),
|
|
242
|
+
title: obj.title,
|
|
243
|
+
content: (obj.message ? obj.message : ''),
|
|
244
|
+
type: (obj.type ? obj.type : 'blue'),
|
|
245
|
+
columnClass: 'col-xs-12 col-sm-8 col-md-6 col-lg-4',
|
|
246
|
+
animateFromElement: false,
|
|
247
|
+
buttons: {
|
|
248
|
+
ok: {
|
|
249
|
+
text: ' OK ',
|
|
250
|
+
btnClass: 'btn-blue',
|
|
251
|
+
keys: ['enter'],
|
|
252
|
+
action: obj.action
|
|
253
|
+
},
|
|
254
|
+
cancel: (obj.cancel ? obj.cancel : function () { })
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
//===========================================================================================
|
|
259
|
+
function showMessage(obj) {
|
|
260
|
+
$.dialog({
|
|
261
|
+
icon: (obj.icon ? obj.icon : 'mdi mdi-information-variant-circle-outline'),
|
|
262
|
+
title: (obj.title ? obj.title : ''),
|
|
263
|
+
backgroundDismiss: true,
|
|
264
|
+
content: (obj.message ? obj.message : ''),
|
|
265
|
+
type: (obj.type ? obj.type : 'blue'),
|
|
266
|
+
columnClass: 'col-xs-12 col-sm-8 col-md-6 col-lg-4',
|
|
267
|
+
animateFromElement: false,
|
|
268
|
+
closeIcon: true,
|
|
269
|
+
buttons: {
|
|
270
|
+
ok: {
|
|
271
|
+
text: ' Close ',
|
|
272
|
+
btnClass: 'btn-' + (obj.type ? obj.type : 'blue')
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
onClose: function () {
|
|
276
|
+
if (obj.onclose) {
|
|
277
|
+
runAction(obj.onclose, { $row: obj.$row, item: obj.item });
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
//===========================================================================================
|
|
283
|
+
function rightTest(rightObject) {
|
|
284
|
+
var returnVal = true
|
|
285
|
+
_.forEach(rightObject, function (rV, rK) {
|
|
286
|
+
if (!returnVal) return;
|
|
287
|
+
var A1 = [];
|
|
288
|
+
var A2 = [];
|
|
289
|
+
|
|
290
|
+
if (_.isArray(rV)) A1 = rV;
|
|
291
|
+
else A1 = [rV];
|
|
292
|
+
|
|
293
|
+
if (_.isArray(vueData.user[rK])) A2 = vueData.user[rK];
|
|
294
|
+
else A2 = [vueData.user[rK]];
|
|
295
|
+
|
|
296
|
+
if (_.intersection(A1, A2).length <= 0) returnVal = false;
|
|
297
|
+
});
|
|
298
|
+
return returnVal;
|
|
299
|
+
}
|
|
300
|
+
//===========================================================================================
|
|
301
|
+
function openWindow(obj) {
|
|
302
|
+
if (window === top) {
|
|
303
|
+
setTimeout(function () {
|
|
304
|
+
if (obj.id != undefined && vueData[obj.id] != undefined) {
|
|
305
|
+
console.log("Cửa sổ bị trùng tên: ", obj.id)
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (!vueData.v_windowVM) vueData.v_windowVM = {};
|
|
310
|
+
if (_.isString(obj)) obj = { url: obj };
|
|
311
|
+
|
|
312
|
+
if (!obj.id) obj.id = "Window" + generateID();
|
|
313
|
+
obj.open = true;
|
|
314
|
+
vueData[obj.id] = obj;
|
|
315
|
+
vueData.v_windowVM[obj.id] = new Vue({
|
|
316
|
+
vuetify: new Vuetify(),
|
|
317
|
+
render: Vue.compile("<f-window id=\"" + obj.id + "\"></f-window>").render,
|
|
318
|
+
}).$mount();
|
|
319
|
+
}, 10);
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
windowSendMessage('#PARENT', 'openWindow', obj);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
//===========================================================================================
|
|
326
|
+
function redirect(obj, query) {
|
|
327
|
+
let Q = '';
|
|
328
|
+
if (obj && obj.url) Q = obj.url;
|
|
329
|
+
else Q = obj;
|
|
330
|
+
|
|
331
|
+
if ((query || (obj && obj.query)) && vueData.p_params != " ") {
|
|
332
|
+
if (Q.indexOf("?") < 0) Q = Q + vueData.p_params;
|
|
333
|
+
else Q = Q + vueData.p_params.replace("?", "");
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
location.assign(Q);
|
|
337
|
+
}
|
|
338
|
+
//===========================================================================================
|
|
339
|
+
function reload() {
|
|
340
|
+
location.assign(window.location.href);
|
|
341
|
+
}
|
|
342
|
+
//===========================================================================================
|
|
343
|
+
function findInArray(obj) {
|
|
344
|
+
if (_.isString(obj.value)) obj.value = JSON.parse(obj.value);
|
|
345
|
+
return _.find(obj.array, obj.value);
|
|
346
|
+
}
|
|
347
|
+
//===========================================================================================
|
|
348
|
+
function fixURL(url) {
|
|
349
|
+
if (url.indexOf('http') == 0) return url;
|
|
350
|
+
else return vueData.v_Set.apiDomain + url
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
//===========================================================================================
|
|
354
|
+
function stringAttrToJson(str, removeVueEvent) {
|
|
355
|
+
var jObj = {}
|
|
356
|
+
|
|
357
|
+
if (typeof str == 'undefined') str = "";
|
|
358
|
+
|
|
359
|
+
if (_.isPlainObject(str)) return { ...str };
|
|
360
|
+
|
|
361
|
+
$(`<div ${str} >`).each(function () {
|
|
362
|
+
$.each(this.attributes, function (i, a) {
|
|
363
|
+
if (removeVueEvent && (typeof a.name == "string" && (a.name.substring(0, 1) == ":" || a.name.substring(0, 1) == "@" || a.name.substring(0, 2) == "v-"))) return;
|
|
364
|
+
jObj[a.name] = a.value;
|
|
365
|
+
})
|
|
366
|
+
})
|
|
367
|
+
return jObj
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
//===========================================================================================
|
|
371
|
+
function json_data_parse(obj, level) {
|
|
372
|
+
//--- convert to json data array --------
|
|
373
|
+
if (level > 3) return;
|
|
374
|
+
|
|
375
|
+
level++;
|
|
376
|
+
if (_.isArray(obj) && obj.length > 0) {
|
|
377
|
+
//console.log("json_data_parse", level)
|
|
378
|
+
if (_.isArray(obj[0])) {
|
|
379
|
+
_.forEach(obj, function (value) {
|
|
380
|
+
json_data_parse(value, level);
|
|
381
|
+
});
|
|
382
|
+
} else if (_.isPlainObject(obj[0])) {
|
|
383
|
+
var R = obj[0];
|
|
384
|
+
var nameOfJson = [];
|
|
385
|
+
_.forEach(R, function (value, key) {
|
|
386
|
+
if (key.substring(0, 10) == 'json_data:') nameOfJson.push(key);
|
|
387
|
+
});
|
|
388
|
+
_.forEach(obj, function (row) {
|
|
389
|
+
_.forEach(nameOfJson, function (key) {
|
|
390
|
+
row[key.replace('json_data:', '')] = JSON.parse(row[key]);
|
|
391
|
+
delete row[key];
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
} else if (_.isPlainObject(obj)) {
|
|
396
|
+
_.forEach(obj, function (value, key) {
|
|
397
|
+
if (key.substring(0, 10) == 'json_data:') {
|
|
398
|
+
obj[key.replace('json_data:', '')] = JSON.parse(value);
|
|
399
|
+
delete obj[key];
|
|
400
|
+
} else {
|
|
401
|
+
if (_.isObject(value)) json_data_parse(value, level);
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
//===========================================================================================
|
|
407
|
+
function ajaxCALL(URL, DATA, callBack, errorCallBack, header) {
|
|
408
|
+
let loadingID = generateID();
|
|
409
|
+
vueData.v_LoadingOverlay.push(loadingID);
|
|
410
|
+
vueData.v_Loading = true;
|
|
411
|
+
|
|
412
|
+
$.ajax({
|
|
413
|
+
type: "POST",
|
|
414
|
+
headers: (header ? header : {
|
|
415
|
+
authorization: $awt
|
|
416
|
+
}),
|
|
417
|
+
url: fixURL(URL),
|
|
418
|
+
contentType: "application/json; charset=utf-8",
|
|
419
|
+
dataType: "json",
|
|
420
|
+
crossDomain: true,
|
|
421
|
+
data: JSON.stringify(DATA),
|
|
422
|
+
success: function (d) {
|
|
423
|
+
json_data_parse(d, 0);
|
|
424
|
+
if (callBack) callBack(d);
|
|
425
|
+
},
|
|
426
|
+
error: function (xhr, ajaxOptions, thrownError) {
|
|
427
|
+
console.log("API:", URL, "IN:", DATA);
|
|
428
|
+
console.log("Error XHR:", xhr);
|
|
429
|
+
|
|
430
|
+
if (errorCallBack) {
|
|
431
|
+
errorCallBack(xhr);
|
|
432
|
+
return
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
showMessage({
|
|
436
|
+
icon: 'mdi mdi-alert-circle-outline',
|
|
437
|
+
title: '',
|
|
438
|
+
message: (xhr.responseJSON ? xhr.responseJSON.Message : (xhr.responseText ? xhr.responseText : "")),
|
|
439
|
+
type: 'red',
|
|
440
|
+
});
|
|
441
|
+
},
|
|
442
|
+
complete: function (data) {
|
|
443
|
+
vueData.v_LoadingOverlay = _.without(vueData.v_LoadingOverlay, loadingID);
|
|
444
|
+
if (vueData.v_LoadingOverlay.length == 0) vueData.v_Loading = false;
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
//===========================================================================================
|
|
449
|
+
function capacityText(numb) {
|
|
450
|
+
if (numb < 1048576) return Math.round(numb / 1024 * 100 / 100) + ' Kb';
|
|
451
|
+
else return Math.round(numb / 1048576 * 100) / 100 + ' Mb';
|
|
452
|
+
}
|
|
453
|
+
//===========================================================================================
|
|
454
|
+
function generateID() {
|
|
455
|
+
var d = new Date();
|
|
456
|
+
var k = d.getTime();
|
|
457
|
+
var str = k.toString(16).slice(1)
|
|
458
|
+
var UUID = 'idxxxxxxxx'.replace(/[xy]/g, function (c) {
|
|
459
|
+
var r = Math.random() * 16 | 0;
|
|
460
|
+
v = c == 'x' ? r : (r & 3 | 8);
|
|
461
|
+
return v.toString(16);
|
|
462
|
+
});
|
|
463
|
+
var newString = UUID.replace(/[z]/, str)
|
|
464
|
+
return newString;
|
|
465
|
+
}
|
|
466
|
+
//===========================================================================================
|
|
467
|
+
function printPDF(obj) {
|
|
468
|
+
pdfMake.fonts = {
|
|
469
|
+
Roboto: {
|
|
470
|
+
normal: 'Roboto-Regular.ttf',
|
|
471
|
+
bold: 'Roboto-Medium.ttf',
|
|
472
|
+
italics: 'Roboto-Italic.ttf',
|
|
473
|
+
bolditalics: 'Roboto-MediumItalic.ttf'
|
|
474
|
+
},
|
|
475
|
+
TimesNewRoman: {
|
|
476
|
+
normal: 'times.ttf',
|
|
477
|
+
bold: 'timesbd.ttf',
|
|
478
|
+
italics: 'timesi.ttf',
|
|
479
|
+
bolditalics: 'timesbi.ttf'
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (obj.download) {
|
|
484
|
+
pdfMake.createPdf(obj.data).download('fp_pdf_' + generateID() + '.pdf');
|
|
485
|
+
} else if (obj.viewer) {
|
|
486
|
+
pdfMake.createPdf(obj.data).getDataUrl(function (outDoc) {
|
|
487
|
+
$(obj.viewer).attr("src", outDoc);
|
|
488
|
+
});
|
|
489
|
+
} else {
|
|
490
|
+
pdfMake.createPdf(obj.data).getBase64(function (outDoc) {
|
|
491
|
+
printJS({ printable: outDoc, type: 'pdf', base64: true });
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
//-------------------------------------------------------
|
|
495
|
+
if (_.isFunction(obj.callBack)) obj.callBack();
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
//===========================================================================================
|
|
499
|
+
function colorLib(color) {
|
|
500
|
+
// Hàm hỗ trợ phân tích và chuyển đổi màu
|
|
501
|
+
function parseColor(input) {
|
|
502
|
+
let r, g, b, a = 1;
|
|
503
|
+
|
|
504
|
+
// Xử lý HEX (3 hoặc 6 ký tự)
|
|
505
|
+
if (typeof input === 'string' && input.startsWith('#')) {
|
|
506
|
+
let hex = input.replace('#', '');
|
|
507
|
+
if (hex.length === 3) {
|
|
508
|
+
hex = hex.split('').map(c => c + c).join('');
|
|
509
|
+
}
|
|
510
|
+
r = parseInt(hex.slice(0, 2), 16);
|
|
511
|
+
g = parseInt(hex.slice(2, 4), 16);
|
|
512
|
+
b = parseInt(hex.slice(4, 6), 16);
|
|
513
|
+
return { r, g, b, a };
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Xử lý RGB/RGBA
|
|
517
|
+
if (typeof input === 'string' && input.startsWith('rgb')) {
|
|
518
|
+
const match = input.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
|
|
519
|
+
if (match) {
|
|
520
|
+
r = parseInt(match[1]);
|
|
521
|
+
g = parseInt(match[2]);
|
|
522
|
+
b = parseInt(match[3]);
|
|
523
|
+
a = match[4] ? parseFloat(match[4]) : 1;
|
|
524
|
+
return { r, g, b, a };
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Xử lý HSL
|
|
529
|
+
if (typeof input === 'string' && input.startsWith('hsl')) {
|
|
530
|
+
const match = input.match(/hsla?\((\d+),\s*(\d+)%,\s*(\d+)%(?:,\s*([\d.]+))?\)/);
|
|
531
|
+
if (match) {
|
|
532
|
+
const h = parseInt(match[1]) / 360;
|
|
533
|
+
const s = parseInt(match[2]) / 100;
|
|
534
|
+
const l = parseInt(match[3]) / 100;
|
|
535
|
+
a = match[4] ? parseFloat(match[4]) : 1;
|
|
536
|
+
return hslToRgb(h, s, l, a);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Mặc định trả về màu đen nếu không phân tích được
|
|
541
|
+
return { r: 0, g: 0, b: 0, a: 1 };
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Chuyển HSL sang RGB
|
|
545
|
+
function hslToRgb(h, s, l, a) {
|
|
546
|
+
let r, g, b;
|
|
547
|
+
if (s === 0) {
|
|
548
|
+
r = g = b = l; // Grayscale
|
|
549
|
+
} else {
|
|
550
|
+
const hue2rgb = (p, q, t) => {
|
|
551
|
+
if (t < 0) t += 1;
|
|
552
|
+
if (t > 1) t -= 1;
|
|
553
|
+
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
|
554
|
+
if (t < 1 / 2) return q;
|
|
555
|
+
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
|
556
|
+
return p;
|
|
557
|
+
};
|
|
558
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
559
|
+
const p = 2 * l - q;
|
|
560
|
+
r = hue2rgb(p, q, h + 1 / 3);
|
|
561
|
+
g = hue2rgb(p, q, h);
|
|
562
|
+
b = hue2rgb(p, q, h - 1 / 3);
|
|
563
|
+
}
|
|
564
|
+
return {
|
|
565
|
+
r: Math.round(r * 255),
|
|
566
|
+
g: Math.round(g * 255),
|
|
567
|
+
b: Math.round(b * 255),
|
|
568
|
+
a
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Chuyển RGB sang HSL
|
|
573
|
+
function rgbToHsl(r, g, b) {
|
|
574
|
+
r /= 255;
|
|
575
|
+
g /= 255;
|
|
576
|
+
b /= 255;
|
|
577
|
+
const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
|
578
|
+
let h, s, l = (max + min) / 2;
|
|
579
|
+
|
|
580
|
+
if (max === min) {
|
|
581
|
+
h = s = 0; // Grayscale
|
|
582
|
+
} else {
|
|
583
|
+
const d = max - min;
|
|
584
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
585
|
+
switch (max) {
|
|
586
|
+
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
|
|
587
|
+
case g: h = (b - r) / d + 2; break;
|
|
588
|
+
case b: h = (r - g) / d + 4; break;
|
|
589
|
+
}
|
|
590
|
+
h /= 6;
|
|
591
|
+
}
|
|
592
|
+
return { h, s, l };
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Hàm chuyển đổi số thành chuỗi hex 2 chữ số
|
|
596
|
+
function toHex(value) {
|
|
597
|
+
const hex = Math.round(value).toString(16);
|
|
598
|
+
return hex.length === 1 ? '0' + hex : hex;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Phân tích màu đầu vào
|
|
602
|
+
const { r, g, b, a } = parseColor(color);
|
|
603
|
+
|
|
604
|
+
// Đối tượng trả về với các phương thức
|
|
605
|
+
return {
|
|
606
|
+
// Trả về chuỗi HEX
|
|
607
|
+
hexString() {
|
|
608
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
609
|
+
},
|
|
610
|
+
|
|
611
|
+
// Trả về chuỗi RGB hoặc RGBA
|
|
612
|
+
rgbString() {
|
|
613
|
+
return a < 1 ? `rgba(${r}, ${g}, ${b}, ${a})` : `rgb(${r}, ${g}, ${b})`;
|
|
614
|
+
},
|
|
615
|
+
|
|
616
|
+
// Trả về chuỗi HSL hoặc HSLA
|
|
617
|
+
hslString() {
|
|
618
|
+
const { h, s, l } = rgbToHsl(r, g, b);
|
|
619
|
+
return a < 1
|
|
620
|
+
? `hsla(${Math.round(h * 360)}, ${Math.round(s * 100)}%, ${Math.round(l * 100)}%, ${a})`
|
|
621
|
+
: `hsl(${Math.round(h * 360)}, ${Math.round(s * 100)}%, ${Math.round(l * 100)}%)`;
|
|
622
|
+
},
|
|
623
|
+
|
|
624
|
+
// Làm sáng màu (factor: 0 đến 1)
|
|
625
|
+
lighten(factor) {
|
|
626
|
+
const { h, s, l } = rgbToHsl(r, g, b);
|
|
627
|
+
const newL = Math.min(1, l + l * factor);
|
|
628
|
+
return colorLib(hslToRgb(h, s, newL, a).rgbString());
|
|
629
|
+
},
|
|
630
|
+
|
|
631
|
+
// Làm tối màu (factor: 0 đến 1)
|
|
632
|
+
darken(factor) {
|
|
633
|
+
const { h, s, l } = rgbToHsl(r, g, b);
|
|
634
|
+
const newL = Math.max(0, l - l * factor);
|
|
635
|
+
return colorLib(hslToRgb(h, s, newL, a).rgbString());
|
|
636
|
+
},
|
|
637
|
+
|
|
638
|
+
// Đặt độ trong suốt
|
|
639
|
+
alpha(value) {
|
|
640
|
+
return colorLib(`rgba(${r}, ${g}, ${b}, ${value})`);
|
|
641
|
+
}
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
//===========================================================================================
|
|
645
|
+
function transparentize(value, opacity) {
|
|
646
|
+
var alpha = opacity === undefined ? 0.5 : 1 - opacity;
|
|
647
|
+
return colorLib(value).alpha(alpha).rgbString();
|
|
648
|
+
}
|
|
649
|
+
//===========================================================================================
|
|
650
|
+
function jsonToExcel(Obj) {
|
|
651
|
+
if (typeof ExcelJS == 'undefined') {
|
|
652
|
+
var loadScript = new Loader();
|
|
653
|
+
loadScript.require(["/include/excel/exceljs.min.js"], function () {
|
|
654
|
+
writeExcelFile(Obj);
|
|
655
|
+
});
|
|
656
|
+
} else writeExcelFile(Obj);
|
|
657
|
+
function writeExcelFile(inputObj) {
|
|
658
|
+
var props = {
|
|
659
|
+
data: null, // Json to download (for single sheet - backward compatibility)
|
|
660
|
+
filename: "dataFP.xlsx", // filename to export
|
|
661
|
+
worksheet: "SheetFP", // sheet name for single sheet
|
|
662
|
+
sheets: null, // New property for multiple sheets
|
|
663
|
+
// optional: [{header, key, type?: 'number'|'date'|'html', numFmt?, width?, sum?: true|numFmt}]
|
|
664
|
+
// Có columns thì cột/tiêu đề/định dạng theo đúng danh sách này (dùng cho f-table excel="UI");
|
|
665
|
+
// không có thì giữ hành vi cũ: cột = key của dòng đầu, giá trị thô. Mỗi sheet có thể có columns riêng.
|
|
666
|
+
columns: null,
|
|
667
|
+
onProgress: null, // optional: function({percent, processedRows, totalRows, phase, sheetName}) - phase: 'building'|'finalizing'|'done'|'error'
|
|
668
|
+
showProgress: true // tự hiện thanh % cố định giữa màn hình khi xuất; truyền false để tắt UI này
|
|
669
|
+
}
|
|
670
|
+
Object.assign(props, inputObj);
|
|
671
|
+
// Gom về một danh sách sheet chung cho cả 2 chế độ cũ (single) và mới (multi) để dùng chung logic tiến trình/chunk
|
|
672
|
+
var sheetList;
|
|
673
|
+
if (props.sheets && Array.isArray(props.sheets)) {
|
|
674
|
+
if (!props.sheets.length) { showMessage({ title: "Sheets data is empty" }); return; }
|
|
675
|
+
sheetList = props.sheets.filter(function (s) { return s.data && s.data.length; });
|
|
676
|
+
if (!sheetList.length) { showMessage({ title: "Sheets data is empty" }); return; }
|
|
677
|
+
} else {
|
|
678
|
+
if (!props.data || !props.data.length) { showMessage({ title: "Data Null" }); return; }
|
|
679
|
+
sheetList = [{ name: props.worksheet, data: props.data, columns: props.columns }];
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
var totalRows = sheetList.reduce(function (sum, s) { return sum + s.data.length; }, 0);
|
|
683
|
+
var processedRows = 0;
|
|
684
|
+
// Chunk động: mỗi đợt ghi ~2% tổng số dòng, nên số lần nhường luồng UI luôn ~50 lần dù
|
|
685
|
+
// data ít hay nhiều (không còn đánh đổi mượt-vs-nhanh như chunk cố định) — data nhỏ thì
|
|
686
|
+
// chặn dưới 50 dòng/đợt để khỏi nhường luồng vô ích cho vài chục dòng.
|
|
687
|
+
var CHUNK_SIZE = Math.max(50, Math.ceil(totalRows * 0.02));
|
|
688
|
+
var progressUI = props.showProgress ? createProgressUI() : null;
|
|
689
|
+
|
|
690
|
+
function report(phase, sheetName) {
|
|
691
|
+
// Giai đoạn ghi dòng ("building") chiếm tối đa 95%; ExcelJS không có hook tiến trình cho bước
|
|
692
|
+
// nén file (writeBuffer) nên "finalizing" chỉ báo mốc cố định 98%, không nội suy %.
|
|
693
|
+
var percent = totalRows ? Math.min(95, Math.floor(processedRows / totalRows * 95)) : 0;
|
|
694
|
+
if (phase === 'finalizing') percent = 98;
|
|
695
|
+
if (phase === 'done') percent = 100;
|
|
696
|
+
if (typeof props.onProgress === 'function') {
|
|
697
|
+
try { props.onProgress({ percent: percent, processedRows: processedRows, totalRows: totalRows, phase: phase, sheetName: sheetName }); }
|
|
698
|
+
catch (e) { console.error('jsonToExcel onProgress error', e); }
|
|
699
|
+
}
|
|
700
|
+
if (progressUI) {
|
|
701
|
+
progressUI.update({ percent: percent, phase: phase });
|
|
702
|
+
if (phase === 'done' || phase === 'error') progressUI.destroy();
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
var workbook = new ExcelJS.Workbook();
|
|
707
|
+
|
|
708
|
+
function buildSheet(sheetIndex) {
|
|
709
|
+
if (sheetIndex >= sheetList.length) { finalize(); return; }
|
|
710
|
+
var sheetCfg = sheetList[sheetIndex];
|
|
711
|
+
var worksheet = workbook.addWorksheet(sheetCfg.name || 'Sheet');
|
|
712
|
+
var cols = Array.isArray(sheetCfg.columns) && sheetCfg.columns.length ? sheetCfg.columns : null;
|
|
713
|
+
// Create header (chỉ cần đọc key của dòng đầu, tương đương logic cũ nhưng không lặp check index===0 mỗi vòng)
|
|
714
|
+
if (cols) setupColumns(worksheet, cols, sheetCfg.data);
|
|
715
|
+
else worksheet.columns = Object.keys(sheetCfg.data[0]).map(function (k) { return { header: k, key: k }; });
|
|
716
|
+
|
|
717
|
+
var rows = sheetCfg.data;
|
|
718
|
+
var i = 0;
|
|
719
|
+
function nextChunk() {
|
|
720
|
+
try {
|
|
721
|
+
var end = Math.min(i + CHUNK_SIZE, rows.length);
|
|
722
|
+
for (var r = i; r < end; r++) { worksheet.addRow(cols ? columnRowValues(cols, rows[r]) : rows[r]); }
|
|
723
|
+
if (cols && end === rows.length) appendSumRow(worksheet, cols, rows);
|
|
724
|
+
processedRows += (end - i);
|
|
725
|
+
i = end;
|
|
726
|
+
report('building', sheetCfg.name);
|
|
727
|
+
} catch (err) {
|
|
728
|
+
console.error('jsonToExcel build error', err);
|
|
729
|
+
report('error', sheetCfg.name);
|
|
730
|
+
showMessage({ title: 'Xuất Excel thất bại' });
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
if (i < rows.length) {
|
|
734
|
+
// Nhường luồng UI giữa các đợt (thay vì ghi hết trong 1 vòng lặp đồng bộ) để tránh
|
|
735
|
+
// trang bị "Not Responding" khi data lớn, đồng thời cho phép % cập nhật lên màn hình.
|
|
736
|
+
(window.requestAnimationFrame || function (cb) { setTimeout(cb, 0); })(nextChunk);
|
|
737
|
+
} else {
|
|
738
|
+
buildSheet(sheetIndex + 1);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
nextChunk();
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function finalize() {
|
|
745
|
+
report('finalizing');
|
|
746
|
+
workbook.xlsx.writeBuffer().then(function (data) {
|
|
747
|
+
var blobType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
|
|
748
|
+
var blob = new Blob([data], { type: blobType });
|
|
749
|
+
var a = document.createElement('a');
|
|
750
|
+
document.body.appendChild(a);
|
|
751
|
+
a.style = 'display: none';
|
|
752
|
+
var url = window.URL.createObjectURL(blob);
|
|
753
|
+
a.href = url;
|
|
754
|
+
a.download = props.filename;
|
|
755
|
+
a.click();
|
|
756
|
+
window.URL.revokeObjectURL(url);
|
|
757
|
+
a.remove();
|
|
758
|
+
report('done');
|
|
759
|
+
}).catch(function (err) {
|
|
760
|
+
console.error('jsonToExcel writeBuffer error', err);
|
|
761
|
+
report('error');
|
|
762
|
+
showMessage({ title: 'Xuất Excel thất bại' });
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
buildSheet(0);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
//---------------------------------------------------- chế độ `columns`
|
|
770
|
+
function readColumnValue(row, key) {
|
|
771
|
+
if (row === null || row === undefined) return null;
|
|
772
|
+
if (row[key] !== undefined) return row[key];
|
|
773
|
+
return _.get(row, key, null);
|
|
774
|
+
}
|
|
775
|
+
// ExcelJS ghi Date theo UTC → dựng lại mốc UTC từ giờ ĐỊA PHƯƠNG, nếu không ngày 00:00 ở UTC+7
|
|
776
|
+
// sẽ lùi thành 17:00 hôm trước trong Excel. "yyyy-mm-dd" trần thì new Date() hiểu là UTC nên tách riêng.
|
|
777
|
+
function toExcelDate(v) {
|
|
778
|
+
if (v === null || v === undefined || v === '') return null;
|
|
779
|
+
var m = typeof v === 'string' && /^(\d{4})-(\d{2})-(\d{2})$/.exec(v);
|
|
780
|
+
if (m) return new Date(Date.UTC(+m[1], +m[2] - 1, +m[3]));
|
|
781
|
+
var d = v instanceof Date ? v : new Date(v);
|
|
782
|
+
if (isNaN(d.getTime())) return v;
|
|
783
|
+
return new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds()));
|
|
784
|
+
}
|
|
785
|
+
function htmlToText(v) {
|
|
786
|
+
var s = String(v);
|
|
787
|
+
if (s.indexOf('<') < 0 && s.indexOf('&') < 0) return s;
|
|
788
|
+
// DOMParser không chạy script/handler — innerHTML trên element sống thì <img onerror> VẪN chạy.
|
|
789
|
+
return new DOMParser().parseFromString(s, 'text/html').body.textContent;
|
|
790
|
+
}
|
|
791
|
+
function convertCellValue(col, v) {
|
|
792
|
+
if (v === undefined) return null;
|
|
793
|
+
if (col.type === 'date') return toExcelDate(v);
|
|
794
|
+
if (v === null) return null;
|
|
795
|
+
if (col.type === 'number') return (typeof v === 'string' && v.trim() !== '' && !isNaN(v)) ? Number(v) : v;
|
|
796
|
+
if (col.type === 'html') return htmlToText(v);
|
|
797
|
+
// object/array đưa thẳng vào ExcelJS sẽ bị hiểu nhầm là rich text/hyperlink/formula
|
|
798
|
+
if (typeof v === 'object' && !(v instanceof Date)) return JSON.stringify(v);
|
|
799
|
+
return v;
|
|
800
|
+
}
|
|
801
|
+
function columnRowValues(cols, row) {
|
|
802
|
+
var out = new Array(cols.length);
|
|
803
|
+
for (var c = 0; c < cols.length; c++) out[c] = convertCellValue(cols[c], readColumnValue(row, cols[c].key));
|
|
804
|
+
return out;
|
|
805
|
+
}
|
|
806
|
+
function estimateWidth(col, rows) {
|
|
807
|
+
var w = String(col.header != null ? col.header : col.key).length;
|
|
808
|
+
if (col.type === 'date') w = Math.max(w, String(col.numFmt || 'dd/mm/yyyy').replace(/"/g, '').length);
|
|
809
|
+
else {
|
|
810
|
+
for (var i = 0, n = Math.min(rows.length, 100); i < n; i++) {
|
|
811
|
+
var v = readColumnValue(rows[i], col.key);
|
|
812
|
+
if (v !== null && v !== undefined) w = Math.max(w, String(v).length + (typeof v === 'number' ? 3 : 0));
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
return Math.min(60, Math.max(8, w + 2));
|
|
816
|
+
}
|
|
817
|
+
function setupColumns(worksheet, cols, rows) {
|
|
818
|
+
// key nội bộ theo vị trí: hai cột trùng tiêu đề/trùng field vẫn không đè nhau (dòng ghi bằng mảng)
|
|
819
|
+
worksheet.columns = cols.map(function (c, idx) {
|
|
820
|
+
var def = { header: c.header != null ? String(c.header) : String(c.key), key: '__c' + idx, width: c.width || estimateWidth(c, rows) };
|
|
821
|
+
if (c.numFmt) def.style = { numFmt: c.numFmt };
|
|
822
|
+
return def;
|
|
823
|
+
});
|
|
824
|
+
worksheet.getRow(1).font = { bold: true };
|
|
825
|
+
worksheet.views = [{ state: 'frozen', ySplit: 1 }];
|
|
826
|
+
}
|
|
827
|
+
// Hàng tổng = công thức SUM trên chính các dòng đã xuất (kèm kết quả tính sẵn để trình xem không
|
|
828
|
+
// tính công thức vẫn thấy số) — sửa số trong Excel thì tổng tự cập nhật.
|
|
829
|
+
function appendSumRow(worksheet, cols, rows) {
|
|
830
|
+
if (!rows.length || !cols.some(function (c) { return c.sum; })) return;
|
|
831
|
+
var row = worksheet.addRow(cols.map(function (c, idx) {
|
|
832
|
+
if (!c.sum) return null;
|
|
833
|
+
var total = 0;
|
|
834
|
+
for (var i = 0; i < rows.length; i++) {
|
|
835
|
+
var n = Number(readColumnValue(rows[i], c.key));
|
|
836
|
+
if (!isNaN(n)) total += n;
|
|
837
|
+
}
|
|
838
|
+
var letter = worksheet.getColumn(idx + 1).letter;
|
|
839
|
+
return { formula: 'SUM(' + letter + '2:' + letter + (rows.length + 1) + ')', result: total };
|
|
840
|
+
}));
|
|
841
|
+
row.font = { bold: true };
|
|
842
|
+
cols.forEach(function (c, idx) {
|
|
843
|
+
if (typeof c.sum === 'string') row.getCell(idx + 1).numFmt = c.sum;
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// Thanh % mặc định, tự tạo/tự dọn bằng DOM thuần (CSS thủ công, không dùng v-dialog/framework nào)
|
|
848
|
+
// — fix cố định giữa màn hình, không phụ thuộc Vue nên dùng chung được cho cả V2 và V3.
|
|
849
|
+
function createProgressUI() {
|
|
850
|
+
var STYLE_ID = 'fui-json-to-excel-progress-style';
|
|
851
|
+
// Luôn ghi đè nội dung CSS mới nhất vào thẻ <style>, kể cả khi tag đã tồn tại từ lần
|
|
852
|
+
// export trước trong cùng phiên trang (SPA không reload) — nếu chỉ tạo-1-lần-rồi-bỏ-qua
|
|
853
|
+
// như trước, CSS cũ bị kẹt lại vĩnh viễn trong DOM, sửa file JS trên server sau đó
|
|
854
|
+
// không có tác dụng cho tới khi người dùng F5 lại cả trang.
|
|
855
|
+
var style = document.getElementById(STYLE_ID);
|
|
856
|
+
if (!style) {
|
|
857
|
+
style = document.createElement('style');
|
|
858
|
+
style.id = STYLE_ID;
|
|
859
|
+
document.head.appendChild(style);
|
|
860
|
+
}
|
|
861
|
+
style.innerHTML = '.fui-xlsx-progress{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:350px;' +
|
|
862
|
+
'box-sizing:border-box;z-index:99999;background:#fff;border-radius:8px;box-shadow:0 12px 40px rgba(0,0,0,.35);' +
|
|
863
|
+
'padding: 30px 30px 40px 30px;font:14px/1.4 inherit;transition:opacity .3s}' +
|
|
864
|
+
'.fui-xlsx-progress .fui-xlsx-label{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;color:#333}' +
|
|
865
|
+
'.fui-xlsx-progress .fui-xlsx-label span:first-child{font-size:15px;font-weight:600}' +
|
|
866
|
+
'.fui-xlsx-progress .fui-xlsx-percent{font-size:22px;font-weight:700;color:#1976d2}' +
|
|
867
|
+
'.fui-xlsx-progress .fui-xlsx-track{height:8px;border-radius:4px;background:#e0e0e0;overflow:hidden}' +
|
|
868
|
+
'.fui-xlsx-progress .fui-xlsx-bar{height:100%;width:0;background:#1976d2;transition:width .2s ease-out}';
|
|
869
|
+
var el = document.createElement('div');
|
|
870
|
+
el.className = 'fui-xlsx-progress';
|
|
871
|
+
el.innerHTML = '<div class="fui-xlsx-label"><span>Đang xuất Excel...</span><span class="fui-xlsx-percent">0%</span></div>' +
|
|
872
|
+
'<div class="fui-xlsx-track"><div class="fui-xlsx-bar"></div></div>';
|
|
873
|
+
document.body.appendChild(el);
|
|
874
|
+
var percentEl = el.querySelector('.fui-xlsx-percent');
|
|
875
|
+
var barEl = el.querySelector('.fui-xlsx-bar');
|
|
876
|
+
var labelEl = el.querySelector('.fui-xlsx-label span');
|
|
877
|
+
var destroyed = false;
|
|
878
|
+
return {
|
|
879
|
+
update: function (info) {
|
|
880
|
+
if (destroyed) return;
|
|
881
|
+
percentEl.textContent = info.percent + '%';
|
|
882
|
+
barEl.style.width = info.percent + '%';
|
|
883
|
+
if (info.phase === 'finalizing') labelEl.textContent = 'Đang tạo file...';
|
|
884
|
+
if (info.phase === 'done') labelEl.textContent = 'Hoàn tất';
|
|
885
|
+
if (info.phase === 'error') labelEl.textContent = 'Xuất thất bại';
|
|
886
|
+
},
|
|
887
|
+
destroy: function () {
|
|
888
|
+
if (destroyed) return;
|
|
889
|
+
destroyed = true;
|
|
890
|
+
el.style.opacity = '0';
|
|
891
|
+
setTimeout(function () { if (el.parentNode) el.parentNode.removeChild(el); }, 500);
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
//===========================================================================================
|
|
897
|
+
function copyToClipboard(textToCopy) {
|
|
898
|
+
if (navigator.clipboard && window.isSecureContext) {
|
|
899
|
+
console.log("Navigator copy: ", textToCopy);
|
|
900
|
+
return navigator.clipboard.writeText(textToCopy);
|
|
901
|
+
} else {
|
|
902
|
+
let textArea = document.createElement("div");
|
|
903
|
+
textArea.innerText = textToCopy;
|
|
904
|
+
textArea.style.position = "fixed";
|
|
905
|
+
textArea.style.left = "0px";
|
|
906
|
+
textArea.style.top = "-999999px";
|
|
907
|
+
document.body.appendChild(textArea);
|
|
908
|
+
|
|
909
|
+
var range = document.createRange();
|
|
910
|
+
range.selectNode(textArea);
|
|
911
|
+
window.getSelection().removeAllRanges();
|
|
912
|
+
window.getSelection().addRange(range);
|
|
913
|
+
return new Promise((res, rej) => {
|
|
914
|
+
document.execCommand('copy') ? res() : rej();
|
|
915
|
+
console.log("Command copy: ", textToCopy);
|
|
916
|
+
textArea.remove();
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
};
|
|
920
|
+
//===========================================================================================
|
|
921
|
+
function fileNameClear(fname) {
|
|
922
|
+
if (fname) return fname.replace(/[\!\@\#\$\^\&\%\*\(\)\+\=\-\[\]\\\/\{\}\|\:\<\>\?\,\.\`\"\'\~\;]/gi, '');
|
|
923
|
+
return fname;
|
|
924
|
+
}
|
|
925
|
+
//===========================================================================================
|
|
926
|
+
function hashCode(s) {
|
|
927
|
+
var h = 0,
|
|
928
|
+
l = s.length,
|
|
929
|
+
i = 0;
|
|
930
|
+
if (l > 0)
|
|
931
|
+
while (i < l)
|
|
932
|
+
h = (h << 5) - h + s.charCodeAt(i++) | 0;
|
|
933
|
+
return h;
|
|
934
|
+
};
|
|
935
|
+
//===========================================================================================
|
|
936
|
+
function getCookie(cname) {
|
|
937
|
+
var name = cname + "=";
|
|
938
|
+
var ca = document.cookie.split(';');
|
|
939
|
+
for (var i = 0; i < ca.length; i++) {
|
|
940
|
+
var c = ca[i];
|
|
941
|
+
while (c.charAt(0) == ' ') {
|
|
942
|
+
c = c.substring(1);
|
|
943
|
+
}
|
|
944
|
+
if (c.indexOf(name) == 0) {
|
|
945
|
+
return c.substring(name.length, c.length);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
return "";
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
function setCookie(name, value, days, domain) {
|
|
952
|
+
var expires = "";
|
|
953
|
+
var domainString = "";
|
|
954
|
+
if (days) {
|
|
955
|
+
var date = new Date();
|
|
956
|
+
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
|
957
|
+
expires = "; expires=" + date.toUTCString();
|
|
958
|
+
}
|
|
959
|
+
if (domain) {
|
|
960
|
+
domainString = "; domain=" + domain
|
|
961
|
+
}
|
|
962
|
+
document.cookie = name + "=" + (value || "") + expires + domainString + ";path=/";
|
|
963
|
+
}
|
|
964
|
+
//===================================================================================
|
|
965
|
+
function logout(cookiesName, domain, url) {
|
|
966
|
+
if (!cookiesName) cookiesName = 'awt';
|
|
967
|
+
if (typeof domain == "undefined" && vueData.v_Set.authDomain) domain = vueData.v_Set.authDomain;
|
|
968
|
+
|
|
969
|
+
setCookie(cookiesName, '', 0, domain);
|
|
970
|
+
|
|
971
|
+
if (!url) location.assign(location.href);
|
|
972
|
+
else location.assign(url);
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
//===========================================================================================
|
|
976
|
+
// WEBSOCKET
|
|
977
|
+
//===========================================================================================
|
|
978
|
+
function webSocketJoinGroup(groupObj, timeout, callBackFunc) {
|
|
979
|
+
groupObj = _.cloneDeep(groupObj)
|
|
980
|
+
_.forEach(groupObj, function (v, k) {
|
|
981
|
+
if (!vueData.webSocket.hasOwnProperty(k)) {
|
|
982
|
+
Vue.set(vueData.webSocket, k, v);
|
|
983
|
+
}
|
|
984
|
+
});
|
|
985
|
+
|
|
986
|
+
setTimeout(function () {
|
|
987
|
+
try {
|
|
988
|
+
$.connection.hub.start().done(function () {
|
|
989
|
+
_.forEach(groupObj, function (v, k) {
|
|
990
|
+
$.connection.chatHub.server.joinGroup(k);
|
|
991
|
+
});
|
|
992
|
+
if (_.isFunction(callBackFunc)) callBackFunc();
|
|
993
|
+
}).fail(function (e) {
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
catch (err) {
|
|
997
|
+
|
|
998
|
+
}
|
|
999
|
+
}, (timeout ? timeout : 0));
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
function webSocketConnection(wsURL, WS, wsState) {
|
|
1003
|
+
$.connection.hub.url = wsURL;
|
|
1004
|
+
|
|
1005
|
+
//=====================================================================
|
|
1006
|
+
$.connection.hub.stateChanged(function (change) {
|
|
1007
|
+
wsState.code = change.newState;
|
|
1008
|
+
if (change.newState === $.signalR.connectionState.reconnecting) {
|
|
1009
|
+
console.log("Websocket Reconnecting")
|
|
1010
|
+
wsState.text = "Reconnecting";
|
|
1011
|
+
}
|
|
1012
|
+
else if (change.newState === $.signalR.connectionState.connected) {
|
|
1013
|
+
console.log("Websocket Connected")
|
|
1014
|
+
wsState.text = "Connected";
|
|
1015
|
+
}
|
|
1016
|
+
else if (change.newState === $.signalR.connectionState.disconnected) {
|
|
1017
|
+
console.log("Websocket Disconnected")
|
|
1018
|
+
wsState.text = "Disconnected";
|
|
1019
|
+
|
|
1020
|
+
webSocketJoinGroup(WS, 5000);
|
|
1021
|
+
}
|
|
1022
|
+
});
|
|
1023
|
+
|
|
1024
|
+
$.connection.chatHub.client.onError = function (Mess) {
|
|
1025
|
+
console.log('Server error: ', Mess);
|
|
1026
|
+
};
|
|
1027
|
+
|
|
1028
|
+
$.connection.chatHub.client.onJoinGroup = function (mess) {
|
|
1029
|
+
console.log("Group", mess)
|
|
1030
|
+
};
|
|
1031
|
+
|
|
1032
|
+
$.connection.chatHub.client.messageReceived = function (group, message) {
|
|
1033
|
+
console.log("Message Received: ", group, message)
|
|
1034
|
+
if (_.isArray(WS[group])) WS[group].push(message);
|
|
1035
|
+
else WS[group] = message;
|
|
1036
|
+
};
|
|
1037
|
+
|
|
1038
|
+
$.connection.chatHub.client.messageSendLog = function (group, message) {
|
|
1039
|
+
console.log("Message Sent: ", group, message)
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
function webSocket_Send(obj) {
|
|
1044
|
+
if (_.isPlainObject(obj.data)) $.connection.chatHub.server.messageSendObject(obj.group, obj.data);
|
|
1045
|
+
else if (_.isArray(obj.data)) $.connection.chatHub.server.messageSendArray(obj.group, obj.data);
|
|
1046
|
+
else $.connection.chatHub.server.messageSend(obj.group, obj.data);
|
|
1047
|
+
}
|
|
1048
|
+
//===========================================================================================
|
|
1049
|
+
//===========================================================================================
|
|
1050
|
+
//===========================================================================================
|
|
1051
|
+
//===========================================================================================
|