@fui-org/fui-cli 0.1.1 → 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 +18 -3
  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,769 @@
1
+ Vue.component("f-table", {
2
+ props: ['value', 'headers', 'updateForm', 'updateFormAttr', "updateApi", 'items', 'label', 'excel', 'showSearch', 'sumFormat', 'disabledHover', 'returnObject', "itemKey", 'itemsPerPage', "disabledUpdate", 'hideDefaultFooter', 'template', 'headercomponent', 'height', 'fixedHeader', 'fixedCols'],
3
+ data: function () {
4
+ var headersAttr = bindData(this.headers);
5
+ var strTableTemplate = this.template || '';
6
+
7
+ if (headersAttr) {
8
+ _.forEach(headersAttr.filter(i => !!i.el), function (c) {
9
+ var ctl = buildControl([c], false);
10
+
11
+ ctl.attr("v-bind", "item.attr");
12
+ ctl.attr(":item", "item");
13
+ ctl.attr(":index", "items.indexOf(item)");
14
+ ctl.attr("v-on:mouseover", "$emit('mouseover:row',item)");
15
+ ctl.attr("v-on:mouseleave", "$emit('mouseleave:row',item)");
16
+
17
+ if (!c.attr['v-model']) ctl.attr("v-model", `item['${c.value}']`);
18
+
19
+ strTableTemplate += `<template v-slot:item.${c.value}="{item}">${ctl[0].outerHTML}</template>`
20
+ });
21
+ }
22
+
23
+ this.$options.template = `
24
+ <v-card outlined v-if="headerCols" ref="myTable">
25
+ <component is="style" v-if="fixedColStyles" v-html="fixedColStyles"></component>
26
+ <v-card-title v-if="showSearch || label || ((newAPI() || newAction()) && !disabledUpdate)" align="end" class="py-2 px-3">
27
+ <div class="primary--text text-left" v-html="label" style="font-weight: 500; font-size: 1.1rem;"></div>
28
+ <v-spacer></v-spacer><component :is="headercomponent"></component>
29
+ <v-btn v-if="excel" icon color="primary" @click="toExcel"><v-icon>mdi-microsoft-excel</v-icon></v-btn>
30
+ <div v-if="showSearch" class="d-flex align-center ml-3">
31
+ <v-btn icon color="primary" @mousedown.prevent @click="toggleSearch"><v-icon>mdi-magnify</v-icon></v-btn>
32
+ <v-expand-x-transition>
33
+ <v-text-field v-if="searchOpen" ref="searchField" dense @input="updateSearch" label="" single-line hide-details autofocus style="width: 300px;" @blur="onSearchBlur" class="ml-1"></v-text-field>
34
+ </v-expand-x-transition>
35
+ </div>
36
+ <v-btn v-if="(newAPI() || newAction()) && !disabledUpdate" class="ml-3" outlined x-small fab dark color="primary" @click="newBtnClick"><v-icon dark>mdi-plus</v-icon></v-btn>
37
+ </v-card-title>
38
+
39
+ <v-data-table class="tTable" :class="[{tTableNoHover: disabledHover}, fixedTableClass]" v-bind="$attrs" :height="tableHeight" :fixed-header="tableFixedHeader" :items="items" :item-key="itemKey" :headers="headerCols" :items-per-page="itemsPerPage" :hide-default-footer="_hideDefaultFooter" v-model="itemselected" calculate-widths v-on:click:row="$emit('click:row', $event)" v-on:item-selected="itemSelect()" v-on:toggle-select-all="itemSelect()" :search="search" :custom-filter="customSearch">
40
+ <template v-for="(h, index) in headerCols" :key="index" v-slot:['header.'+h.value]="{ header }"><span v-html="header.text"></span></template>
41
+ ${strTableTemplate}
42
+ <template v-if="!disabledUpdate" v-slot:item.ctrl-update="{item}">
43
+ <div class="d-flex justify-center" v-if="item['ctrl-update'] != 0">
44
+ <v-btn v-if="deleteAPI()" @click="deleteItem(item, deleteAPI())" x-small fab icon><v-icon color="red darken-3">mdi-trash-can-outline</v-icon></v-btn>
45
+ <v-btn v-if="editAPI() || editAction()" @click="editItem(item)" x-small fab icon><v-icon color="green darken-2">mdi-square-edit-outline</v-icon></v-btn>
46
+ </div>
47
+ </template>
48
+ <template v-if="sumFormat && typeof sumFormat === 'object'" v-slot:body.append="{ headers }">
49
+ <tr>
50
+ <td v-for="h in headers" :key="'sum-'+h.value" class="text-right font-weight-medium">
51
+ <span v-if="sumFormat[h.value]">{{ sumByColumn(h.value) }}</span>
52
+ </td>
53
+ </tr>
54
+ </template>
55
+ </v-data-table>
56
+
57
+ <v-divider v-if="sumFormat && typeof sumFormat === 'string'"></v-divider>
58
+ <v-card-actions v-if="sumFormat && typeof sumFormat === 'string'">
59
+ <v-spacer></v-spacer><span class="title font-weight-light">{{sumText}}</span>
60
+ </v-card-actions>
61
+ <f-dialog v-model="editDialog" v-bind="updateFormAttr" :data.sync="editData" :data-out.sync="vueData[dataOut]" :title="formTitle" :button="updateButton" :controls="updateForm"></f-dialog>
62
+ </v-card>
63
+ `;
64
+ //----------------------------------------------------------------------
65
+
66
+ return {
67
+ editDialog: false,
68
+ search: '',
69
+ searchOpen: false,
70
+ itemselected: [],
71
+ returnValue: [],
72
+ editData: {},
73
+ editedIndex: -1,
74
+ fillHeight: 0,
75
+ fixedTableClass: 'f-table-fixed-' + this._uid,
76
+ autoHeaders: this.headers ? null : this.buildAutoHeaders(this.items || []),
77
+ updateButton: [
78
+ {
79
+ "v-if": "title=='Cập nhật'",
80
+ label: "Lưu",
81
+ getoutdata: true,
82
+ action: () => {
83
+ this.save(this.editAPI());
84
+ }
85
+ },
86
+ {
87
+ "v-if": "title=='Thêm mới'",
88
+ label: "Thêm mới",
89
+ getoutdata: true,
90
+ action: () => {
91
+ this.addnew(this.newAPI())
92
+ }
93
+ }
94
+ ],
95
+ newAction: function () {
96
+ if (this.updateApi) return this.updateApi['new-action']
97
+ else return null;
98
+ },
99
+ newAPI: function () {
100
+ if (this.updateApi) return this.updateApi.new;
101
+ else return null;
102
+ },
103
+ editAction: function () {
104
+ if (this.updateApi) return this.updateApi['edit-action']
105
+ else return null;
106
+ },
107
+ editAPI: function () {
108
+ if (this.updateApi) return this.updateApi.edit;
109
+ else return null;
110
+ },
111
+ deleteAPI: function () {
112
+ if (this.updateApi) return this.updateApi.delete;
113
+ else return null;
114
+ },
115
+ defaultItem: function () {
116
+ if (this.updateApi) {
117
+ if (typeof this.updateApi['default-item'] === 'undefined') return {};
118
+ var dataItem = {}
119
+ mapData(this.updateApi['default-item'], dataItem, vueData);
120
+ return dataItem;
121
+ }
122
+ else return {};
123
+ },
124
+ }
125
+ },
126
+ mounted: function () {
127
+ this.editData = Object.assign({}, this.defaultItem())
128
+ this.editedIndex = -1
129
+ this.itemSelectRebuild(this.value);
130
+
131
+ if (this.heightNum !== null && this.heightNum <= 0) {
132
+ this.updateFillHeight();
133
+ this.resizeHandler = this.updateFillHeight.bind(this);
134
+ window.addEventListener('resize', this.resizeHandler);
135
+ }
136
+ },
137
+ beforeDestroy: function () { this.removeResize(); },
138
+ beforeUnmount: function () { this.removeResize(); },
139
+ watch: {
140
+ editDialog(val) {
141
+ if (!val) {
142
+ _.forEach(this.editData, (v, k) => {
143
+ this.editData[k] = undefined;
144
+ });
145
+ this.editedIndex = -1
146
+ } else {
147
+ this.editData.dialogModel = (this.edit ? 'edit' : 'new')
148
+ }
149
+ },
150
+ value(newVal, oldVal,) {
151
+ t = this;
152
+ if (t.returnValue != newVal) this.itemSelectRebuild(newVal);
153
+ },
154
+ items(newVal) {
155
+ if (!this.headers) this.autoHeaders = this.buildAutoHeaders(newVal || []);
156
+ if (this.resizeHandler) this.updateFillHeight();
157
+ }
158
+ },
159
+ computed: {
160
+ // null = không khai height → bảng tự giãn theo số dòng (mặc định của v-data-table)
161
+ heightNum() {
162
+ if (this.height === undefined || this.height === null || this.height === '') return null;
163
+ var n = Number(this.height);
164
+ return isNaN(n) ? null : n;
165
+ },
166
+ tableHeight() {
167
+ if (this.heightNum === null) return undefined;
168
+ if (this.heightNum > 0) return this.heightNum;
169
+ return this.fillHeight || undefined;
170
+ },
171
+ tableFixedHeader() {
172
+ if (this.fixedHeader !== undefined) return this.fixedHeader;
173
+ return this.tableHeight ? true : undefined;
174
+ },
175
+ fixedColStyles() {
176
+ var fixedCols = parseInt(this.fixedCols, 10);
177
+ var headers = this.headerCols || [];
178
+ if (isNaN(fixedCols) || fixedCols <= 0 || headers.length === 0) return '';
179
+
180
+ fixedCols = Math.min(fixedCols, headers.length);
181
+ var scope = '.' + this.fixedTableClass;
182
+ var css = scope + ' th:nth-child(-n+' + fixedCols + '),' +
183
+ scope + ' td:nth-child(-n+' + fixedCols + '){' +
184
+ 'position:sticky!important;background-color:#fff;' +
185
+ 'background-color:rgb(var(--v-theme-surface,255,255,255));z-index:1;}';
186
+ css += '.theme--dark ' + scope + ' th:nth-child(-n+' + fixedCols + '),' +
187
+ '.theme--dark ' + scope + ' td:nth-child(-n+' + fixedCols + '){background-color:#1e1e1e;}';
188
+ // Vuetify fixed-header đặt th ở z-index 2 bằng selector có specificity cao.
189
+ // Ô body cố định phải ở dưới mức đó; giao điểm header + cột cố định phải thắng tuyệt đối.
190
+ css += scope + ' th:nth-child(-n+' + fixedCols + '){z-index:4!important;}';
191
+
192
+ var left = [];
193
+ for (var i = 0; i < fixedCols; i++) {
194
+ var width = this.fixedColumnWidth(headers[i] && headers[i].width);
195
+ css += scope + ' th:nth-child(' + (i + 1) + '),' +
196
+ scope + ' td:nth-child(' + (i + 1) + '){' +
197
+ 'left:calc(' + (left.length ? left.join(' + ') : '0px') + ')!important;' +
198
+ 'min-width:' + width + '!important;max-width:' + width + '!important;width:' + width + '!important;}';
199
+ left.push(width);
200
+ }
201
+ return css;
202
+ },
203
+ headerCols() {
204
+ var h = this.headers || this.autoHeaders;
205
+ if (_.isBoolean(this.disabledUpdate) && this.disabledUpdate) {
206
+ _.remove(h, function (n) {
207
+ return n.value == 'ctrl-update';
208
+ });
209
+ };
210
+
211
+ return h;
212
+ },
213
+ _hideDefaultFooter() {
214
+ if (this.items) {
215
+ if (this.itemsPerPage > 0 && this.itemsPerPage < this.items.length) return false;
216
+ }
217
+ if (this.hideDefaultFooter) return this.hideDefaultFooter;
218
+ return false;
219
+ },
220
+ edit() {
221
+ return this.editedIndex >= 0;
222
+ },
223
+ formTitle() {
224
+ return this.edit ? 'Cập nhật' : 'Thêm mới';
225
+ },
226
+ dataOut() {
227
+ if (this.updateApi) return this.updateApi['data-out']
228
+ else return null;
229
+ },
230
+ sumText() {
231
+ var items = this.items;
232
+ return _.replace(this.sumFormat, /\[\[(.*?)\]\]/gm, function (s, key) {
233
+ return numeral(_.sumBy(items, key)).format('0,0');
234
+ });
235
+ }
236
+ },
237
+ methods: {
238
+ fixedColumnWidth(width) {
239
+ if (width === undefined || width === null || width === '') return '150px';
240
+ var value = String(width).trim();
241
+ return /^\d+(\.\d+)?$/.test(value) ? value + 'px' : value;
242
+ },
243
+ removeResize() {
244
+ if (this.resizeHandler) {
245
+ window.removeEventListener('resize', this.resizeHandler);
246
+ this.resizeHandler = null;
247
+ }
248
+ },
249
+ updateFillHeight() {
250
+ var t = this;
251
+ t.$nextTick(function () {
252
+ requestAnimationFrame(function () {
253
+ var root = t.$refs.myTable && t.$refs.myTable.$el;
254
+ if (!root) return;
255
+ var wrapper = root.querySelector('.v-data-table__wrapper');
256
+ if (!wrapper) return;
257
+ var rootRect = root.getBoundingClientRect();
258
+ var wrapRect = wrapper.getBoundingClientRect();
259
+ // phần nằm dưới vùng cuộn (footer phân trang, dòng tổng, viền card)
260
+ var below = Math.max(0, rootRect.bottom - wrapRect.bottom);
261
+ var margin = Math.abs(t.heightNum);
262
+ t.fillHeight = Math.max(150, window.innerHeight - wrapRect.top - below - margin);
263
+ });
264
+ });
265
+ },
266
+ buildAutoHeaders(items) {
267
+ var arrHeaders = buildHeader(items);
268
+ var hasEditOrDelete = this.updateApi && (this.updateApi.edit || this.updateApi.delete);
269
+ if (arrHeaders.length > 0 && hasEditOrDelete && !this.disabledUpdate) {
270
+ arrHeaders.push({ "text": "", "value": "ctrl-update", "divider": true });
271
+ }
272
+ return arrHeaders;
273
+ },
274
+ sumByColumn(key) {
275
+ return numeral(_.sumBy(this.items, key)).format(this.sumFormat[key]);
276
+ },
277
+ toggleSearch() {
278
+ this.searchOpen = !this.searchOpen;
279
+ if (this.searchOpen) {
280
+ var t = this;
281
+ this.$nextTick(function () {
282
+ t.$refs.searchField && t.$refs.searchField.focus();
283
+ });
284
+ }
285
+ },
286
+ onSearchBlur() {
287
+ if (!this.search) this.searchOpen = false;
288
+ },
289
+ itemSelect() {
290
+ var t = this;
291
+
292
+ setTimeout(function () {
293
+ if (!t.returnObject) t.returnValue = _.map(t.itemselected, t.itemKey);
294
+ else t.returnValue = t.itemselected;
295
+
296
+ t.$emit('input', t.returnValue);
297
+ }, 10);
298
+ },
299
+
300
+ itemSelectRebuild(newVal) {
301
+ var t = this;
302
+ if (t.returnObject) t.itemselected = newVal;
303
+ else {
304
+ t.itemselected = [];
305
+
306
+ _.forEach(newVal, function (val) {
307
+ t.itemselected.push(_.find(t.items, [t.itemKey, val]));
308
+ });
309
+ }
310
+ },
311
+ newBtnClick() {
312
+ if (this.newAction()) runAction(this.newAction());
313
+ if (this.newAPI()) {
314
+ Object.assign(this.editData, this.defaultItem());
315
+ this.editDialog = true;
316
+ }
317
+ },
318
+ editItem(item) {
319
+
320
+ if (this.editAction()) runAction(this.editAction(), { item: item });
321
+
322
+ if (this.editAPI()) {
323
+ this.editedIndex = this.items.indexOf(item);
324
+ Object.assign(this.editData, this.defaultItem());
325
+ this.editData = Object.assign({}, item);
326
+ this.editDialog = true;
327
+ }
328
+ },
329
+
330
+ deleteItem(item, deleteAPI) {
331
+ const items = this.items;
332
+ var actionAPI = deleteAPI;
333
+ var indexDelete = items.indexOf(item)
334
+ confirm({
335
+ title: (actionAPI.CONFIRM ? bindDataToString(actionAPI.CONFIRM, _.assignIn({ $row: item, item: item }, vueData)) : "Bạn có chắc chắn muốn xoá ?"),
336
+ action: function () {
337
+ vueAction(actionAPI, { $row: item, item: item }, function () {
338
+ items.splice(indexDelete, 1);
339
+ });
340
+ }
341
+ });
342
+ },
343
+
344
+ save(actionAPI) {
345
+ var diaCtl = this;
346
+ if (diaCtl.editedIndex > -1) {
347
+ if (actionAPI) {
348
+ if (actionAPI.CONFIRM) {
349
+ confirm({
350
+ title: bindDataToString(actionAPI.CONFIRM, _.assignIn({ $row: diaCtl.editData, item: diaCtl.editData }, vueData)),
351
+ action: function () {
352
+ vueAction(actionAPI, { $row: diaCtl.editData, item: diaCtl.editData }, function (d) {
353
+ if (!d.Message) {
354
+ if (_.isArray(d.data)) {
355
+ if (d.data[0]) {
356
+ _.forEach(d.data[0], function (value, key) {
357
+ diaCtl.editData[key] = value;
358
+ });
359
+ }
360
+ }
361
+ }
362
+ _.assignIn(diaCtl.items[diaCtl.editedIndex], diaCtl.editData);
363
+ diaCtl.editDialog = false;
364
+ });
365
+ },
366
+ cancel: function () { }
367
+ });
368
+ } else {
369
+ vueAction(actionAPI, { $row: diaCtl.editData, item: diaCtl.editData }, function (d) {
370
+ if (!d.Message) {
371
+ if (_.isArray(d.data)) {
372
+ if (d.data[0]) {
373
+ _.forEach(d.data[0], function (value, key) {
374
+ diaCtl.editData[key] = value;
375
+ });
376
+ }
377
+ }
378
+ }
379
+ _.assignIn(diaCtl.items[diaCtl.editedIndex], diaCtl.editData);
380
+ diaCtl.editDialog = false;
381
+ });
382
+ }
383
+ } else {
384
+ _.assignIn(diaCtl.items[diaCtl.editedIndex], diaCtl.editData);
385
+ diaCtl.editDialog = false;
386
+ }
387
+ }
388
+ },
389
+
390
+ addnew(actionAPI) {
391
+ var diaCtl = this;
392
+ if (diaCtl.editedIndex == -1) {
393
+ if (actionAPI) {
394
+ if (actionAPI.CONFIRM) {
395
+ confirm({
396
+ title: bindDataToString(actionAPI.CONFIRM, _.assignIn({ $row: diaCtl.editData, item: diaCtl.editData }, vueData)),
397
+ action: function () {
398
+ vueAction(actionAPI, { $row: diaCtl.editData, item: diaCtl.editData }, function (d) {
399
+ if (d) {
400
+ if (_.isArray(d.data)) {
401
+ if (d.data[0]) {
402
+ _.forEach(d.data[0], function (value, key) {
403
+ diaCtl.editData[key] = value;
404
+ });
405
+ }
406
+ }
407
+ }
408
+ diaCtl.items.push(_.cloneDeep(diaCtl.editData));
409
+ diaCtl.editDialog = false;
410
+ });
411
+ },
412
+ cancel: function () { }
413
+ });
414
+ } else {
415
+ vueAction(actionAPI, { $row: diaCtl.editData, item: diaCtl.editData }, function (d) {
416
+ if (d) {
417
+ if (_.isArray(d.data)) {
418
+ if (d.data[0]) {
419
+ _.forEach(d.data[0], function (value, key) {
420
+ diaCtl.editData[key] = value;
421
+ });
422
+ }
423
+ }
424
+ }
425
+ diaCtl.items.push(_.cloneDeep(diaCtl.editData));
426
+ diaCtl.editDialog = false;
427
+ });
428
+ }
429
+ } else {
430
+ diaCtl.items.push(_.cloneDeep(diaCtl.editData));
431
+ diaCtl.editDialog = false;
432
+ }
433
+ }
434
+ },
435
+ updateSearch: _.debounce(function (text) {
436
+ this.search = text;
437
+ }, 300),
438
+ customSearch(value, search, item) {
439
+ return Object.values(item).some(v => v && v.toString().toLowerCase().includes(search.toLowerCase()))
440
+ },
441
+ toExcel() {
442
+ jsonToExcel({ data: this.items, filename: (this.label && this.label != '' ? this.label : 'tableFP') + '.xlsx' });
443
+ Vue.$toast.success("Đã xuất Excel", { position: 'top' });
444
+ },
445
+ }
446
+ });
447
+ //===========================================================================================
448
+ //===========================================================================================
449
+ //===========================================================================================
450
+
451
+ Vue.component('t-html', {
452
+ template: `<div class="my-3" v-bind="$attrs" v-html="$attrs['value']"></div>`,
453
+ });
454
+ //-----------------------------------------------------------------------------------------------------------
455
+ Vue.component('t-label', {
456
+ template: `<div class="text-no-wrap" v-bind="$attrs">{{$attrs['value']}}</div>`,
457
+ });
458
+ //-----------------------------------------------------------------------------------------------------------
459
+ Vue.component('t-num', {
460
+ props: {
461
+ value: {
462
+ type: Number,
463
+ default: 0
464
+ },
465
+ hidezero: {
466
+ type: Boolean,
467
+ default: true
468
+ },
469
+ format: {
470
+ type: String,
471
+ default: ''
472
+ }
473
+ },
474
+ template: `<div v-if="!_.isNull(value) && !(hidezero && value==0)" class="text-no-wrap text-end" v-bind="$attrs">{{ format != ''? numeral(value).format(format) : Number.isInteger(value)?numeral(value).format('0,0'): numeral(value).format('0,0.0') }}</div>`,
475
+ });
476
+ //-----------------------------------------------------------------------------------------------------------
477
+ Vue.component('t-time', {
478
+ props: {
479
+ format: {
480
+ type: String,
481
+ default: 'DD/MM/YYYY'
482
+ }
483
+ },
484
+ template: `<div v-if="$attrs['value']" class="text-no-wrap" v-bind="$attrs">{{ moment($attrs['value']).format(format) }}</div>`,
485
+ });
486
+ //-----------------------------------------------------------------------------------------------------------
487
+ Vue.component('t-boolean', {
488
+ props: {
489
+ value: {
490
+ default: null
491
+ },
492
+ colorTrue: {
493
+ default: "primary"
494
+ },
495
+ colorFalse: {
496
+ default: "red"
497
+ },
498
+ iconTrue: {
499
+ default: "mdi-check"
500
+ },
501
+ iconFalse: {
502
+ default: "mdi-close"
503
+ },
504
+ },
505
+ template: `<v-icon v-bind="$attrs" :color="value?colorTrue:colorFalse">{{value?iconTrue:iconFalse}}</v-icon>`,
506
+ });
507
+ //-----------------------------------------------------------------------------------------------------------
508
+ Vue.component('t-check', {
509
+ props: {
510
+ value: {
511
+ default: null
512
+ },
513
+ falseValue: {
514
+ default: false
515
+ }
516
+ },
517
+ template: `<v-checkbox v-model="ctlValue" v-bind="$attrs" :false-value="falseValue" hide-details class="d-inline-flex ma-1 pa-1" v-on:change="onchange($event)" />`,
518
+ data: function () {
519
+ return {
520
+ ctlValue: this.value
521
+ }
522
+ },
523
+ watch: {
524
+ value(newVal) {
525
+ this.ctlValue = newVal
526
+ }
527
+ },
528
+ methods: {
529
+ onchange: function (e) {
530
+ if (!e) this.ctlValue = false;
531
+ tableActionEvent(this);
532
+ }
533
+ }
534
+ });
535
+ //-----------------------------------------------------------------------------------------------------------
536
+ Vue.component('t-text', {
537
+ props: ['value'],
538
+ template: `<v-text-field v-model="ctlValue" v-bind="$attrs" v-on:change="onchange($event)" dense outlined hide-details/>`,
539
+ data: function () {
540
+ return {
541
+ ctlValue: this.value
542
+ }
543
+ },
544
+ watch: {
545
+ value(newVal) {
546
+ this.ctlValue = newVal
547
+ }
548
+ },
549
+ methods: {
550
+ onchange: function (e) {
551
+ console.log(e)
552
+ tableActionEvent(this);
553
+ }
554
+ }
555
+ });
556
+
557
+ //-----------------------------------------------------------------------------------------------------------
558
+ Vue.component('t-select', {
559
+ props: ['value'],
560
+ template: `<v-select v-model="ctlValue" v-bind="$attrs" v-on:change="onchange($event)" dense hide-details append-icon="mdi-menu-swap"/>`,
561
+ data: function () {
562
+ return {
563
+ ctlValue: this.value
564
+ }
565
+ },
566
+ watch: {
567
+ value(newVal) {
568
+ this.ctlValue = newVal
569
+ }
570
+ },
571
+ methods: {
572
+ onchange: function (e) {
573
+ console.log(e)
574
+ tableActionEvent(this);
575
+ }
576
+ }
577
+ });
578
+ //-----------------------------------------------------------------------------------------------------------
579
+ Vue.component('t-combobox', {
580
+ props: {
581
+ value: {
582
+ default: undefined
583
+ },
584
+ text: {
585
+ default: undefined
586
+ },
587
+ items: {
588
+ type: Array,
589
+ default: []
590
+ },
591
+ api: {
592
+ type: String,
593
+ default: undefined
594
+ },
595
+ apiData: {
596
+ type: Object,
597
+ default: {}
598
+ },
599
+
600
+ },
601
+ template: `<v-autocomplete refs="abc" v-model="ctlValue" v-bind="$attrs" :loading="loading" :items="dataItems" :search-input.sync="search" v-on:change="onchange($event)" hide-details dense></v-autocomplete>`,
602
+ data: function () {
603
+ return {
604
+ dataItems: this.items,
605
+ ctlValue: this.value,
606
+ loading: false,
607
+ search: null,
608
+ getDataFormAPI: _.debounce((str) => {
609
+ if ((this.search && this.search.length < 2) || !this.api) return;
610
+ var self = this;
611
+ this.loading = true
612
+
613
+ if (typeof this.apiData != 'object') this.apiData = JSON.parse(this.apiData);
614
+
615
+ mapData(this.apiData, this.apiData, _.assignIn({}, vueData, { TEXT: this.search }));
616
+
617
+ console.log("API-CALL: ", this.api)
618
+ ajaxCALL(this.api, this.apiData, function (d) {
619
+ if (_.isArray(d.data)) self.dataItems = d.data;
620
+ else self.dataItems = d;
621
+
622
+ self.loading = false
623
+ }, function (e) {
624
+ console.log("API Error", e);
625
+ })
626
+
627
+ }, 500)
628
+ }
629
+ },
630
+ watch: {
631
+ search(newval, oldval) {
632
+ newval && oldval && this.querySelections(newval)
633
+ },
634
+ items(val) {
635
+ this.dataItems = val
636
+ },
637
+ value(newVal) {
638
+ this.ctlValue = newVal
639
+ }
640
+ },
641
+ methods: {
642
+ querySelections(val) {
643
+ this.getDataFormAPI(val);
644
+ },
645
+ onchange: function (e) {
646
+ if (e) tableActionEvent(this);
647
+ }
648
+ },
649
+ });
650
+ //-----------------------------------------------------------------------------------------------------------
651
+ Vue.component('t-menu', {
652
+ props: {
653
+ iconText: {
654
+ default: 'mdi-menu'
655
+ },
656
+ items: {
657
+ default: []
658
+ }
659
+ },
660
+ template: `
661
+ <v-menu offset-y origin="center center" transition="slide-y-transition" max-height = 500>
662
+ <template v-slot:activator="{ on }">
663
+ <v-icon v-on="on">{{iconText}}</v-icon>
664
+ </template>
665
+ <v-list >
666
+ <v-list-item v-bind="$attrs" v-for="(item, index) in items" :key="index" @click="onSelect(item)" >
667
+ <v-list-item-icon v-if="item['icon-text']" class="mr-3">
668
+ <v-icon :color="item.color">{{item['icon-text']}}</v-icon>
669
+ </v-list-item-icon>
670
+ <v-list-item-content class="mr-2">
671
+ <v-list-item-title v-text="item.text"></v-list-item-title>
672
+ </v-list-item-content>
673
+ </v-list-item>
674
+ </v-list>
675
+ </v-menu>
676
+ `,
677
+ data: function () {
678
+ return {
679
+ }
680
+ },
681
+ methods: {
682
+ onSelect: function (e) {
683
+ this.$attrs['action'] = e['action'];
684
+ tableActionEvent(this);
685
+ }
686
+ }
687
+ });
688
+ //-----------------------------------------------------------------------------------------------------------
689
+ Vue.component('t-link', {
690
+ props: ['id', 'target', 'url', 'title', 'onclose'],
691
+ template: `<a :href="_url" :target="target" v-bind="$attrs" @click="onclick()"><v-icon v-if="$attrs['icon-text']" left color="primary">{{$attrs['icon-text']}}</v-icon> {{ $attrs['value']}}</a>`,
692
+ computed: {
693
+ _url: function () {
694
+ if (this.target != 'dialog' && this.url) return this.url;
695
+ return null;
696
+ },
697
+ },
698
+ methods: {
699
+ onclick: function (e) {
700
+ if (this.target == 'dialog') {
701
+ tableActionEvent(this);
702
+ openWindow({
703
+ id: this.id,
704
+ title: this.title,
705
+ url: this.url,
706
+ onclose: this.onclose,
707
+ });
708
+ }
709
+ }
710
+ }
711
+ });
712
+ //-----------------------------------------------------------------------------------------------------------
713
+ Vue.component('t-button', {
714
+ props: {
715
+ text: {
716
+ default: true
717
+ },
718
+ color: {
719
+ default: "primary"
720
+ },
721
+ label: {
722
+ default: null
723
+ },
724
+ iconText: {
725
+ default: null
726
+ },
727
+ target: {
728
+ default: null
729
+ },
730
+ wid: {
731
+ default: null
732
+ },
733
+ url: {
734
+ default: null
735
+ },
736
+ title: {
737
+ default: null
738
+ },
739
+ onclose: {
740
+ default: null
741
+ },
742
+ quset: {
743
+ default: []
744
+ },
745
+ },
746
+ template: `
747
+ <v-btn class="custom-transform-class text-none" v-bind="$attrs" :text="text" :color="color" :target="target" :href="_url" @click="clickButton()">
748
+ <v-icon v-if="iconText" :left="label" class="ml-0">{{iconText}}</v-icon>{{label}}
749
+ </v-btn>`,
750
+ computed: {
751
+ _url: function () {
752
+ if (this.target != 'dialog' && this.url) return this.url;
753
+ return null;
754
+ },
755
+ },
756
+ methods: {
757
+ clickButton: function (e) {
758
+ if (this.target == 'dialog') {
759
+ console.log("dialog onclose", this.onclose)
760
+ openWindow({
761
+ id: this.wid,
762
+ title: this.title,
763
+ url: this.url,
764
+ onclose: this.onclose
765
+ });
766
+ } else tableActionEvent(this);
767
+ }
768
+ }
769
+ });