@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,909 @@
1
+ //===========================================================================================
2
+ Vue.component("FTableItemComponent", {
3
+ template: `<component :is="dynamicComponent"></component>`,
4
+ props: {
5
+ template: {
6
+ required: true
7
+ },
8
+ data: {
9
+ type: Object,
10
+ default: () => ({}),
11
+ },
12
+ },
13
+ data() {
14
+ return {
15
+ }
16
+ },
17
+ computed: {
18
+ dynamicComponent() {
19
+ return {
20
+ template: this.buildTemplateString(this.template),
21
+ data: () => { return this.data },
22
+ }
23
+ },
24
+ },
25
+ methods: {
26
+ buildTemplateString(jObj) {
27
+ if (Array.isArray(jObj)) {
28
+ var templateString = '';
29
+ jObj.forEach((o) => {
30
+ templateString = templateString + this.buildTemplateString(o)
31
+ });
32
+ return templateString;
33
+ }
34
+
35
+ if (typeof jObj === 'object') {
36
+ if (!jObj.attr) jObj.attr = {}
37
+ jObj.attr[":item"] = "item"
38
+ let d = $(`<${jObj.el}>`, this.fixAttrValueObject(jObj.attr)).append(this.buildTemplateString(jObj.innerHTML));
39
+ return $('<div>').append(d).html();
40
+ }
41
+ if (typeof jObj === 'string') return jObj;
42
+ return ""
43
+ },
44
+ fixAttrValueObject(jObj) {
45
+ let returnObj = {}
46
+ if (jObj) {
47
+ for (const [key, value] of Object.entries(jObj)) {
48
+ if (typeof value === 'object') returnObj[key] = JSON.stringify(value);
49
+ else returnObj[key] = value;
50
+ }
51
+ }
52
+ return returnObj
53
+ }
54
+ }
55
+ });
56
+ //===========================================================================================
57
+ Vue.component("FTable", {
58
+ props: {
59
+ modelValue: {
60
+ default: null
61
+ },
62
+ headers: {
63
+ default: null
64
+ },
65
+ updateForm: {
66
+ default: null
67
+ },
68
+ updateApi: {
69
+ default: null
70
+ },
71
+ items: {
72
+ default: null
73
+ },
74
+ label: {
75
+ default: null
76
+ },
77
+ sumFormat: {
78
+ default: null
79
+ },
80
+ returnObject: {
81
+ default: null
82
+ },
83
+ itemKey: {
84
+ default: null
85
+ },
86
+ disabledUpdate: {
87
+ default: null
88
+ },
89
+ template: {
90
+ default: null
91
+ },
92
+ excel: {
93
+ default: true
94
+ },
95
+ hover: {
96
+ default: true
97
+ },
98
+ showSearch: {
99
+ default: true
100
+ },
101
+ returnObject: {
102
+ default: true
103
+ },
104
+ fixedHeader: {
105
+ default: true
106
+ },
107
+ fixedCols: {
108
+ default: 0
109
+ },
110
+ // null/bỏ trống = bảng tự giãn theo số dòng; <=0 = fill tới đáy màn hình chừa
111
+ // abs(n) px (tự đo vị trí thật, theo dõi resize); >0 = px cố định.
112
+ height: {
113
+ default: null
114
+ },
115
+ disableSort: {
116
+ default: false
117
+ },
118
+ density: {
119
+ default: 'compact'
120
+ },
121
+ hideDefaultFooter: {
122
+ default: true
123
+ },
124
+ returnObject: {
125
+ default: true
126
+ },
127
+ itemsPerPage: {
128
+ default: 50
129
+ },
130
+ footerProps:
131
+ {
132
+ default: "{'items-per-page-options':[50, 100, 150, 200, -1]}"
133
+ },
134
+ updateFormAttr: {
135
+ default: null
136
+ },
137
+ slots: {
138
+ default: null
139
+ },
140
+ groupTemplate: {
141
+ default: null
142
+ },
143
+ },
144
+ template: `
145
+ <v-card ref="myTable" variant="outlined" v-if="headersArray" class="border" style="text-align: initial;">
146
+ <component is="style" v-if="fixedColStyles" v-html="fixedColStyles"></component>
147
+ <v-card-actions>
148
+ <div v-if="showSearch || label || ((newAPI() || newAction()) && !disabledUpdate)" class="text-primary text-darken-2 text-left w-66 pl-2 text-h6" v-html="label"></div>
149
+ <v-spacer></v-spacer>
150
+ <v-btn v-if="excel" class="mr-4" small icon density="compact" color="primary" @click="toExcel"><v-icon>mdi-microsoft-excel</v-icon></v-btn>
151
+ <div v-if="showSearch" class="d-flex align-center">
152
+ <v-btn icon density="compact" color="primary" @click="toggleSearch"><v-icon>mdi-magnify</v-icon></v-btn>
153
+ <v-expand-x-transition>
154
+ <v-text-field v-if="searchOpen" ref="searchField" density="compact" @update:modelValue="updateSearch" label="" variant="underlined" hide-details autofocus style="width: 300px;" @blur="onSearchBlur" class="ml-1"></v-text-field>
155
+ </v-expand-x-transition>
156
+ </div>
157
+ <v-btn v-if="(newAPI() || newAction()) && !disabledUpdate" class="ml-5 mr-2" density="compact" variant="outlined" icon="mdi-plus" color="primary" @click="newBtnClick"></v-btn>
158
+ </v-card-actions>
159
+ <v-data-table class="tTable" :class="fixedTableClass" v-bind="$attrs" :height="tableHeight" :fixed-header="tableFixedHeader" :hover="hover" :items="items" :item-key="itemKey" :return-object="returnObject" :headers="headersArray" :density="density" :items-per-page="itemsPerPage" :hide-default-footer="hideDefaultFooter_cacl" 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">
160
+ <template v-slot:headers="{ headers, isSorted, getSortIcon, toggleSort, allSelected, selectAll, someSelected, groupHeaders }">
161
+ <tr v-for="columns in headers">
162
+ <template v-for="column in columns" :key="column.key">
163
+ <th v-if="column.key=='data-table-select'" class="px-2" :colspan="column.colspan || 1" :rowspan="column.rowspan || 1">
164
+ <v-checkbox-btn :indeterminate="someSelected && !allSelected" :model-value="allSelected" color="primary" @update:model-value="selectAll(!allSelected)" ></v-checkbox-btn>
165
+ </th>
166
+ <th v-else-if="column.key=='data-table-group'" v-bind="column.headerProps" :colspan="column.colspan || 1" :rowspan="column.rowspan || 1" style="width:40px" class="px-0 text-center">
167
+ <VIcon icon="mdi-format-list-group" size="small"></VIcon>
168
+ </th>
169
+ <th v-else v-bind="column.headerProps" :colspan="column.colspan || 1" :rowspan="column.rowspan || 1">
170
+ <div :class="['cursor-pointer', 'd-flex', 'justify-' + column.align]" @click="() => toggleSort(column)"><span v-html="column.title"></span><template v-if="isSorted(column)"><v-icon class="ml-1" :icon="getSortIcon(column)"></v-icon></template></div>
171
+ </th>
172
+ </template>
173
+ </tr>
174
+ </template>
175
+
176
+ <template v-slot:group-header="{ item, columns, toggleGroup, isGroupOpen }">
177
+ <tr><td :colspan="columns.length" class="px-0">
178
+ <VBtn :icon="isGroupOpen(item) ? '$expand' : '$next'" size="small" variant="text" @click="toggleGroup(item)"></VBtn>
179
+ <FDynamicComponent :componentData="{template:groupTemplate,data: () => { return { vueData, ...this.$props, item, columns, toggleGroup, isGroupOpen } }}"/>
180
+ </td></tr>
181
+ </template>
182
+
183
+ <template v-for="(c, index) in headersArray.filter(i => !!i.el)" :key="index" v-slot:['item.'+c.key]="{item}">
184
+ <FTableItemComponent :template="c" :data="{item, vueData}" />
185
+ </template>
186
+
187
+ <template v-if="!disabledUpdate" v-slot:item.ctrl-update="{item}">
188
+ <div style="min-width: 70px;" v-if="item['ctrl-update'] != 0">
189
+ <v-btn v-if="deleteAPI()" @click="deleteItem(item, deleteAPI())" icon="mdi-trash-can-outline" size="small" color="red-darken-1" density="comfortable" variant="plain"></v-btn>
190
+ <v-btn v-if="editAPI() || editAction()" @click="editItem(item)" icon="mdi-square-edit-outline" size="small" color="green-darken-2" density="comfortable" variant="plain"></v-btn>
191
+ </div>
192
+ </template>
193
+
194
+ <template v-for="(sData, index) in slots" :key="'dynamic-slot-' + index" v-slot:[sData.name]="{item, columns}">
195
+ <FDynamicComponent :componentData="{template:sData.html,data: () => { return { vueData, ...this.$props, item, columns} }}"/>
196
+ </template>
197
+
198
+ <template v-if="sumFormat && typeof sumFormat === 'object'" v-slot:body.append="{ columns }">
199
+ <tr class="tTableSumRow">
200
+ <td v-for="col in columns" :key="'sum-' + col.key" class="text-right font-weight-medium">
201
+ <span v-if="sumFormat[col.key]">{{ sumByColumn(col.key) }}</span>
202
+ </td>
203
+ </tr>
204
+ </template>
205
+ </v-data-table>
206
+
207
+ <v-divider v-if="sumFormat && typeof sumFormat === 'string'"></v-divider>
208
+ <v-card-actions v-if="sumFormat && typeof sumFormat === 'string'">
209
+ <v-spacer></v-spacer><span class="text-h6 font-weight-light">{{sumText}}</span>
210
+ </v-card-actions>
211
+ <f-dialog v-model="editDialog" v-model:data="editData" v-bind="updateFormAttr" :title="formTitle" :button="updateButton" :controls="updateForm"></f-dialog>
212
+ </v-card>
213
+ `,
214
+ data: function () {
215
+ return {
216
+ editDialog: false,
217
+ search: '',
218
+ searchOpen: false,
219
+ fillHeight: 0,
220
+ fixedTableClass: 'f-table-fixed-' + (this.$ && this.$.uid !== undefined ? this.$.uid : this._uid),
221
+ itemselected: [],
222
+ returnValue: [],
223
+ editData: {},
224
+ editedIndex: -1,
225
+ updateButton: [
226
+ {
227
+ "v-if": "title=='Cập nhật'",
228
+ label: "Lưu",
229
+ type: "submit",
230
+ action: () => {
231
+ this.save(this.editAPI());
232
+ }
233
+ },
234
+ {
235
+ "v-if": "title=='Thêm mới'",
236
+ label: "Thêm mới",
237
+ type: "submit",
238
+ action: () => {
239
+ this.addnew(this.newAPI())
240
+ }
241
+ }
242
+ ],
243
+ newAction: function () {
244
+ if (this.updateApi) return this.updateApi['new-action']
245
+ else return null;
246
+ },
247
+ newAPI: function () {
248
+ if (this.updateApi) return this.updateApi.new;
249
+ else return null;
250
+ },
251
+ editAction: function () {
252
+ if (this.updateApi) return this.updateApi['edit-action']
253
+ else return null;
254
+ },
255
+ editAPI: function () {
256
+ if (this.updateApi) return this.updateApi.edit;
257
+ else return null;
258
+ },
259
+ deleteAPI: function () {
260
+ if (this.updateApi) return this.updateApi.delete;
261
+ else return null;
262
+ },
263
+ defaultItem: function () {
264
+ if (this.updateApi) {
265
+ if (typeof this.updateApi['default-item'] === 'undefined') return {};
266
+ var dataItem = {}
267
+ mapData(this.updateApi['default-item'], dataItem, vueData);
268
+ return dataItem;
269
+ }
270
+ else return {};
271
+ },
272
+ }
273
+ },
274
+ mounted: function () {
275
+ this.editData = Object.assign({}, this.defaultItem())
276
+ this.editedIndex = -1
277
+ this.itemSelectRebuild(this.modelValue);
278
+
279
+ if (this.heightNum !== null && this.heightNum <= 0) {
280
+ this.updateFillHeight();
281
+ this.resizeHandler = this.updateFillHeight.bind(this);
282
+ window.addEventListener('resize', this.resizeHandler);
283
+ }
284
+ },
285
+ beforeUnmount: function () { this.removeResize(); },
286
+ beforeDestroy: function () { this.removeResize(); },
287
+ watch: {
288
+ items() {
289
+ if (this.resizeHandler) this.updateFillHeight();
290
+ },
291
+ editDialog(val) {
292
+ if (!val) {
293
+ _.forEach(this.editData, (v, k) => {
294
+ this.editData[k] = undefined;
295
+ });
296
+ this.editedIndex = -1
297
+ } else {
298
+ this.editData.dMode = (this.edit ? 'edit' : 'new')
299
+ }
300
+ },
301
+ modelValue(newVal, oldVal,) {
302
+ t = this;
303
+ if (t.returnValue != newVal) this.itemSelectRebuild(newVal);
304
+ },
305
+ },
306
+ computed: {
307
+ bindAttr(attr, item) {
308
+ var attrTmp = {}
309
+ if (attr && attr['v-model']) attrTmp.modelvalue = eval(attr['v-model'])
310
+ return attrTmp
311
+ },
312
+ headersArray() {
313
+ var headersBuild = [];
314
+ if (this.headers) {
315
+ headersBuild = this.headerSetDefaultProps(bindData(this.headers));
316
+ }
317
+ else if (this.items.length > 0) headersBuild = this.buildHeaderData(this.items[0]);
318
+ return headersBuild;
319
+ },
320
+ hideDefaultFooter_cacl() {
321
+ if (this.items) {
322
+ if (this.itemsPerPage > 0 && this.itemsPerPage < this.items.length) return false;
323
+ }
324
+ if (this.hideDefaultFooter) return this.hideDefaultFooter;
325
+ return false;
326
+ },
327
+ // null = không khai height → bảng tự giãn theo số dòng
328
+ heightNum() {
329
+ if (this.height === undefined || this.height === null || this.height === '') return null;
330
+ var n = Number(this.height);
331
+ return isNaN(n) ? null : n;
332
+ },
333
+ tableHeight() {
334
+ if (this.heightNum === null) return undefined;
335
+ if (this.heightNum > 0) return this.heightNum;
336
+ return this.fillHeight || undefined;
337
+ },
338
+ tableFixedHeader() {
339
+ return this.tableHeight ? this.fixedHeader : false;
340
+ },
341
+ fixedColStyles() {
342
+ var fixedCols = parseInt(this.fixedCols, 10);
343
+ var headers = this.headersArray || [];
344
+ if (isNaN(fixedCols) || fixedCols <= 0 || headers.length === 0) return '';
345
+
346
+ fixedCols = Math.min(fixedCols, headers.length);
347
+ var scope = '.' + this.fixedTableClass;
348
+ var css = scope + ' th:nth-child(-n+' + fixedCols + '),' +
349
+ scope + ' td:nth-child(-n+' + fixedCols + '){' +
350
+ 'position:sticky!important;background-color:#fff;' +
351
+ 'background-color:rgb(var(--v-theme-surface,255,255,255));z-index:1;}';
352
+ css += '.v-theme--dark ' + scope + ' th:nth-child(-n+' + fixedCols + '),' +
353
+ '.v-theme--dark ' + scope + ' td:nth-child(-n+' + fixedCols + '){background-color:#1e1e1e;}';
354
+ // Vuetify fixed-header đặt th trên body; giữ fixed body ở lớp 1 và nâng riêng
355
+ // giao điểm header + cột cố định lên lớp 4 để không thua specificity của framework.
356
+ css += scope + ' th:nth-child(-n+' + fixedCols + '){z-index:4!important;}';
357
+
358
+ var left = [];
359
+ for (var i = 0; i < fixedCols; i++) {
360
+ var width = this.fixedColumnWidth(headers[i] && headers[i].width);
361
+ css += scope + ' th:nth-child(' + (i + 1) + '),' +
362
+ scope + ' td:nth-child(' + (i + 1) + '){' +
363
+ 'left:calc(' + (left.length ? left.join(' + ') : '0px') + ')!important;' +
364
+ 'min-width:' + width + '!important;max-width:' + width + '!important;width:' + width + '!important;}';
365
+ left.push(width);
366
+ }
367
+ return css;
368
+ },
369
+ edit() {
370
+ return this.editedIndex >= 0;
371
+ },
372
+ formTitle() {
373
+ return this.edit ? 'Cập nhật' : 'Thêm mới';
374
+ },
375
+ dataOut() {
376
+ if (this.updateApi) return this.updateApi['data-out']
377
+ else return null;
378
+ },
379
+ sumText() {
380
+ var items = this.items;
381
+ return _.replace(this.sumFormat, /\[\[(.*?)\]\]/gm, function (s, key) {
382
+ return numeral(_.sumBy(items, key)).format('0,0');
383
+ });
384
+ }
385
+ },
386
+ methods: {
387
+ fixedColumnWidth(width) {
388
+ if (width === undefined || width === null || width === '') return '150px';
389
+ var value = String(width).trim();
390
+ return /^\d+(\.\d+)?$/.test(value) ? value + 'px' : value;
391
+ },
392
+ removeResize() {
393
+ if (this.resizeHandler) {
394
+ window.removeEventListener('resize', this.resizeHandler);
395
+ this.resizeHandler = null;
396
+ }
397
+ },
398
+ // Đo vị trí thật của vùng cuộn sau khi layout xong rồi fill tới đáy màn hình.
399
+ // Footer phân trang / dòng tổng nằm NGOÀI wrapper nên phải trừ phần dưới đó ra.
400
+ updateFillHeight() {
401
+ var t = this;
402
+ t.$nextTick(function () {
403
+ requestAnimationFrame(function () {
404
+ var root = t.$refs.myTable && t.$refs.myTable.$el;
405
+ if (!root) return;
406
+ var wrapper = root.querySelector('.v-table__wrapper, .v-data-table__wrapper');
407
+ if (!wrapper) return;
408
+ var rootRect = root.getBoundingClientRect();
409
+ var wrapRect = wrapper.getBoundingClientRect();
410
+ var below = Math.max(0, rootRect.bottom - wrapRect.bottom);
411
+ var margin = Math.abs(t.heightNum);
412
+ t.fillHeight = Math.max(150, window.innerHeight - wrapRect.top - below - margin);
413
+ });
414
+ });
415
+ },
416
+ // NEW: sum theo từng cột, format riêng cho mỗi cột theo sumFormat[key] (dùng khi sumFormat là object)
417
+ sumByColumn(key) {
418
+ return numeral(_.sumBy(this.items, key)).format(this.sumFormat[key]);
419
+ },
420
+ itemSelect() {
421
+ var t = this;
422
+
423
+ setTimeout(function () {
424
+ if (!t.returnObject) t.returnValue = _.map(t.itemselected, t.itemKey);
425
+ else t.returnValue = t.itemselected;
426
+
427
+ t.$emit('input', t.returnValue);
428
+ }, 10);
429
+ },
430
+ isDateValid(dateStr) {
431
+ return typeof dateStr == 'string' && !isNaN(new Date(dateStr));
432
+ },
433
+ toggleSearch() {
434
+ this.searchOpen = !this.searchOpen;
435
+ if (this.searchOpen) {
436
+ this.$nextTick(() => {
437
+ this.$refs.searchField && this.$refs.searchField.focus();
438
+ });
439
+ }
440
+ },
441
+ onSearchBlur() {
442
+ if (!this.search) this.searchOpen = false;
443
+ },
444
+ buildHeader(item_0) {
445
+ var header = [];
446
+
447
+ if (item_0) {
448
+ _.forEach(item_0, (value, key) => {
449
+ if (_.isNumber(value)) header.push({ "el": "t-num", "title": key, "key": key, headerProps: { class: "divider" }, cellProps: { class: "divider" }, attr: { "v-model": "item." + key } });
450
+ else if (this.isDateValid(value)) header.push({ "el": "t-time", "title": key, "key": key, headerProps: { class: "divider" }, cellProps: { class: "divider" }, attr: { "v-model": "item." + key } });
451
+ else header.push({ "title": key, "key": key, headerProps: { class: "divider" }, cellProps: { class: "divider" }, });
452
+ });
453
+ }
454
+ console.log("Build Header: ", header)
455
+ return header;
456
+ },
457
+ buildHeaderData(item_0) {
458
+ var arrHeaders = this.buildHeader(item_0);
459
+ // NEW: chỉ tự thêm cột ctrl-update khi có khai báo edit hoặc delete API (đồng bộ với bản Vue 2)
460
+ var hasEditOrDelete = this.updateApi && (this.updateApi.edit || this.updateApi.delete);
461
+ if (arrHeaders.length > 0 && this.items && hasEditOrDelete && _.isBoolean(this.disabledUpdate) && !this.disabledUpdate) arrHeaders.push({ "title": "update", "key": "ctrl-update" });
462
+
463
+ return arrHeaders;
464
+ },
465
+ headerSetDefaultProps(col) {
466
+ _.forEach(col, (c) => {
467
+ if (c.divider) {
468
+ if (c.headerProps) {
469
+ if (c.headerProps.class) c.headerProps.class = c.headerProps.class + ' divider'
470
+ else c.headerProps.class = "divider";
471
+ }
472
+ else c.headerProps = { class: "divider" }
473
+
474
+ if (c.cellProps) {
475
+ if (c.cellProps.class) c.cellProps.class = c.cellProps.class + ' divider'
476
+ else c.cellProps.class = "divider";
477
+ }
478
+ else c.cellProps = { class: "divider" }
479
+
480
+ if (c.children) c.children = this.headerSetDefaultProps(c.children);
481
+ }
482
+ });
483
+ return col
484
+ },
485
+ itemSelectRebuild(newVal) {
486
+ var t = this;
487
+ if (t.returnObject) t.itemselected = newVal;
488
+ else {
489
+ t.itemselected = [];
490
+
491
+ _.forEach(newVal, function (val) {
492
+ t.itemselected.push(_.find(t.items, [t.itemKey, val]));
493
+ });
494
+ }
495
+ },
496
+ newBtnClick() {
497
+ if (this.newAction()) runAction(this.newAction());
498
+ if (this.newAPI()) {
499
+ Object.assign(this.editData, this.defaultItem());
500
+ this.editDialog = true;
501
+ }
502
+ },
503
+ editItem(item) {
504
+
505
+ if (this.editAction()) runAction(this.editAction(), { item: item });
506
+
507
+ if (this.editAPI()) {
508
+ this.editedIndex = this.items.indexOf(item);
509
+ Object.assign(this.editData, this.defaultItem());
510
+ this.editData = Object.assign({}, item);
511
+ this.editDialog = true;
512
+ }
513
+ },
514
+
515
+ deleteItem(item, deleteAPI) {
516
+ const items = this.items;
517
+ var actionAPI = deleteAPI;
518
+ var indexDelete = items.indexOf(item)
519
+ confirm({
520
+ title: (actionAPI.CONFIRM ? bindDataToString(actionAPI.CONFIRM, _.assignIn({ $row: item, item: item }, vueData)) : "Bạn có chắc chắn muốn xoá ?"),
521
+ action: function () {
522
+ vueAction(actionAPI, { $row: item, item: item }, function () {
523
+ items.splice(indexDelete, 1);
524
+ });
525
+ }
526
+ });
527
+ },
528
+
529
+ save(actionAPI) {
530
+ var diaCtl = this;
531
+ if (diaCtl.editedIndex > -1) {
532
+ if (actionAPI) {
533
+ if (actionAPI.CONFIRM) {
534
+ confirm({
535
+ title: bindDataToString(actionAPI.CONFIRM, _.assignIn({ $row: diaCtl.editData, item: diaCtl.editData }, vueData)),
536
+ content: '',
537
+ action: function () {
538
+ vueAction(actionAPI, { $row: diaCtl.editData, item: diaCtl.editData }, function (d) {
539
+ if (!d.Message) {
540
+ if (_.isArray(d.data)) {
541
+ if (d.data[0]) {
542
+ _.forEach(d.data[0], function (value, key) {
543
+ diaCtl.editData[key] = value;
544
+ });
545
+ }
546
+ }
547
+ }
548
+ _.assignIn(diaCtl.items[diaCtl.editedIndex], diaCtl.editData);
549
+ diaCtl.editDialog = false;
550
+ });
551
+ },
552
+ cancel: function () { }
553
+ });
554
+ } else {
555
+ vueAction(actionAPI, { $row: diaCtl.editData, item: diaCtl.editData }, function (d) {
556
+ if (!d.Message) {
557
+ if (_.isArray(d.data)) {
558
+ if (d.data[0]) {
559
+ _.forEach(d.data[0], function (value, key) {
560
+ diaCtl.editData[key] = value;
561
+ });
562
+ }
563
+ }
564
+ }
565
+ _.assignIn(diaCtl.items[diaCtl.editedIndex], diaCtl.editData);
566
+ diaCtl.editDialog = false;
567
+ });
568
+ }
569
+ } else {
570
+ _.assignIn(diaCtl.items[diaCtl.editedIndex], diaCtl.editData);
571
+ diaCtl.editDialog = false;
572
+ }
573
+ }
574
+ },
575
+
576
+ addnew(actionAPI) {
577
+ var diaCtl = this;
578
+ if (diaCtl.editedIndex == -1) {
579
+ if (actionAPI) {
580
+ if (actionAPI.CONFIRM) {
581
+ confirm({
582
+ title: bindDataToString(actionAPI.CONFIRM, _.assignIn({ $row: diaCtl.editData, item: diaCtl.editData }, vueData)),
583
+ action: function () {
584
+ vueAction(actionAPI, { $row: diaCtl.editData, item: diaCtl.editData }, function (d) {
585
+ if (d) {
586
+ if (_.isArray(d.data)) {
587
+ if (d.data[0]) {
588
+ _.forEach(d.data[0], function (value, key) {
589
+ diaCtl.editData[key] = value;
590
+ });
591
+ }
592
+ }
593
+ }
594
+ diaCtl.items.push(_.cloneDeep(diaCtl.editData));
595
+ diaCtl.editDialog = false;
596
+ });
597
+ },
598
+ cancel: function () { }
599
+ });
600
+ } else {
601
+ vueAction(actionAPI, { $row: diaCtl.editData, item: diaCtl.editData }, function (d) {
602
+ if (d) {
603
+ if (_.isArray(d.data)) {
604
+ if (d.data[0]) {
605
+ _.forEach(d.data[0], function (value, key) {
606
+ diaCtl.editData[key] = value;
607
+ });
608
+ }
609
+ }
610
+ }
611
+ diaCtl.items.push(_.cloneDeep(diaCtl.editData));
612
+ diaCtl.editDialog = false;
613
+ });
614
+ }
615
+ } else {
616
+ diaCtl.items.push(_.cloneDeep(diaCtl.editData));
617
+ diaCtl.editDialog = false;
618
+ }
619
+ }
620
+ },
621
+ updateSearch: _.debounce(function (text) {
622
+ this.search = text;
623
+ }, 300),
624
+ customSearch(value, search, item) {
625
+ return Object.values(item.columns).some(v=>v&&v.toString().toLowerCase().includes(search.toLowerCase()))
626
+ },
627
+ toExcel() {
628
+ jsonToExcel({ data: this.items, filename: (this.label && this.label != '' ? this.label : 'tableFP') + '.xlsx' });
629
+ Vue.$toast.success("Đã xuất Excel", { position: 'top' });
630
+ },
631
+ }
632
+ });
633
+
634
+ //===========================================================================================
635
+
636
+ Vue.component('THtml', {
637
+ props: {
638
+ modelValue: {
639
+ default: null
640
+ },
641
+ },
642
+ template: `<div class="my-2" v-bind="$attrs" v-html="modelValue"></div>`,
643
+ });
644
+ //-----------------------------------------------------------------------------------------------------------
645
+ Vue.component('TLabel', {
646
+ props: {
647
+ modelValue: {
648
+ default: null
649
+ },
650
+ },
651
+ template: `<div class="text-no-wrap" v-bind="$attrs">{{modelValue}}</div>`,
652
+ });
653
+ //-----------------------------------------------------------------------------------------------------------
654
+ Vue.component('TNum', {
655
+ props: {
656
+ modelValue: {
657
+ type: Number,
658
+ default: 0
659
+ },
660
+ hidezero: {
661
+ type: Boolean,
662
+ default: true
663
+ },
664
+ format: {
665
+ type: String,
666
+ default: ''
667
+ }
668
+ },
669
+ template: `<div v-if="showData" class="text-no-wrap text-end" v-bind="$attrs">{{ format != ''? numeral(modelValue).format(format) : Number.isInteger(modelValue)?numeral(modelValue).format('0,0'): numeral(modelValue).format('0,0.0') }}</div>`,
670
+ data: function () {
671
+ return {
672
+ numeral: numeral
673
+ }
674
+ },
675
+ computed: {
676
+ showData: function () {
677
+ return !_.isNull(this.modelValue) && !(this.hidezero && this.modelValue == 0);
678
+ },
679
+ },
680
+ });
681
+ //-----------------------------------------------------------------------------------------------------------
682
+ Vue.component('TTime', {
683
+ props: {
684
+ format: {
685
+ type: String,
686
+ default: 'DD/MM/YYYY'
687
+ }
688
+ },
689
+ template: `<div v-if="$attrs['modelValue']" class="text-no-wrap" v-bind="$attrs">{{ dayjs($attrs['modelValue']).format(format) }}</div>`,
690
+ data: function () {
691
+ return {
692
+ dayjs: dayjs
693
+ }
694
+ },
695
+ });
696
+ //-----------------------------------------------------------------------------------------------------------
697
+ Vue.component('TCheck', {
698
+ props: {
699
+ modelValue: {
700
+ default: null
701
+ },
702
+ falseValue: {
703
+ default: false
704
+ },
705
+ },
706
+ template: `<v-checkbox v-bind="$attrs" v-model="ctlValue" :false-value="falseValue" @change="onchange($event)" class="d-inline-flex" hide-details />`,
707
+ data: function () {
708
+ return {
709
+ ctlValue: Boolean(this.modelValue)
710
+ }
711
+ },
712
+ watch: {
713
+ modelValue(newVal) {
714
+ this.ctlValue = Boolean(newVal)
715
+ },
716
+ },
717
+ methods: {
718
+ onchange: function (e) {
719
+ console.log("onchange", this)
720
+ tableActionEvent(this.$attrs);
721
+ }
722
+ }
723
+ });
724
+ //-----------------------------------------------------------------------------------------------------------
725
+ Vue.component('TSelect', {
726
+ props: ['modelValue'],
727
+ template: `<v-select v-model="ctlValue" v-bind="$attrs" @update:modelValue="onchange" variant="plain" density="compact" hide-details menu-icon="mdi-menu-swap"></v-select>`,
728
+ data: function () {
729
+ return {
730
+ ctlValue: this.modelValue
731
+ }
732
+ },
733
+ watch: {
734
+ modelValue(newVal) {
735
+ this.ctlValue = newVal
736
+ },
737
+ },
738
+ methods: {
739
+ onchange: function (e) {
740
+ tableActionEvent(this.$attrs);
741
+ }
742
+ }
743
+ });
744
+ //-----------------------------------------------------------------------------------------------------------
745
+ Vue.component('TCombobox', {
746
+ props: {
747
+ modelValue: {
748
+ default: undefined
749
+ },
750
+ text: {
751
+ default: undefined
752
+ },
753
+ items: {
754
+ type: Array,
755
+ default: []
756
+ },
757
+ api: {
758
+ type: String,
759
+ default: undefined
760
+ },
761
+ apiData: {
762
+ type: Object,
763
+ default: {}
764
+ },
765
+
766
+ },
767
+ template: `<v-autocomplete v-model="ctlValue" v-bind="$attrs" @update:modelValue="onchange" :loading="loading" :items="dataItems" @update:search="querySelections" auto-select-first density="compact" hide-details></v-autocomplete>`,
768
+ data: function () {
769
+ return {
770
+ dataItems: this.items,
771
+ ctlValue: this.modelValue,
772
+ loading: false,
773
+ getDataFormAPI: _.debounce((searchText) => {
774
+ if ((searchText && searchText.length < 2) || !this.api) return;
775
+ var self = this;
776
+ this.loading = true
777
+
778
+ if (typeof this.apiData != 'object') this.apiData = JSON.parse(this.apiData);
779
+
780
+ mapData(this.apiData, this.apiData, _.assignIn({}, vueData, { TEXT: searchText }));
781
+
782
+ ajaxCALL(this.api, this.apiData, function (d) {
783
+ if (_.isArray(d.data)) self.dataItems = d.data;
784
+ else self.dataItems = d;
785
+
786
+ self.loading = false
787
+ }, function (e) {
788
+ console.log("API Error", e);
789
+ })
790
+
791
+ }, 500)
792
+ }
793
+ },
794
+ watch: {
795
+ items(val) {
796
+ this.dataItems = val
797
+ },
798
+ modelValue(newVal) {
799
+ this.ctlValue = newVal
800
+ }
801
+ },
802
+ methods: {
803
+ querySelections(val) {
804
+ if (!val) return false
805
+ this.getDataFormAPI(val);
806
+ },
807
+ onchange: function (e) {
808
+ if (e) tableActionEvent(this.$attrs);
809
+ }
810
+ },
811
+ });
812
+ //-----------------------------------------------------------------------------------------------------------
813
+ Vue.component('TMenu', {
814
+ props: {
815
+ iconText: {
816
+ default: 'mdi-menu'
817
+ },
818
+ items: {
819
+ default: []
820
+ }
821
+ },
822
+ template: `
823
+ <v-menu offset-y origin="center center" transition="slide-y-transition" max-height = 500>
824
+ <template v-slot:activator="{ props }"><v-icon color="primary" v-bind="props" :icon="iconText"></v-icon></template>
825
+ <v-list >
826
+ <v-list-item v-bind="$attrs" v-for="(item, index) in items" :key="index" @click="onSelect(item)" >
827
+ <template v-slot:prepend><v-icon :color="item.color" :icon="item['icon-text']"></v-icon></template>
828
+ <v-list-item-title v-text="item.text"></v-list-item-title>
829
+ </v-list-item>
830
+ </v-list>
831
+ </v-menu>
832
+ `,
833
+ methods: {
834
+ onSelect: function (e) {
835
+ tableActionEvent({ action: e.action, ...this.$attrs });
836
+ }
837
+ }
838
+ });
839
+ //-----------------------------------------------------------------------------------------------------------
840
+ Vue.component('TLink', {
841
+ props: ['id', 'target', 'url', 'title', 'onclose'],
842
+ template: `<a :href="iURL" :target="target" v-bind="$attrs" @click="onclick()"><v-icon v-if="$attrs['icon-text']" left color="primary">{{$attrs['icon-text']}}</v-icon>{{$attrs['modelValue']}}</a>`,
843
+ data: function () {
844
+ return {}
845
+ },
846
+ computed: {
847
+ iURL: function () {
848
+ if (this.target != 'dialog' && this.url) return this.url;
849
+ return null;
850
+ },
851
+ },
852
+ methods: {
853
+ onclick: function (e) {
854
+ if (this.target == 'dialog') {
855
+ tableActionEvent(this.$attrs);
856
+ openWindow({
857
+ id: this.id,
858
+ title: this.title,
859
+ url: this.url,
860
+ onclose: this.onclose,
861
+ });
862
+ }
863
+ }
864
+ }
865
+ });
866
+ //-----------------------------------------------------------------------------------------------------------
867
+ Vue.component('TButton', {
868
+ props: {
869
+ label: {
870
+ default: null
871
+ },
872
+ variant: {
873
+ default: "text"
874
+ },
875
+ color: {
876
+ default: "primary"
877
+ },
878
+ wid: {
879
+ default: null
880
+ },
881
+ url: {
882
+ default: null
883
+ },
884
+ title: {
885
+ default: null
886
+ },
887
+ density: {
888
+ default: 'compact'
889
+ },
890
+ onclose: {
891
+ default: null
892
+ },
893
+ },
894
+ template: `<v-btn v-bind="$attrs" :variant="variant" :text="label" :color="color" :density="density" @click="clickButton"></v-btn>`,
895
+ methods: {
896
+ clickButton: function (e) {
897
+ if (this.url) {
898
+ console.log("dialog onclose", this.onclose)
899
+ openWindow({
900
+ id: this.wid,
901
+ title: this.title,
902
+ url: this.url,
903
+ onclose: this.onclose
904
+ });
905
+ } else tableActionEvent(this.$attrs);
906
+ }
907
+ }
908
+
909
+ });