@fui-org/fui-cli 0.2.0 → 0.3.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.
Files changed (212) hide show
  1. package/README.md +13 -1
  2. package/dist/fui.js +160 -43
  3. package/package.json +4 -2
  4. package/skills/fui/SKILL.md +131 -66
  5. package/skills/fui-skill/README.md +112 -0
  6. package/skills/fui-skill/SKILL.md +269 -0
  7. package/skills/fui-skill/assets/projectdefaultstyle.css +518 -0
  8. package/skills/fui-skill/design-md/airbnb/DESIGN.md +545 -0
  9. package/skills/fui-skill/design-md/airbnb/README.md +5 -0
  10. package/skills/fui-skill/design-md/airtable/DESIGN.md +554 -0
  11. package/skills/fui-skill/design-md/airtable/README.md +5 -0
  12. package/skills/fui-skill/design-md/apple/DESIGN.md +562 -0
  13. package/skills/fui-skill/design-md/apple/README.md +5 -0
  14. package/skills/fui-skill/design-md/asu/DESIGN.md +179 -0
  15. package/skills/fui-skill/design-md/asu/README.md +107 -0
  16. package/skills/fui-skill/design-md/binance/DESIGN.md +634 -0
  17. package/skills/fui-skill/design-md/binance/README.md +5 -0
  18. package/skills/fui-skill/design-md/bmw/DESIGN.md +544 -0
  19. package/skills/fui-skill/design-md/bmw/README.md +5 -0
  20. package/skills/fui-skill/design-md/bmw-m/DESIGN.md +503 -0
  21. package/skills/fui-skill/design-md/bmw-m/README.md +5 -0
  22. package/skills/fui-skill/design-md/bugatti/DESIGN.md +454 -0
  23. package/skills/fui-skill/design-md/bugatti/README.md +5 -0
  24. package/skills/fui-skill/design-md/cal/DESIGN.md +542 -0
  25. package/skills/fui-skill/design-md/cal/README.md +5 -0
  26. package/skills/fui-skill/design-md/claude/DESIGN.md +589 -0
  27. package/skills/fui-skill/design-md/claude/README.md +5 -0
  28. package/skills/fui-skill/design-md/clay/DESIGN.md +541 -0
  29. package/skills/fui-skill/design-md/clay/README.md +5 -0
  30. package/skills/fui-skill/design-md/clickhouse/DESIGN.md +544 -0
  31. package/skills/fui-skill/design-md/clickhouse/README.md +5 -0
  32. package/skills/fui-skill/design-md/cohere/DESIGN.md +451 -0
  33. package/skills/fui-skill/design-md/cohere/README.md +5 -0
  34. package/skills/fui-skill/design-md/coinbase/DESIGN.md +570 -0
  35. package/skills/fui-skill/design-md/coinbase/README.md +5 -0
  36. package/skills/fui-skill/design-md/composio/DESIGN.md +506 -0
  37. package/skills/fui-skill/design-md/composio/README.md +5 -0
  38. package/skills/fui-skill/design-md/cursor/DESIGN.md +537 -0
  39. package/skills/fui-skill/design-md/cursor/README.md +5 -0
  40. package/skills/fui-skill/design-md/elevenlabs/DESIGN.md +504 -0
  41. package/skills/fui-skill/design-md/elevenlabs/README.md +5 -0
  42. package/skills/fui-skill/design-md/expo/DESIGN.md +526 -0
  43. package/skills/fui-skill/design-md/expo/README.md +5 -0
  44. package/skills/fui-skill/design-md/ferrari/DESIGN.md +531 -0
  45. package/skills/fui-skill/design-md/ferrari/README.md +5 -0
  46. package/skills/fui-skill/design-md/figma/DESIGN.md +578 -0
  47. package/skills/fui-skill/design-md/figma/README.md +5 -0
  48. package/skills/fui-skill/design-md/framer/DESIGN.md +544 -0
  49. package/skills/fui-skill/design-md/framer/README.md +5 -0
  50. package/skills/fui-skill/design-md/fui/DESIGN.md +532 -0
  51. package/skills/fui-skill/design-md/hashicorp/DESIGN.md +575 -0
  52. package/skills/fui-skill/design-md/hashicorp/README.md +5 -0
  53. package/skills/fui-skill/design-md/ibm/DESIGN.md +550 -0
  54. package/skills/fui-skill/design-md/ibm/README.md +5 -0
  55. package/skills/fui-skill/design-md/intercom/DESIGN.md +546 -0
  56. package/skills/fui-skill/design-md/intercom/README.md +5 -0
  57. package/skills/fui-skill/design-md/kraken/DESIGN.md +125 -0
  58. package/skills/fui-skill/design-md/kraken/README.md +5 -0
  59. package/skills/fui-skill/design-md/lamborghini/DESIGN.md +288 -0
  60. package/skills/fui-skill/design-md/lamborghini/README.md +5 -0
  61. package/skills/fui-skill/design-md/linear.app/DESIGN.md +548 -0
  62. package/skills/fui-skill/design-md/linear.app/README.md +5 -0
  63. package/skills/fui-skill/design-md/lovable/DESIGN.md +298 -0
  64. package/skills/fui-skill/design-md/lovable/README.md +5 -0
  65. package/skills/fui-skill/design-md/mastercard/DESIGN.md +365 -0
  66. package/skills/fui-skill/design-md/mastercard/README.md +5 -0
  67. package/skills/fui-skill/design-md/meta/DESIGN.md +683 -0
  68. package/skills/fui-skill/design-md/meta/README.md +5 -0
  69. package/skills/fui-skill/design-md/minimax/DESIGN.md +746 -0
  70. package/skills/fui-skill/design-md/minimax/README.md +5 -0
  71. package/skills/fui-skill/design-md/mintlify/DESIGN.md +852 -0
  72. package/skills/fui-skill/design-md/mintlify/README.md +5 -0
  73. package/skills/fui-skill/design-md/miro/DESIGN.md +825 -0
  74. package/skills/fui-skill/design-md/miro/README.md +5 -0
  75. package/skills/fui-skill/design-md/mistral.ai/DESIGN.md +773 -0
  76. package/skills/fui-skill/design-md/mistral.ai/README.md +5 -0
  77. package/skills/fui-skill/design-md/mongodb/DESIGN.md +767 -0
  78. package/skills/fui-skill/design-md/mongodb/README.md +5 -0
  79. package/skills/fui-skill/design-md/nike/DESIGN.md +575 -0
  80. package/skills/fui-skill/design-md/nike/README.md +5 -0
  81. package/skills/fui-skill/design-md/notion/DESIGN.md +821 -0
  82. package/skills/fui-skill/design-md/notion/README.md +5 -0
  83. package/skills/fui-skill/design-md/nvidia/DESIGN.md +640 -0
  84. package/skills/fui-skill/design-md/nvidia/README.md +5 -0
  85. package/skills/fui-skill/design-md/ollama/DESIGN.md +539 -0
  86. package/skills/fui-skill/design-md/ollama/README.md +5 -0
  87. package/skills/fui-skill/design-md/opencode.ai/DESIGN.md +521 -0
  88. package/skills/fui-skill/design-md/opencode.ai/README.md +5 -0
  89. package/skills/fui-skill/design-md/pinterest/DESIGN.md +597 -0
  90. package/skills/fui-skill/design-md/pinterest/README.md +5 -0
  91. package/skills/fui-skill/design-md/playstation/DESIGN.md +661 -0
  92. package/skills/fui-skill/design-md/playstation/README.md +5 -0
  93. package/skills/fui-skill/design-md/posthog/DESIGN.md +690 -0
  94. package/skills/fui-skill/design-md/posthog/README.md +5 -0
  95. package/skills/fui-skill/design-md/raycast/DESIGN.md +669 -0
  96. package/skills/fui-skill/design-md/raycast/README.md +5 -0
  97. package/skills/fui-skill/design-md/renault/DESIGN.md +589 -0
  98. package/skills/fui-skill/design-md/renault/README.md +5 -0
  99. package/skills/fui-skill/design-md/replicate/DESIGN.md +616 -0
  100. package/skills/fui-skill/design-md/replicate/README.md +5 -0
  101. package/skills/fui-skill/design-md/resend/DESIGN.md +585 -0
  102. package/skills/fui-skill/design-md/resend/README.md +5 -0
  103. package/skills/fui-skill/design-md/revolut/DESIGN.md +636 -0
  104. package/skills/fui-skill/design-md/revolut/README.md +5 -0
  105. package/skills/fui-skill/design-md/runwayml/DESIGN.md +244 -0
  106. package/skills/fui-skill/design-md/runwayml/README.md +5 -0
  107. package/skills/fui-skill/design-md/sanity/DESIGN.md +357 -0
  108. package/skills/fui-skill/design-md/sanity/README.md +5 -0
  109. package/skills/fui-skill/design-md/sentry/DESIGN.md +551 -0
  110. package/skills/fui-skill/design-md/sentry/README.md +5 -0
  111. package/skills/fui-skill/design-md/shopify/DESIGN.md +516 -0
  112. package/skills/fui-skill/design-md/shopify/README.md +5 -0
  113. package/skills/fui-skill/design-md/slack/DESIGN.md +482 -0
  114. package/skills/fui-skill/design-md/spacex/DESIGN.md +363 -0
  115. package/skills/fui-skill/design-md/spacex/README.md +5 -0
  116. package/skills/fui-skill/design-md/spotify/DESIGN.md +246 -0
  117. package/skills/fui-skill/design-md/spotify/README.md +5 -0
  118. package/skills/fui-skill/design-md/starbucks/DESIGN.md +580 -0
  119. package/skills/fui-skill/design-md/starbucks/README.md +5 -0
  120. package/skills/fui-skill/design-md/stripe/DESIGN.md +487 -0
  121. package/skills/fui-skill/design-md/stripe/README.md +5 -0
  122. package/skills/fui-skill/design-md/supabase/DESIGN.md +462 -0
  123. package/skills/fui-skill/design-md/supabase/README.md +5 -0
  124. package/skills/fui-skill/design-md/superhuman/DESIGN.md +448 -0
  125. package/skills/fui-skill/design-md/superhuman/README.md +5 -0
  126. package/skills/fui-skill/design-md/tesla/DESIGN.md +286 -0
  127. package/skills/fui-skill/design-md/tesla/README.md +5 -0
  128. package/skills/fui-skill/design-md/theverge/DESIGN.md +339 -0
  129. package/skills/fui-skill/design-md/theverge/README.md +5 -0
  130. package/skills/fui-skill/design-md/together.ai/DESIGN.md +633 -0
  131. package/skills/fui-skill/design-md/together.ai/README.md +5 -0
  132. package/skills/fui-skill/design-md/uber/DESIGN.md +636 -0
  133. package/skills/fui-skill/design-md/uber/README.md +5 -0
  134. package/skills/fui-skill/design-md/vercel/DESIGN.md +736 -0
  135. package/skills/fui-skill/design-md/vercel/README.md +5 -0
  136. package/skills/fui-skill/design-md/vodafone/DESIGN.md +538 -0
  137. package/skills/fui-skill/design-md/vodafone/README.md +5 -0
  138. package/skills/fui-skill/design-md/voltagent/DESIGN.md +521 -0
  139. package/skills/fui-skill/design-md/voltagent/README.md +5 -0
  140. package/skills/fui-skill/design-md/warp/DESIGN.md +526 -0
  141. package/skills/fui-skill/design-md/warp/README.md +5 -0
  142. package/skills/fui-skill/design-md/webflow/DESIGN.md +588 -0
  143. package/skills/fui-skill/design-md/webflow/README.md +5 -0
  144. package/skills/fui-skill/design-md/wired/DESIGN.md +497 -0
  145. package/skills/fui-skill/design-md/wired/README.md +5 -0
  146. package/skills/fui-skill/design-md/wise/DESIGN.md +544 -0
  147. package/skills/fui-skill/design-md/wise/README.md +5 -0
  148. package/skills/fui-skill/design-md/x.ai/DESIGN.md +465 -0
  149. package/skills/fui-skill/design-md/x.ai/README.md +5 -0
  150. package/skills/fui-skill/design-md/zapier/DESIGN.md +537 -0
  151. package/skills/fui-skill/design-md/zapier/README.md +5 -0
  152. package/skills/fui-skill/examples/component.vue +162 -0
  153. package/skills/fui-skill/examples/f-table-patterns.json +331 -0
  154. package/skills/fui-skill/examples/module-patterns.json +973 -0
  155. package/skills/fui-skill/examples/project-patterns.json +222 -0
  156. package/skills/fui-skill/metadata.json +75 -0
  157. package/skills/fui-skill/references/INDEX.md +144 -0
  158. package/skills/fui-skill/references/advanced-techniques.md +160 -0
  159. package/skills/fui-skill/references/coding-standards.md +112 -0
  160. package/skills/fui-skill/references/component-design.md +455 -0
  161. package/skills/fui-skill/references/component-quickref.md +77 -0
  162. package/skills/fui-skill/references/component-table.md +276 -0
  163. package/skills/fui-skill/references/components-dialog.md +192 -0
  164. package/skills/fui-skill/references/components-display.md +147 -0
  165. package/skills/fui-skill/references/components-echart.md +391 -0
  166. package/skills/fui-skill/references/components-input.md +359 -0
  167. package/skills/fui-skill/references/controls-patterns.md +847 -0
  168. package/skills/fui-skill/references/controls-styling-vocabulary.md +140 -0
  169. package/skills/fui-skill/references/db-table-design.md +77 -0
  170. package/skills/fui-skill/references/db-workflow.md +504 -0
  171. package/skills/fui-skill/references/default-function.md +415 -0
  172. package/skills/fui-skill/references/design-modes.md +85 -0
  173. package/skills/fui-skill/references/echart-templates.md +481 -0
  174. package/skills/fui-skill/references/fastproject.md +97 -0
  175. package/skills/fui-skill/references/fsheet.md +218 -0
  176. package/skills/fui-skill/references/fullstack-workflow.md +351 -0
  177. package/skills/fui-skill/references/module-data-patterns.md +126 -0
  178. package/skills/fui-skill/references/module-json-anatomy.md +137 -0
  179. package/skills/fui-skill/references/module-structure.md +260 -0
  180. package/skills/fui-skill/references/new-session.md +108 -0
  181. package/skills/fui-skill/references/pdfmake.md +60 -0
  182. package/skills/fui-skill/references/permission-system.md +169 -0
  183. package/skills/fui-skill/references/platform-architecture.md +294 -0
  184. package/skills/fui-skill/references/project-config.md +335 -0
  185. package/skills/fui-skill/references/project-provisioning.md +383 -0
  186. package/skills/fui-skill/references/script-map.md +296 -0
  187. package/skills/fui-skill/references/sql-clr-functions.md +224 -0
  188. package/skills/fui-skill/references/system-design.md +116 -0
  189. package/skills/fui-skill/references/tapi-file-api.md +191 -0
  190. package/skills/fui-skill/references/tapi-permission-patterns.md +158 -0
  191. package/skills/fui-skill/references/tapi-reference.md +549 -0
  192. package/skills/fui-skill/references/tools-registry.md +460 -0
  193. package/skills/fui-skill/references/ui-crosswindow-patterns.md +317 -0
  194. package/skills/fui-skill/references/ui-dialog-patterns.md +229 -0
  195. package/skills/fui-skill/references/ui-layout-patterns.md +176 -0
  196. package/skills/fui-skill/references/ui-patterns.md +315 -0
  197. package/skills/fui-skill/references/ui-screenshot-review.md +94 -0
  198. package/skills/fui-skill/references/ui-table-cell-patterns.md +316 -0
  199. package/skills/fui-skill/references/ui-templates.md +29 -0
  200. package/skills/fui-skill/references/verification.md +246 -0
  201. package/skills/fui-skill/references/watcher-patterns.md +196 -0
  202. package/skills/fui-skill/references/websocket-realtime.md +254 -0
  203. package/skills/fui-skill/scripts/component-3.0.js +2549 -0
  204. package/skills/fui-skill/scripts/component.js +3142 -0
  205. package/skills/fui-skill/scripts/componentTable-3.0.js +909 -0
  206. package/skills/fui-skill/scripts/componentTable.js +769 -0
  207. package/skills/fui-skill/scripts/defaultfunction-3.0.js +781 -0
  208. package/skills/fui-skill/scripts/defaultfunction.js +966 -0
  209. package/skills/fui-skill/scripts/fastproject-3.0.js +870 -0
  210. package/skills/fui-skill/scripts/fastproject.js +828 -0
  211. package/skills/fui-skill/scripts/fechart.js +890 -0
  212. package/skills/fui-skill/scripts/fsheet.js +1330 -0
@@ -0,0 +1,966 @@
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: '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;OK&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;',
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: '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Close&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;',
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
+ onProgress: null, // optional: function({percent, processedRows, totalRows, phase, sheetName}) - phase: 'building'|'finalizing'|'done'|'error'
664
+ 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
665
+ }
666
+ Object.assign(props, inputObj);
667
+ // 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
668
+ var sheetList;
669
+ if (props.sheets && Array.isArray(props.sheets)) {
670
+ if (!props.sheets.length) { showMessage({ title: "Sheets data is empty" }); return; }
671
+ sheetList = props.sheets.filter(function (s) { return s.data && s.data.length; });
672
+ if (!sheetList.length) { showMessage({ title: "Sheets data is empty" }); return; }
673
+ } else {
674
+ if (!props.data || !props.data.length) { showMessage({ title: "Data Null" }); return; }
675
+ sheetList = [{ name: props.worksheet, data: props.data }];
676
+ }
677
+
678
+ var totalRows = sheetList.reduce(function (sum, s) { return sum + s.data.length; }, 0);
679
+ var processedRows = 0;
680
+ // 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ù
681
+ // data ít hay nhiều (không còn đánh đổi mượt-vs-nhanh như chunk cố định) — data nhỏ thì
682
+ // chặn dưới 50 dòng/đợt để khỏi nhường luồng vô ích cho vài chục dòng.
683
+ var CHUNK_SIZE = Math.max(50, Math.ceil(totalRows * 0.02));
684
+ var progressUI = props.showProgress ? createProgressUI() : null;
685
+
686
+ function report(phase, sheetName) {
687
+ // 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
688
+ // nén file (writeBuffer) nên "finalizing" chỉ báo mốc cố định 98%, không nội suy %.
689
+ var percent = totalRows ? Math.min(95, Math.floor(processedRows / totalRows * 95)) : 0;
690
+ if (phase === 'finalizing') percent = 98;
691
+ if (phase === 'done') percent = 100;
692
+ if (typeof props.onProgress === 'function') {
693
+ try { props.onProgress({ percent: percent, processedRows: processedRows, totalRows: totalRows, phase: phase, sheetName: sheetName }); }
694
+ catch (e) { console.error('jsonToExcel onProgress error', e); }
695
+ }
696
+ if (progressUI) {
697
+ progressUI.update({ percent: percent, phase: phase });
698
+ if (phase === 'done' || phase === 'error') progressUI.destroy();
699
+ }
700
+ }
701
+
702
+ var workbook = new ExcelJS.Workbook();
703
+
704
+ function buildSheet(sheetIndex) {
705
+ if (sheetIndex >= sheetList.length) { finalize(); return; }
706
+ var sheetCfg = sheetList[sheetIndex];
707
+ var worksheet = workbook.addWorksheet(sheetCfg.name || 'Sheet');
708
+ // 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)
709
+ worksheet.columns = Object.keys(sheetCfg.data[0]).map(function (k) { return { header: k, key: k }; });
710
+
711
+ var rows = sheetCfg.data;
712
+ var i = 0;
713
+ function nextChunk() {
714
+ try {
715
+ var end = Math.min(i + CHUNK_SIZE, rows.length);
716
+ for (var r = i; r < end; r++) { worksheet.addRow(rows[r]); }
717
+ processedRows += (end - i);
718
+ i = end;
719
+ report('building', sheetCfg.name);
720
+ } catch (err) {
721
+ console.error('jsonToExcel build error', err);
722
+ report('error', sheetCfg.name);
723
+ showMessage({ title: 'Xuất Excel thất bại' });
724
+ return;
725
+ }
726
+ if (i < rows.length) {
727
+ // 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
728
+ // trang bị "Not Responding" khi data lớn, đồng thời cho phép % cập nhật lên màn hình.
729
+ (window.requestAnimationFrame || function (cb) { setTimeout(cb, 0); })(nextChunk);
730
+ } else {
731
+ buildSheet(sheetIndex + 1);
732
+ }
733
+ }
734
+ nextChunk();
735
+ }
736
+
737
+ function finalize() {
738
+ report('finalizing');
739
+ workbook.xlsx.writeBuffer().then(function (data) {
740
+ var blobType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
741
+ var blob = new Blob([data], { type: blobType });
742
+ var a = document.createElement('a');
743
+ document.body.appendChild(a);
744
+ a.style = 'display: none';
745
+ var url = window.URL.createObjectURL(blob);
746
+ a.href = url;
747
+ a.download = props.filename;
748
+ a.click();
749
+ window.URL.revokeObjectURL(url);
750
+ a.remove();
751
+ report('done');
752
+ }).catch(function (err) {
753
+ console.error('jsonToExcel writeBuffer error', err);
754
+ report('error');
755
+ showMessage({ title: 'Xuất Excel thất bại' });
756
+ });
757
+ }
758
+
759
+ buildSheet(0);
760
+ }
761
+
762
+ // 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)
763
+ // — 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.
764
+ function createProgressUI() {
765
+ var STYLE_ID = 'fui-json-to-excel-progress-style';
766
+ // 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
767
+ // 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
768
+ // như trước, CSS cũ bị kẹt lại vĩnh viễn trong DOM, sửa file JS trên server sau đó
769
+ // không có tác dụng cho tới khi người dùng F5 lại cả trang.
770
+ var style = document.getElementById(STYLE_ID);
771
+ if (!style) {
772
+ style = document.createElement('style');
773
+ style.id = STYLE_ID;
774
+ document.head.appendChild(style);
775
+ }
776
+ style.innerHTML = '.fui-xlsx-progress{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:350px;' +
777
+ 'box-sizing:border-box;z-index:99999;background:#fff;border-radius:8px;box-shadow:0 12px 40px rgba(0,0,0,.35);' +
778
+ 'padding: 30px 30px 40px 30px;font:14px/1.4 inherit;transition:opacity .3s}' +
779
+ '.fui-xlsx-progress .fui-xlsx-label{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;color:#333}' +
780
+ '.fui-xlsx-progress .fui-xlsx-label span:first-child{font-size:15px;font-weight:600}' +
781
+ '.fui-xlsx-progress .fui-xlsx-percent{font-size:22px;font-weight:700;color:#1976d2}' +
782
+ '.fui-xlsx-progress .fui-xlsx-track{height:8px;border-radius:4px;background:#e0e0e0;overflow:hidden}' +
783
+ '.fui-xlsx-progress .fui-xlsx-bar{height:100%;width:0;background:#1976d2;transition:width .2s ease-out}';
784
+ var el = document.createElement('div');
785
+ el.className = 'fui-xlsx-progress';
786
+ el.innerHTML = '<div class="fui-xlsx-label"><span>Đang xuất Excel...</span><span class="fui-xlsx-percent">0%</span></div>' +
787
+ '<div class="fui-xlsx-track"><div class="fui-xlsx-bar"></div></div>';
788
+ document.body.appendChild(el);
789
+ var percentEl = el.querySelector('.fui-xlsx-percent');
790
+ var barEl = el.querySelector('.fui-xlsx-bar');
791
+ var labelEl = el.querySelector('.fui-xlsx-label span');
792
+ var destroyed = false;
793
+ return {
794
+ update: function (info) {
795
+ if (destroyed) return;
796
+ percentEl.textContent = info.percent + '%';
797
+ barEl.style.width = info.percent + '%';
798
+ if (info.phase === 'finalizing') labelEl.textContent = 'Đang tạo file...';
799
+ if (info.phase === 'done') labelEl.textContent = 'Hoàn tất';
800
+ if (info.phase === 'error') labelEl.textContent = 'Xuất thất bại';
801
+ },
802
+ destroy: function () {
803
+ if (destroyed) return;
804
+ destroyed = true;
805
+ el.style.opacity = '0';
806
+ setTimeout(function () { if (el.parentNode) el.parentNode.removeChild(el); }, 500);
807
+ }
808
+ };
809
+ }
810
+ }
811
+ //===========================================================================================
812
+ function copyToClipboard(textToCopy) {
813
+ if (navigator.clipboard && window.isSecureContext) {
814
+ console.log("Navigator copy: ", textToCopy);
815
+ return navigator.clipboard.writeText(textToCopy);
816
+ } else {
817
+ let textArea = document.createElement("div");
818
+ textArea.innerText = textToCopy;
819
+ textArea.style.position = "fixed";
820
+ textArea.style.left = "0px";
821
+ textArea.style.top = "-999999px";
822
+ document.body.appendChild(textArea);
823
+
824
+ var range = document.createRange();
825
+ range.selectNode(textArea);
826
+ window.getSelection().removeAllRanges();
827
+ window.getSelection().addRange(range);
828
+ return new Promise((res, rej) => {
829
+ document.execCommand('copy') ? res() : rej();
830
+ console.log("Command copy: ", textToCopy);
831
+ textArea.remove();
832
+ });
833
+ }
834
+ };
835
+ //===========================================================================================
836
+ function fileNameClear(fname) {
837
+ if (fname) return fname.replace(/[\!\@\#\$\^\&\%\*\(\)\+\=\-\[\]\\\/\{\}\|\:\<\>\?\,\.\`\"\'\~\;]/gi, '');
838
+ return fname;
839
+ }
840
+ //===========================================================================================
841
+ function hashCode(s) {
842
+ var h = 0,
843
+ l = s.length,
844
+ i = 0;
845
+ if (l > 0)
846
+ while (i < l)
847
+ h = (h << 5) - h + s.charCodeAt(i++) | 0;
848
+ return h;
849
+ };
850
+ //===========================================================================================
851
+ function getCookie(cname) {
852
+ var name = cname + "=";
853
+ var ca = document.cookie.split(';');
854
+ for (var i = 0; i < ca.length; i++) {
855
+ var c = ca[i];
856
+ while (c.charAt(0) == ' ') {
857
+ c = c.substring(1);
858
+ }
859
+ if (c.indexOf(name) == 0) {
860
+ return c.substring(name.length, c.length);
861
+ }
862
+ }
863
+ return "";
864
+ }
865
+
866
+ function setCookie(name, value, days, domain) {
867
+ var expires = "";
868
+ var domainString = "";
869
+ if (days) {
870
+ var date = new Date();
871
+ date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
872
+ expires = "; expires=" + date.toUTCString();
873
+ }
874
+ if (domain) {
875
+ domainString = "; domain=" + domain
876
+ }
877
+ document.cookie = name + "=" + (value || "") + expires + domainString + ";path=/";
878
+ }
879
+ //===================================================================================
880
+ function logout(cookiesName, domain, url) {
881
+ if (!cookiesName) cookiesName = 'awt';
882
+ if (typeof domain == "undefined" && vueData.v_Set.authDomain) domain = vueData.v_Set.authDomain;
883
+
884
+ setCookie(cookiesName, '', 0, domain);
885
+
886
+ if (!url) location.assign(location.href);
887
+ else location.assign(url);
888
+ }
889
+
890
+ //===========================================================================================
891
+ // WEBSOCKET
892
+ //===========================================================================================
893
+ function webSocketJoinGroup(groupObj, timeout, callBackFunc) {
894
+ groupObj = _.cloneDeep(groupObj)
895
+ _.forEach(groupObj, function (v, k) {
896
+ if (!vueData.webSocket.hasOwnProperty(k)) {
897
+ Vue.set(vueData.webSocket, k, v);
898
+ }
899
+ });
900
+
901
+ setTimeout(function () {
902
+ try {
903
+ $.connection.hub.start().done(function () {
904
+ _.forEach(groupObj, function (v, k) {
905
+ $.connection.chatHub.server.joinGroup(k);
906
+ });
907
+ if (_.isFunction(callBackFunc)) callBackFunc();
908
+ }).fail(function (e) {
909
+ });
910
+ }
911
+ catch (err) {
912
+
913
+ }
914
+ }, (timeout ? timeout : 0));
915
+ }
916
+
917
+ function webSocketConnection(wsURL, WS, wsState) {
918
+ $.connection.hub.url = wsURL;
919
+
920
+ //=====================================================================
921
+ $.connection.hub.stateChanged(function (change) {
922
+ wsState.code = change.newState;
923
+ if (change.newState === $.signalR.connectionState.reconnecting) {
924
+ console.log("Websocket Reconnecting")
925
+ wsState.text = "Reconnecting";
926
+ }
927
+ else if (change.newState === $.signalR.connectionState.connected) {
928
+ console.log("Websocket Connected")
929
+ wsState.text = "Connected";
930
+ }
931
+ else if (change.newState === $.signalR.connectionState.disconnected) {
932
+ console.log("Websocket Disconnected")
933
+ wsState.text = "Disconnected";
934
+
935
+ webSocketJoinGroup(WS, 5000);
936
+ }
937
+ });
938
+
939
+ $.connection.chatHub.client.onError = function (Mess) {
940
+ console.log('Server error: ', Mess);
941
+ };
942
+
943
+ $.connection.chatHub.client.onJoinGroup = function (mess) {
944
+ console.log("Group", mess)
945
+ };
946
+
947
+ $.connection.chatHub.client.messageReceived = function (group, message) {
948
+ console.log("Message Received: ", group, message)
949
+ if (_.isArray(WS[group])) WS[group].push(message);
950
+ else WS[group] = message;
951
+ };
952
+
953
+ $.connection.chatHub.client.messageSendLog = function (group, message) {
954
+ console.log("Message Sent: ", group, message)
955
+ };
956
+ }
957
+
958
+ function webSocket_Send(obj) {
959
+ if (_.isPlainObject(obj.data)) $.connection.chatHub.server.messageSendObject(obj.group, obj.data);
960
+ else if (_.isArray(obj.data)) $.connection.chatHub.server.messageSendArray(obj.group, obj.data);
961
+ else $.connection.chatHub.server.messageSend(obj.group, obj.data);
962
+ }
963
+ //===========================================================================================
964
+ //===========================================================================================
965
+ //===========================================================================================
966
+ //===========================================================================================