@wp1001/ui 2.9.13

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 (177) hide show
  1. package/.env +6 -0
  2. package/@vant-D4fmGxs6.js +3891 -0
  3. package/index.js +8419 -0
  4. package/package.json +59 -0
  5. package/packages/assets/devtools-detector.js +2 -0
  6. package/packages/components/xarray/index.js +64 -0
  7. package/packages/components/xarray/xarray.vue +57 -0
  8. package/packages/components/xautorows/index.js +35 -0
  9. package/packages/components/xautorows/xautorows.vue +29 -0
  10. package/packages/components/xbutton/mobile.js +3 -0
  11. package/packages/components/xbutton/mobile.vue +9 -0
  12. package/packages/components/xbutton/pc.js +3 -0
  13. package/packages/components/xbutton/pc.vue +9 -0
  14. package/packages/components/xbuttons/mobile.js +51 -0
  15. package/packages/components/xbuttons/mobile.vue +12 -0
  16. package/packages/components/xbuttons/pc.js +51 -0
  17. package/packages/components/xbuttons/pc.vue +16 -0
  18. package/packages/components/xchart/constants.js +58 -0
  19. package/packages/components/xchart/index.js +263 -0
  20. package/packages/components/xchart/utils.js +121 -0
  21. package/packages/components/xchart/xchart.vue +173 -0
  22. package/packages/components/xcheckboxs/mobile.js +58 -0
  23. package/packages/components/xcheckboxs/mobile.vue +38 -0
  24. package/packages/components/xcheckboxs/pc.js +49 -0
  25. package/packages/components/xcheckboxs/pc.vue +42 -0
  26. package/packages/components/xcol/mobile.js +10 -0
  27. package/packages/components/xcol/mobile.vue +9 -0
  28. package/packages/components/xcol/pc.js +10 -0
  29. package/packages/components/xcol/pc.vue +9 -0
  30. package/packages/components/xdatepicker/mobile.js +71 -0
  31. package/packages/components/xdatepicker/mobile.vue +44 -0
  32. package/packages/components/xdatepicker/pc.js +9 -0
  33. package/packages/components/xdatepicker/pc.vue +12 -0
  34. package/packages/components/xdialog/mobile.js +60 -0
  35. package/packages/components/xdialog/mobile.vue +43 -0
  36. package/packages/components/xdialog/pc.js +64 -0
  37. package/packages/components/xdialog/pc.vue +51 -0
  38. package/packages/components/xdict/index.js +47 -0
  39. package/packages/components/xdict/xdict.vue +9 -0
  40. package/packages/components/xdistrictselect/mobile.js +79 -0
  41. package/packages/components/xdistrictselect/mobile.vue +28 -0
  42. package/packages/components/xdistrictselect/pc.js +127 -0
  43. package/packages/components/xdistrictselect/pc.vue +32 -0
  44. package/packages/components/xform/mobile.js +29 -0
  45. package/packages/components/xform/mobile.vue +43 -0
  46. package/packages/components/xform/pc.js +42 -0
  47. package/packages/components/xform/pc.vue +76 -0
  48. package/packages/components/xform/utils.js +95 -0
  49. package/packages/components/xformitem/mobile.js +56 -0
  50. package/packages/components/xformitem/mobile.vue +3 -0
  51. package/packages/components/xformitem/pc.js +72 -0
  52. package/packages/components/xformitem/pc.vue +10 -0
  53. package/packages/components/xformitem/utils.jsx +181 -0
  54. package/packages/components/xicon/mobile.js +35 -0
  55. package/packages/components/xicon/mobile.vue +9 -0
  56. package/packages/components/xicon/pc.js +35 -0
  57. package/packages/components/xicon/pc.vue +11 -0
  58. package/packages/components/xinfo/index.js +100 -0
  59. package/packages/components/xinfo/xinfo.vue +140 -0
  60. package/packages/components/xlooper/index.js +7 -0
  61. package/packages/components/xlooper/xlooper.vue +20 -0
  62. package/packages/components/xpagination/mobile.js +21 -0
  63. package/packages/components/xpagination/mobile.vue +31 -0
  64. package/packages/components/xpagination/pc.js +21 -0
  65. package/packages/components/xpagination/pc.vue +16 -0
  66. package/packages/components/xpicker/index.js +38 -0
  67. package/packages/components/xpicker/xpicker.vue +29 -0
  68. package/packages/components/xradios/mobile.js +40 -0
  69. package/packages/components/xradios/mobile.vue +22 -0
  70. package/packages/components/xradios/pc.js +53 -0
  71. package/packages/components/xradios/pc.vue +43 -0
  72. package/packages/components/xrow/mobile.js +9 -0
  73. package/packages/components/xrow/mobile.vue +23 -0
  74. package/packages/components/xrow/pc.js +9 -0
  75. package/packages/components/xrow/pc.vue +22 -0
  76. package/packages/components/xscan/mobile.js +24 -0
  77. package/packages/components/xscan/mobile.vue +21 -0
  78. package/packages/components/xscan/pc.js +20 -0
  79. package/packages/components/xscan/pc.vue +18 -0
  80. package/packages/components/xsearcher/index.js +198 -0
  81. package/packages/components/xsearcher/xsearcher.vue +170 -0
  82. package/packages/components/xselect/mobile.js +86 -0
  83. package/packages/components/xselect/mobile.vue +24 -0
  84. package/packages/components/xselect/pc.js +114 -0
  85. package/packages/components/xselect/pc.vue +55 -0
  86. package/packages/components/xselect/util.js +66 -0
  87. package/packages/components/xselectv2/index.js +91 -0
  88. package/packages/components/xselectv2/xselectv2.vue +46 -0
  89. package/packages/components/xtable/mobile.js +108 -0
  90. package/packages/components/xtable/mobile.vue +246 -0
  91. package/packages/components/xtable/pc.js +143 -0
  92. package/packages/components/xtable/pc.vue +421 -0
  93. package/packages/components/xtable/searcher.js +477 -0
  94. package/packages/components/xtable/searcher.jsx +330 -0
  95. package/packages/components/xtable/searcher.vue +133 -0
  96. package/packages/components/xtable/settings.js +80 -0
  97. package/packages/components/xtable/settings.vue +77 -0
  98. package/packages/components/xtable/utils.js +692 -0
  99. package/packages/components/xtabletools/mobile.js +25 -0
  100. package/packages/components/xtabletools/mobile.vue +126 -0
  101. package/packages/components/xtabletools/pc.js +18 -0
  102. package/packages/components/xtabletools/pc.vue +135 -0
  103. package/packages/components/xtablev2/index.js +53 -0
  104. package/packages/components/xtablev2/utils.jsx +214 -0
  105. package/packages/components/xtablev2/xtablev2.vue +147 -0
  106. package/packages/components/xtags/mobile.js +17 -0
  107. package/packages/components/xtags/mobile.vue +21 -0
  108. package/packages/components/xtags/pc.js +17 -0
  109. package/packages/components/xtags/pc.vue +22 -0
  110. package/packages/components/xtinymce/index.js +71 -0
  111. package/packages/components/xtinymce/xtinymce.vue +9 -0
  112. package/packages/components/xuploader/xfileuploader.js +48 -0
  113. package/packages/components/xuploader/xfileuploader.vue +54 -0
  114. package/packages/components/xuploader/ximageuploader.js +53 -0
  115. package/packages/components/xuploader/ximageuploader.vue +52 -0
  116. package/packages/comps.js +108 -0
  117. package/packages/controllers/BaseController.js +125 -0
  118. package/packages/controllers/CrudController.js +907 -0
  119. package/packages/controllers/TempCrudController.js +32 -0
  120. package/packages/controllers/index.js +15 -0
  121. package/packages/directives/el-table-infinite-scroll.js +55 -0
  122. package/packages/directives/index.js +5 -0
  123. package/packages/index.js +81 -0
  124. package/packages/index.scss +4 -0
  125. package/packages/layout/breadcrumb/breadcrumb.vue +31 -0
  126. package/packages/layout/breadcrumb/index.js +41 -0
  127. package/packages/layout/header/header.vue +281 -0
  128. package/packages/layout/header/inner.js +11 -0
  129. package/packages/layout/header/inner.vue +3 -0
  130. package/packages/layout/mobile-menu.vue +83 -0
  131. package/packages/layout/mobile-tabs.vue +54 -0
  132. package/packages/layout/pc.vue +85 -0
  133. package/packages/layout/screenlock/index.js +129 -0
  134. package/packages/layout/screenlock/screenlock.vue +85 -0
  135. package/packages/layout/sidebar/item.js +16 -0
  136. package/packages/layout/sidebar/item.vue +16 -0
  137. package/packages/layout/sidebar/menu.js +72 -0
  138. package/packages/layout/sidebar/menu.vue +106 -0
  139. package/packages/layout/sidebar/sidebar.vue +147 -0
  140. package/packages/layout/tagsview/ScrollPane.js +65 -0
  141. package/packages/layout/tagsview/ScrollPane.vue +24 -0
  142. package/packages/layout/tagsview/index.js +169 -0
  143. package/packages/layout/tagsview/index.vue +124 -0
  144. package/packages/plop/actions/make-fill-admin-partials-action.js +95 -0
  145. package/packages/plop/generators/make-admin-page.js +39 -0
  146. package/packages/plop/generators/make-database-admin-pages.js +84 -0
  147. package/packages/plop/generators/make-page-generator.js +52 -0
  148. package/packages/plop/generators/make-simple-page.js +20 -0
  149. package/packages/plop/plopfile.js +24 -0
  150. package/packages/plop/templates/admin_page/controller.js +3 -0
  151. package/packages/plop/templates/admin_page/model.js +24 -0
  152. package/packages/plop/templates/admin_page/{{snakeCase pagename}}-scoped.scss +3 -0
  153. package/packages/plop/templates/admin_page/{{snakeCase pagename}}.vue +11 -0
  154. package/packages/plop/templates/simple_page/controller.js +3 -0
  155. package/packages/plop/templates/simple_page/model.js +6 -0
  156. package/packages/plop/templates/simple_page/{{snakeCase pagename}}-scoped.scss +3 -0
  157. package/packages/plop/templates/simple_page/{{snakeCase pagename}}.vue +7 -0
  158. package/packages/plop/utils/index.js +168 -0
  159. package/packages/plop/utils/plop-utils.js +86 -0
  160. package/packages/styles/common.scss +137 -0
  161. package/packages/styles/element-ui.scss +142 -0
  162. package/packages/styles/vant.scss +133 -0
  163. package/packages/styles/variables.scss +23 -0
  164. package/packages/utils/crypt.js +24 -0
  165. package/packages/utils/decorators.js +67 -0
  166. package/packages/utils/disallowDevtools.js +53 -0
  167. package/packages/utils/effects.js +173 -0
  168. package/packages/utils/funcs.js +78 -0
  169. package/packages/utils/index.js +95 -0
  170. package/packages/utils/message.js +110 -0
  171. package/packages/utils/middlewares.js +86 -0
  172. package/packages/utils/model.js +71 -0
  173. package/packages/utils/modelUtils.js +203 -0
  174. package/packages/utils/request.js +57 -0
  175. package/packages/utils/site.js +33 -0
  176. package/packages/vite-plugins.js +141 -0
  177. package/publish.sh +12 -0
@@ -0,0 +1,907 @@
1
+ import { watch, nextTick } from 'vue'
2
+ import BaseController from './BaseController.js'
3
+ import { Message, Confirm, Prompt } from '../utils/message.js'
4
+ const { funcs, highdict, dates } = StardustJs
5
+ const { file, excel } = StardustBrowser
6
+
7
+ class CrudController extends BaseController {
8
+ constructor (props) {
9
+ super(props)
10
+
11
+ const { model, table, dialog, dbModelName = '', idField = 'id', listProp = 'data' } = props
12
+
13
+ this.table = table || model?.table
14
+ this.dialog = dialog || model?.dialog
15
+ this.dbModelName = dbModelName
16
+ this.idField = idField
17
+ this.listProp = listProp
18
+
19
+ // 是否在提交中
20
+ this._isSubmitting = false
21
+ // 上次查询条件,json 字符串
22
+ this._lastSearchParams = null
23
+ // 初始的 limit
24
+ this._initialLimit = this.table?.query?.limit
25
+
26
+ this._dbTable = null
27
+
28
+ this._unwatchs = []
29
+
30
+ nextTick(() => {
31
+ const { name } = this.route
32
+ const unwatch = watch(() => this.router.currentRoute, route => {
33
+ if (name !== route.name) {
34
+ this._unwatchs.forEach(un => un())
35
+ unwatch()
36
+ }
37
+ })
38
+ })
39
+ }
40
+
41
+ onInit () {
42
+ super.onInit()
43
+ this.table?.uid && this._initSearching()
44
+ }
45
+
46
+ get dbTable () {
47
+ if (!this._dbTable) {
48
+ let [database, table] = this.dbModelName.split('.')
49
+ if (!table) {
50
+ database = ''
51
+ table = database
52
+ }
53
+ this._dbTable = new this.service.Table(database, table)
54
+ }
55
+ return this._dbTable
56
+ }
57
+
58
+ get form () {
59
+ if (this.model?.form && this.dialog?.form) throw 'conflict of model.form and dialog.form'
60
+ return this.model?.form || this.dialog?.form
61
+ }
62
+
63
+ _getMethods () {
64
+ return [
65
+ ...super._getMethods(),
66
+
67
+ 'handleKeywordsSearch',
68
+ 'handleSearch',
69
+ 'handleAdd',
70
+ 'handleEdit',
71
+ 'handleDelete',
72
+ 'handleRowEdit',
73
+ 'handleExport',
74
+ 'handleSearchExport',
75
+ 'handleImport',
76
+ 'handleMultiDelete',
77
+ 'handleSave',
78
+ 'handleSubmit',
79
+ 'handleCancel',
80
+ 'handleSortChange',
81
+ 'handleLoad',
82
+
83
+ 'onSearch',
84
+ 'onAdd',
85
+ 'onEdit',
86
+ 'onDelete',
87
+ 'onSubmit',
88
+ 'onCancel',
89
+ 'onRowEdit',
90
+ 'onCancelEdit',
91
+ 'onExport',
92
+ 'onSearchExport',
93
+ 'onImport',
94
+ 'onMultiDelete',
95
+
96
+ 'search',
97
+ 'add',
98
+ 'update',
99
+ 'remove',
100
+
101
+ 'getSearchParams',
102
+ 'getAddParams',
103
+ 'getUpdateParams',
104
+ 'getDeleteParams',
105
+ 'getSearchExportParams',
106
+
107
+ 'injectSearchParams',
108
+ 'injectAddParams',
109
+ 'injectUpdateParams',
110
+ 'injectDeleteParams',
111
+
112
+ 'beforeSearch',
113
+ 'beforeAdd',
114
+ 'beforeEdit',
115
+ 'beforeDelete',
116
+ 'afterSearch',
117
+ 'afterAdd',
118
+ 'afterEdit',
119
+ 'afterDelete',
120
+ 'afterSubmit',
121
+
122
+ 'updatePartials',
123
+
124
+ 'formatList',
125
+ 'injectList',
126
+ 'processExportingColumns',
127
+ 'processExportingData',
128
+ 'processExporting',
129
+ 'processImportingData',
130
+
131
+ '_initSearching',
132
+ '_defaultFormatList',
133
+ '_fillRelatedField',
134
+ '_resetForm',
135
+ '_clearValidate',
136
+ '_trimForm',
137
+ '_validateForm',
138
+ '_checkAllNone',
139
+ '_showError',
140
+ '_focusDialogInput'
141
+ ]
142
+ }
143
+
144
+ async handleKeywordsSearch (keywords) {
145
+ this._lastSearchParams = null
146
+ keywords = keywords.replace(/,/g, ' ').split(' ').filter(k => k)
147
+ if (!keywords.length) return this.handleSearch({ page: 1 })
148
+ let { searchFields, columns } = this.table
149
+ if (!searchFields.length) {
150
+ searchFields = [...new Set(columns.filter(c => {
151
+ if (typeof c.canSearch === 'boolean') return c.canSearch
152
+ return c.prop && c.type !== 'number' && !c.comp && !c.virtual
153
+ }).map(c => c.prop))]
154
+ }
155
+ if (!searchFields.length) return this.handleSearch({ page: 1 })
156
+ const ands = keywords.map(k => {
157
+ const ors = []
158
+ searchFields.forEach(field => {
159
+ ors.push({ [field]: { '[Op.like]': '%' + k + '%' } })
160
+ })
161
+ return { '[Op.or]': ors }
162
+ })
163
+ return this.handleSearch({ page: 1, where: { '[Op.and]': ands } })
164
+ }
165
+
166
+ async handleSearch (params, { isInfinite = false } = {}) {
167
+ if (params instanceof Event) params = null
168
+ this.table.isInfinite = isInfinite
169
+ if (this.table.loading || !(await this.beforeSearch(params))) return
170
+ params = this.getSearchParams(params)
171
+ this.injectSearchParams(params)
172
+ this.table.loading = true
173
+ const data = await this.search(params)
174
+ let list = highdict.get(data, this.listProp)
175
+ list = this.formatList(this._defaultFormatList(list, data), data)
176
+ list.forEach(this.injectList)
177
+ Object.assign(this.table, {
178
+ list,
179
+ total: data.total,
180
+ loading: false
181
+ })
182
+ this.afterSearch(list, params, data)
183
+ return data
184
+ }
185
+
186
+ async handleAdd () {
187
+ if (!await this.beforeAdd()) return
188
+ this._resetForm()
189
+ Object.assign(this.dialog, {
190
+ visible: true,
191
+ isEditing: false
192
+ })
193
+ await nextTick()
194
+ await funcs.sleep(50)
195
+ this._clearValidate()
196
+ this._focusDialogInput()
197
+ this.afterAdd()
198
+ }
199
+
200
+ async handleEdit ({ $index, row }) {
201
+ if (!await this.beforeEdit({ $index, row })) return
202
+ if (this.table?.isRowEdit) {
203
+ row.originData = JSON.stringify(row)
204
+ row.isEditing = true
205
+ } else {
206
+ this._resetForm()
207
+ const form = {
208
+ ...this.dialog.form,
209
+ ...row
210
+ }
211
+ this.dialog.formItems.forEach(item => {
212
+ if (item.seperator && typeof form[item.prop] === 'string') {
213
+ form[item.prop] = form[item.prop].split(item.seperator)
214
+ }
215
+ })
216
+ Object.assign(this.dialog, {
217
+ visible: true,
218
+ isEditing: true,
219
+ editingIndex: $index,
220
+ editingRow: row,
221
+ form
222
+ })
223
+ await nextTick()
224
+ this.dialog.formRef?.validate().catch(Function())
225
+ this._focusDialogInput()
226
+ }
227
+ this.afterEdit({ $index, row })
228
+ }
229
+
230
+ async handleDelete ({ $index, row }) {
231
+ if (this.table.loading) return
232
+ if (!await this.beforeDelete({ $index, row })) return
233
+ const ok = await Confirm.w({ message: '确定要删除吗?', title: '警告' })
234
+ if (!ok) return
235
+ this.table.loading = true
236
+ const params = this.getDeleteParams(row)
237
+ this.injectDeleteParams(params)
238
+ const data = await this.remove(params, row)
239
+ this.table.loading = false
240
+ if (data.err) return
241
+ this.afterDelete(data)
242
+ this.handleSearch()
243
+ }
244
+
245
+ async handleRowEdit ({ row }) {
246
+ if (row._loading) return
247
+ row._loading = true
248
+ const params = this.getUpdateParams(row)
249
+ this.injectUpdateParams(params)
250
+ if (!(await this._checkAllNone(params))) {
251
+ row._loading = false
252
+ return
253
+ }
254
+ try {
255
+ await this.update(row[this.idField], params)
256
+ } catch (err) {
257
+ this._showError(err.data.err)
258
+ row._loading = false
259
+ return
260
+ }
261
+ delete row.originData
262
+ row.isEditing = false
263
+ row._loading = false
264
+ }
265
+
266
+ async handleCancelEdit ({ row }) {
267
+ Object.assign(row, JSON.parse(row.originData))
268
+ delete row.originData
269
+ row.isEditing = false
270
+ }
271
+
272
+ async handleExport (type = this.exportType, filename) {
273
+ filename ||= this.table.ref.title || document.title
274
+ if (this.table.loading) return
275
+ if (type instanceof Event) type = ''
276
+ type = type || this.config.exportType || 'csv'
277
+ if (!['csv', 'excel'].includes(type)) {
278
+ Message('不支持的导出类型')
279
+ return
280
+ }
281
+ this.table.loading = true
282
+ const { list, selection, ref } = this.table
283
+ let data = selection.length > 0 ? selection : list
284
+ data = funcs.deepCopy(data)
285
+ data = this.processExportingData(data)
286
+ const cols = this.processExportingColumns(ref._visibleColumns, 'current')
287
+ const props = cols.map(col => col.prop)
288
+ const header = cols.map(col => col.label)
289
+ data = data.map(row => props.map(prop => row[prop]))
290
+ let func = null
291
+ if (type === 'csv') {
292
+ func = excel.export2Csv
293
+ } else {
294
+ func = excel.export2Excel
295
+ }
296
+ let options = { list, header, data, filename }
297
+ options = await this.processExporting(options)
298
+ func(options)
299
+ this.table.loading = false
300
+ }
301
+
302
+ async handleSearchExport (type = this.exportType, filename) {
303
+ filename ||= this.table.ref.title || document.title
304
+ if (this.table.loading) {
305
+ Message.w('导出中...')
306
+ return
307
+ }
308
+ type = type || this.config.exportType || 'csv'
309
+ if (!['csv', 'excel'].includes(type)) {
310
+ Message('不支持的导出类型')
311
+ return
312
+ }
313
+ this.table.loading = true
314
+ const res = await this.dbTable.search(this.getSearchExportParams())
315
+ let data = res.data
316
+ data = this.formatList(data, res)
317
+ data.forEach(this.injectList)
318
+ data = this.processExportingData(data, 'search')
319
+ const cols = this.processExportingColumns(this.table.ref._visibleColumns, 'search-export')
320
+ const props = cols.map(col => col.prop)
321
+ const header = cols.map(col => col.label)
322
+ data = data.map(row => props.map(prop => row[prop]))
323
+ let func = null
324
+ if (type === 'csv') {
325
+ func = excel.export2Csv
326
+ } else {
327
+ func = excel.export2Excel
328
+ }
329
+ let options = { list: res.data, header, data, filename }
330
+ options = await this.processExporting(options)
331
+ func(options)
332
+ this.table.loading = false
333
+ }
334
+
335
+ async handleImport () {
336
+ if (this.table.loading) return
337
+ const f = await file.select('.xlsx,.csv')
338
+ this.table.loading = true
339
+ const isCsv = f.name.toLowerCase().endsWith('.csv')
340
+ const content = await file.toType(f, isCsv ? 'text' : 'arraybuffer')
341
+ let data = []
342
+ if (isCsv) {
343
+ await window.DynamicLibs?.use('Papa')
344
+ data = window.Papa.parse(content, { header: true }).data
345
+ } else {
346
+ await window.DynamicLibs?.use('XLSX')
347
+ const workbook = window.XLSX.read(content, {})
348
+ const sheets = Object.values(workbook.Sheets)
349
+ data = XLSX.utils.sheet_to_json(sheets[0])
350
+ }
351
+ if (data.length > 0) {
352
+ const labelPropDict = {}
353
+ this.table.columns.forEach(col => labelPropDict[col.label] = col.prop)
354
+ const labels = Object.keys(data[0])
355
+ data = data.map(row => {
356
+ const ele = {}
357
+ labels.forEach(label => ele[labelPropDict[label]] = row[label])
358
+ return ele
359
+ })
360
+ }
361
+ data = this.processImportingData(data)
362
+ await this.dbTable.func(['bulkCreate', data])
363
+ Message.s('导入成功')
364
+ this.table.loading = false
365
+ this.handleSearch()
366
+ }
367
+
368
+ async handleMultiDelete () {
369
+ if (this.table.loading) return
370
+ const { selection } = this.table
371
+ if (!selection.length) {
372
+ Message.w('尚未选择要删除的数据')
373
+ return
374
+ }
375
+ const ok = await Confirm.w({ title: '警告', message: `确定删除选中的 ${selection.length} 条数据吗?` })
376
+ if (!ok) return
377
+ this.table.loading = true
378
+ const ids = selection.map(ele => ele[this.idField])
379
+ await this.dbTable.func(['destroy', {
380
+ where: {
381
+ [this.idField]: { '[Op.in]': ids }
382
+ }
383
+ }])
384
+ this.table.loading = false
385
+ this.handleSearch()
386
+ }
387
+
388
+ async handleSave (form) {
389
+ form = form instanceof Event ? this.form : form
390
+ if (this._isSubmitting) {
391
+ Message.w('正在保存...')
392
+ return
393
+ }
394
+ const formRef = this.model.formRef || this.dialog.formRef
395
+ if (!(await this._validateForm(formRef))) return
396
+ this._isSubmitting = true
397
+ const params = this.getAddParams(form)
398
+ this.injectAddParams(params)
399
+ if (!(await this._checkAllNone(params))) {
400
+ this._isSubmitting = false
401
+ return
402
+ }
403
+ let data = null
404
+ try {
405
+ if (form[this.idField]) {
406
+ data = await this.update(form[this.idField], params)
407
+ } else {
408
+ data = await this.add(params)
409
+ }
410
+ } catch (err) {
411
+ this._showError(err.data.err)
412
+ this._isSubmitting = false
413
+ return
414
+ }
415
+ this._isSubmitting = false
416
+ if (!data.err) Message.s('保存成功')
417
+ this.router.go(-1)
418
+ return data
419
+ }
420
+
421
+ async handleSubmit (params) {
422
+ params = params instanceof Event ? null : params
423
+ if (this._isSubmitting) {
424
+ Message.w('正在提交...')
425
+ return false
426
+ }
427
+ if (!this.dialog.visible) return false
428
+ this._isSubmitting = true
429
+ const form = params || this.form
430
+ if (!params) {
431
+ if (this.dialog.shouldTrim ?? true) {
432
+ Object.assign(form, this._trimForm(form))
433
+ }
434
+ if (!(await this._validateForm())) {
435
+ Message.w('请正确填写')
436
+ this._isSubmitting = false
437
+ return false
438
+ }
439
+ }
440
+ let data = null
441
+ try {
442
+ if (this.dialog.isEditing) {
443
+ const params = this.getUpdateParams(form)
444
+ this.injectUpdateParams(params)
445
+ if (!(await this._checkAllNone(params))) {
446
+ this._isSubmitting = false
447
+ return false
448
+ }
449
+ data = await this.update(this.dialog.editingRow[this.idField], params)
450
+ } else {
451
+ const params = this.getAddParams(form)
452
+ this.injectAddParams(params)
453
+ if (!(await this._checkAllNone(params))) {
454
+ this._isSubmitting = false
455
+ return false
456
+ }
457
+ data = await this.add(params)
458
+ }
459
+ } catch (err) {
460
+ this._showError(err.data.err)
461
+ this._isSubmitting = false
462
+ return false
463
+ }
464
+ this.dialog.visible = false
465
+ this._isSubmitting = false
466
+
467
+ if (!data.err) {
468
+ this.handleSearch()
469
+ }
470
+ this.afterSubmit(data)
471
+ return data
472
+ }
473
+
474
+ handleCancel () {
475
+ this.dialog.visible = false
476
+ }
477
+
478
+ handleSortChange (params) {
479
+ if (!params) {
480
+ this.table.query.order = []
481
+ } else if (Array.isArray(params)) {
482
+ this.table.query.order = params
483
+ } else {
484
+ const { prop, order } = params
485
+ this.table.query.order = (!prop || !order) ? [] : [
486
+ [prop, order.slice(0, -6)]
487
+ ]
488
+ }
489
+ this.handleSearch()
490
+ }
491
+
492
+ async handleLoad () {
493
+ const { query } = this.table
494
+ if (!this.table.list.length) {
495
+ await this.handleSearch()
496
+ if (query.page * query.limit >= this.table.total) {
497
+ this.table.finished = true
498
+ }
499
+ return this.table.moreLoading = false
500
+ }
501
+ const { loading, total } = this.table
502
+ if (loading || !total || this.table.finished) {
503
+ return this.table.moreLoading = false
504
+ }
505
+ if (query.page * query.limit >= total) {
506
+ this.table.moreLoading = false
507
+ return this.table.finished = true
508
+ }
509
+ this.table.isInfinite = true
510
+ query.page ++
511
+ const list = this.table.list.slice()
512
+ await this.handleSearch({}, { isInfinite: true })
513
+ while (this.table.loading) {
514
+ await this.$sleep(20)
515
+ }
516
+ this.table.loading = true
517
+ await this.$sleep(50)
518
+ this.table.list = list.concat(this.table.list)
519
+ this.table.loading = false
520
+ this.table.moreLoading = false
521
+ }
522
+
523
+ get (id) {
524
+ return this.dbTable.get(id)
525
+ }
526
+
527
+ search (params) {
528
+ this._lastSearchParams = JSON.stringify(params)
529
+ if (this.table?.uid) {
530
+ const key = 'Settings[' + this.table.uid + ']'
531
+ const settings = this.$local.getJson(key, {})
532
+ if (settings.limit || params.limit !== this._initialLimit) {
533
+ settings.limit = params.limit
534
+ }
535
+ if (Object.keys(settings).length) {
536
+ this.$local.setJson(key, settings)
537
+ }
538
+ }
539
+ return this.dbTable.search(params)
540
+ }
541
+
542
+ add (params) {
543
+ return this.dbTable.add(params)
544
+ }
545
+
546
+ update (id, params) {
547
+ return this.dbTable.update(id, params)
548
+ }
549
+
550
+ remove (params, row) {
551
+ return this.dbTable.remove(params[this.idField])
552
+ }
553
+
554
+ getSearchParams (params, setQuery = true) {
555
+ if (setQuery) {
556
+ if (params?.page) this.table.query.page = params.page
557
+ if (params?.limit) this.table.query.limit = params.limit
558
+ }
559
+ return Object.assign({ where: {} }, JSON.parse(this._lastSearchParams), this.table.query, params)
560
+ }
561
+
562
+ getAddParams (params) {
563
+ const fields = Object.keys(this.dialog.initialForm)
564
+ const data = {}
565
+ if (!fields.length) {
566
+ Object.assign(data, params)
567
+ } else {
568
+ fields.forEach(field => data[field] = params[field])
569
+ }
570
+ this.dialog.formItems.forEach(item => {
571
+ if (!item.model && !item.prop || item.virtual) return
572
+ let value = data[item.model || item.prop]
573
+ if (item.type === 'number') {
574
+ value = this.uiUtils.formatPrecision(value, item.precision || 3) * 1
575
+ } else if (['ElDatePicker', 'el-date-picker', 'XDatePicker', 'x-date-picker'].includes(item.comp)) {
576
+ if (item.type === 'datetime') {
577
+ value = dates.format(value)
578
+ } else if (!item.type || item.type === 'date') {
579
+ value = dates.format(value, '', false)
580
+ }
581
+ }
582
+ if (Array.isArray(value)) {
583
+ const { seperator = ',' } = item
584
+ if (seperator) {
585
+ value = value.join(seperator)
586
+ }
587
+ } else if (value && typeof value === 'object') {
588
+ value = JSON.stringify(value, null, 4)
589
+ }
590
+ if (value === '') {
591
+ if (item.comp && !['ElInput', 'el-input'].includes(item.comp)) {
592
+ value = null
593
+ }
594
+ }
595
+ data[item.model || item.prop] = value
596
+ })
597
+ return data
598
+ }
599
+
600
+ getUpdateParams (params) {
601
+ return this.getAddParams(params)
602
+ }
603
+
604
+ getDeleteParams (row) {
605
+ return {
606
+ [this.idField]: row[this.idField]
607
+ }
608
+ }
609
+
610
+ getSearchExportParams () {
611
+ const params = this.getSearchParams()
612
+ this.injectSearchParams(params)
613
+ return Object.assign({}, params, {
614
+ page: 1,
615
+ limit: - 1,
616
+ attributes: this.processExportingColumns(this.table.ref._visibleColumns, 'search-attributes').map(col => col.prop)
617
+ })
618
+ }
619
+
620
+ injectSearchParams (params) { }
621
+
622
+ injectAddParams (params) { }
623
+
624
+ injectUpdateParams (params) {
625
+ this.injectAddParams(params)
626
+ }
627
+
628
+ injectDeleteParams (params) { }
629
+
630
+ beforeSearch (params) { return true }
631
+
632
+ beforeAdd () { return true }
633
+
634
+ beforeEdit ({ $index, row }) { return true }
635
+
636
+ beforeDelete ({ $index, row }) { return true }
637
+
638
+ afterSearch (list, params, data) {
639
+ const stringify = JSON.stringify(params)
640
+ if (this.table.query.count === false && this.table.needCount) {
641
+ if (stringify !== this._lastSearchParams) {
642
+ const { page, limit, order, count, ...others } = params
643
+ this.dbTable.func(['count', others]).then(data => this.table.total = data.data)
644
+ }
645
+ }
646
+ }
647
+
648
+ afterAdd () { }
649
+
650
+ afterEdit ({ $index, row }) { }
651
+
652
+ afterDelete (data) { }
653
+
654
+ afterSubmit (data) { }
655
+
656
+ async updatePartials ({ row }, fields = []) {
657
+ if (!fields.length) return
658
+ this.table.loading = true
659
+ const partials = {}
660
+ fields.forEach(f => partials[f] = row[f])
661
+ await this.update(row[this.idField], partials)
662
+ this.table.loading = false
663
+ }
664
+
665
+ _initSearching () {
666
+ this.table.loading = true
667
+ const params = {}
668
+ const settings = this.$local.getJson('Settings[' + this.table.uid + ']')
669
+ if (settings) {
670
+ if (settings.viewName) {
671
+ const searcher = this.table.ref.$refs.searcher
672
+ const view = searcher.views.find(v => v.name === settings.viewName)
673
+ searcher.setCurrentView(view)
674
+ try {
675
+ Object.assign(params, searcher.calcParams())
676
+ } catch {}
677
+ }
678
+ if (settings.limit) {
679
+ this.table.query.limit = settings.limit
680
+ params.limit = settings.limit
681
+ }
682
+ if (settings.order?.length) {
683
+ this.table.query.order = settings.order
684
+ params.order = settings.order
685
+ }
686
+ }
687
+ this.table.loading = false
688
+ this.handleSearch(params)
689
+ }
690
+
691
+ _defaultFormatList (list, res) {
692
+ const { columns, query } = this.table
693
+ const { page, limit } = query
694
+ list.forEach((ele, index) => {
695
+ ele._idx = index + 1
696
+ ele._index = (page - 1) * limit + index + 1
697
+ })
698
+ columns.forEach(col => {
699
+ let { prop, options, seperator } = col
700
+ const { format, autoFill } = col.tableAttrs || {}
701
+ const { modelName } = col.formAttrs || {}
702
+ if (modelName && autoFill) {
703
+ list.forEach(ele => ele[`_formatted_${prop}`] = '')
704
+ this._fillRelatedField(list, col)
705
+ } else {
706
+ if (Array.isArray(options) && format !== false) {
707
+ const update = (newVal, oldVal) => {
708
+ const rows = oldVal ? this.table.list : list
709
+ const kvMap = makeOptionsKvMap(col)
710
+ rows.forEach((ele, index) => {
711
+ const value = highdict.get(ele, prop)
712
+ ele[`_formatted_${prop}`] = kvMap[value] || value
713
+ })
714
+ }
715
+ const un = watch(() => col.options, update, { immediate: true, deep: true })
716
+ this._unwatchs.push(un)
717
+ }
718
+ }
719
+ if (seperator) {
720
+ list.forEach(ele => {
721
+ if (typeof ele[prop] === 'string') {
722
+ ele[prop] = ele[prop].split(seperator)
723
+ }
724
+ })
725
+ }
726
+ })
727
+ return list
728
+ }
729
+
730
+ async _fillRelatedField (list, column) {
731
+ const ids = [...new Set(list.map(ele => ele[column.prop]))]
732
+ if (!ids.length) return
733
+ const { modelName, text, value } = column.formAttrs
734
+ const data = await this.service.restful.search(modelName, {
735
+ limit: -1,
736
+ attributes: [text, value],
737
+ where: {
738
+ [value]: {
739
+ '[Op.in]': ids
740
+ }
741
+ }
742
+ })
743
+ if (!data.data.length) return
744
+ const dict = highdict.mapField(data.data, value, text)
745
+ this.table.list.forEach(ele => {
746
+ ele[`_formatted_${column.prop}`] = dict[ele[column.prop]]
747
+ })
748
+ }
749
+
750
+ formatList (list, res) {
751
+ return list
752
+ }
753
+
754
+ injectList (ele, index, list) { }
755
+
756
+ processExportingColumns (columns, mode = 'current') {
757
+ return columns.filter(col => {
758
+ return !['index', 'selection', 'expand', 'radio', '_index'].includes(col.type)
759
+ }).filter(col => {
760
+ return mode === 'search-export' ? true : !col.virtual
761
+ })
762
+ }
763
+
764
+ processExportingData (data, mode = 'current') {
765
+ if (!data.length) return data
766
+ const dict = {}
767
+ this.table.ref._visibleColumns.forEach(it => {
768
+ let { formatter = it.formatter, tagValues = it.tagValues, options = it.options } = it.tableAttrs || {}
769
+ if (!formatter && typeof tagValues === 'function') formatter = tagValues
770
+ dict[it.prop] = { formatter, tagValues, options }
771
+ })
772
+ const keys = [...new Set(Object.keys(data[0]).concat(this.table.ref._visibleColumns.map(c => c.prop).filter(p => p)))]
773
+ data.forEach(ele => {
774
+ keys.forEach(key => {
775
+ const value = ele[key]
776
+ if (ele.hasOwnProperty('_formatted_' + key)) return ele[key] = ele['_formatted_' + key]
777
+ if (dict[key]?.formatter) return ele[key] = dict[key].formatter(value, ele)
778
+ if (dict[key]?.tagValues) return ele[key] = dict[key].tagValues[value]
779
+ if (dict[key]?.options) return ele[key] = dict[key].options.find(o => o.value === ele[key])?.text ?? ele[key]
780
+ if (typeof value === 'boolean') {
781
+ ele[key] = value && 1 || 0
782
+ } else if (value instanceof Date) {
783
+ ele[key] = dates.format(value)
784
+ if (ele[key].endsWith(' 00:00:00')) {
785
+ ele[key] = ele[key].slice(0, -9)
786
+ }
787
+ } else if (value === undefined) {
788
+ ele[key] = highdict.get(ele, key)
789
+ }
790
+ })
791
+ })
792
+ data.forEach(ele => {
793
+ keys.forEach(key => {
794
+ if (ele[key] && typeof ele[key] === 'object') {
795
+ ele[key] = JSON.stringify(ele[key])
796
+ }
797
+ })
798
+ })
799
+ return data
800
+ }
801
+
802
+ processExporting (options) { return options }
803
+
804
+ processImportingData (data) {
805
+ data.forEach(ele => {
806
+ delete ele[this.idField]
807
+ delete ele._index
808
+ })
809
+ return data
810
+ }
811
+
812
+ _resetForm (host = this.dialog) {
813
+ host.form = JSON.parse(JSON.stringify(host.initialForm))
814
+ }
815
+
816
+ _trimForm (form) {
817
+ form ||= this.dialog.form
818
+ const trimed = {}
819
+ for (let key in form) {
820
+ if (form[key]?.trim) {
821
+ trimed[key] = form[key].trim()
822
+ } else {
823
+ trimed[key] = form[key]
824
+ }
825
+ }
826
+ return trimed
827
+ }
828
+
829
+ _validateForm (formRef) {
830
+ const ref = formRef || this.dialog.formRef
831
+ if (ref) {
832
+ return new Promise(resolve => {
833
+ if (this._isMobile) {
834
+ ref.validate().then(() => resolve(true)).catch(() => resolve(false))
835
+ } else {
836
+ ref.validate((ok) => resolve(ok)).catch(() => resolve(false))
837
+ }
838
+ })
839
+ }
840
+ return true
841
+ }
842
+
843
+ _clearValidate (ref = this.dialog.formRef) {
844
+ if (ref) {
845
+ this._isMobile ? ref.resetValidation() : ref.clearValidate()
846
+ }
847
+ }
848
+
849
+ async _checkAllNone (data) {
850
+ const nones = [null, undefined, '']
851
+ const hasValid = Object.values(data).some(v => !nones.includes(v))
852
+ if (hasValid) return true
853
+ return Confirm.w({ message: '表单所有数据都是空,确定要继续提交吗?', title: '警告' })
854
+ }
855
+
856
+ _showError (err) {
857
+ Message(typeof err === 'object' ? (err.message || err.err || err.toString()) : err)
858
+ }
859
+
860
+ _focusDialogInput () {
861
+ const node = document.querySelector('.el-dialog')
862
+ if (!node) return
863
+ let inputs = [...node.querySelectorAll('input')].filter(i => !i.disabled && !i.readonly)
864
+ let input = inputs.find(i => i.type === 'text' || i.type === 'number')
865
+ if (!input) {
866
+ inputs = [...node.querySelectorAll('textarea')].filter(i => !i.disabled && !i.readonly)
867
+ input = inputs[0]
868
+ }
869
+ input?.focus()
870
+ }
871
+
872
+ get _isMobile () {
873
+ const ref = this.table?.formRef || this.dialog?.formRef
874
+ if (ref) {
875
+ return ref.$.attrs.class.indexOf('mobile') >= 0
876
+ }
877
+ return window.isMobile
878
+ }
879
+
880
+ onKeywordsSearch (...props) { return this.handleKeywordsSearch(...props) }
881
+ onSearch (...props) { return this.handleSearch(...props) }
882
+ onAdd (...props) { return this.handleAdd(...props) }
883
+ onEdit (...props) { return this.handleEdit(...props) }
884
+ onDelete (...props) { return this.handleDelete(...props) }
885
+ onSubmit (...props) { return this.handleSubmit(...props) }
886
+ onCancel (...props) { return this.handleCancel(...props) }
887
+ onRowEdit (...props) { return this.handleRowEdit(...props) }
888
+ onCancelEdit (...props) { return this.handleCancelEdit(...props) }
889
+ onExport (...props) { return this.handleExport(...props) }
890
+ onSearchExport (...props) { return this.handleSearchExport(...props) }
891
+ onImport (...props) { return this.handleImport(...props) }
892
+ onMultiDelete (...props) { return this.handleMultiDelete(...props) }
893
+ }
894
+
895
+ const makeOptionsKvMap = (field) => {
896
+ const { options, formAttrs = {} } = field
897
+ const { text = 'text', value = 'value' } = formAttrs
898
+ const kvMap = {}
899
+ if (options && typeof options === 'object' && typeof options[0] === 'object') {
900
+ options.forEach(op => {
901
+ kvMap[op[value]] = op[text]
902
+ })
903
+ }
904
+ return kvMap
905
+ }
906
+
907
+ export default CrudController