monkey 5 lat temu
rodzic
commit
8f7e89db67
100 zmienionych plików z 22085 dodań i 5345 usunięć
  1. 8
    4
      package.json
  2. 4
    1
      src/api/admin/crm.js
  3. 48
    1
      src/api/admin/employeeDep.js
  4. 22
    0
      src/api/common.js
  5. 4
    1
      src/api/crm/customer.js
  6. 13
    0
      src/api/crm/invoice.js
  7. 23
    12
      src/api/crm/workbench.js
  8. 15
    0
      src/api/oa/journal.js
  9. 16
    0
      src/api/pm/project.js
  10. 345
    0
      src/assets/iconfont copy1/iconfont.css
  11. BIN
      src/assets/iconfont copy1/iconfont.eot
  12. 704
    0
      src/assets/iconfont copy1/iconfont.svg
  13. BIN
      src/assets/iconfont copy1/iconfont.ttf
  14. BIN
      src/assets/iconfont copy1/iconfont.woff
  15. BIN
      src/assets/iconfont copy1/iconfont.woff2
  16. BIN
      src/assets/img/crm/receivables_plan.png
  17. 15
    4
      src/components/CRMImport/index.vue
  18. 0
    1
      src/components/CreateCom/CrmRelativeTable.vue
  19. 212
    0
      src/components/NewCom/WkCheckbox/index.vue
  20. 61
    0
      src/components/NewCom/WkDescText/index.vue
  21. 194
    0
      src/components/NewCom/WkDetailTable/View.vue
  22. 160
    0
      src/components/NewCom/WkDetailTable/WkTableItems.vue
  23. 168
    0
      src/components/NewCom/WkDetailTable/index.vue
  24. 149
    0
      src/components/NewCom/WkDistpicker/index.vue
  25. 39
    5
      src/components/NewCom/WkForm/Mixin.js
  26. 322
    0
      src/components/NewCom/WkForm/WkField.vue
  27. 192
    0
      src/components/NewCom/WkForm/WkFieldView.vue
  28. 130
    0
      src/components/NewCom/WkForm/WkFormItem.vue
  29. 47
    176
      src/components/NewCom/WkForm/WkFormItems.vue
  30. 19
    19
      src/components/NewCom/WkForm/index.vue
  31. 62
    0
      src/components/NewCom/WkForm/utils.js
  32. 133
    0
      src/components/NewCom/WkLocation/index.vue
  33. 210
    0
      src/components/NewCom/WkLocationPointDialog/index.vue
  34. 281
    0
      src/components/NewCom/WkPercentInput/index.vue
  35. 139
    0
      src/components/NewCom/WkPosition/index.vue
  36. 205
    0
      src/components/NewCom/WkSelect/index.vue
  37. 97
    0
      src/components/NewCom/WkSignaturePad/Image.vue
  38. 196
    0
      src/components/NewCom/WkSignaturePad/VueSignaturePad/index.vue
  39. BIN
      src/components/NewCom/WkSignaturePad/VueSignaturePad/signature.png
  40. 167
    0
      src/components/NewCom/WkSignaturePad/index.vue
  41. 26
    0
      src/components/NewCom/WkSignaturePad/utils/index.js
  42. 2
    0
      src/components/NewCom/WkUserSelect/src/WkUser.vue
  43. 4
    3
      src/components/SlideView.vue
  44. 81
    285
      src/components/VDistpicker/Distpicker.vue
  45. 12012
    4688
      src/components/VDistpicker/districts.js
  46. 68
    0
      src/components/WkEmpty/index.vue
  47. 207
    0
      src/components/WkImport/ImportHistory.vue
  48. 77
    0
      src/components/WkImport/ImportMixins.js
  49. 91
    0
      src/components/WkImport/XrImport.vue
  50. 868
    0
      src/components/WkImport/index.vue
  51. 15
    0
      src/components/WkImport/main.js
  52. 1
    0
      src/components/XrMenu/XrMenuItem.vue
  53. 17
    7
      src/components/XrUpgradeDialog.vue
  54. 1
    1
      src/config.js
  55. 3
    0
      src/main.js
  56. 69
    47
      src/mixins/AdvancedFilter.js
  57. 1
    1
      src/mixins/CheckStatusMixin.js
  58. 385
    53
      src/mixins/CustomFields.js
  59. 11
    1
      src/router/modules/admin.js
  60. 6
    0
      src/styles/button.scss
  61. 1
    1
      src/styles/emoji-sprite.scss
  62. 226
    10
      src/styles/iconfont/iconfont.css
  63. BIN
      src/styles/iconfont/iconfont.eot
  64. 166
    4
      src/styles/iconfont/iconfont.svg
  65. BIN
      src/styles/iconfont/iconfont.ttf
  66. BIN
      src/styles/iconfont/iconfont.woff
  67. BIN
      src/styles/iconfont/iconfont.woff2
  68. 11
    1
      src/styles/index.scss
  69. 30
    0
      src/styles/org-tree.scss
  70. 9
    9
      src/styles/side-detail.scss
  71. 1
    1
      src/styles/table.scss
  72. 1
    1
      src/views/admin/config/index.vue
  73. 2
    2
      src/views/admin/crm/components/FieldInfo.vue
  74. 8
    6
      src/views/admin/crm/customField/index.vue
  75. 39
    0
      src/views/admin/fields/components/FieldItem/FieldBoolean.vue
  76. 86
    0
      src/views/admin/fields/components/FieldItem/FieldCheckbox.vue
  77. 56
    0
      src/views/admin/fields/components/FieldItem/FieldDateInterval.vue
  78. 77
    0
      src/views/admin/fields/components/FieldItem/FieldDescText.vue
  79. 224
    0
      src/views/admin/fields/components/FieldItem/FieldDetailTable.vue
  80. 41
    0
      src/views/admin/fields/components/FieldItem/FieldFile.vue
  81. 47
    0
      src/views/admin/fields/components/FieldItem/FieldInput.vue
  82. 45
    0
      src/views/admin/fields/components/FieldItem/FieldLocation.vue
  83. 52
    0
      src/views/admin/fields/components/FieldItem/FieldPercent.vue
  84. 79
    0
      src/views/admin/fields/components/FieldItem/FieldPosition.vue
  85. 79
    0
      src/views/admin/fields/components/FieldItem/FieldSelect.vue
  86. 63
    0
      src/views/admin/fields/components/FieldItem/FieldTextarea.vue
  87. 216
    0
      src/views/admin/fields/components/FieldItem/FieldWrapper.vue
  88. 38
    0
      src/views/admin/fields/components/FieldItem/FieldWritingSign.vue
  89. 13
    0
      src/views/admin/fields/components/FieldItem/index.js
  90. 132
    0
      src/views/admin/fields/components/FieldItem/mixins.js
  91. 372
    0
      src/views/admin/fields/components/SettingField/SettingDefault.vue
  92. 88
    0
      src/views/admin/fields/components/SettingField/SettingDescText.vue
  93. 164
    0
      src/views/admin/fields/components/SettingField/SettingDetailTable.vue
  94. 321
    0
      src/views/admin/fields/components/SettingField/SettingLogicForm.vue
  95. 211
    0
      src/views/admin/fields/components/SettingField/SettingNumber.vue
  96. 395
    0
      src/views/admin/fields/components/SettingField/SettingOptions.vue
  97. 83
    0
      src/views/admin/fields/components/SettingField/SettingPrecisions.vue
  98. 325
    0
      src/views/admin/fields/components/SettingField/index.vue
  99. 39
    0
      src/views/admin/fields/field.js
  100. 0
    0
      src/views/admin/fields/fieldTypeLib.js

+ 8
- 4
package.json Wyświetl plik

@@ -24,20 +24,21 @@
24 24
     "axios": "0.18.0",
25 25
     "babel-polyfill": "^6.26.0",
26 26
     "clipboard": "^2.0.4",
27
-    "echarts": "4.3.0",
27
+    "echarts": "^4.3.0",
28 28
     "el-bigdata-table": "^1.0.32",
29 29
     "element-ui": "^2.12.0",
30 30
     "file-saver": "^2.0.1",
31 31
     "id-validator": "^1.3.0",
32
-    "js-cookie": "2.2.0",
33
-    "js-md5": "^0.7.3",
32
+    "js-cookie": "2.2.1",
34 33
     "lockr": "^0.8.5",
35 34
     "normalize.css": "7.0.0",
36 35
     "nprogress": "0.2.0",
36
+    "number-precision": "^1.5.0",
37 37
     "numeral": "^2.0.6",
38 38
     "nzh": "^1.0.4",
39 39
     "pinyin-match": "1.0.9",
40 40
     "qrcodejs2": "0.0.2",
41
+    "signature_pad": "3.0.0-beta.4",
41 42
     "throttle-debounce": "^2.1.0",
42 43
     "vue": "2.5.17",
43 44
     "vue-bus": "^1.1.0",
@@ -48,7 +49,8 @@
48 49
     "vue-radial-progress": "^0.2.10",
49 50
     "vue-router": "3.0.1",
50 51
     "vue2-animate": "^2.1.2",
51
-    "vuedraggable": "^2.16.0",
52
+    "vue2-org-tree": "1.3.1",
53
+    "vuedraggable": "2.24.3",
52 54
     "vuex": "3.0.1",
53 55
     "xlsx": "^0.14.1",
54 56
     "xss": "^1.0.6"
@@ -75,6 +77,8 @@
75 77
     "file-loader": "1.1.11",
76 78
     "friendly-errors-webpack-plugin": "1.7.0",
77 79
     "html-webpack-plugin": "4.0.0-alpha",
80
+    "less": "3.0.0",
81
+    "less-loader": "4.1.0",
78 82
     "mini-css-extract-plugin": "0.4.1",
79 83
     "node-notifier": "5.2.1",
80 84
     "node-sass": "^4.7.2",

+ 4
- 1
src/api/admin/crm.js Wyświetl plik

@@ -78,7 +78,10 @@ export function customFieldHandleAPI(data) {
78 78
   return request({
79 79
     url: 'admin/field/update',
80 80
     method: 'post',
81
-    data: data
81
+    data: data,
82
+    headers: {
83
+      'Content-Type': 'application/json;charset=UTF-8'
84
+    }
82 85
   })
83 86
 }
84 87
 

+ 48
- 1
src/api/admin/employeeDep.js Wyświetl plik

@@ -52,10 +52,19 @@ export function userAddAPI(params) {
52 52
   })
53 53
 }
54 54
 
55
+// // 角色列表
56
+// export function roleListAPI(data) {
57
+//   return request({
58
+//     url: 'admin/groups/index',
59
+//     method: 'post',
60
+//     data: data
61
+//   })
62
+// }
63
+
55 64
 // 角色列表
56 65
 export function roleListAPI(data) {
57 66
   return request({
58
-    url: 'admin/groups/index',
67
+    url: 'admin/rules/getgroupauth',
59 68
     method: 'post',
60 69
     data: data
61 70
   })
@@ -195,3 +204,41 @@ export function adminUserSetUserDeptPI(data) {
195 204
     }
196 205
   })
197 206
 }
207
+
208
+/**
209
+ * 查询配置的角色范围
210
+ * @param {*} data
211
+ * @returns
212
+ */
213
+export function adminRoleQueryAuthRoleAPI(data) {
214
+  return request({
215
+    url: `admin/rules/groupauthId`,
216
+    method: 'post',
217
+    data
218
+  })
219
+}
220
+
221
+/**
222
+ * 更新配置的角色范围
223
+ * @param {*} data
224
+ * @returns
225
+ */
226
+export function adminRoleUpdateAuthRoleAPI(data) {
227
+  return request({
228
+    url: `admin/rules/upgroupauth`,
229
+    method: 'post',
230
+    data: data,
231
+    headers: {
232
+      'Content-Type': 'application/json;charset=UTF-8'
233
+    }
234
+  })
235
+}
236
+
237
+// 部分角色列表
238
+export function adminRoleGetRoleListAPI(data) {
239
+  return request({
240
+    url: 'admin/rules/groupauth',
241
+    method: 'post',
242
+    data: data
243
+  })
244
+}

+ 22
- 0
src/api/common.js Wyświetl plik

@@ -361,3 +361,25 @@ export function readUpdateNoticeAPI(data) {
361 361
     data: data
362 362
   })
363 363
 }
364
+
365
+/**
366
+ * 公共web文件上传
367
+ * @param data
368
+ */
369
+export function crmFileSingleSaveAPI(data) {
370
+  var param = new FormData()
371
+  Object.keys(data).forEach(key => {
372
+    param.append(key, data[key])
373
+    // param.append('isPublic', '1')
374
+    // param.append('module', 'print')
375
+    param.append('type', 'img')
376
+  })
377
+  return request({
378
+    url: 'admin/file/save',
379
+    method: 'post',
380
+    data: param,
381
+    headers: {
382
+      'Content-Type': 'multipart/form-data'
383
+    }
384
+  })
385
+}

+ 4
- 1
src/api/crm/customer.js Wyświetl plik

@@ -9,7 +9,10 @@ export function crmCustomerSaveAPI(data) {
9 9
   return request({
10 10
     url: 'crm/customer/' + url,
11 11
     method: 'post',
12
-    data: data
12
+    data: data,
13
+    headers: {
14
+      'Content-Type': 'application/json;charset=UTF-8'
15
+    }
13 16
   })
14 17
 }
15 18
 

+ 13
- 0
src/api/crm/invoice.js Wyświetl plik

@@ -188,3 +188,16 @@ export function crmInvoiceDeleteInvoiceInfoAPI(data) {
188 188
     data: data
189 189
   })
190 190
 }
191
+
192
+/**
193
+ * 发票全部导出
194
+ * @param {*} data
195
+ */
196
+export function crmInvoiceExcelAllExportAPI(data) {
197
+  return request({
198
+    url: 'crm/invoice/excelExport',
199
+    method: 'post',
200
+    data: data,
201
+    responseType: 'blob'
202
+  })
203
+}

+ 23
- 12
src/api/crm/workbench.js Wyświetl plik

@@ -108,7 +108,7 @@ export function crmIndexFunnelAPI(data) {
108 108
  */
109 109
 export function crmInstrumentSellFunnelBusinessListAPI(data) {
110 110
   return request({
111
-    url: 'crmInstrument/sellFunnelBusinessList',
111
+    url: 'crm/index/businessList',
112 112
     method: 'post',
113 113
     data: data,
114 114
     headers: {
@@ -251,44 +251,55 @@ export function crmIndexUnContactCustomerAPI(data) {
251 251
 
252 252
 //* **********************************
253 253
 /**
254
- *
254
+ *  跟进记录导出
255 255
  * @param {*} data
256 256
  */
257 257
 export function crmInstrumentExportRecordListAPI(data) {
258
+  var param = new FormData()
259
+  Object.keys(data).forEach(key => {
260
+    param.append(key, data[key])
261
+  })
258 262
   return request({
259
-    url: '',
263
+    url: 'crm/activity/excelExport',
260 264
     method: 'post',
261
-    data: data,
265
+    data: param,
266
+    responseType: 'blob',
262 267
     headers: {
263
-      'Content-Type': 'application/json;charset=UTF-8'
264
-    }
268
+      'Content-Type': 'multipart/form-data'
269
+    },
270
+    timeout: 60000
265 271
   })
266 272
 }
267 273
 
268 274
 /**
269
- *
275
+ * 跟进记录导入
270 276
  * @param {*} data
271 277
  */
272 278
 export function crmInstrumentImportRecordListAPI(data) {
279
+  var param = new FormData()
280
+  Object.keys(data).forEach(key => {
281
+    param.append(key, data[key])
282
+  })
273 283
   return request({
274
-    url: '',
284
+    url: 'crm/activity/excelImport',
275 285
     method: 'post',
276
-    data: data,
286
+    data: param,
277 287
     headers: {
278
-      'Content-Type': 'application/json;charset=UTF-8'
288
+      'Content-Type': 'multipart/form-data'
279 289
     }
280 290
   })
281 291
 }
282 292
 
283 293
 /**
284
- *
294
+ * 日志导入模板下载
285 295
  * @param {*} data
286 296
  */
287 297
 export function crmInstrumentDownloadRecordExcelAPI(data) {
288 298
   return request({
289
-    url: '',
299
+    url: 'crm/activity/excelDownload',
290 300
     method: 'post',
291 301
     data: data,
302
+    responseType: 'blob',
292 303
     headers: {
293 304
       'Content-Type': 'application/json;charset=UTF-8'
294 305
     }

+ 15
- 0
src/api/oa/journal.js Wyświetl plik

@@ -200,3 +200,18 @@ export function journalQueryActivityCountAPI(data) {
200 200
     }
201 201
   })
202 202
 }
203
+
204
+/**
205
+ * 日志点赞
206
+ * @param {*} data
207
+ */
208
+export function oaLogFavourOrCancelAPI(data) {
209
+  return request({
210
+    url: 'oa/log/favourUpdate',
211
+    method: 'post',
212
+    data: data,
213
+    headers: {
214
+      'Content-Type': 'application/json;charset=UTF-8'
215
+    }
216
+  })
217
+}

+ 16
- 0
src/api/pm/project.js Wyświetl plik

@@ -383,3 +383,19 @@ export function workWorkAddUserSetRoleGroupAPI(data) {
383 383
     data: data
384 384
   })
385 385
 }
386
+
387
+// /**
388
+//  * 项目成员权限列表
389
+//  * @param {*} data
390
+//  */
391
+// export function (data) {
392
+//   return request({
393
+//     url: 'work/work/addUserGroup',
394
+//     method: 'post',
395
+//     headers: {
396
+//       'Content-Type': 'application/json;charset=UTF-8'
397
+//     },
398
+//     data: data
399
+//   })
400
+// }
401
+

+ 345
- 0
src/assets/iconfont copy1/iconfont.css
Plik diff jest za duży
Wyświetl plik


BIN
src/assets/iconfont copy1/iconfont.eot Wyświetl plik


+ 704
- 0
src/assets/iconfont copy1/iconfont.svg
Plik diff jest za duży
Wyświetl plik


BIN
src/assets/iconfont copy1/iconfont.ttf Wyświetl plik


BIN
src/assets/iconfont copy1/iconfont.woff Wyświetl plik


BIN
src/assets/iconfont copy1/iconfont.woff2 Wyświetl plik


BIN
src/assets/img/crm/receivables_plan.png Wyświetl plik


+ 15
- 4
src/components/CRMImport/index.vue Wyświetl plik

@@ -114,6 +114,7 @@
114 114
       slot="footer"
115 115
       class="dialog-footer">
116 116
       <el-popover
117
+        v-if="config.historyShow"
117 118
         v-model="historyPopoverShow"
118 119
         placement="top"
119 120
         width="800"
@@ -181,9 +182,11 @@ import merge from '@/utils/merge'
181 182
 
182 183
 const DefaultProps = {
183 184
   typeName: '', // 模块名称
184
-  ownerSelectShow: true,
185
+  historyShow: true,
186
+  ownerSelectShow: false,
185 187
   poolSelectShow: false,
186 188
   repeatHandleShow: true,
189
+  importParams: null, // 导入参数
187 190
   repeatRuleShow: true, // 步骤二的重复规则是否展示
188 191
   importRequest: null, // 导入请求
189 192
   templateRequest: null // 模板请求
@@ -419,10 +422,12 @@ export default {
419 422
      * 第一步上传
420 423
      */
421 424
     firstUpdateFile(result) {
422
-      const params = {}
425
+      let params = {}
423 426
       params.config = this.repeatHandling
424 427
       params.file = this.file
425
-      params.owner_user_id = this.user.length > 0 ? this.user[0].id : ''
428
+      if (this.config.ownerSelectShow) {
429
+        params.owner_user_id = this.user.length > 0 ? this.user[0].id : ''
430
+      }
426 431
       if (this.config.poolSelectShow) {
427 432
         params.pool_id = this.pool_id
428 433
       }
@@ -434,6 +439,12 @@ export default {
434 439
         product: crmProductExcelImportAPI
435 440
       }[this.crmType]
436 441
       this.loading = true
442
+      if (this.config.importParams) {
443
+        params = {
444
+          ...params,
445
+          ...this.config.importParams
446
+        }
447
+      }
437 448
       request(params)
438 449
         .then(res => {
439 450
           if (result) {
@@ -524,7 +535,7 @@ export default {
524 535
         contacts: crmContactsDownloadExcelAPI,
525 536
         product: crmProductDownloadExcelAPI
526 537
       }[this.crmType]
527
-      request({ pool_id: this.config.poolSelectShow && this.pool_id })
538
+      request(this.config.templateParams || { pool_id: this.config.poolSelectShow && this.pool_id })
528 539
         .then(res => {
529 540
           downloadExcelWithResData(res)
530 541
         })

+ 0
- 1
src/components/CreateCom/CrmRelativeTable.vue Wyświetl plik

@@ -376,7 +376,6 @@ export default {
376 376
 
377 377
           params[this.action.data.moduleType + '_id'] = this.action.data[ this.action.data.moduleType + 'Id'] || this.action.data[ this.action.data.moduleType + '_id']
378 378
 
379
-
380 379
           if (this.action.data.params) {
381 380
             for (const field in this.action.data.params) {
382 381
               params[field] = this.action.data.params[field]

+ 212
- 0
src/components/NewCom/WkCheckbox/index.vue Wyświetl plik

@@ -0,0 +1,212 @@
1
+<template>
2
+  <div class="wk-checkbox">
3
+    <template v-if="valueIsObject">
4
+      <el-select
5
+        v-if="showType === 'default'"
6
+        v-model="dataValue.select"
7
+        :disabled="disabled"
8
+        :clearable="clearable"
9
+        :placeholder="placeholder"
10
+        style="width: 100%;"
11
+        multiple
12
+        @change="valueChange">
13
+        <el-option
14
+          v-for="(item, index) in options"
15
+          :key="index"
16
+          :label="!isEmptyValue(item.value) ? item.label || item.name : item "
17
+          :value="getValue(item)"/>
18
+      </el-select>
19
+      <el-checkbox-group
20
+        v-else-if="showType === 'tiled'"
21
+        v-model="dataValue.select"
22
+        :disabled="disabled"
23
+        @change="valueChange">
24
+        <el-checkbox
25
+          v-for="(item, index) in options"
26
+          :key="index"
27
+          :label="getValue(item)">
28
+          {{ !isEmptyValue(item.value) ? item.label || item.name : item }}
29
+        </el-checkbox>
30
+      </el-checkbox-group>
31
+      <el-input
32
+        v-if="dataValue.select.includes('其他')"
33
+        v-model="dataValue.otherValue"
34
+        :disabled="disabled"
35
+        :maxlength="100"
36
+        placeholder="其他选项需填写,否则为无效选项"
37
+        @blur="inputBlur"/>
38
+    </template>
39
+  </div>
40
+</template>
41
+
42
+<script>
43
+import { isObject, isEmpty } from '@/utils/types'
44
+import { valueEquals } from 'element-ui/lib/utils/util'
45
+import Emitter from 'element-ui/lib/mixins/emitter'
46
+
47
+export default {
48
+  // 自定义字段库 多选
49
+  name: 'WkCheckbox',
50
+
51
+  components: {},
52
+
53
+  mixins: [Emitter],
54
+
55
+  props: {
56
+    // eslint-disable-next-line vue/require-prop-types
57
+    value: {},
58
+    // 选择其他展示input输入框
59
+    otherShowInput: {
60
+      type: Boolean,
61
+      default: false
62
+    },
63
+    disabled: Boolean,
64
+    clearable: Boolean,
65
+    placeholder: String,
66
+    options: {
67
+      type: Array,
68
+      default: () => {
69
+        return []
70
+      }
71
+    },
72
+    showType: {
73
+      type: String,
74
+      default: 'default' //  下拉 default 平铺 tiled
75
+    }
76
+  },
77
+
78
+  data() {
79
+    return {
80
+      dataValue: {
81
+        select: [],
82
+        otherValue: ''
83
+      }
84
+    }
85
+  },
86
+
87
+  computed: {
88
+    valueIsObject() {
89
+      return isObject(this.dataValue)
90
+    }
91
+  },
92
+
93
+  watch: {
94
+    value: {
95
+      handler(newVal, oldVal) {
96
+        this.validValue()
97
+      },
98
+      immediate: true
99
+    }
100
+  },
101
+
102
+  created() {
103
+  },
104
+
105
+  mounted() {},
106
+
107
+  beforeDestroy() {},
108
+
109
+  methods: {
110
+    /**
111
+     * 验证值
112
+     */
113
+    validValue() {
114
+      if (isEmpty(this.value)) {
115
+        this.dataValue = {
116
+          select: [],
117
+          otherValue: ''
118
+        }
119
+      } else {
120
+        if (this.otherShowInput) {
121
+          const otherItem = this.value.filter((name) => !this.options.includes(name))
122
+          if (otherItem.length > 0) {
123
+            const newValue = this.value.filter((name) => !otherItem.includes(name))
124
+            newValue.push('其他')
125
+            this.dataValue = {
126
+              select: newValue,
127
+              otherValue: otherItem[otherItem.length - 1]
128
+            }
129
+          } else {
130
+            if (!valueEquals(this.value, this.dataValue.select)) {
131
+              this.dataValue = {
132
+                select: this.value,
133
+                otherValue: ''
134
+              }
135
+            }
136
+          }
137
+        } else {
138
+          this.dataValue = {
139
+            select: this.value,
140
+            otherValue: ''
141
+          }
142
+        }
143
+      }
144
+    },
145
+
146
+    /**
147
+     * 选项值
148
+     */
149
+    getValue(item) {
150
+      return !this.isEmptyValue(item.value) ? item.value : item
151
+    },
152
+
153
+    /**
154
+     * 判断是空值
155
+     */
156
+    isEmptyValue(value) {
157
+      return value === null || value == undefined
158
+    },
159
+
160
+    /**
161
+     * 值更新
162
+     */
163
+    valueChange() {
164
+      if (this.dataValue.select.includes('其他')) {
165
+        const newValue = this.dataValue.select.filter(item => item !== '其他')
166
+        newValue.push(this.dataValue.otherValue.trim())
167
+        this.$emit('input', newValue)
168
+        this.$emit('change', newValue)
169
+        this.dispatch('ElFormItem', 'el.form.change', newValue)
170
+      } else {
171
+        this.dataValue.otherValue = ''
172
+        this.$emit('input', this.dataValue.select)
173
+        this.$emit('change', this.dataValue.select)
174
+        this.dispatch('ElFormItem', 'el.form.change', this.dataValue.select)
175
+      }
176
+    },
177
+
178
+    /**
179
+     * 失去焦点
180
+     */
181
+    inputBlur() {
182
+      this.valueChange()
183
+      // const value = this.dataValue.otherValue
184
+      // const eIsObject = this.options.length > 0 && !this.isEmptyValue(this.options[0].value)
185
+      // const has = this.options.find(item => {
186
+      //   if (eIsObject) {
187
+      //     return item.value === value.trim()
188
+      //   } else {
189
+      //     return item === value.trim()
190
+      //   }
191
+      // })
192
+      // if (has) {
193
+      //   this.dataValue.otherValue = ''
194
+      // }
195
+      // this.$emit('change', this.dataValue.select)
196
+      // this.$emit('input', this.dataValue.select)
197
+    }
198
+  }
199
+}
200
+</script>
201
+
202
+<style lang="scss" scoped>
203
+.wk-checkbox {
204
+  .el-input {
205
+    margin-top: 5px;
206
+  }
207
+
208
+  .el-checkbox-group {
209
+    line-height: 1.5;
210
+  }
211
+}
212
+</style>

+ 61
- 0
src/components/NewCom/WkDescText/index.vue Wyświetl plik

@@ -0,0 +1,61 @@
1
+<template>
2
+  <div class="wk-desc-text">
3
+    <tinymce
4
+      v-bind="$attrs"
5
+      :disabled="true" :toolbar="[]" :init="{
6
+        statusbar: false,
7
+        placeholder: '描述文字内容',
8
+        content_style: ' * {color: #262626; margin: 0;} body { font-size: 14px; }',
9
+        quickbars_selection_toolbar: false,
10
+        contextmenu: '',
11
+        plugins: 'autoresize',
12
+        autoresize_bottom_margin: 0
13
+    }" />
14
+  </div>
15
+</template>
16
+
17
+<script>
18
+import Tinymce from '@/components/Tinymce'
19
+
20
+export default {
21
+  // 描述文字
22
+  name: 'WkDescText',
23
+
24
+  components: {
25
+    Tinymce
26
+  },
27
+
28
+  inheritAttrs: false,
29
+
30
+  props: {},
31
+
32
+  data() {
33
+    return {
34
+    }
35
+  },
36
+
37
+  computed: {},
38
+
39
+  watch: {},
40
+
41
+  created() {},
42
+
43
+  mounted() {},
44
+
45
+  beforeDestroy() {},
46
+
47
+  methods: {}
48
+}
49
+</script>
50
+
51
+<style lang="scss">
52
+.wk-desc-text {
53
+  .tox-tinymce {
54
+    border: none;
55
+  }
56
+
57
+  .tox .tox-edit-area__iframe {
58
+    background-color: unset;
59
+  }
60
+}
61
+</style>

+ 194
- 0
src/components/NewCom/WkDetailTable/View.vue Wyświetl plik

@@ -0,0 +1,194 @@
1
+<template>
2
+  <div v-if="fieldForm && fieldForm.length > 0" class="wk-detail-table-view">
3
+    <template v-if="showType === 'default'">
4
+      <div
5
+        v-for="(children, sectionIndex) in fieldList"
6
+        :key="sectionIndex"
7
+        class="detail-item">
8
+        <flexbox class="detail-item__head">
9
+          <div class="detail-item__head-title">{{ title }}({{ sectionIndex+1 }})</div>
10
+        </flexbox>
11
+        <flexbox
12
+          class="wk-form-items"
13
+          align="flex-start"
14
+          wrap="wrap"
15
+          justify="flex-start">
16
+          <template v-for="(item, index) in children">
17
+            <div
18
+              v-if="getShowValue(item)"
19
+              :key="index"
20
+              :class="[`is-${item.formType}`]"
21
+              :label="item.name"
22
+              :style="{width: item.stylePercent ? `${item.stylePercent}%` : 'auto'}"
23
+              class="wk-form-item">
24
+              <div class="wk-form-item__label">{{ item.name }}</div>
25
+              <wk-field-view
26
+                :props="item"
27
+                :form-type="item.formType"
28
+                :value="fieldForm[sectionIndex][item.field]"
29
+              >
30
+                <template slot-scope="{ data }">
31
+                  <slot :data="data" />
32
+                </template>
33
+              </wk-field-view>
34
+            </div>
35
+          </template>
36
+        </flexbox>
37
+      </div>
38
+    </template>
39
+    <div
40
+      v-else-if="showType === 'table'"
41
+      class="detail-item">
42
+      <el-table
43
+        :data="fieldForm"
44
+        :row-key="Date.now().toString()"
45
+        class="wk-table-items"
46
+        style="width: 100%">
47
+        <el-table-column
48
+          v-for="(item, index) in addFieldList"
49
+          v-if="getShowValue(item)"
50
+          :key="index"
51
+          :prop="item.field"
52
+          :label="item.name"
53
+          :min-width="getMinWidth(item.formType)">
54
+          <template slot-scope="{ row, column, $index }">
55
+            <wk-field-view
56
+              :props="item"
57
+              :form-type="item.formType"
58
+              :value="row[item.field]"
59
+            >
60
+              <template slot-scope="{ data }">
61
+                <slot :data="data" />
62
+              </template>
63
+            </wk-field-view>
64
+          </template>
65
+        </el-table-column>
66
+      </el-table>
67
+    </div>
68
+  </div>
69
+</template>
70
+
71
+<script>
72
+export default {
73
+  // WkDetailTableView 明细表格详情
74
+  name: 'WkDetailTableView',
75
+
76
+  components: {
77
+    WkFieldView: () => import('@/components/NewCom/WkForm/WkFieldView')
78
+  },
79
+
80
+  props: {
81
+    title: String,
82
+    showType: {
83
+      type: String,
84
+      default: 'defalut' // defalut table
85
+    },
86
+    addFieldList: Array, // 表头展示用
87
+    fieldForm: {
88
+      type: [Array, String],
89
+      default: () => {
90
+        return []
91
+      }
92
+    },
93
+    fieldList: { // 块布局用
94
+      type: Array,
95
+      default: () => {
96
+        return []
97
+      }
98
+    }
99
+  },
100
+
101
+  data() {
102
+    return {
103
+
104
+    }
105
+  },
106
+
107
+  computed: {},
108
+
109
+  watch: {},
110
+
111
+  created() {},
112
+
113
+  mounted() {},
114
+
115
+  beforeDestroy() {},
116
+
117
+  methods: {
118
+    getMinWidth(formType) {
119
+      if (formType === 'date_interval' ||
120
+      formType === 'dateRange' ||
121
+       formType === 'file' ||
122
+       formType === 'location' ||
123
+       formType === 'position') {
124
+        return 250
125
+      }
126
+      return 150
127
+    },
128
+
129
+    /**
130
+     * 判断展示
131
+     */
132
+    getShowValue(item) {
133
+      if (item.hasOwnProperty('show')) {
134
+        return item.show
135
+      }
136
+      return true
137
+    }
138
+  }
139
+}
140
+</script>
141
+
142
+<style lang="scss" scoped>
143
+.wk-detail-table-view {
144
+  font-size: 13px;
145
+  line-height: normal;
146
+
147
+  .detail-item {
148
+    border-radius: 3px;
149
+    border: 1px solid #e1e1e1;
150
+    background-color: white;
151
+     &__head {
152
+      padding: 10px 20px;
153
+      background-color: #f5f5f5;
154
+      &-title {
155
+        height: auto;
156
+        font-size: 12px;
157
+        color: #333;
158
+        flex: 1;
159
+        line-height: normal;
160
+      }
161
+      .el-button {
162
+        padding: 0;
163
+      }
164
+    }
165
+  }
166
+
167
+  .detail-item + .detail-item {
168
+    margin-top: 10px;
169
+  }
170
+
171
+  .wk-form-items {
172
+    padding-bottom: 10px;
173
+  }
174
+
175
+  .wk-form-item {
176
+    padding: 12px 12px 0;
177
+    margin-bottom: 0;
178
+
179
+    &__label {
180
+      line-height: 1.5;
181
+      padding-bottom: 5px;
182
+      word-break: break-all;
183
+      word-wrap: break-word;
184
+      color: #666;
185
+    }
186
+
187
+    &.is-desc_text {
188
+      .wk-form-item__label {
189
+        display: none;
190
+      }
191
+    }
192
+  }
193
+}
194
+</style>

+ 160
- 0
src/components/NewCom/WkDetailTable/WkTableItems.vue Wyświetl plik

@@ -0,0 +1,160 @@
1
+<template>
2
+  <el-table
3
+    :data="fieldFrom"
4
+    :row-key="Date.now().toString()"
5
+    class="wk-table-items"
6
+    style="width: 100%">
7
+    <el-table-column
8
+      label="序号"
9
+      width="50">
10
+      <template slot-scope="{ row, column, $index }">
11
+        {{ $index + 1 }}
12
+      </template>
13
+    </el-table-column>
14
+    <el-table-column
15
+      v-for="(item, index) in fieldList"
16
+      v-if="getShowValue(item)"
17
+      :key="index"
18
+      :prop="item.field"
19
+      :min-width="getMinWidth(item.formType)">
20
+      <template
21
+        slot="header"
22
+        slot-scope="scope">
23
+        <span v-if="item.isNull == 1" class="red">*</span>{{ item.name }}
24
+      </template>
25
+      <template slot-scope="{ row, column, $index }">
26
+        <wk-form-item
27
+          :prop-prefix="`${propPrefix || ''}[${$index}].`"
28
+          :item="fieldList[index]"
29
+          :index="$index"
30
+          :field-from="fieldFrom[$index]"
31
+          :disabled="disabled"
32
+          @change="fieldChange"
33
+        >
34
+          <template slot-scope="{ data, index }">
35
+            <slot :data="data" :index="$index" />
36
+          </template>
37
+        </wk-form-item>
38
+      </template>
39
+    </el-table-column>
40
+    <el-table-column
41
+      :resizable="false"
42
+      fixed="right"
43
+      label="操作"
44
+      width="60">
45
+      <template slot-scope="{ row, column, $index }">
46
+        <el-button
47
+          icon="wk wk-icon-bin" type="text" @click="deleteClick($index)"/>
48
+      </template>
49
+
50
+    </el-table-column>
51
+  </el-table>
52
+</template>
53
+
54
+<script>
55
+
56
+export default {
57
+  // table 风格展示事项
58
+  name: 'WkTableItems',
59
+
60
+  components: {
61
+    WkFormItem: () => import('../WkForm/WkFormItem')
62
+  },
63
+
64
+  props: {
65
+    // 表单验证前缀
66
+    propPrefix: {
67
+      type: String,
68
+      default: ''
69
+    },
70
+    fieldFrom: {
71
+      type: Array,
72
+      default: () => {
73
+        return []
74
+      }
75
+    },
76
+    fieldList: {
77
+      type: Array,
78
+      default: () => {
79
+        return []
80
+      }
81
+    },
82
+    disabled: Boolean
83
+  },
84
+
85
+  data() {
86
+    return {
87
+    }
88
+  },
89
+
90
+  computed: {},
91
+
92
+  watch: {},
93
+
94
+  created() {
95
+  },
96
+
97
+  mounted() {},
98
+
99
+  beforeDestroy() {},
100
+
101
+  methods: {
102
+    deleteClick(index) {
103
+      this.$emit('delete', index)
104
+    },
105
+
106
+    getMinWidth(formType) {
107
+      if (formType === 'date_interval' ||
108
+      formType === 'dateRange' ||
109
+       formType === 'file' ||
110
+       formType === 'location' ||
111
+       formType === 'position') {
112
+        return 250
113
+      }
114
+      return 150
115
+    },
116
+
117
+    /**
118
+     * 判断展示
119
+     */
120
+    getShowValue(item) {
121
+      if (item.hasOwnProperty('show')) {
122
+        return item.show
123
+      }
124
+      return true
125
+    },
126
+
127
+    /**
128
+     * 字段change
129
+     */
130
+    fieldChange(item, index, value, valueList) {
131
+      this.$emit('change', item, index, value, valueList)
132
+    }
133
+  }
134
+}
135
+</script>
136
+
137
+<style lang="scss">
138
+.wk-table-items {
139
+  th {
140
+    line-height: initial;
141
+  }
142
+
143
+  .wk-form-item {
144
+    padding: 8px 0 !important;
145
+    margin-bottom: 0 !important;
146
+    .el-form-item__label {
147
+      display: none;
148
+    }
149
+
150
+    width: auto !important;
151
+  }
152
+}
153
+</style>
154
+
155
+<style lang="scss" scoped>
156
+.red {
157
+  color: #F56C6C;
158
+  margin-right: 4px;
159
+}
160
+</style>

+ 168
- 0
src/components/NewCom/WkDetailTable/index.vue Wyświetl plik

@@ -0,0 +1,168 @@
1
+<template>
2
+  <div class="wk-detail-table">
3
+    <template v-if="showType === 'default'">
4
+      <div
5
+        v-for="(children, index) in fieldList"
6
+        :key="index"
7
+        class="detail-item">
8
+        <flexbox class="detail-item__head">
9
+          <div class="detail-item__head-title">{{ title }}({{ index+1 }})</div>
10
+          <el-button
11
+            v-if="fieldList.length > 1"
12
+            icon="wk wk-icon-bin" type="text" @click="deleteClick(index)"/>
13
+        </flexbox>
14
+        <wk-form-items
15
+          :field-from="fieldForm[index]"
16
+          :field-list="children"
17
+          :prop-prefix="`${propPrefix || ''}[${index}].`"
18
+          :disabled="disabled"
19
+          @change="formChange"
20
+        />
21
+        <div class="add-btn">
22
+          <el-button type="text" @click="addClick">
23
+            <i class="wk wk-l-plus" />
24
+            {{ btnName }}
25
+          </el-button>
26
+        </div>
27
+      </div>
28
+    </template>
29
+    <div
30
+      v-else-if="showType === 'table'"
31
+      class="detail-item">
32
+      <wk-table-items
33
+        :field-from="fieldForm"
34
+        :field-list="addFieldList"
35
+        :prop-prefix="propPrefix"
36
+        :disabled="disabled"
37
+        @delete="deleteClick"
38
+        @change="formChange"
39
+      />
40
+      <div class="add-btn">
41
+        <el-button type="text" @click="addClick">
42
+          <i class="wk wk-l-plus" />
43
+          {{ btnName }}
44
+        </el-button>
45
+      </div>
46
+    </div>
47
+  </div>
48
+</template>
49
+
50
+<script>
51
+import WkTableItems from './WkTableItems'
52
+
53
+import { objDeepCopy } from '@/utils'
54
+import Emitter from 'element-ui/lib/mixins/emitter'
55
+
56
+export default {
57
+  // 明细表格
58
+  name: 'WkDetailTable',
59
+
60
+  components: {
61
+    WkFormItems: () => import('../WkForm/WkFormItems'),
62
+    WkTableItems
63
+  },
64
+
65
+  mixins: [Emitter],
66
+
67
+  props: {
68
+    title: String,
69
+    showType: {
70
+      type: String,
71
+      default: 'defalut' // defalut table
72
+    },
73
+    propPrefix: String,
74
+    btnName: String,
75
+    addFieldList: Array,
76
+    addFieldForm: Object,
77
+    fieldForm: {
78
+      type: Array,
79
+      default: () => {
80
+        return []
81
+      }
82
+    },
83
+    fieldList: {
84
+      type: Array,
85
+      default: () => {
86
+        return []
87
+      }
88
+    },
89
+    disabled: Boolean
90
+  },
91
+
92
+  data() {
93
+    return {
94
+    }
95
+  },
96
+
97
+  computed: {},
98
+
99
+  watch: {},
100
+
101
+  created() {
102
+  },
103
+
104
+  mounted() {},
105
+
106
+  beforeDestroy() {},
107
+
108
+  methods: {
109
+    formChange(item, index, value, valueList) {
110
+      this.$emit('change', item, index, value, valueList)
111
+      this.dispatch('ElFormItem', 'el.form.change', this.fieldForm)
112
+    },
113
+
114
+    addClick() {
115
+      this.fieldList.push(objDeepCopy(this.addFieldList))
116
+      this.fieldForm.push(objDeepCopy(this.addFieldForm))
117
+    },
118
+
119
+    deleteClick(index) {
120
+      this.fieldList.splice(index, 1)
121
+      this.fieldForm.splice(index, 1)
122
+    }
123
+  }
124
+}
125
+</script>
126
+
127
+<style lang="scss" scoped>
128
+.wk-detail-table {
129
+  font-size: 14px;
130
+  line-height: inherit;
131
+
132
+  .wk-form-items {
133
+    padding: 0;
134
+  }
135
+
136
+  .detail-item {
137
+    border-radius: 3px;
138
+    border: 1px solid #e1e1e1;
139
+    background-color: white;
140
+     &__head {
141
+      padding: 10px 20px;
142
+      background-color: #f5f5f5;
143
+      &-title {
144
+        height: auto;
145
+        font-size: 12px;
146
+        color: #333;
147
+        flex: 1;
148
+        line-height: normal;
149
+      }
150
+      .el-button {
151
+        padding: 0;
152
+      }
153
+    }
154
+  }
155
+
156
+  .detail-item + .detail-item {
157
+    margin-top: 10px;
158
+  }
159
+
160
+  .add-btn {
161
+    text-align: right;
162
+    padding-right: 10px;
163
+    .wk-l-plus {
164
+      font-size: 12px;
165
+    }
166
+  }
167
+}
168
+</style>

+ 149
- 0
src/components/NewCom/WkDistpicker/index.vue Wyświetl plik

@@ -0,0 +1,149 @@
1
+<template>
2
+  <div class="wk-distpicker">
3
+    <el-cascader
4
+      v-bind="$attrs"
5
+      v-model="dataValue"
6
+      :options="options"
7
+      :props="config"
8
+      @change="handleChange"/>
9
+  </div>
10
+</template>
11
+
12
+<script>
13
+import DISTRICTS from '../../VDistpicker/districts'
14
+import { valueEquals } from 'element-ui/lib/utils/util'
15
+import { isObject } from '@/utils/types'
16
+
17
+export default {
18
+  // WkDistpicker
19
+  name: 'WkDistpicker',
20
+
21
+  components: {},
22
+
23
+  inheritAttrs: false,
24
+
25
+  props: {
26
+    hideArea: { type: Boolean, default: false },
27
+    onlyProvince: { type: Boolean, default: false },
28
+    value: Array,
29
+    props: Object
30
+  },
31
+
32
+  data() {
33
+    return {
34
+      dataValue: []
35
+    }
36
+  },
37
+
38
+  computed: {
39
+    options() {
40
+      return this.getOptions()
41
+    },
42
+
43
+    config() {
44
+      const props = this.props || {}
45
+      return {
46
+        label: 'name',
47
+        value: 'code',
48
+        ...props
49
+      }
50
+    }
51
+  },
52
+
53
+  watch: {
54
+    value: {
55
+      handler() {
56
+        this.validateValue()
57
+      },
58
+      immediate: true
59
+    }
60
+  },
61
+
62
+  created() {
63
+
64
+  },
65
+
66
+  mounted() {},
67
+
68
+  beforeDestroy() {},
69
+
70
+  methods: {
71
+    getOptions() {
72
+      const list = DISTRICTS
73
+      if (!this.onlyProvince && !this.hideArea) {
74
+        return list
75
+      }
76
+
77
+      const newList = []
78
+      list.forEach(provinceItem => {
79
+        const province = {
80
+          code: provinceItem.code,
81
+          name: provinceItem.name
82
+        }
83
+        if (!this.onlyProvince) {
84
+          province.children = []
85
+          provinceItem.children.forEach(cityItem => {
86
+            const city = {
87
+              code: cityItem.code,
88
+              name: cityItem.name
89
+            }
90
+            province.children.push(city)
91
+            if (!this.hideArea) {
92
+              city.children = cityItem.children
93
+            }
94
+          })
95
+        }
96
+
97
+        newList.push(province)
98
+      })
99
+
100
+      return newList
101
+    },
102
+
103
+    validateValue() {
104
+      if (this.value && this.value.length) {
105
+        let dataValue = this.value
106
+        if (isObject(this.value[0])) {
107
+          dataValue = this.value.map(item => item[this.config.value])
108
+        }
109
+        if (!valueEquals(dataValue, this.dataValue)) {
110
+          this.dataValue = dataValue
111
+        }
112
+      } else {
113
+        this.dataValue = []
114
+      }
115
+    },
116
+
117
+    handleChange() {
118
+      const objValue = this.getCascaderValArr(this.dataValue, this.options)
119
+      this.$emit('input', objValue)
120
+      this.$emit('change', objValue)
121
+    },
122
+
123
+    getCascaderValArr(value, data) {
124
+      const res = []
125
+      if (value.length === 0) return res
126
+      let index = 0
127
+      do {
128
+        const findRes = data.find(o => o.code === value[index])
129
+        if (findRes) {
130
+          data = findRes.children || []
131
+          res.push({
132
+            code: findRes.code,
133
+            name: findRes.name,
134
+            id: index + 1
135
+          })
136
+        }
137
+        index++
138
+      } while (index <= value.length)
139
+      return res
140
+    }
141
+  }
142
+}
143
+</script>
144
+
145
+<style lang="scss" scoped>
146
+.el-cascader {
147
+  width: 100%;
148
+}
149
+</style>

+ 39
- 5
src/components/NewCom/WkForm/Mixin.js Wyświetl plik

@@ -2,13 +2,14 @@ export default {
2 2
   methods: {
3 3
     /**
4 4
      * 判断是否为普通 整句 文本框
5
-     * @param formType 字段类型
5
+     * @param form_type 字段类型
6 6
      */
7
-    isTrimInput(formType) {
7
+    isTrimInput(form_type) {
8 8
       return [
9 9
         'mobile',
10
-        'email'
11
-      ].includes(formType)
10
+        'email',
11
+        'website'
12
+      ].includes(form_type)
12 13
     },
13 14
     /**
14 15
      * 部门事件
@@ -37,7 +38,10 @@ export default {
37 38
     oldChange(dataValue, field, index) {
38 39
       this.$set(this.fieldFrom, field.field, dataValue.value)
39 40
       this.$emit('change', field, index, dataValue.value)
40
-      this.$refs.form.validateField(field.field)
41
+      // this.$refs.form.validateField(field.field)
42
+      if (this.$refs.form) {
43
+        this.$refs.form.validateField(field.field)
44
+      }
41 45
     },
42 46
 
43 47
     /**
@@ -68,6 +72,36 @@ export default {
68 72
     getTips(data) {
69 73
       const tips = data.tips || data.inputTips
70 74
       return tips ? `(${tips})` : ''
75
+    },
76
+    /**
77
+     * 判断展示
78
+     */
79
+    getShowValue(item) {
80
+      if (item.hasOwnProperty('show')) {
81
+        return item.show
82
+      }
83
+      return true
84
+    },
85
+    /**
86
+     * 获取类型图标
87
+     * @param {*} formType
88
+     */
89
+    getInputIcon(formType) {
90
+      return {
91
+        mobile: 'wk wk-icon-mobile',
92
+        email: 'wk wk-icon-email-outline',
93
+        website: 'wk wk-icon-link'
94
+      }[formType]
95
+    },
96
+    /**
97
+     * 获取输入最大长度
98
+     * @param {*} formType
99
+     */
100
+    getInputMaxlength(formType) {
101
+      if (formType === 'website') {
102
+        return 800
103
+      }
104
+      return 100
71 105
     }
72 106
   }
73 107
 }

+ 322
- 0
src/components/NewCom/WkForm/WkField.vue Wyświetl plik

@@ -0,0 +1,322 @@
1
+<template>
2
+  <div v-if="ignoreFields.includes(item.field)">
3
+    <slot :data="item" :index="index" />
4
+  </div>
5
+  <el-input
6
+    v-else-if="item.form_type == 'text'"
7
+    v-model="fieldFrom[item.field]"
8
+    :disabled="item.disabled || disabled"
9
+    :maxlength="100"
10
+    :placeholder="item.placeholder"
11
+    :type="item.form_type"
12
+    @input="commonChange(item, index, $event)"/>
13
+  <el-input
14
+    v-else-if="isTrimInput(item.form_type)"
15
+    v-model.trim="fieldFrom[item.field]"
16
+    :disabled="item.disabled || disabled"
17
+    :prefix-icon="getInputIcon(item.form_type)"
18
+    :maxlength="getInputMaxlength(item.form_type)"
19
+    :placeholder="item.placeholder"
20
+    type="text"
21
+    @input="commonChange(item, index, $event)"/>
22
+  <el-input-number
23
+    v-else-if="item.form_type == 'number'"
24
+    v-model="fieldFrom[item.field]"
25
+    :placeholder="item.placeholder"
26
+    :disabled="item.disabled || disabled"
27
+    :controls="false"
28
+    @change="commonChange(item, index, $event)" />
29
+  <el-input-number
30
+    v-else-if="item.form_type == 'floatnumber'"
31
+    v-model="fieldFrom[item.field]"
32
+    :placeholder="item.placeholder"
33
+    :disabled="item.disabled || disabled"
34
+    :controls="false"
35
+    @change="commonChange(item, index, $event)" />
36
+  <wk-percent-input
37
+    v-else-if="item.form_type == 'percent'"
38
+    v-model="fieldFrom[item.field]"
39
+    :placeholder="item.placeholder"
40
+    :disabled="item.disabled || disabled"
41
+    :controls="false"
42
+    @change="commonChange(item, index, $event)" />
43
+  <el-input
44
+    v-else-if="item.form_type == 'textarea'"
45
+    v-model="fieldFrom[item.field]"
46
+    :disabled="item.disabled || disabled"
47
+    :rows="3"
48
+    :autosize="{ minRows: 3}"
49
+    :maxlength="item.maxlength || 800"
50
+    :placeholder="item.placeholder"
51
+    :type="item.form_type"
52
+    resize="none"
53
+    @input="commonChange(item, index, $event)" />
54
+  <wk-select
55
+    v-else-if="['select'].includes(item.form_type)"
56
+    v-model="fieldFrom[item.field]"
57
+    :disabled="item.disabled || disabled"
58
+    :clearable="item.clearable"
59
+    :placeholder="item.placeholder"
60
+    :options="item.setting"
61
+    :show-type="item.precisions === 1 ? 'tiled' : 'default'"
62
+    :other-show-input="item.hasOwnProperty('optionsData')"
63
+    @change="commonChange(item, index, $event)"/>
64
+  <wk-checkbox
65
+    v-else-if="['checkbox'].includes(item.form_type)"
66
+    v-model="fieldFrom[item.field]"
67
+    :disabled="item.disabled || disabled"
68
+    :clearable="item.clearable"
69
+    :placeholder="item.placeholder"
70
+    :options="item.setting"
71
+    :show-type="item.precisions === 1 ? 'tiled' : 'default'"
72
+    :other-show-input="item.hasOwnProperty('optionsData')"
73
+    @change="commonChange(item, index, $event)"/>
74
+  <!-- <el-select
75
+        v-else-if="['checkbox', 'select'].includes(item.form_type)"
76
+        v-model="fieldFrom[item.field]"
77
+        :disabled="item.disabled || disabled"
78
+        :clearable="item.clearable"
79
+        :placeholder="item.placeholder"
80
+        :multiple="item.form_type === 'checkbox'"
81
+        style="width: 100%;"
82
+        @change="commonChange(item, index, $event)">
83
+        <el-option
84
+          v-for="(item, index) in item.setting"
85
+          :key="index"
86
+          :label="!isEmptyValue(item.value) ? item.label || item.name : item "
87
+          :value="!isEmptyValue(item.value) ? item.value : item"/>
88
+      </el-select> -->
89
+  <!-- <el-select
90
+        v-else-if="item.form_type == 'checkbox'"
91
+        v-model="fieldFrom[item.field]"
92
+        :disabled="item.disabled || disabled"
93
+        :clearable="item.clearable"
94
+        :placeholder="item.placeholder"
95
+        multiple
96
+        style="width: 100%;"
97
+        @change="commonChange(item, index, $event)">
98
+        <el-option
99
+          v-for="(item, index) in item.setting"
100
+          :key="index"
101
+          :label="!isEmptyValue(item.value) ? item.label || item.name : item "
102
+          :value="!isEmptyValue(item.value) ? item.value : item"/>
103
+      </el-select> -->
104
+  <el-date-picker
105
+    v-else-if="item.form_type == 'date'"
106
+    v-model="fieldFrom[item.field]"
107
+    :disabled="item.disabled || disabled"
108
+    clearable
109
+    style="width: 100%;"
110
+    type="date"
111
+    value-format="yyyy-MM-dd"
112
+    placeholder="选择日期"
113
+    @change="commonChange(item, index, $event)"/>
114
+  <el-date-picker
115
+    v-else-if="item.form_type == 'dateRange'"
116
+    v-model="fieldFrom[item.field]"
117
+    :disabled="item.disabled || disabled"
118
+    :type="item.dateType || 'daterange'"
119
+    :value-format="item.dateValueFormat || 'yyyy-MM-dd'"
120
+    clearable
121
+    style="width: 100%;vertical-align: middle;"
122
+    start-placeholder="开始日期"
123
+    end-placeholder="结束日期"
124
+    @change="commonChange(item, index, $event)"/>
125
+  <el-date-picker
126
+    v-else-if="item.form_type == 'datetime'"
127
+    v-model="fieldFrom[item.field]"
128
+    :disabled="item.disabled || disabled"
129
+    clearable
130
+    style="width: 100%;"
131
+    type="datetime"
132
+    value-format="yyyy-MM-dd HH:mm:ss"
133
+    placeholder="选择日期"
134
+    @change="commonChange(item, index, $event)"/>
135
+  <wk-dep-select
136
+    v-else-if="item.form_type == 'structure'"
137
+    v-model="fieldFrom[item.field]"
138
+    :request="item.request"
139
+    :props="item.props"
140
+    :params="item.params"
141
+    :disabled="item.disabled || disabled"
142
+    :radio="!isEmptyValue(item.radio) ? item.radio : true"
143
+    style="width: 100%;"
144
+    @change="depOrUserChange(item, index, arguments[0], arguments[1])"
145
+  />
146
+  <wk-user-select
147
+    v-else-if="['single_user', 'user'].includes(item.form_type)"
148
+    v-model="fieldFrom[item.field]"
149
+    :request="item.request"
150
+    :props="item.props"
151
+    :params="item.params"
152
+    :disabled="item.disabled || disabled"
153
+    :radio="!isEmptyValue(item.radio) ? item.radio : true"
154
+    style="width: 100%;"
155
+    @change="depOrUserChange(item, index, arguments[0], arguments[1])"
156
+  />
157
+  <el-radio-group
158
+    v-else-if="item.form_type == 'radio'"
159
+    v-model="fieldFrom[item.field]"
160
+    :disabled="item.disabled || disabled"
161
+    :placeholder="item.placeholder"
162
+    @change="commonChange(item, index, $event)">
163
+    <el-radio
164
+      v-for="(item, index) in item.setting"
165
+      :key="index"
166
+      :label="!isEmptyValue(item.value) ? item.value : item">
167
+      {{ !isEmptyValue(item.value) ? item.label || item.name : item }}
168
+    </el-radio>
169
+  </el-radio-group>
170
+  <el-switch
171
+    v-else-if="item.form_type == 'boolean_value'"
172
+    v-model="fieldFrom[item.field]"
173
+    :disabled="item.disabled || disabled"
174
+    active-value="1"
175
+    inactive-value="0"
176
+    @change="commonChange(item, index, $event)"/>
177
+  <wk-position
178
+    v-else-if="item.form_type == 'position'"
179
+    :hide-area="item.hideArea"
180
+    :only-province="item.onlyProvince"
181
+    :show-detail="item.showDetail"
182
+    v-model="fieldFrom[item.field]"
183
+    :disabled="item.disabled || disabled"
184
+    @change="commonChange(item, index, $event)"/>
185
+  <wk-location
186
+    v-else-if="item.form_type == 'location'"
187
+    v-model="fieldFrom[item.field]"
188
+    :disabled="item.disabled || disabled"
189
+    @change="commonChange(item, index, $event)"/>
190
+  <wk-signature-pad
191
+    v-else-if="item.form_type == 'handwriting_sign'"
192
+    v-model="fieldFrom[item.field]"
193
+    :disabled="item.disabled || disabled"/>
194
+  <wk-desc-text
195
+    v-else-if="item.form_type == 'desc_text'"
196
+    :value="fieldFrom[item.field]"/>
197
+  <el-date-picker
198
+    v-else-if="item.form_type === 'date_interval'"
199
+    v-model="fieldFrom[item.field]"
200
+    :type="item.dateType || 'daterange'"
201
+    :value-format="item.dateValueFormat || 'yyyy-MM-dd'"
202
+    :disabled="item.disabled || disabled"
203
+    style="width: 100%;vertical-align: middle;"
204
+    clearable
205
+    start-placeholder="开始日期"
206
+    end-placeholder="结束日期"
207
+    @change="commonChange(item, index, $event)"/>
208
+  <v-distpicker
209
+    v-else-if="item.form_type == 'address'"
210
+    :province="fieldFrom[item.field].province"
211
+    :city="fieldFrom[item.field].city"
212
+    :area="fieldFrom[item.field].area"
213
+    @province="selectProvince($event, item, index)"
214
+    @city="selectCity($event, item, index)"
215
+    @area="selectArea($event, item, index)"/>
216
+  <xh-files
217
+    v-else-if="item.form_type == 'file'"
218
+    :value="fieldFrom[item.field]"
219
+    :disabled="item.disabled || disabled"
220
+    @value-change="oldChange($event, item, index)"
221
+  />
222
+  <wk-detail-table
223
+    v-else-if="item.form_type == 'detail_table'"
224
+    :show-type="item.precisions === 2 ? 'table' : 'default'"
225
+    :title="item.name"
226
+    :prop-prefix="item.field"
227
+    :btn-name="item.remark"
228
+    :add-field-list="item.fieldExtendList"
229
+    :add-field-form="item.fieldForm"
230
+    :field-form="fieldFrom[item.field]"
231
+    :field-list="item.fieldList"
232
+    :disabled="item.disabled || disabled"/>
233
+  <div v-else>
234
+    <slot :data="item" :index="index" />
235
+  </div>
236
+</template>
237
+
238
+<script>
239
+import WkUserSelect from '@/components/NewCom/WkUserSelect'
240
+import WkDepSelect from '@/components/NewCom/WkDepSelect'
241
+import WkPosition from '@/components/NewCom/WkPosition'
242
+import WkLocation from '@/components/NewCom/WkLocation'
243
+import WkSignaturePad from '@/components/NewCom/WkSignaturePad'
244
+import WkDescText from '@/components/NewCom/WkDescText'
245
+import WkPercentInput from '@/components/NewCom/WkPercentInput'
246
+import WkSelect from '@/components/NewCom/WkSelect'
247
+import WkCheckbox from '@/components/NewCom/WkCheckbox'
248
+import WkDetailTable from '@/components/NewCom/WkDetailTable'
249
+import VDistpicker from '@/components/VDistpicker'
250
+import { XhFiles } from '@/components/CreateCom'
251
+
252
+import Mixin from './Mixin'
253
+
254
+export default {
255
+  // 字段
256
+  name: 'WkField',
257
+
258
+  components: {
259
+    WkUserSelect,
260
+    WkDepSelect,
261
+    WkPosition,
262
+    WkLocation,
263
+    WkSignaturePad,
264
+    WkDescText,
265
+    WkPercentInput,
266
+    WkSelect,
267
+    WkCheckbox,
268
+    WkDetailTable,
269
+    VDistpicker,
270
+    XhFiles
271
+  },
272
+
273
+  mixins: [Mixin],
274
+
275
+  props: {
276
+    item: Object,
277
+    index: Number,
278
+    fieldFrom: {
279
+      type: Object,
280
+      default: () => {
281
+        return {}
282
+      }
283
+    },
284
+    // 忽略的字段直接输出
285
+    ignoreFields: {
286
+      type: Array,
287
+      default: () => {
288
+        return []
289
+      }
290
+    },
291
+    disabled: Boolean
292
+  },
293
+
294
+  data() {
295
+    return {
296
+
297
+    }
298
+  },
299
+
300
+  computed: {},
301
+
302
+  watch: {},
303
+
304
+  created() {},
305
+
306
+  mounted() {},
307
+
308
+  beforeDestroy() {},
309
+
310
+  methods: {}
311
+}
312
+</script>
313
+
314
+<style lang="scss" scoped>
315
+.el-input-number {
316
+  width: 100%;
317
+  /deep/ .el-input__inner {
318
+    text-align: left;
319
+    padding: 0 8px;
320
+  }
321
+}
322
+</style>

+ 192
- 0
src/components/NewCom/WkForm/WkFieldView.vue Wyświetl plik

@@ -0,0 +1,192 @@
1
+<template>
2
+  <div
3
+    :class="`is-${form_type}`"
4
+    class="wk-field-view"
5
+  >
6
+    <template v-if="ignoreFields.includes(props.field)">
7
+      <slot :data="$props" />
8
+    </template>
9
+    <span v-else-if="isCommonType">{{ getCommonShowValue() }}</span>
10
+    <el-switch
11
+      v-else-if="form_type == 'boolean_value'"
12
+      :value="value"
13
+      disabled
14
+      active-value="1"
15
+      inactive-value="0"
16
+    />
17
+    <wk-signature-image
18
+      v-else-if="form_type == 'handwriting_sign'"
19
+      :src=" !!value ? value.url:''"
20
+      :height="config.signatureHeight"
21
+    />
22
+    <wk-desc-text
23
+      v-else-if="form_type == 'desc_text'"
24
+      :key="Date.now().toString()"
25
+      :value="value"
26
+    />
27
+    <span
28
+      v-else-if="form_type == 'location'"
29
+      :class="{'can-check':objectHasValue(value, 'address')}"
30
+      @click.stop="mapViewShow=true"
31
+    >{{ objectHasValue(value, 'address') ? value.address : '--' }}</span>
32
+    <span
33
+      v-else-if="form_type == 'website'"
34
+      :class="{'can-check': !isEmpty}"
35
+      @click.stop="openUrl(value)"
36
+    >{{ value }}</span>
37
+    <file-list-view
38
+      v-else-if="form_type == 'file'"
39
+      :list="value || []"
40
+    />
41
+    <wk-detail-table-view
42
+      v-else-if="form_type == 'detail_table'"
43
+      :show-type="props.precisions === 2 ? 'table' : 'default'"
44
+      :title="props.name"
45
+      :add-field-list="props.fieldExtendList"
46
+      :field-form="value"
47
+      :field-list="props.fieldList"
48
+    >
49
+      <template slot-scope="{ data }">
50
+        <slot :data="data" />
51
+      </template>
52
+    </wk-detail-table-view>
53
+    <template v-else>
54
+      <slot :data="$props" />
55
+    </template>
56
+
57
+    <map-view
58
+      v-if="mapViewShow"
59
+      :title="value.address"
60
+      :lat="value.lat"
61
+      :lng="value.lng"
62
+      @hidden="mapViewShow=false"
63
+    />
64
+  </div>
65
+</template>
66
+
67
+<script>
68
+import WkSignatureImage from '@/components/NewCom/WkSignaturePad/Image'
69
+import WkDescText from '@/components/NewCom/WkDescText'
70
+import MapView from '@/components/MapView' // 地图详情
71
+import FileListView from '@/components/FileListView' // 附件
72
+import WkDetailTableView from '@/components/NewCom/WkDetailTable/View'
73
+
74
+import merge from '@/utils/merge'
75
+import { isObject, isEmpty } from '@/utils/types'
76
+import { getFormFieldShowName } from './utils'
77
+
78
+const DefaultWkFieldView = {
79
+  signatureHeight: '26px'
80
+}
81
+
82
+export default {
83
+  // 特殊字段展示
84
+  name: 'WkFieldView',
85
+
86
+  components: {
87
+    WkSignatureImage,
88
+    WkDescText,
89
+    MapView,
90
+    FileListView,
91
+    WkDetailTableView
92
+  },
93
+
94
+  props: {
95
+    props: Object, // 自定义字段参数信息
96
+    form_type: String,
97
+    value: [String, Object, Array, Number],
98
+    // 忽略的字段直接输出
99
+    ignoreFields: {
100
+      type: Array,
101
+      default: () => {
102
+        return []
103
+      }
104
+    }
105
+  },
106
+
107
+  data() {
108
+    return {
109
+      // 控制展示地图详情
110
+      mapViewShow: false
111
+    }
112
+  },
113
+
114
+  computed: {
115
+    config() {
116
+      return merge({ ...DefaultWkFieldView }, this.props || {})
117
+    },
118
+    isEmpty() {
119
+      return isEmpty(this.value)
120
+    },
121
+    isCommonType() {
122
+      return [
123
+        'text',
124
+        'textarea',
125
+        'website',
126
+        'select',
127
+        'checkbox',
128
+        'number',
129
+        'floatnumber',
130
+        'percent',
131
+        'mobile',
132
+        'email',
133
+        'date',
134
+        'datetime',
135
+        'date_interval',
136
+        'user',
137
+        'structure',
138
+        'position'
139
+      ].includes(this.form_type)
140
+    }
141
+  },
142
+
143
+  watch: {},
144
+
145
+  created() {},
146
+
147
+  mounted() {},
148
+
149
+  beforeDestroy() {},
150
+
151
+  methods: {
152
+    /**
153
+		 * 判断对象是否值
154
+		 */
155
+    objectHasValue(obj, key) {
156
+      if (isObject(obj)) {
157
+        return !isEmpty(obj[key])
158
+      }
159
+      return false
160
+    },
161
+
162
+    openUrl(url) {
163
+      if (!url.match(/^https?:\/\//i)) {
164
+        url = 'http://' + url
165
+      }
166
+      window.open(url)
167
+    },
168
+
169
+    /**
170
+     * 获取类型的展示值
171
+     */
172
+    getCommonShowValue() {
173
+      return getFormFieldShowName(this.form_type, this.value, '', this.props)
174
+    }
175
+  }
176
+}
177
+</script>
178
+
179
+<style lang="scss" scoped>
180
+.wk-field-view {
181
+  overflow: hidden;
182
+  text-overflow: ellipsis;
183
+	.can-check {
184
+		color: $xr-color-primary;
185
+		cursor: pointer;
186
+	}
187
+
188
+	&.is-website {
189
+		display: inline;
190
+	}
191
+}
192
+</style>

+ 130
- 0
src/components/NewCom/WkForm/WkFormItem.vue Wyświetl plik

@@ -0,0 +1,130 @@
1
+<template>
2
+  <el-form-item
3
+    v-if="getShowValue(item)"
4
+    :key="index"
5
+    :prop="`${propPrefix || ''}${item.field}`"
6
+    :rules="item.rules"
7
+    :class="[item.className || '', `is-${item.form_type}`]"
8
+    :style="{width: item.style_percent ? `${item.style_percent}%` : 'auto'}"
9
+    class="wk-form-item">
10
+    <template slot="label">
11
+      {{ item.name }}
12
+      <el-tooltip
13
+        v-if="item.tipType == 'tooltip'"
14
+        effect="dark"
15
+        placement="top">
16
+        <div slot="content" v-html="getTips(item)"/>
17
+        <i class="wk wk-help wk-help-tips"/>
18
+      </el-tooltip>
19
+      <span v-else style="color:#999;">
20
+        {{ getTips(item) }}
21
+      </span>
22
+    </template>
23
+    <wk-field
24
+      :item="item"
25
+      :index="index"
26
+      :field-from="fieldFrom"
27
+      :ignore-fields="ignoreFields"
28
+      :disabled="disabled"
29
+      @change="fieldChange"
30
+    >
31
+      <template slot-scope="{ data, index }">
32
+        <slot :data="data" :index="index" />
33
+      </template>
34
+    </wk-field>
35
+  </el-form-item>
36
+</template>
37
+
38
+<script>
39
+import WkField from './WkField'
40
+
41
+import Mixin from './Mixin'
42
+
43
+export default {
44
+  // item
45
+  name: 'WkFormItem',
46
+
47
+  components: {
48
+    WkField
49
+  },
50
+
51
+  mixins: [Mixin],
52
+
53
+  props: {
54
+    // 表单验证前缀
55
+    propPrefix: {
56
+      type: String,
57
+      default: ''
58
+    },
59
+    item: Object,
60
+    index: Number,
61
+    fieldFrom: {
62
+      type: Object,
63
+      default: () => {
64
+        return {}
65
+      }
66
+    },
67
+    // 忽略的字段直接输出
68
+    ignoreFields: {
69
+      type: Array,
70
+      default: () => {
71
+        return []
72
+      }
73
+    },
74
+    disabled: Boolean
75
+  },
76
+
77
+  data() {
78
+    return {
79
+    }
80
+  },
81
+
82
+  computed: {},
83
+
84
+  watch: {},
85
+
86
+  created() {},
87
+
88
+  mounted() {},
89
+
90
+  beforeDestroy() {},
91
+
92
+  methods: {
93
+    fieldChange(item, index, value, valueList) {
94
+      this.$emit('change', item, index, value, valueList)
95
+    }
96
+  }
97
+}
98
+</script>
99
+
100
+<style lang="scss">
101
+.wk-form-item {
102
+  .el-form-item__label {
103
+    line-height: 1.5;
104
+    padding-bottom: 8px;
105
+    word-break: break-all;
106
+    word-wrap: break-word;
107
+    color: #333;
108
+  }
109
+
110
+  .el-form-item__error {
111
+    position: relative;
112
+    top: auto;
113
+    left: auto;
114
+  }
115
+
116
+  .el-form-item.is-desc_text {
117
+    .el-form-item__label {
118
+      display: none;
119
+    }
120
+  }
121
+}
122
+</style>
123
+
124
+
125
+<style lang="scss" scoped>
126
+.wk-form-item {
127
+  padding: 12px 12px 0;
128
+  margin-bottom: 0;
129
+}
130
+</style>

+ 47
- 176
src/components/NewCom/WkForm/WkFormItems.vue Wyświetl plik

@@ -1,188 +1,45 @@
1 1
 <template>
2
-  <div>
3
-    <el-form-item
4
-      v-for="(item, index) in fieldList"
5
-      :key="index"
6
-      :prop="item.field"
7
-      :class="[item.className || '', `is-${item.formType}`]"
8
-      :rules="item.rules">
9
-      <template slot="label">
10
-        {{ item.name }}
11
-        <span style="color:#999;">
12
-          {{ getTips(item) }}
13
-        </span>
14
-      </template>
15
-      <el-input
16
-        v-if="item.formType == 'text'"
17
-        v-model="fieldFrom[item.field]"
18
-        :disabled="item.disabled"
19
-        :maxlength="100"
20
-        :placeholder="item.placeholder"
21
-        :type="item.formType"
22
-        @input="commonChange(item, index, $event)"/>
23
-      <el-input
24
-        v-else-if="isTrimInput(item.formType)"
25
-        v-model.trim="fieldFrom[item.field]"
26
-        :disabled="item.disabled"
27
-        :maxlength="100"
28
-        :placeholder="item.placeholder"
29
-        :type="item.formType"
30
-        @input="commonChange(item, index, $event)"/>
31
-      <el-input-number
32
-        v-else-if="item.formType == 'number'"
33
-        v-model="fieldFrom[item.field]"
34
-        :placeholder="item.placeholder"
35
-        :disabled="item.disabled"
36
-        :controls="false"
37
-        @input="commonChange(item, index, $event)" />
38
-      <el-input-number
39
-        v-else-if="item.formType == 'floatnumber'"
40
-        v-model="fieldFrom[item.field]"
41
-        :placeholder="item.placeholder"
42
-        :disabled="item.disabled"
43
-        :controls="false"
44
-        @change="commonChange(item, index, $event)" />
45
-      <el-input
46
-        v-else-if="item.formType == 'textarea'"
47
-        v-model="fieldFrom[item.field]"
48
-        :disabled="item.disabled"
49
-        :autosize="{ minRows: 3}"
50
-        :maxlength="800"
51
-        :placeholder="item.placeholder"
52
-        :type="item.formType"
53
-        resize="none"
54
-        @input="commonChange(item, index, $event)" />
55
-      <el-select
56
-        v-else-if="['checkbox', 'select'].includes(item.formType)"
57
-        v-model="fieldFrom[item.field]"
58
-        :disabled="item.disabled"
59
-        :clearable="item.clearable"
60
-        :placeholder="item.placeholder"
61
-        :multiple="item.formType === 'checkbox'"
62
-        style="width: 100%;"
63
-        @change="commonChange(item, index, $event)">
64
-        <el-option
65
-          v-for="(item, index) in item.setting"
66
-          :key="index"
67
-          :label="!isEmptyValue(item.value) ? item.label || item.name : item "
68
-          :value="!isEmptyValue(item.value) ? item.value : item"/>
69
-      </el-select>
70
-      <el-select
71
-        v-else-if="item.formType == 'checkbox'"
72
-        v-model="fieldFrom[item.field]"
73
-        :disabled="item.disabled"
74
-        :clearable="item.clearable"
75
-        :placeholder="item.placeholder"
76
-        multiple
77
-        style="width: 100%;"
78
-        @change="commonChange(item, index, $event)">
79
-        <el-option
80
-          v-for="(item, index) in item.setting"
81
-          :key="index"
82
-          :label="!isEmptyValue(item.value) ? item.label || item.name : item "
83
-          :value="!isEmptyValue(item.value) ? item.value : item"/>
84
-      </el-select>
85
-      <el-date-picker
86
-        v-else-if="item.formType == 'date'"
87
-        v-model="fieldFrom[item.field]"
88
-        :disabled="item.disabled"
89
-        clearable
90
-        style="width: 100%;"
91
-        type="date"
92
-        value-format="yyyy-MM-dd"
93
-        placeholder="选择日期"
94
-        @change="commonChange(item, index, $event)"/>
95
-      <el-date-picker
96
-        v-else-if="item.formType == 'dateRange'"
97
-        v-model="fieldFrom[item.field]"
98
-        :disabled="item.disabled"
99
-        clearable
100
-        style="width: 100%;"
101
-        type="daterange"
102
-        value-format="yyyy-MM-dd"
103
-        start-placeholder="开始日期"
104
-        end-placeholder="结束日期"
105
-        @change="commonChange(item, index, $event)"/>
106
-      <el-date-picker
107
-        v-else-if="item.formType == 'datetime'"
108
-        v-model="fieldFrom[item.field]"
109
-        :disabled="item.disabled"
110
-        clearable
111
-        style="width: 100%;"
112
-        type="datetime"
113
-        value-format="yyyy-MM-dd HH:mm:ss"
114
-        placeholder="选择日期"
115
-        @change="commonChange(item, index, $event)"/>
116
-      <wk-dep-select
117
-        v-else-if="item.formType == 'structure'"
118
-        v-model="fieldFrom[item.field]"
119
-        :request="item.request"
120
-        :props="item.props"
121
-        :params="item.params"
122
-        :disabled="item.disabled"
123
-        :radio="!isEmptyValue(item.radio) ? item.radio : true"
124
-        style="width: 100%;"
125
-        @change="depOrUserChange(item, index, arguments[0], arguments[1])"
126
-      />
127
-      <wk-user-select
128
-        v-else-if="['single_user', 'user'].includes(item.formType)"
129
-        v-model="fieldFrom[item.field]"
130
-        :request="item.request"
131
-        :props="item.props"
132
-        :params="item.params"
133
-        :disabled="item.disabled"
134
-        :radio="!isEmptyValue(item.radio) ? item.radio : true"
135
-        style="width: 100%;"
136
-        @change="depOrUserChange(item, index, arguments[0], arguments[1])"
137
-      />
138
-      <el-radio-group
139
-        v-else-if="item.formType == 'radio'"
140
-        v-model="fieldFrom[item.field]"
141
-        :disabled="item.disabled"
142
-        :placeholder="item.placeholder"
143
-        @change="commonChange(item, index, $event)">
144
-        <el-radio
145
-          v-for="(item, index) in item.setting"
146
-          :key="index"
147
-          :label="!isEmptyValue(item.value) ? item.value : item">
148
-          {{ !isEmptyValue(item.value) ? item.label || item.name : item }}
149
-        </el-radio>
150
-      </el-radio-group>
151
-      <v-distpicker
152
-        v-if="item.formType == 'address'"
153
-        :province="fieldFrom[item.field].province"
154
-        :city="fieldFrom[item.field].city"
155
-        :area="fieldFrom[item.field].area"
156
-        @province="selectProvince($event, item, index)"
157
-        @city="selectCity($event, item, index)"
158
-        @area="selectArea($event, item, index)"/>
159
-      <template v-else>
160
-        <slot :data="item" />
161
-      </template>
162
-    </el-form-item>
163
-  </div>
2
+  <flexbox
3
+    class="wk-form-items"
4
+    align="flex-start"
5
+    wrap="wrap"
6
+    justify="flex-start">
7
+    <template v-for="(item, index) in fieldList">
8
+      <wk-form-item
9
+        :key="index"
10
+        :prop-prefix="propPrefix"
11
+        :item="item"
12
+        :index="index"
13
+        :field-from="fieldFrom"
14
+        :ignore-fields="ignoreFields"
15
+        :disabled="disabled"
16
+        @change="fieldChange"
17
+      >
18
+        <template slot-scope="{ data, index }">
19
+          <slot :data="item" :index="index" />
20
+        </template>
21
+      </wk-form-item>
22
+    </template>
23
+  </flexbox>
164 24
 </template>
165 25
 
166 26
 <script>
167
-import WkUserSelect from '@/components/NewCom/WkUserSelect'
168
-import WkDepSelect from '@/components/NewCom/WkDepSelect'
169
-import VDistpicker from '@/components/VDistpicker'
170
-
171
-import Mixin from './Mixin'
27
+import WkFormItem from './WkFormItem'
172 28
 
173 29
 export default {
174
-  // 多块形式的form-item
30
+  // 多块形式的form-item 用于字段库
175 31
   name: 'WkFormItems',
176 32
 
177 33
   components: {
178
-    WkUserSelect,
179
-    WkDepSelect,
180
-    VDistpicker
34
+    WkFormItem
181 35
   },
182 36
 
183
-  mixins: [Mixin],
184
-
185 37
   props: {
38
+    // 表单验证前缀
39
+    propPrefix: {
40
+      type: String,
41
+      default: ''
42
+    },
186 43
     fieldFrom: {
187 44
       type: Object,
188 45
       default: () => {
@@ -194,7 +51,15 @@ export default {
194 51
       default: () => {
195 52
         return []
196 53
       }
197
-    }
54
+    },
55
+    // 忽略的字段直接输出
56
+    ignoreFields: {
57
+      type: Array,
58
+      default: () => {
59
+        return []
60
+      }
61
+    },
62
+    disabled: Boolean
198 63
   },
199 64
 
200 65
   data() {
@@ -212,10 +77,16 @@ export default {
212 77
 
213 78
   beforeDestroy() {},
214 79
 
215
-  methods: {}
80
+  methods: {
81
+    fieldChange(item, index, value, valueList) {
82
+      this.$emit('change', item, index, value, valueList)
83
+    }
84
+  }
216 85
 }
217 86
 </script>
218 87
 
219 88
 <style lang="scss" scoped>
220
-
89
+.wk-form-items {
90
+  padding: 0 12px;
91
+}
221 92
 </style>

+ 19
- 19
src/components/NewCom/WkForm/index.vue Wyświetl plik

@@ -16,7 +16,7 @@
16 16
       v-for="(item, index) in fieldList"
17 17
       :key="index"
18 18
       :prop="item.field"
19
-      :class="[item.className || '', `is-${item.formType}`]">
19
+      :class="[item.className || '', `is-${item.form_type}`]">
20 20
       <template slot="label">
21 21
         {{ item.name }}
22 22
         <span style="color:#999;">
@@ -24,15 +24,15 @@
24 24
         </span>
25 25
       </template>
26 26
       <el-input
27
-        v-if="item.formType == 'text'"
27
+        v-if="item.form_type == 'text'"
28 28
         v-model="fieldFrom[item.field]"
29 29
         :disabled="item.disabled"
30 30
         :maxlength="100"
31 31
         :placeholder="item.placeholder"
32
-        :type="item.formType"
32
+        :type="item.form_type"
33 33
         @input="commonChange(item, index, $event)"/>
34 34
       <el-input
35
-        v-if="isTrimInput(item.formType)"
35
+        v-if="isTrimInput(item.form_type)"
36 36
         v-model.trim="fieldFrom[item.field]"
37 37
         :disabled="item.disabled"
38 38
         :maxlength="100"
@@ -40,37 +40,37 @@
40 40
         type="text"
41 41
         @input="commonChange(item, index, $event)"/>
42 42
       <el-input-number
43
-        v-else-if="item.formType == 'number'"
43
+        v-else-if="item.form_type == 'number'"
44 44
         v-model="fieldFrom[item.field]"
45 45
         :placeholder="item.placeholder"
46 46
         :disabled="item.disabled"
47 47
         :controls="false"
48 48
         @input="commonChange(item, index, $event)" />
49 49
       <el-input-number
50
-        v-else-if="item.formType == 'floatnumber'"
50
+        v-else-if="item.form_type == 'floatnumber'"
51 51
         v-model="fieldFrom[item.field]"
52 52
         :placeholder="item.placeholder"
53 53
         :disabled="item.disabled"
54 54
         :controls="false"
55 55
         @change="commonChange(item, index, $event)" />
56 56
       <el-input
57
-        v-else-if="item.formType == 'textarea'"
57
+        v-else-if="item.form_type == 'textarea'"
58 58
         v-model="fieldFrom[item.field]"
59 59
         :disabled="item.disabled"
60 60
         :rows="3"
61 61
         :autosize="{ minRows: 3}"
62 62
         :maxlength="800"
63 63
         :placeholder="item.placeholder"
64
-        :type="item.formType"
64
+        :type="item.form_type"
65 65
         resize="none"
66 66
         @input="commonChange(item, index, $event)" />
67 67
       <el-select
68
-        v-else-if="['checkbox', 'select'].includes(item.formType)"
68
+        v-else-if="['checkbox', 'select'].includes(item.form_type)"
69 69
         v-model="fieldFrom[item.field]"
70 70
         :disabled="item.disabled"
71 71
         :clearable="item.clearable"
72 72
         :placeholder="item.placeholder"
73
-        :multiple="item.formType === 'checkbox'"
73
+        :multiple="item.form_type === 'checkbox'"
74 74
         style="width: 100%;"
75 75
         @change="commonChange(item, index, $event)">
76 76
         <el-option
@@ -80,7 +80,7 @@
80 80
           :value="!isEmptyValue(item.value) ? item.value : item"/>
81 81
       </el-select>
82 82
       <el-select
83
-        v-else-if="item.formType == 'checkbox'"
83
+        v-else-if="item.form_type == 'checkbox'"
84 84
         v-model="fieldFrom[item.field]"
85 85
         :disabled="item.disabled"
86 86
         :clearable="item.clearable"
@@ -95,7 +95,7 @@
95 95
           :value="!isEmptyValue(item.value) ? item.value : item"/>
96 96
       </el-select>
97 97
       <el-date-picker
98
-        v-else-if="item.formType == 'date'"
98
+        v-else-if="item.form_type == 'date'"
99 99
         v-model="fieldFrom[item.field]"
100 100
         :disabled="item.disabled"
101 101
         clearable
@@ -105,7 +105,7 @@
105 105
         placeholder="选择日期"
106 106
         @change="commonChange(item, index, $event)"/>
107 107
       <el-date-picker
108
-        v-else-if="item.formType == 'dateRange'"
108
+        v-else-if="item.form_type == 'dateRange'"
109 109
         v-model="fieldFrom[item.field]"
110 110
         :disabled="item.disabled"
111 111
         :type="item.dateType || 'daterange'"
@@ -116,7 +116,7 @@
116 116
         end-placeholder="结束日期"
117 117
         @change="commonChange(item, index, $event)"/>
118 118
       <el-date-picker
119
-        v-else-if="item.formType == 'datetime'"
119
+        v-else-if="item.form_type == 'datetime'"
120 120
         v-model="fieldFrom[item.field]"
121 121
         :disabled="item.disabled"
122 122
         clearable
@@ -126,7 +126,7 @@
126 126
         placeholder="选择日期"
127 127
         @change="commonChange(item, index, $event)"/>
128 128
       <wk-dep-select
129
-        v-else-if="item.formType == 'structure'"
129
+        v-else-if="item.form_type == 'structure'"
130 130
         v-model="fieldFrom[item.field]"
131 131
         :request="item.request"
132 132
         :props="item.props"
@@ -137,7 +137,7 @@
137 137
         @change="depOrUserChange(item, index, arguments[0], arguments[1])"
138 138
       />
139 139
       <wk-user-select
140
-        v-else-if="['single_user', 'user'].includes(item.formType)"
140
+        v-else-if="['single_user', 'user'].includes(item.form_type)"
141 141
         v-model="fieldFrom[item.field]"
142 142
         :request="item.request"
143 143
         :props="item.props"
@@ -148,7 +148,7 @@
148 148
         @change="depOrUserChange(item, index, arguments[0], arguments[1])"
149 149
       />
150 150
       <el-radio-group
151
-        v-else-if="item.formType == 'radio'"
151
+        v-else-if="item.form_type == 'radio'"
152 152
         v-model="fieldFrom[item.field]"
153 153
         :disabled="item.disabled"
154 154
         :placeholder="item.placeholder"
@@ -161,7 +161,7 @@
161 161
         </el-radio>
162 162
       </el-radio-group>
163 163
       <v-distpicker
164
-        v-if="item.formType == 'address'"
164
+        v-if="item.form_type == 'address'"
165 165
         :province="fieldFrom[item.field].province"
166 166
         :city="fieldFrom[item.field].city"
167 167
         :area="fieldFrom[item.field].area"
@@ -169,7 +169,7 @@
169 169
         @city="selectCity($event, item, index)"
170 170
         @area="selectArea($event, item, index)"/>
171 171
       <xh-files
172
-        v-if="item.formType == 'file'"
172
+        v-if="item.form_type == 'file'"
173 173
         :value="fieldFrom[item.field]"
174 174
         :disabled="item.disabled"
175 175
         @value-change="oldChange($event, item, index)"

+ 62
- 0
src/components/NewCom/WkForm/utils.js Wyświetl plik

@@ -0,0 +1,62 @@
1
+import { isArray, isObject, isEmpty } from '@/utils/types'
2
+import CheckStatusMixin from '@/mixins/CheckStatusMixin'
3
+import CustomFieldsMixin from '@/mixins/CustomFields'
4
+import { separator } from '@/filters/vueNumeralFilter/filters'
5
+import { getWkDateTime } from '@/utils'
6
+
7
+/**
8
+ * 获取自定义字段展示值
9
+ * @param {*} form_type
10
+ * @param {*} value
11
+ * @param {*} placeholder
12
+ * @param {*} item 自定义字段模型
13
+ * @returns 字符串
14
+ */
15
+export function getFormFieldShowName(form_type, value, placeholder = '--', item) {
16
+  if (form_type === 'position') {
17
+    return isArray(value) ? value.map(item => item.name).join() : placeholder
18
+  } else if (form_type === 'floatnumber') {
19
+    return isEmpty(value) ? '' : separator(value)
20
+  } else if (form_type === 'date') {
21
+    return getWkDateTime(value)
22
+  } else if (form_type === 'location') {
23
+    return isObject(value) ? value.address : placeholder
24
+  } else if (form_type === 'date_interval') {
25
+    return isArray(value) ? value.join('-') : placeholder
26
+  } else if (form_type === 'percent') {
27
+    return isEmpty(value) ? placeholder : `${value}%`
28
+  } else if (form_type === 'single_user') {
29
+    if (isObject(value)) {
30
+      return value.realname || placeholder
31
+    }
32
+    return value || placeholder
33
+  } else if (form_type === 'select') {
34
+    const newValue = CustomFieldsMixin.methods.getRealParams({ form_type }, value)
35
+    if (isEmpty(newValue)) {
36
+      return placeholder
37
+    } else {
38
+      return newValue
39
+    }
40
+  } else if (form_type === 'checkbox') {
41
+    const newValue = CustomFieldsMixin.methods.getRealParams({ form_type }, value)
42
+    if (isEmpty(newValue)) {
43
+      return placeholder
44
+    } else {
45
+      return newValue
46
+    }
47
+  } else if (form_type === 'structure') {
48
+    if (isArray(value)) {
49
+      return value.map(item => item.name).join() || placeholder
50
+    }
51
+    return value || placeholder
52
+  } else if (form_type === 'user') {
53
+    if (isArray(value)) {
54
+      return value.map(item => item.realname).join() || placeholder
55
+    }
56
+    return value || placeholder
57
+  } else if (form_type === 'check_status') {
58
+    return CheckStatusMixin.methods.getStatusName(value)
59
+  }
60
+
61
+  return isEmpty(value) ? placeholder : value
62
+}

+ 133
- 0
src/components/NewCom/WkLocation/index.vue Wyświetl plik

@@ -0,0 +1,133 @@
1
+<template>
2
+  <div
3
+    class="wk-location"
4
+    @mouseenter="hovering = true"
5
+    @mouseleave="hovering = false">
6
+    <el-input
7
+      v-model="dataValue.address"
8
+      :disabled="disabled"
9
+      readonly
10
+      clearable
11
+      placeholder="点击定位"
12
+      @click.native="inputClick"
13
+      @clear="inputClear">
14
+      <template slot="suffix">
15
+        <i
16
+          v-if="dataValue.address && hovering"
17
+          class="el-icon-circle-close"
18
+          style="cursor: pointer;"
19
+          @click.stop="inputClear" />
20
+        <i v-else class="wk wk-icon-location" />
21
+      </template>
22
+    </el-input>
23
+
24
+    <wk-location-point-dialog
25
+      :value="dataValue"
26
+      :visible.sync="pointDialogVisible"
27
+      @select="pointSelect"
28
+    />
29
+  </div>
30
+</template>
31
+
32
+<script>
33
+import WkLocationPointDialog from '../WkLocationPointDialog'
34
+
35
+import { isObject } from '@/utils/types'
36
+import { valueEquals } from 'element-ui/lib/utils/util'
37
+import Emitter from 'element-ui/lib/mixins/emitter'
38
+
39
+export default {
40
+  // 定位
41
+  name: 'WkLocation',
42
+
43
+  components: {
44
+    WkLocationPointDialog
45
+  },
46
+
47
+  mixins: [Emitter],
48
+
49
+  props: {
50
+    // eslint-disable-next-line vue/require-prop-types
51
+    value: {
52
+      required: true
53
+    },
54
+    disabled: Boolean
55
+  },
56
+
57
+  data() {
58
+    return {
59
+      hovering: false,
60
+      dataValue: this.getDefaultValue(),
61
+      pointDialogVisible: false
62
+    }
63
+  },
64
+
65
+  computed: {},
66
+
67
+  watch: {
68
+    value: {
69
+      handler(val) {
70
+        if (!valueEquals(val, this.dataValue)) {
71
+          if (isObject(this.value)) {
72
+            this.dataValue = val
73
+          } else {
74
+            this.dataValue = this.getDefaultValue()
75
+            this.$emit('input', this.dataValue)
76
+          }
77
+        }
78
+      },
79
+      immediate: true
80
+    }
81
+  },
82
+
83
+  created() {
84
+  },
85
+
86
+  mounted() {},
87
+
88
+  beforeDestroy() {},
89
+
90
+  methods: {
91
+    getDefaultValue() {
92
+      return {
93
+        lat: '',
94
+        lng: '',
95
+        address: ''
96
+      }
97
+    },
98
+
99
+    inputClick() {
100
+      if (!this.disabled) {
101
+        this.pointDialogVisible = true
102
+      }
103
+    },
104
+
105
+    inputClear() {
106
+      this.dataValue = this.getDefaultValue()
107
+      this.valueChange()
108
+    },
109
+
110
+    pointSelect(data) {
111
+      if (data) {
112
+        this.dataValue = {
113
+          lat: data.point.lat,
114
+          lng: data.point.lng,
115
+          address: data.address + data.title
116
+        }
117
+      }
118
+
119
+      this.valueChange()
120
+    },
121
+
122
+    valueChange() {
123
+      this.$emit('input', this.dataValue)
124
+      this.$emit('change', this.dataValue)
125
+      this.dispatch('ElFormItem', 'el.form.change', this.dataValue)
126
+    }
127
+  }
128
+}
129
+</script>
130
+
131
+<style lang="scss" scoped>
132
+
133
+</style>

+ 210
- 0
src/components/NewCom/WkLocationPointDialog/index.vue Wyświetl plik

@@ -0,0 +1,210 @@
1
+<template>
2
+  <el-dialog
3
+    :visible="visible"
4
+    :close-on-click-modal="false"
5
+    append-to-body
6
+    title="选择位置"
7
+    width="500px"
8
+    @close="close">
9
+    <flexbox align="stretch">
10
+      <flexbox-item>
11
+        <div class="area-title">定位</div>
12
+        <el-autocomplete
13
+          v-model="searchInput"
14
+          :fetch-suggestions="querySearchAsync"
15
+          style="width: 100%;"
16
+          placeholder="请输入详细位置名称"
17
+          @blur="inputBlur"
18
+          @focus="inputFocus"
19
+          @select="handleSelect">
20
+          <template slot-scope="{ item }">
21
+            <div class="name">{{ item.address + item.title }}</div>
22
+          </template>
23
+        </el-autocomplete>
24
+        <div
25
+          ref="myMap"
26
+          class="map"/>
27
+      </flexbox-item>
28
+    </flexbox>
29
+    <span slot="footer" class="dialog-footer">
30
+      <el-button @click="close">取 消</el-button>
31
+      <el-button type="primary" @click="selectSure">确 定</el-button>
32
+    </span>
33
+  </el-dialog>
34
+</template>
35
+<script type="text/javascript">
36
+import VDistpicker from '@/components/VDistpicker'
37
+import { getBaiduMap } from '@/utils'
38
+import { isEmpty } from '@/utils/types'
39
+
40
+export default {
41
+  name: 'WkLocationPointDialog',
42
+  components: {
43
+    VDistpicker
44
+  },
45
+  props: {
46
+    visible: {
47
+      type: Boolean,
48
+      default: false
49
+    },
50
+    value: {
51
+      type: Object,
52
+      default: () => {
53
+        return {}
54
+      }
55
+    }
56
+  },
57
+  data() {
58
+    return {
59
+      map: null,
60
+      searchInput: '', // 搜索
61
+      searchCopyInput: '', // 避免修改
62
+      pointAddress: null // 经纬度点
63
+    }
64
+  },
65
+  computed: {},
66
+  watch: {
67
+    visible(val) {
68
+      if (val) {
69
+        this.$nextTick(() => {
70
+          getBaiduMap()
71
+            .then(() => {
72
+              const map = new BMap.Map(this.$refs.myMap, { enableMapClick: true })
73
+              let point = new BMap.Point(116.404, 39.915)
74
+              if (this.value) {
75
+                if (!isEmpty(this.value.lat) && !isEmpty(this.value.lng)) {
76
+                  point = new BMap.Point(this.value.lng, this.value.lat)
77
+                }
78
+
79
+                if (!isEmpty(this.value.address)) {
80
+                  this.searchInput = this.value.address
81
+                  this.searchCopyInput = this.searchInput
82
+                }
83
+              }
84
+
85
+              if (!this.value || (
86
+                this.value && isEmpty(this.value.lat) && isEmpty(this.value.lng) &&
87
+                 isEmpty(this.value.address)
88
+              )) {
89
+                const self = this
90
+                // 定位逻辑
91
+                var geolocation = new BMap.Geolocation()
92
+                geolocation.getCurrentPosition(function(r) {
93
+                  if (this.getStatus() == BMAP_STATUS_SUCCESS) {
94
+                    var myGeo = new BMap.Geocoder({ extensions_town: true })
95
+                    // 根据坐标得到地址描述
96
+                    myGeo.getLocation(r.point, function(result) {
97
+                      if (result) {
98
+                        self.searchInput = result.address
99
+                        self.searchCopyInput = result.address
100
+                        self.addMarkerLabel(result.point)
101
+                        result.title = ''
102
+                        self.pointAddress = result
103
+                      }
104
+                    })
105
+                  }
106
+                })
107
+              }
108
+              map.centerAndZoom(point, 14)
109
+              map.enableScrollWheelZoom()
110
+              this.map = map
111
+              this.addMarkerLabel(point)
112
+            })
113
+        })
114
+      }
115
+    }
116
+  },
117
+  mounted() {
118
+
119
+  },
120
+  methods: {
121
+    querySearchAsync(queryString, cb) {
122
+      if (queryString) {
123
+        var options = {
124
+          onSearchComplete: function(results) {
125
+            if (local.getStatus() == BMAP_STATUS_SUCCESS) {
126
+              var address = []
127
+              for (var i = 0; i < results.getCurrentNumPois(); i++) {
128
+                address.push(results.getPoi(i))
129
+              }
130
+              cb(address)
131
+            } else {
132
+              cb([])
133
+            }
134
+          },
135
+          pageCapacity: 20
136
+        }
137
+        var local = new BMap.LocalSearch(this.map, options)
138
+        local.search(queryString)
139
+      } else {
140
+        cb([])
141
+      }
142
+    },
143
+    /**
144
+     * 搜索结果选择
145
+     **/
146
+    handleSelect(item) {
147
+      this.searchInput = item.address + item.title
148
+      this.searchCopyInput = this.searchInput // 只能通过这种方式修改
149
+
150
+      this.addMarkerLabel(item.point)
151
+      this.pointAddress = item
152
+    },
153
+    /**
154
+     * Input 失去焦点  searchInput 只能通过选择更改
155
+     **/
156
+    inputBlur() {
157
+      if (this.searchCopyInput !== this.searchInput) {
158
+        this.searchInput = this.searchCopyInput
159
+      }
160
+    },
161
+    inputFocus() {
162
+      this.searchCopyInput = this.searchInput
163
+    },
164
+    /**
165
+     * 创建标注
166
+     */
167
+    addMarkerLabel(point) {
168
+      this.map.clearOverlays()
169
+      this.map.centerAndZoom(point, 14)
170
+      this.map.addOverlay(new BMap.Marker(point))
171
+    },
172
+    /**
173
+     * 关闭
174
+     */
175
+    close() {
176
+      this.$emit('update:visible', false)
177
+    },
178
+    /**
179
+     * 确定选择
180
+     */
181
+    selectSure() {
182
+      this.$emit('select', this.pointAddress)
183
+      this.close()
184
+    }
185
+  }
186
+}
187
+</script>
188
+<style rel="stylesheet/scss" lang="scss" scoped>
189
+.map {
190
+  height: 150px;
191
+  width: 100%;
192
+  overflow: hidden;
193
+  margin-top: 5px;
194
+}
195
+
196
+.area-title {
197
+  font-size: 12px;
198
+  color: #aaa;
199
+  padding-bottom: 10px;
200
+}
201
+
202
+.distpicker-address-wrapper /deep/ select {
203
+  height: 34px;
204
+  font-size: 12px;
205
+  border-radius: 0.1rem;
206
+}
207
+/deep/ .el-dialog__body {
208
+  padding: 10px 20px 20px;
209
+}
210
+</style>

+ 281
- 0
src/components/NewCom/WkPercentInput/index.vue Wyświetl plik

@@ -0,0 +1,281 @@
1
+<template>
2
+  <div
3
+    :class="[
4
+      'el-input-number',
5
+      inputNumberSize ? 'el-input-number--' + inputNumberSize : '',
6
+      { 'is-disabled': inputNumberDisabled },
7
+      { 'is-without-controls': !controls },
8
+      { 'is-controls-right': controlsAtRight }
9
+    ]"
10
+    @dragstart.prevent>
11
+    <span
12
+      v-repeat-click="decrease"
13
+      v-if="controls"
14
+      :class="{'is-disabled': minDisabled}"
15
+      class="el-input-number__decrease"
16
+      role="button"
17
+      @keydown.enter="decrease">
18
+      <i :class="`el-icon-${controlsAtRight ? 'arrow-down' : 'minus'}`"/>
19
+    </span>
20
+    <span
21
+      v-repeat-click="increase"
22
+      v-if="controls"
23
+      :class="{'is-disabled': maxDisabled}"
24
+      class="el-input-number__increase"
25
+      role="button"
26
+      @keydown.enter="increase">
27
+      <i :class="`el-icon-${controlsAtRight ? 'arrow-up' : 'plus'}`"/>
28
+    </span>
29
+    <el-input
30
+      ref="input"
31
+      :value="displayValue"
32
+      :placeholder="placeholder"
33
+      :disabled="inputNumberDisabled"
34
+      :size="inputNumberSize"
35
+      :max="max"
36
+      :min="min"
37
+      :name="name"
38
+      :label="label"
39
+      @keydown.up.native.prevent="increase"
40
+      @keydown.down.native.prevent="decrease"
41
+      @blur="handleBlur"
42
+      @focus="handleFocus"
43
+      @input="handleInput"
44
+      @change="handleInputChange">
45
+      <template slot="suffix">%</template>
46
+    </el-input>
47
+  </div>
48
+</template>
49
+<script>
50
+import Focus from 'element-ui/src/mixins/focus'
51
+import RepeatClick from 'element-ui/src/directives/repeat-click'
52
+
53
+export default {
54
+  name: 'WkPercentInput',
55
+  directives: {
56
+    RepeatClick
57
+  },
58
+  mixins: [Focus('input')],
59
+  inject: {
60
+    elForm: {
61
+      default: ''
62
+    },
63
+    elFormItem: {
64
+      default: ''
65
+    }
66
+  },
67
+  props: {
68
+    step: {
69
+      type: Number,
70
+      default: 1
71
+    },
72
+    stepStrictly: {
73
+      type: Boolean,
74
+      default: false
75
+    },
76
+    max: {
77
+      type: Number,
78
+      default: Infinity
79
+    },
80
+    min: {
81
+      type: Number,
82
+      default: -Infinity
83
+    },
84
+    // eslint-disable-next-line vue/require-prop-types
85
+    value: {},
86
+    disabled: Boolean,
87
+    size: String,
88
+    controls: {
89
+      type: Boolean,
90
+      default: true
91
+    },
92
+    controlsPosition: {
93
+      type: String,
94
+      default: ''
95
+    },
96
+    name: String,
97
+    label: String,
98
+    placeholder: String,
99
+    precision: {
100
+      type: Number,
101
+      validator(val) {
102
+        return val >= 0 && val === parseInt(val, 10)
103
+      }
104
+    }
105
+  },
106
+  data() {
107
+    return {
108
+      currentValue: 0,
109
+      userInput: null
110
+    }
111
+  },
112
+  computed: {
113
+    minDisabled() {
114
+      return this._decrease(this.value, this.step) < this.min
115
+    },
116
+    maxDisabled() {
117
+      return this._increase(this.value, this.step) > this.max
118
+    },
119
+    numPrecision() {
120
+      const { value, step, getPrecision, precision } = this
121
+      const stepPrecision = getPrecision(step)
122
+      if (precision !== undefined) {
123
+        if (stepPrecision > precision) {
124
+          console.warn('[Element Warn][InputNumber]precision should not be less than the decimal places of step')
125
+        }
126
+        return precision
127
+      } else {
128
+        return Math.max(getPrecision(value), stepPrecision)
129
+      }
130
+    },
131
+    controlsAtRight() {
132
+      return this.controls && this.controlsPosition === 'right'
133
+    },
134
+    _elFormItemSize() {
135
+      return (this.elFormItem || {}).elFormItemSize
136
+    },
137
+    inputNumberSize() {
138
+      return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size
139
+    },
140
+    inputNumberDisabled() {
141
+      return this.disabled || !!(this.elForm || {}).disabled
142
+    },
143
+    displayValue() {
144
+      if (this.userInput !== null) {
145
+        return this.userInput
146
+      }
147
+
148
+      let currentValue = this.currentValue
149
+
150
+      if (typeof currentValue === 'number') {
151
+        if (this.stepStrictly) {
152
+          const stepPrecision = this.getPrecision(this.step)
153
+          const precisionFactor = Math.pow(10, stepPrecision)
154
+          currentValue = Math.round(currentValue / this.step) * precisionFactor * this.step / precisionFactor
155
+        }
156
+
157
+        if (this.precision !== undefined) {
158
+          currentValue = currentValue.toFixed(this.precision)
159
+        }
160
+      }
161
+
162
+      return currentValue
163
+    }
164
+  },
165
+  watch: {
166
+    value: {
167
+      immediate: true,
168
+      handler(value) {
169
+        let newVal = value === undefined ? value : Number(value)
170
+        if (newVal !== undefined) {
171
+          if (isNaN(newVal)) {
172
+            return
173
+          }
174
+
175
+          if (this.stepStrictly) {
176
+            const stepPrecision = this.getPrecision(this.step)
177
+            const precisionFactor = Math.pow(10, stepPrecision)
178
+            newVal = Math.round(newVal / this.step) * precisionFactor * this.step / precisionFactor
179
+          }
180
+
181
+          if (this.precision !== undefined) {
182
+            newVal = this.toPrecision(newVal, this.precision)
183
+          }
184
+        }
185
+        if (newVal >= this.max) newVal = this.max
186
+        if (newVal <= this.min) newVal = this.min
187
+        this.currentValue = newVal
188
+        this.userInput = null
189
+        this.$emit('input', newVal)
190
+      }
191
+    }
192
+  },
193
+  mounted() {
194
+    const innerInput = this.$refs.input.$refs.input
195
+    innerInput.setAttribute('role', 'spinbutton')
196
+    innerInput.setAttribute('aria-valuemax', this.max)
197
+    innerInput.setAttribute('aria-valuemin', this.min)
198
+    innerInput.setAttribute('aria-valuenow', this.currentValue)
199
+    innerInput.setAttribute('aria-disabled', this.inputNumberDisabled)
200
+  },
201
+  updated() {
202
+    if (!this.$refs || !this.$refs.input) return
203
+    const innerInput = this.$refs.input.$refs.input
204
+    innerInput.setAttribute('aria-valuenow', this.currentValue)
205
+  },
206
+  methods: {
207
+    toPrecision(num, precision) {
208
+      if (precision === undefined) precision = this.numPrecision
209
+      return parseFloat(Math.round(num * Math.pow(10, precision)) / Math.pow(10, precision))
210
+    },
211
+    getPrecision(value) {
212
+      if (value === undefined) return 0
213
+      const valueString = value.toString()
214
+      const dotPosition = valueString.indexOf('.')
215
+      let precision = 0
216
+      if (dotPosition !== -1) {
217
+        precision = valueString.length - dotPosition - 1
218
+      }
219
+      return precision
220
+    },
221
+    _increase(val, step) {
222
+      if (typeof val !== 'number' && val !== undefined) return this.currentValue
223
+
224
+      const precisionFactor = Math.pow(10, this.numPrecision)
225
+      // Solve the accuracy problem of JS decimal calculation by converting the value to integer.
226
+      return this.toPrecision((precisionFactor * val + precisionFactor * step) / precisionFactor)
227
+    },
228
+    _decrease(val, step) {
229
+      if (typeof val !== 'number' && val !== undefined) return this.currentValue
230
+
231
+      const precisionFactor = Math.pow(10, this.numPrecision)
232
+
233
+      return this.toPrecision((precisionFactor * val - precisionFactor * step) / precisionFactor)
234
+    },
235
+    increase() {
236
+      if (this.inputNumberDisabled || this.maxDisabled) return
237
+      const value = this.value || 0
238
+      const newVal = this._increase(value, this.step)
239
+      this.setCurrentValue(newVal)
240
+    },
241
+    decrease() {
242
+      if (this.inputNumberDisabled || this.minDisabled) return
243
+      const value = this.value || 0
244
+      const newVal = this._decrease(value, this.step)
245
+      this.setCurrentValue(newVal)
246
+    },
247
+    handleBlur(event) {
248
+      this.$emit('blur', event)
249
+    },
250
+    handleFocus(event) {
251
+      this.$emit('focus', event)
252
+    },
253
+    setCurrentValue(newVal) {
254
+      const oldVal = this.currentValue
255
+      if (typeof newVal === 'number' && this.precision !== undefined) {
256
+        newVal = this.toPrecision(newVal, this.precision)
257
+      }
258
+      if (newVal >= this.max) newVal = this.max
259
+      if (newVal <= this.min) newVal = this.min
260
+      if (oldVal === newVal) return
261
+      this.userInput = null
262
+      this.$emit('input', newVal)
263
+      this.$emit('change', newVal, oldVal)
264
+      this.currentValue = newVal
265
+    },
266
+    handleInput(value) {
267
+      this.userInput = value
268
+    },
269
+    handleInputChange(value) {
270
+      const newVal = value === '' ? undefined : Number(value)
271
+      if (!isNaN(newVal) || value === '') {
272
+        this.setCurrentValue(newVal)
273
+      }
274
+      this.userInput = null
275
+    },
276
+    select() {
277
+      this.$refs.input.select()
278
+    }
279
+  }
280
+}
281
+</script>

+ 139
- 0
src/components/NewCom/WkPosition/index.vue Wyświetl plik

@@ -0,0 +1,139 @@
1
+<template>
2
+  <div class="wk-position">
3
+    <wk-distpicker
4
+      :hide-area="hideArea"
5
+      :only-province="onlyProvince"
6
+      :disabled="disabled"
7
+      v-bind="$attrs"
8
+      v-model="distpickerValue"
9
+      clearable
10
+      @change="valueChange"/>
11
+    <el-input
12
+      v-if="showDetail"
13
+      :rows="2"
14
+      :disabled="disabled"
15
+      v-model="detailAddress"
16
+      :maxlength="100"
17
+      type="textarea"
18
+      placeholder="详细地址"
19
+      @change="valueChange"/>
20
+  </div>
21
+</template>
22
+
23
+<script>
24
+import WkDistpicker from '../WkDistpicker'
25
+
26
+import { isArray, isEmpty } from '@/utils/types'
27
+import Emitter from 'element-ui/lib/mixins/emitter'
28
+
29
+export default {
30
+  // 地址
31
+  name: 'WkPosition',
32
+
33
+  components: {
34
+    WkDistpicker
35
+  },
36
+
37
+  mixins: [Emitter],
38
+
39
+  inheritAttrs: false,
40
+
41
+  props: {
42
+    hideArea: { type: Boolean, default: false },
43
+    onlyProvince: { type: Boolean, default: false },
44
+    showDetail: { type: Boolean, default: true },
45
+    // eslint-disable-next-line vue/require-prop-types
46
+    value: {
47
+      required: true
48
+    },
49
+    disabled: Boolean
50
+  },
51
+
52
+  data() {
53
+    return {
54
+      distpickerValue: [],
55
+      detailAddress: '',
56
+      dataValue: []
57
+    }
58
+  },
59
+
60
+  computed: {},
61
+
62
+  watch: {
63
+    value: {
64
+      handler(val) {
65
+        if (!this.valueEquals(val, this.dataValue)) {
66
+          this.getDefaultValue()
67
+        }
68
+      },
69
+      immediate: true
70
+    }
71
+  },
72
+
73
+  created() {},
74
+
75
+  mounted() {},
76
+
77
+  beforeDestroy() {},
78
+
79
+  methods: {
80
+    getDefaultValue() {
81
+      const value = isArray(this.value) ? this.value : []
82
+
83
+      const distpickerValue = []
84
+      let hasAddress = false
85
+      value.forEach(item => {
86
+        if (item.id === 4) {
87
+          hasAddress = true
88
+          this.detailAddress = item.name
89
+        } else {
90
+          distpickerValue.push(item)
91
+        }
92
+      })
93
+
94
+      if (!hasAddress) {
95
+        this.detailAddress = ''
96
+      }
97
+
98
+      this.distpickerValue = distpickerValue
99
+    },
100
+
101
+    /**
102
+     * 值更新
103
+     */
104
+    valueChange() {
105
+      const dataValue = this.showDetail && isEmpty(this.detailAddress) ? this.distpickerValue : this.distpickerValue.concat([{
106
+        code: '',
107
+        name: this.detailAddress,
108
+        id: 4
109
+      }])
110
+      this.dataValue = dataValue
111
+
112
+      this.$emit('input', this.dataValue)
113
+      this.$emit('change', this.dataValue)
114
+      this.dispatch('ElFormItem', 'el.form.change', this.dataValue)
115
+    },
116
+
117
+    valueEquals(a, b) {
118
+      if (a === b) return true
119
+      if (!(a instanceof Array)) return false
120
+      if (!(b instanceof Array)) return false
121
+      if (a.length !== b.length) return false
122
+      for (let i = 0; i !== a.length; ++i) {
123
+        const aValue = a[i]
124
+        const bValue = b[i]
125
+        if (aValue.id !== bValue.id || aValue.name !== bValue.name) return false
126
+      }
127
+      return true
128
+    }
129
+  }
130
+}
131
+</script>
132
+
133
+<style lang="scss" scoped>
134
+.wk-position {
135
+  .el-textarea {
136
+    margin-top: 15px;
137
+  }
138
+}
139
+</style>

+ 205
- 0
src/components/NewCom/WkSelect/index.vue Wyświetl plik

@@ -0,0 +1,205 @@
1
+<template>
2
+  <div class="wk-select">
3
+    <template v-if="valueIsObject">
4
+      <el-select
5
+        v-if="showType === 'default'"
6
+        v-model="dataValue.select"
7
+        :disabled="disabled"
8
+        :clearable="clearable"
9
+        :placeholder="placeholder"
10
+        style="width: 100%;"
11
+        @change="valueChange">
12
+        <el-option
13
+          v-for="(item, index) in options"
14
+          :key="index"
15
+          :label="!isEmptyValue(item.value) ? item.label || item.name : item "
16
+          :value="getValue(item)"/>
17
+      </el-select>
18
+      <el-radio-group
19
+        v-else-if="showType === 'tiled'"
20
+        v-model="dataValue.select"
21
+        @change="valueChange">
22
+        <el-radio
23
+          v-for="(item, index) in options"
24
+          :key="index"
25
+          :label="getValue(item)">
26
+          {{ !isEmptyValue(item.value) ? item.label || item.name : item }}
27
+        </el-radio>
28
+      </el-radio-group>
29
+      <el-input
30
+        v-if="dataValue.select === '其他' && otherShowInput"
31
+        v-model="dataValue.otherValue"
32
+        :disabled="disabled"
33
+        :maxlength="100"
34
+        placeholder="其他选项需填写,否则为无效选项"
35
+        @blur="inputBlur"/>
36
+    </template>
37
+  </div>
38
+</template>
39
+
40
+<script>
41
+import { isObject, isEmpty } from '@/utils/types'
42
+import Emitter from 'element-ui/lib/mixins/emitter'
43
+
44
+export default {
45
+  // 自定义字段库 单选
46
+  name: 'WkSelect',
47
+
48
+  components: {},
49
+
50
+  mixins: [Emitter],
51
+
52
+  props: {
53
+    value: [String, Number],
54
+    // 选择其他展示input输入框
55
+    otherShowInput: {
56
+      type: Boolean,
57
+      default: false
58
+    },
59
+    disabled: Boolean,
60
+    clearable: Boolean,
61
+    placeholder: String,
62
+    options: {
63
+      type: Array,
64
+      default: () => {
65
+        return []
66
+      }
67
+    },
68
+    showType: {
69
+      type: String,
70
+      default: 'default' //  下拉 default 平铺 tiled
71
+    }
72
+  },
73
+
74
+  data() {
75
+    return {
76
+      dataValue: {
77
+        select: '',
78
+        otherValue: ''
79
+      }
80
+    }
81
+  },
82
+
83
+  computed: {
84
+    valueIsObject() {
85
+      return isObject(this.dataValue)
86
+    }
87
+  },
88
+
89
+  watch: {
90
+    value: {
91
+      handler(newVal, oldVal) {
92
+        this.validValue()
93
+      },
94
+      immediate: true
95
+    }
96
+  },
97
+
98
+  created() {
99
+    // this.validValue()
100
+  },
101
+
102
+  mounted() {},
103
+
104
+  beforeDestroy() {},
105
+
106
+  methods: {
107
+    /**
108
+     * 验证值
109
+     */
110
+    validValue() {
111
+      if (this.value !== this.dataValue.select && this.value !== this.dataValue.otherValue) {
112
+        if (isEmpty(this.value)) {
113
+          this.dataValue = {
114
+            select: '',
115
+            otherValue: ''
116
+          }
117
+        } else {
118
+          if (this.otherShowInput) {
119
+            const valueObj = this.options.find(item => this.getValue(item) === this.value)
120
+            if (valueObj) {
121
+              if (this.dataValue.select !== this.value) {
122
+                this.dataValue = {
123
+                  select: this.value,
124
+                  otherValue: ''
125
+                }
126
+              }
127
+            } else {
128
+              if (this.dataValue.otherValue !== this.value) {
129
+                this.dataValue = {
130
+                  select: '其他',
131
+                  otherValue: this.value
132
+                }
133
+              }
134
+            }
135
+          } else {
136
+            this.dataValue = {
137
+              select: this.value,
138
+              otherValue: ''
139
+            }
140
+          }
141
+        }
142
+      }
143
+    },
144
+
145
+    /**
146
+     * 选项值
147
+     */
148
+    getValue(item) {
149
+      return !this.isEmptyValue(item.value) ? item.value : item
150
+    },
151
+
152
+    /**
153
+     * 判断是空值
154
+     */
155
+    isEmptyValue(value) {
156
+      return value === null || value == undefined
157
+    },
158
+
159
+    /**
160
+     * 值更新
161
+     */
162
+    valueChange() {
163
+      const value = this.dataValue.select === '其他' ? this.dataValue.otherValue.trim() : this.dataValue.select
164
+      this.$emit('input', value)
165
+      this.$emit('change', value)
166
+      this.dispatch('ElFormItem', 'el.form.change', value)
167
+    },
168
+
169
+    /**
170
+     * 失去焦点
171
+     */
172
+    inputBlur() {
173
+      // 暂未加input触发change逻辑
174
+      const value = this.dataValue.otherValue
175
+      const eIsObject = this.options.length > 0 && !this.isEmptyValue(this.options[0].value)
176
+      const has = this.options.find(item => {
177
+        if (eIsObject) {
178
+          return item.value === value.trim()
179
+        } else {
180
+          return item === value.trim()
181
+        }
182
+      })
183
+      if (has) {
184
+        this.dataValue.otherValue = ''
185
+      }
186
+
187
+      this.$emit('input', this.dataValue.otherValue)
188
+      this.$emit('change', this.dataValue.otherValue)
189
+      this.dispatch('ElFormItem', 'el.form.change', this.dataValue.otherValue)
190
+    }
191
+  }
192
+}
193
+</script>
194
+
195
+<style lang="scss" scoped>
196
+.wk-select {
197
+  .el-input {
198
+    margin-top: 5px;
199
+  }
200
+
201
+  .el-radio {
202
+    margin: 5px 30px 5px 0;
203
+  }
204
+}
205
+</style>

+ 97
- 0
src/components/NewCom/WkSignaturePad/Image.vue Wyświetl plik

@@ -0,0 +1,97 @@
1
+<template>
2
+  <el-popover
3
+    :disabled="!hasContent"
4
+    placement="bottom"
5
+    width="200"
6
+    trigger="hover">
7
+    <el-image
8
+      :src="url"
9
+      :style="styleObj"
10
+      style="width: 100%; height: 100%"
11
+      fit="contain"/>
12
+    <div slot="reference">
13
+      <el-image
14
+        v-if="hasContent"
15
+        :src="url"
16
+        :style="{height: height, ...styleObj}"
17
+        style="width: 100%;"
18
+        fit="contain"/>
19
+      <template v-else>--</template>
20
+    </div>
21
+  </el-popover>
22
+</template>
23
+
24
+<script>
25
+// import { adminFileQueryOneByBatchIdAPI } from '@/api/admin/file'
26
+
27
+export default {
28
+  // WkSignatureImage
29
+  name: 'WkSignatureImage',
30
+
31
+  components: {},
32
+
33
+  props: {
34
+    height: {
35
+      type: String,
36
+      default: '100%'
37
+    },
38
+    src: String // batchId 交互
39
+  },
40
+
41
+  data() {
42
+    return {
43
+      url: '',
44
+      styleObj: {}
45
+    }
46
+  },
47
+
48
+  computed: {
49
+    hasContent() {
50
+      return !!this.url
51
+    }
52
+  },
53
+
54
+  watch: {
55
+    src(newVal, oldVal) {
56
+      if (newVal) {
57
+        this.getData()
58
+      } else {
59
+        this.url = ''
60
+      }
61
+    }
62
+  },
63
+
64
+  created() {
65
+    if (this.src) {
66
+      this.getData()
67
+    }
68
+  },
69
+
70
+  mounted() {},
71
+
72
+  beforeDestroy() {},
73
+
74
+  methods: {
75
+    getData() {
76
+      // adminFileQueryOneByBatchIdAPI(this.src).then(res => {
77
+      //   const resData = res.data
78
+      //   if (resData) {
79
+      //     const url = process.env.BASE_API + resData.url
80
+      //     if (this.url !== url) {
81
+      //       this.url = url
82
+      //     }
83
+      //   } else {
84
+      //     this.url = ''
85
+      //   }
86
+      // }).catch(() => {
87
+
88
+      // })
89
+      this.url = this.src
90
+    }
91
+  }
92
+}
93
+</script>
94
+
95
+<style lang="scss" scoped>
96
+
97
+</style>

+ 196
- 0
src/components/NewCom/WkSignaturePad/VueSignaturePad/index.vue Wyświetl plik

@@ -0,0 +1,196 @@
1
+<script>
2
+import SignaturePad from 'signature_pad'
3
+// import mergeImages from 'merge-images'
4
+import {
5
+  DEFAULT_OPTIONS,
6
+  TRANSPARENT_PNG,
7
+  IMAGE_TYPES,
8
+  checkSaveType,
9
+  convert2NonReactive
10
+} from '../utils/index'
11
+
12
+export default {
13
+  name: 'VueSignaturePad',
14
+  props: {
15
+    width: {
16
+      type: String,
17
+      default: '100%'
18
+    },
19
+    height: {
20
+      type: String,
21
+      default: '100%'
22
+    },
23
+    customStyle: {
24
+      type: Object,
25
+      default: () => ({})
26
+    },
27
+    options: {
28
+      type: Object,
29
+      default: () => ({})
30
+    },
31
+    images: {
32
+      type: Array,
33
+      default: () => []
34
+    }
35
+  },
36
+  data: () => ({
37
+    signaturePad: {},
38
+    cacheImages: [],
39
+    signatureData: TRANSPARENT_PNG,
40
+    onResizeHandler: null
41
+  }),
42
+  computed: {
43
+    propsImagesAndCustomImages() {
44
+      const nonReactiveProrpImages = convert2NonReactive(this.images)
45
+      const nonReactiveCachImages = convert2NonReactive(this.cacheImages)
46
+      return [...nonReactiveProrpImages, ...nonReactiveCachImages]
47
+    },
48
+    canvas() {
49
+      return this.$refs.signaturePadCanvas
50
+    }
51
+  },
52
+  watch: {
53
+    options: function(nextOptions) {
54
+      Object.keys(nextOptions).forEach(option => {
55
+        if (this.signaturePad[option]) {
56
+          this.signaturePad[option] = nextOptions[option]
57
+        }
58
+      })
59
+    },
60
+    height() {
61
+      this.resizeCanvas()
62
+    }
63
+  },
64
+  mounted() {
65
+    const { options } = this
66
+    const canvas = this.$refs.signaturePadCanvas
67
+    const signaturePad = new SignaturePad(canvas, {
68
+      ...DEFAULT_OPTIONS,
69
+      ...options
70
+    })
71
+    this.signaturePad = signaturePad
72
+    this.onResizeHandler = this.resizeCanvas.bind(this)
73
+    window.addEventListener('resize', this.onResizeHandler, false)
74
+    this.resizeCanvas()
75
+  },
76
+  beforeDestroy() {
77
+    if (this.onResizeHandler) {
78
+      window.removeEventListener('resize', this.onResizeHandler, false)
79
+    }
80
+  },
81
+  methods: {
82
+    resizeCanvas() {
83
+      const canvas = this.$refs.signaturePadCanvas
84
+      const data = this.signaturePad.toData()
85
+      const ratio = Math.max(window.devicePixelRatio || 1, 1)
86
+      canvas.width = canvas.offsetWidth * ratio
87
+      canvas.height = canvas.offsetHeight * ratio
88
+      canvas.getContext('2d').scale(ratio, ratio)
89
+      this.signaturePad.clear()
90
+      this.signatureData = TRANSPARENT_PNG
91
+      this.signaturePad.fromData(data)
92
+    },
93
+    saveSignature(type = IMAGE_TYPES[0], encoderOptions) {
94
+      const { signaturePad } = this
95
+      const status = { isEmpty: false, data: undefined }
96
+      if (!checkSaveType(type)) {
97
+        const imageTypesString = IMAGE_TYPES.join(', ')
98
+        throw new Error(
99
+          `The Image type is incorrect! We are support ${imageTypesString} types.`
100
+        )
101
+      }
102
+      if (signaturePad.isEmpty()) {
103
+        return {
104
+          ...status,
105
+          isEmpty: true
106
+        }
107
+      } else {
108
+        this.signatureData = signaturePad.toDataURL(type, encoderOptions)
109
+        return {
110
+          ...status,
111
+          data: this.signatureData
112
+        }
113
+      }
114
+    },
115
+    undoSignature() {
116
+      const { signaturePad } = this
117
+      const record = signaturePad.toData()
118
+      if (record) {
119
+        return signaturePad.fromData(record.slice(0, -1))
120
+      }
121
+    },
122
+    // mergeImageAndSignature(customSignature) {
123
+    //   this.signatureData = customSignature
124
+    //   return mergeImages([
125
+    //     ...this.images,
126
+    //     ...this.cacheImages,
127
+    //     this.signatureData
128
+    //   ])
129
+    // },
130
+    // addImages(images = []) {
131
+    //   this.cacheImages = [...this.cacheImages, ...images]
132
+    //   return mergeImages([
133
+    //     ...this.images,
134
+    //     ...this.cacheImages,
135
+    //     this.signatureData
136
+    //   ])
137
+    // },
138
+    fromDataURL(data, options = {}, callback) {
139
+      return this.signaturePad.fromDataURL(data, options, callback)
140
+    },
141
+    fromData(data) {
142
+      return this.signaturePad.fromData(data)
143
+    },
144
+    toData() {
145
+      return this.signaturePad.toData()
146
+    },
147
+    lockSignaturePad() {
148
+      return this.signaturePad.off()
149
+    },
150
+    openSignaturePad() {
151
+      return this.signaturePad.on()
152
+    },
153
+    isEmpty() {
154
+      return this.signaturePad.isEmpty()
155
+    },
156
+    getPropImagesAndCacheImages() {
157
+      return this.propsImagesAndCustomImages
158
+    },
159
+    clearCacheImages() {
160
+      this.cacheImages = []
161
+      return this.cacheImages
162
+    },
163
+    clearSignature() {
164
+      return this.signaturePad.clear()
165
+    },
166
+    toBlob(callback, type, encoderOptions) {
167
+      this.$refs.signaturePadCanvas.toBlob(callback, type, encoderOptions)
168
+    }
169
+  },
170
+  render(createElement) {
171
+    const { width, height, customStyle } = this
172
+    const urlObj = require('./signature.png')
173
+    return createElement(
174
+      'div',
175
+      {
176
+        style: {
177
+          width,
178
+          height,
179
+          ...customStyle
180
+        }
181
+      },
182
+      [
183
+        createElement('canvas', {
184
+          style: {
185
+            width: '100%',
186
+            height: '100%',
187
+            cursor: `url(${urlObj}),default`
188
+          },
189
+          ref: 'signaturePadCanvas'
190
+        })
191
+      ]
192
+    )
193
+  }
194
+}
195
+</script>
196
+

BIN
src/components/NewCom/WkSignaturePad/VueSignaturePad/signature.png Wyświetl plik


+ 167
- 0
src/components/NewCom/WkSignaturePad/index.vue Wyświetl plik

@@ -0,0 +1,167 @@
1
+<template>
2
+  <div ref="wkSignaturePad" class="wk-signature-pad">
3
+    <wk-signature-image
4
+      v-if="disabled"
5
+      :src="value.url"
6
+      :height="height"
7
+    />
8
+    <template v-else>
9
+      <vue-signature-pad
10
+        ref="signaturePad"
11
+        :key="height"
12
+        :options="options"
13
+        :height="height"
14
+        width="100%"
15
+      />
16
+      <div class="wk-signature-pad__handle">
17
+        <el-button type="text" icon="wk wk-icon-reply" @click="handleClick('undo')">撤回</el-button>
18
+        <el-button type="text" icon="wk wk-icon-bin" @click="handleClick('clear')">清空</el-button>
19
+      </div>
20
+    </template>
21
+  </div>
22
+</template>
23
+
24
+<script>
25
+
26
+import { crmFileSingleSaveAPI } from '@/api/common'
27
+
28
+import VueSignaturePad from './VueSignaturePad'
29
+import WkSignatureImage from './Image'
30
+
31
+import { valueEquals } from 'element-ui/lib/utils/util'
32
+import { getImageData } from '@/utils'
33
+
34
+
35
+export default {
36
+  // 签名
37
+  name: 'WkSignaturePad',
38
+
39
+  components: {
40
+    VueSignaturePad,
41
+    WkSignatureImage
42
+  },
43
+
44
+  props: {
45
+    value: [Object, String], // batchId 交互
46
+    data: String, // 同步数据源
47
+    disabled: Boolean
48
+  },
49
+
50
+  data() {
51
+    return {
52
+      options: {
53
+        backgroundColor: '#fff',
54
+        onEnd: this.onEnd,
55
+        minWidth: 1,
56
+        maxWidth: 3
57
+      },
58
+      height: '150px'
59
+    }
60
+  },
61
+
62
+  computed: {
63
+  },
64
+
65
+  watch: {
66
+    data(newVal, oldVal) {
67
+      if (!valueEquals(newVal, oldVal)) {
68
+        this.$refs.signaturePad.fromDataURL(newVal)
69
+      }
70
+    }
71
+  },
72
+
73
+  created() {},
74
+
75
+  mounted() {
76
+    if (this.value) {
77
+      this.getData()
78
+    } else {
79
+      this.$emit('input', {})
80
+    }
81
+
82
+    this.height = `${parseInt(this.$refs.wkSignaturePad.clientWidth / 2.6)}px`
83
+  },
84
+
85
+  beforeDestroy() {},
86
+
87
+  methods: {
88
+    getData() {
89
+      getImageData(this.value.url)
90
+        .then(data => {
91
+          var img = new Image()
92
+          img.onload = () => {
93
+            const canvasWidth = this.$refs.signaturePad.canvas.width
94
+            const width = img.width
95
+            const ratio = canvasWidth / width
96
+            console.log(width, canvasWidth, ratio)
97
+            this.options.minWidth = this.options.minWidth * ratio
98
+            this.options.maxWidth = this.options.maxWidth * ratio
99
+
100
+            this.$nextTick(() => {
101
+              this.$refs.signaturePad.fromDataURL(data.src)
102
+            })
103
+          }
104
+          img.src = data.src
105
+        })
106
+        .catch(() => {
107
+        })
108
+    },
109
+
110
+    onEnd() {
111
+      const { isEmpty, data } = this.$refs.signaturePad.saveSignature()
112
+      this.$emit('update:data', data)
113
+      if (!isEmpty) {
114
+        this.$refs.signaturePad.toBlob((blob) => {
115
+          this.uploadFile(blob)
116
+        })
117
+      }
118
+    },
119
+
120
+    uploadFile(blob) {
121
+      crmFileSingleSaveAPI({
122
+        file: blob,
123
+        batchId: this.value
124
+      }).then((res) => {
125
+        this.$emit('input', res.data)
126
+      }).catch(() => {})
127
+    },
128
+
129
+    handleClick(type) {
130
+      if (type === 'clear') {
131
+        this.$refs.signaturePad.clearSignature()
132
+      } else if (type === 'undo') {
133
+        this.$refs.signaturePad.undoSignature()
134
+      }
135
+    }
136
+  }
137
+}
138
+</script>
139
+<style lang="scss">
140
+.el-form-item.is-error {
141
+  .wk-signature-pad {
142
+    border-color: #f56c6c;
143
+  }
144
+}
145
+</style>
146
+
147
+<style lang="scss" scoped>
148
+.wk-signature-pad {
149
+  position: relative;
150
+  border-radius: 4px;
151
+  border: 1px solid #dcdfe6;
152
+  overflow: hidden;
153
+
154
+  &__handle {
155
+    position: absolute;
156
+    right: 15px;
157
+    bottom: 0;
158
+    .el-button--text {
159
+      color: #999;
160
+      &:hover {
161
+        color: $xr-color-primary;
162
+      }
163
+    }
164
+  }
165
+}
166
+</style>
167
+

+ 26
- 0
src/components/NewCom/WkSignaturePad/utils/index.js Wyświetl plik

@@ -0,0 +1,26 @@
1
+export const IMAGE_TYPES = ['image/png', 'image/jpeg', 'image/svg+xml']
2
+
3
+export const checkSaveType = type => IMAGE_TYPES.includes(type)
4
+
5
+export const DEFAULT_OPTIONS = {
6
+  dotSize: (0.5 + 2.5) / 2,
7
+  minWidth: 0.5,
8
+  maxWidth: 2.5,
9
+  throttle: 16,
10
+  minDistance: 5,
11
+  backgroundColor: 'rgba(0,0,0,0)',
12
+  penColor: 'black',
13
+  velocityFilterWeight: 0.7,
14
+  onBegin: () => {},
15
+  onEnd: () => {}
16
+}
17
+
18
+export const convert2NonReactive = observerValue =>
19
+  JSON.parse(JSON.stringify(observerValue))
20
+
21
+export const TRANSPARENT_PNG = {
22
+  src:
23
+    'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=',
24
+  x: 0,
25
+  y: 0
26
+}

+ 2
- 0
src/components/NewCom/WkUserSelect/src/WkUser.vue Wyświetl plik

@@ -145,8 +145,10 @@ export default {
145 145
           }
146 146
         })
147 147
         this.$emit('input', ids)
148
+        this.$emit('change', val)
148 149
       } else {
149 150
         this.$emit('input', [])
151
+        this.$emit('change', [])
150 152
       }
151 153
     },
152 154
 

+ 4
- 3
src/components/SlideView.vue Wyświetl plik

@@ -10,7 +10,7 @@
10 10
       class="slide-detail-card-container">
11 11
       <el-button
12 12
         v-if="showClose"
13
-        class="close-btn"
13
+        class="close-btn xr-btn--orange"
14 14
         type="primary"
15 15
         icon="el-icon-close"
16 16
         @click="close"/>
@@ -218,8 +218,8 @@ export default {
218 218
 
219 219
 .close-btn {
220 220
   position: absolute;
221
-  top: 160px;
222
-  left: -40px;
221
+  top: 153px;
222
+  left: -46px;
223 223
   z-index: 1;
224 224
   border-top-right-radius: 0;
225 225
   border-bottom-right-radius: 0;
@@ -227,6 +227,7 @@ export default {
227 227
 
228 228
   /deep/ i {
229 229
     font-size: 26px;
230
+    margin-left: 0;
230 231
   }
231 232
 }
232 233
 </style>

+ 81
- 285
src/components/VDistpicker/Distpicker.vue Wyświetl plik

@@ -1,74 +1,27 @@
1 1
 <template>
2 2
   <div :class="wrapper">
3
-    <template v-if="type != 'mobile'">
4
-      <el-select v-model="currentProvince" :disabled="disabled || provinceDisabled" placeholder="省" @change="getCities">
5
-        <!-- <el-option :value="placeholders.province">{{ placeholders.province }}</el-option> -->
3
+    <el-select v-model="currentProvince" :disabled="disabled || provinceDisabled" placeholder="省" @change="getCities">
4
+      <el-option
5
+        v-for="(item, index) in provinces"
6
+        :value="item.name"
7
+        :key="index"
8
+        :label="item.name"/>
9
+    </el-select>
10
+    <template v-if="!onlyProvince">
11
+      <el-select v-model="currentCity" :disabled="disabled || cityDisabled" placeholder="市" @change="getAreas">
6 12
         <el-option
7
-          v-for="(item, index) in provinces"
8
-          :value="item"
13
+          v-for="(item, index) in cities"
14
+          :value="item.name"
9 15
           :key="index"
10
-          :label="item"/>
16
+          :label="item.name"/>
17
+      </el-select>
18
+      <el-select v-if="!hideArea" v-model="currentArea" :disabled="disabled || areaDisabled" placeholder="区">
19
+        <el-option
20
+          v-for="(item, index) in areas "
21
+          :value="item.name"
22
+          :key="index"
23
+          :label="item.name"/>
11 24
       </el-select>
12
-      <template v-if="!onlyProvince">
13
-        <el-select v-model="currentCity" :disabled="disabled || cityDisabled" placeholder="市" @change="getAreas">
14
-          <!-- <el-option :value="placeholders.city">{{ placeholders.city }}</el-option> -->
15
-          <el-option
16
-            v-for="(item, index) in cities"
17
-            :value="item"
18
-            :key="index"
19
-            :label="item"/>
20
-        </el-select>
21
-        <el-select v-if="!hideArea" v-model="currentArea" :disabled="disabled || areaDisabled" placeholder="区">
22
-          <!-- <el-option :value="placeholders.area">{{ placeholders.area }}</el-option> -->
23
-          <el-option
24
-            v-for="(item, index) in areas "
25
-            :value="item"
26
-            :key="index"
27
-            :label="item"/>
28
-        </el-select>
29
-      </template>
30
-    </template>
31
-    <template v-else>
32
-      <div :class="addressHeader">
33
-        <ul>
34
-          <li :class="{'active': tab === 1}" @click="resetProvince">{{ currentProvince && !staticPlaceholder ? currentProvince : placeholders.province }}</li>
35
-          <template v-if="!onlyProvince">
36
-            <li v-if="showCityTab" :class="{'active': tab === 2}" @click="resetCity">{{ currentCity && !staticPlaceholder ? currentCity : placeholders.city }}</li>
37
-            <li v-if="showAreaTab && !hideArea" :class="{'active': tab === 3}">{{ currentArea && !staticPlaceholder ? currentArea : placeholders.area }}</li>
38
-          </template>
39
-        </ul>
40
-      </div>
41
-      <div :class="addressContainer">
42
-        <ul v-if="tab === 1">
43
-          <li
44
-            v-for="(item, index) in provinces"
45
-            :class="{'active': item === currentProvince}"
46
-            :key="index"
47
-            @click="chooseProvince(item)">
48
-            {{ item }}
49
-          </li>
50
-        </ul>
51
-        <template v-if="!onlyProvince">
52
-          <ul v-if="tab === 2">
53
-            <li
54
-              v-for="(item, index) in cities"
55
-              :class="{'active': item === currentCity}"
56
-              :key="index"
57
-              @click="chooseCity(item)">
58
-              {{ item }}
59
-            </li>
60
-          </ul>
61
-          <ul v-if="tab === 3 && !hideArea">
62
-            <li
63
-              v-for="(item, index) in areas"
64
-              :class="{'active': item === currentArea}"
65
-              :key="index"
66
-              @click="chooseArea(item)">
67
-              {{ item }}
68
-            </li>
69
-          </ul>
70
-        </template>
71
-      </div>
72 25
     </template>
73 26
   </div>
74 27
 </template>
@@ -76,252 +29,142 @@
76 29
 <script>
77 30
 import DISTRICTS from './districts'
78 31
 
79
-const DEFAULT_CODE = 100000
80
-
81 32
 export default {
82 33
   name: 'VDistpicker',
83 34
   props: {
84 35
     province: { type: [String, Number], default: '' },
85 36
     city: { type: [String, Number], default: '' },
86 37
     area: { type: [String, Number], default: '' },
87
-    type: { type: String, default: '' },
88 38
     hideArea: { type: Boolean, default: false },
89 39
     onlyProvince: { type: Boolean, default: false },
90
-    staticPlaceholder: { type: Boolean, default: false },
91
-    placeholders: {
92
-      type: Object,
93
-      default() {
94
-        return {
95
-          province: '',
96
-          city: '',
97
-          area: ''
98
-        }
99
-      }
100
-    },
101 40
     disabled: { type: Boolean, default: false },
102 41
     provinceDisabled: { type: Boolean, default: false },
103 42
     cityDisabled: { type: Boolean, default: false },
104 43
     areaDisabled: { type: Boolean, default: false },
105
-    addressHeader: { type: String, default: 'address-header' },
106
-    addressContainer: { type: String, default: 'address-container' },
107 44
     wrapper: { type: String, default: 'distpicker-address-wrapper' }
108 45
   },
109 46
   data() {
110 47
     return {
111
-      tab: 1,
112
-      showCityTab: false,
113
-      showAreaTab: false,
114 48
       provinces: [],
115 49
       cities: [],
116 50
       areas: [],
117
-      currentProvince: this.determineType(this.province) || this.placeholders.province,
118
-      currentCity: this.determineType(this.city) || this.placeholders.city,
119
-      currentArea: this.determineType(this.area) || this.placeholders.area
51
+      currentProvince: '',
52
+      currentCity: '',
53
+      currentArea: ''
120 54
     }
121 55
   },
122 56
   watch: {
123
-    currentProvince(vaule) {
124
-      this.$emit('province', this.setData(vaule))
57
+    currentProvince(value) {
58
+      this.$emit('province', this.setData(value, this.provinces))
125 59
       if (this.onlyProvince) this.emit('selected')
126 60
     },
127 61
     currentCity(value) {
128
-      this.$emit('city', this.setData(value, this.currentProvince))
129
-      if (value != this.placeholders.city && this.hideArea) this.emit('selected')
62
+      this.$emit('city', this.setData(value, this.cities))
63
+      if (this.hideArea) this.emit('selected')
130 64
     },
131 65
     currentArea(value) {
132
-      this.$emit('area', this.setData(value, this.currentProvince, true))
66
+      this.$emit('area', this.setData(value, this.areas))
133 67
       this.emit('selected')
134 68
     },
135 69
     province(value) {
136
-      this.currentProvince = this.province || this.placeholders.province
137
-      this.cities = this.determineValue(this.currentProvince, this.placeholders.province)
70
+      this.currentProvince = this.province
71
+      this.cities = this.getDataList(this.currentProvince, this.provinces)
138 72
     },
139 73
     city(value) {
140
-      this.currentCity = this.city || this.placeholders.city
141
-      this.areas = this.determineValue(this.currentCity, this.placeholders.city, this.currentProvince)
74
+      this.currentCity = this.city
75
+      this.areas = this.getDataList(this.currentCity, this.cities)
142 76
     },
143 77
     area(value) {
144
-      this.currentArea = this.area || this.placeholders.area
78
+      this.currentArea = this.area
145 79
     }
146 80
   },
147 81
   created() {
148
-    if (this.type != 'mobile') {
149
-      this.provinces = this.getDistricts()
150
-      this.cities = this.province ? this.getDistricts(this.getAreaCode(this.determineType(this.province))) : []
151
-      this.areas = this.city ? this.getDistricts(this.getAreaCode(this.determineType(this.city), this.area)) : []
152
-    } else {
153
-      if (this.area && !this.hideArea && !this.onlyProvince) {
154
-        this.tab = 3
155
-        this.showCityTab = true
156
-        this.showAreaTab = true
157
-        this.areas = this.getDistricts(this.getAreaCode(this.determineType(this.city), this.area))
158
-      } else if (this.city && this.hideArea && !this.onlyProvince) {
159
-        this.tab = 2
160
-        this.showCityTab = true
161
-        this.cities = this.getDistricts(this.getAreaCode(this.determineType(this.province)))
162
-      } else {
163
-        this.provinces = this.getDistricts()
164
-      }
165
-    }
82
+    this.provinces = DISTRICTS
83
+    this.currentProvince = this.getNameValue(this.province, this.provinces)
84
+    this.cities = this.province ? this.getDataList(this.province, this.provinces) : []
85
+    this.currentCity = this.getNameValue(this.city, this.cities)
86
+    this.areas = this.city ? this.getDataList(this.city, this.cities) : []
87
+    this.currentArea = this.getNameValue(this.area, this.areas)
166 88
   },
167 89
   methods: {
168
-    setData(value, check = '', isArea = false) {
169
-      let code
170
-      if (isArea) {
171
-        code = this.getCodeByArea(value)
172
-      } else {
173
-        code = this.getAreaCode(value, check)
174
-      }
175
-
176
-      return {
177
-        code: code,
178
-        value: value
90
+    setData(value, list) {
91
+      const valueObj = list ? list.find(item => item.name === value) : null
92
+      return valueObj ? {
93
+        code: valueObj.code,
94
+        value: valueObj.name
95
+      } : {
96
+        code: '',
97
+        value: ''
179 98
       }
180 99
     },
181
-    getCodeByArea(value) {
182
-      let code
183
-      Object.values(this.areas).forEach((item, key) => {
184
-        if (item === value) {
185
-          code = Object.keys(this.areas)[key]
186
-        }
187
-      })
188
-      return code
189
-    },
100
+
190 101
     emit(name) {
191 102
       const data = {
192
-        province: this.setData(this.currentProvince)
103
+        province: this.setData(this.currentProvince, this.provinces)
193 104
       }
194 105
 
195 106
       if (!this.onlyProvince) {
196
-        this.$set(data, 'city', this.setData(this.currentCity))
107
+        this.$set(data, 'city', this.setData(this.currentCity, this.cities))
197 108
       }
198 109
 
199 110
       if (!this.onlyProvince || this.hideArea) {
200
-        this.$set(data, 'area', this.setData(this.currentArea, this.currentCity))
111
+        this.$set(data, 'area', this.setData(this.currentArea, this.areas))
201 112
       }
202 113
 
203 114
       this.$emit(name, data)
204 115
     },
116
+
205 117
     getCities() {
206
-      this.currentCity = this.placeholders.city
207
-      this.currentArea = this.placeholders.area
208
-      this.cities = this.determineValue(this.currentProvince, this.placeholders.province)
118
+      this.currentCity = ''
119
+      this.currentArea = ''
120
+      this.cities = this.getDataList(this.currentProvince, this.provinces)
209 121
       this.cleanList('areas')
210 122
       if (this.cities.length === 0) {
211 123
         this.emit('selected')
212
-        this.tab = 1
213
-        this.showCityTab = false
214 124
       }
215 125
     },
126
+
216 127
     getAreas() {
217
-      this.currentArea = this.placeholders.area
218
-      this.areas = this.determineValue(this.currentCity, this.placeholders.city, this.currentProvince)
128
+      this.currentArea = ''
129
+      this.areas = this.getDataList(this.currentCity, this.cities)
219 130
       if (this.areas.length === 0) {
220 131
         this.emit('selected')
221
-        this.tab = 2
222
-        this.showAreaTab = false
223 132
       }
224 133
     },
225
-    resetProvince() {
226
-      this.tab = 1
227
-      this.provinces = this.getDistricts()
228
-      this.showCityTab = false
229
-      this.showAreaTab = false
230
-    },
231
-    resetCity() {
232
-      this.tab = 2
233
-      this.showCityTab = true
234
-      this.showAreaTab = false
235
-      this.getCities()
236
-    },
237
-    chooseProvince(name) {
238
-      this.currentProvince = name
239
-      if (this.onlyProvince) return
240
-      this.tab = 2
241
-      this.showCityTab = true
242
-      this.showAreaTab = false
243
-      this.getCities()
244
-    },
245
-    chooseCity(name) {
246
-      this.currentCity = name
247
-      if (this.hideArea) return
248
-      this.tab = 3
249
-      this.showCityTab = true
250
-      this.showAreaTab = true
251
-      this.getAreas()
252
-    },
253
-    chooseArea(name) {
254
-      this.currentArea = name
255
-    },
256
-    getAreaCodeByPreCode(name, preCode) {
257
-      const codes = []
258 134
 
259
-      for (const x in DISTRICTS) {
260
-        for (const y in DISTRICTS[x]) {
261
-          if (name === DISTRICTS[x][y]) {
262
-            codes.push(y)
263
-          }
135
+    /**
136
+     * 获取数据信息
137
+     * value code/name
138
+     * list 上一级数据
139
+     */
140
+    getDataList(value, list) {
141
+      const isCode = typeof value === 'number'
142
+      for (let index = 0; index < list.length; index++) {
143
+        const item = list[index]
144
+        if ((isCode && item.code === value) ||
145
+        !isCode && item.name === value) {
146
+          return item.children
264 147
         }
265 148
       }
266
-
267
-      if (codes.length > 1) {
268
-        let index
269
-        codes.forEach((item, i) => {
270
-          if (item.slice(0, 2) == preCode) {
271
-            index = i
272
-          }
273
-        })
274
-
275
-        return codes[index]
276
-      } else {
277
-        return codes[0]
278
-      }
279 149
     },
280
-    getAreaCode(name, check = '') {
281
-      for (const x in DISTRICTS) {
282
-        for (const y in DISTRICTS[x]) {
283
-          if (name === DISTRICTS[x][y]) {
284
-            if (check.length > 0) {
285
-              const code = this.getAreaCodeByPreCode(check, y.slice(0, 2))
286 150
 
287
-              if (!code || y.slice(0, 2) !== code.slice(0, 2)) {
288
-                continue
289
-              } else {
290
-                return y
291
-              }
292
-            } else {
293
-              return y
294
-            }
295
-          }
296
-        }
297
-      }
298
-    },
299
-    getCodeValue(code) {
300
-      for (const x in DISTRICTS) {
301
-        for (const y in DISTRICTS[x]) {
302
-          if (code === parseInt(y)) {
303
-            return DISTRICTS[x][y]
151
+    /**
152
+     * 获取名称值
153
+     * value code/name
154
+     * list 当前类型数据
155
+     */
156
+    getNameValue(value, list) {
157
+      if (typeof value === 'number') {
158
+        for (let index = 0; index < list.length; index++) {
159
+          const item = list[index]
160
+          if (item.code === value) {
161
+            return item.name
304 162
           }
305 163
         }
306 164
       }
307
-    },
308
-    getDistricts(code = DEFAULT_CODE) {
309
-      return DISTRICTS[code] || []
310
-    },
311
-    determineValue(currentValue, placeholderValue, check = '') {
312
-      if (currentValue === placeholderValue) {
313
-        return []
314
-      } else {
315
-        return this.getDistricts(this.getAreaCode(currentValue, check))
316
-      }
317
-    },
318
-    determineType(value) {
319
-      if (typeof value === 'number') {
320
-        return this.getCodeValue(value)
321
-      }
322
-
323 165
       return value
324 166
     },
167
+
325 168
     cleanList(name) {
326 169
       this[name] = []
327 170
     }
@@ -337,52 +180,5 @@ export default {
337 180
     width: 30%;
338 181
   }
339 182
 
340
-  ul {
341
-    margin: 0;
342
-    padding: 0;
343
-
344
-    li {
345
-      list-style: none;
346
-    }
347
-  }
348
-  .address-header {
349
-    background-color: #fff;
350
-
351
-    ul {
352
-      display: flex;
353
-      justify-content: space-around;
354
-      align-items: stretch;
355
-
356
-      li {
357
-        display: inline-block;
358
-        padding: 10px 10px 7px;
359
-
360
-        &.active {
361
-          border-bottom: #52697f solid 3px;
362
-          color: #52697f;
363
-        }
364
-      }
365
-    }
366
-  }
367
-  .address-container {
368
-    background-color: #fff;
369
-
370
-    ul {
371
-      height: 100%;
372
-      overflow: auto;
373
-
374
-      li {
375
-        padding: 8px 10px;
376
-        border-top: 1px solid #f6f6f6;
377
-
378
-        &.active {
379
-          color: #52697f;
380
-        }
381
-      }
382
-    }
383
-  }
384
-}
385
-.disabled-color{
386
-    background: #f8f8f8;
387 183
 }
388 184
 </style>

+ 12012
- 4688
src/components/VDistpicker/districts.js
Plik diff jest za duży
Wyświetl plik


+ 68
- 0
src/components/WkEmpty/index.vue Wyświetl plik

@@ -0,0 +1,68 @@
1
+<template>
2
+  <div class="wk-empty">
3
+    <div class="wk-empty__title">{{ config.emptyText || '暂无数据' }}</div>
4
+    <el-button
5
+      v-if="config.showButton"
6
+      :icon="config.buttonIcon"
7
+      class="wk-empty__button xr-btn--orange"
8
+      type="primary"
9
+      @click="btnClick">{{ config.buttonTitle || '新建' }}</el-button>
10
+  </div>
11
+</template>
12
+
13
+<script>
14
+import merge from '@/utils/merge'
15
+
16
+const DefaultEmptyProps = {
17
+  emptyText: '', // 是否搜索
18
+  showButton: false,
19
+  buttonIcon: 'el-icon-plus', // 员工列表请求
20
+  buttonTitle: '' // 空参数
21
+}
22
+
23
+export default {
24
+  // 空数据
25
+  name: 'WkEmpty',
26
+
27
+  components: {},
28
+
29
+  props: {
30
+    props: Object
31
+  },
32
+
33
+  data() {
34
+    return {
35
+    }
36
+  },
37
+
38
+  computed: {
39
+    config() {
40
+      return merge({ ...DefaultEmptyProps }, this.props || {})
41
+    }
42
+  },
43
+
44
+  watch: {},
45
+
46
+  created() {},
47
+
48
+  mounted() {},
49
+
50
+  beforeDestroy() {},
51
+
52
+  methods: {
53
+    btnClick() {
54
+      this.$emit('click')
55
+    }
56
+  }
57
+}
58
+</script>
59
+
60
+<style lang="scss">
61
+.wk-empty {
62
+  color: #999;
63
+
64
+  &__button {
65
+    margin-top: 8px;
66
+  }
67
+}
68
+</style>

+ 207
- 0
src/components/WkImport/ImportHistory.vue Wyświetl plik

@@ -0,0 +1,207 @@
1
+<template>
2
+  <div class="import-history">
3
+    <div class="import-history__hd">
4
+      <span class="import-history__title">查看历史导入记录</span>
5
+      <span class="import-history__des">(错误数据只保存七天,七天后将自动失效)</span>
6
+
7
+      <el-button
8
+        class="xr-icon-close-btn"
9
+        icon="el-icon-close"
10
+        @click="closeClick" />
11
+    </div>
12
+
13
+    <el-table
14
+      :data="tableList"
15
+      class="import-history__bd"
16
+      height="250"
17
+      style="width: 100%">
18
+      <el-table-column
19
+        prop="createTime"
20
+        label="导入时间"
21
+        width="120">
22
+        <template slot-scope="scope">
23
+          {{ scope.row.createTime | createTime }}
24
+        </template>
25
+      </el-table-column>
26
+      <el-table-column
27
+        prop="realname"
28
+        label="操作人"
29
+        width="80"/>
30
+      <el-table-column
31
+        prop="address"
32
+        label="导入结果">
33
+        <template slot-scope="scope">
34
+          {{ `导入总数据${scope.row.title}条,${getImportContent(scope.row.title, scope.row.content)}` }}
35
+        </template>
36
+      </el-table-column>
37
+      <el-table-column
38
+        prop="option"
39
+        width="150"
40
+        label="操作">
41
+        <template slot-scope="scope">
42
+          <template v-if="getErrorNum(scope.row.content) > 0">
43
+            <el-button
44
+              v-if="scope.row.valid == 1"
45
+              type="text"
46
+              @click="downloadError(scope.row.messageId)">下载错误数据</el-button>
47
+            <span v-else-if="scope.row.valid == 0" class="invalid-tips">已失效</span>
48
+          </template>
49
+        </template>
50
+      </el-table-column>
51
+    </el-table>
52
+
53
+    <div class="import-history__ft">
54
+      <el-button
55
+        type="primary"
56
+        @click="closeClick">关闭</el-button>
57
+    </div>
58
+  </div>
59
+</template>
60
+
61
+<script>
62
+import { systemMessageListAPI } from '@/api/common'
63
+import {
64
+  crmDownImportErrorAPI
65
+} from '@/api/crm/common'
66
+
67
+import { downloadExcelWithResData } from '@/utils/index'
68
+
69
+export default {
70
+  // 导入历史
71
+  name: 'CRMImportHistory',
72
+  components: {},
73
+  filters: {
74
+    createTime(time) {
75
+      const times = time.split(' ')
76
+      return times.length > 0 ? times[0] : ''
77
+    }
78
+  },
79
+  props: {
80
+    show: {
81
+      type: Boolean,
82
+      default: false
83
+    },
84
+    // CRM类型
85
+    crmType: {
86
+      type: String,
87
+      default: ''
88
+    },
89
+    // moduleType
90
+    props: Object
91
+  },
92
+  data() {
93
+    return {
94
+      loading: false,
95
+      tableList: []
96
+    }
97
+  },
98
+  computed: {},
99
+  watch: {
100
+    show(value) {
101
+      if (value) {
102
+        this.getList()
103
+      }
104
+    }
105
+  },
106
+  mounted() {},
107
+
108
+  beforeDestroy() {},
109
+  methods: {
110
+    getList() {
111
+      this.loading = true
112
+      systemMessageListAPI({
113
+        page: 1,
114
+        limit: 9999,
115
+        type: {
116
+          customer: 14,
117
+          contacts: 16,
118
+          leads: 18,
119
+          product: 20,
120
+          hrm: 50
121
+        }[this.crmType] || this.props.moduleType
122
+      })
123
+        .then(res => {
124
+          this.loading = false
125
+          this.tableList = res.data.list
126
+        })
127
+        .catch(() => {
128
+          this.loading = false
129
+        })
130
+    },
131
+
132
+    /**
133
+     * 下载错误数据
134
+     */
135
+    downloadError(messageId) {
136
+      crmDownImportErrorAPI({ messageId })
137
+        .then(res => {
138
+          downloadExcelWithResData(res)
139
+        })
140
+        .catch(() => {
141
+        })
142
+    },
143
+
144
+    getImportContent(totalSize, content) {
145
+      const list = content.split(',') || []
146
+      const updateSize = Number(list[1] || '0')
147
+      const errSize = Number(list[0] || '0')
148
+      return `覆盖${updateSize}条,导入成功${totalSize - errSize}条,导入失败${errSize}条。`
149
+    },
150
+
151
+    /**
152
+     * 获取错误数据数
153
+     */
154
+    getErrorNum(content) {
155
+      const list = content.split(',') || []
156
+      const errSize = Number(list[0] || '0')
157
+      return parseInt(errSize)
158
+    },
159
+
160
+    /**
161
+     * 关闭
162
+     */
163
+    closeClick() {
164
+      this.$emit('close')
165
+    }
166
+  }
167
+}
168
+</script>
169
+
170
+<style lang="scss" scoped>
171
+.import-history {
172
+  &__hd {
173
+    padding: 20px;
174
+    padding-bottom: 10px;
175
+  }
176
+
177
+  &__title {
178
+    font-size: 16px;
179
+    font-weight: 600;
180
+    color: #333;
181
+  }
182
+
183
+  &__des {
184
+    font-size: 12px;
185
+    color: #ccc;
186
+  }
187
+
188
+  &__bd {
189
+    border-top: 1px solid #e6e6e6;
190
+  }
191
+
192
+  &__ft {
193
+    padding: 10px;
194
+    background-color: #F7F8FA;
195
+
196
+    text-align: right;
197
+  }
198
+
199
+  .xr-icon-close-btn {
200
+    float: right;
201
+  }
202
+}
203
+
204
+.invalid-tips {
205
+  color: #999;
206
+}
207
+</style>

+ 77
- 0
src/components/WkImport/ImportMixins.js Wyświetl plik

@@ -0,0 +1,77 @@
1
+import { crmQueryImportNumAPI } from '@/api/crm/common'
2
+
3
+import Lockr from 'lockr'
4
+
5
+export default {
6
+  data() {
7
+    return {
8
+      showCRMImport: false,
9
+      crmType: '',
10
+      crmProps: null,
11
+      crmImportStatus: '',
12
+      cacheShow: false, // 缓存展示
13
+      cacheDone: false, // 缓存导入是否完成
14
+
15
+      userInfo: null
16
+    }
17
+  },
18
+
19
+  created() {
20
+    // 处理上次缓存
21
+    const beforeImportInfo = Lockr.get('crmImportInfo')
22
+    if (beforeImportInfo && beforeImportInfo.messageId) {
23
+      this.crmType = beforeImportInfo.crmType
24
+      this.crmProps = beforeImportInfo.crmProps
25
+      this.lockrSecondQueryNum(beforeImportInfo.messageId)
26
+      this.cacheShow = true
27
+    } else {
28
+      this.cacheShow = false
29
+    }
30
+  },
31
+
32
+  computed: {
33
+    // 1.导入框展示 2.导入状态状态为空或者是等待状态  缩小框不展示
34
+    showFixImport() {
35
+      return !this.showCRMImport && this.crmImportStatus && this.crmImportStatus != 'wait'
36
+    }
37
+  },
38
+
39
+  watch: {},
40
+
41
+  methods: {
42
+    crmImportChange(status) {
43
+      this.crmImportStatus = this.showCRMImport && status == 'finish' ? '' : status
44
+    },
45
+
46
+    fixImportClick() {
47
+      this.showCRMImport = true
48
+    },
49
+
50
+    crmImportClose(status) {
51
+      if (status == 'finish') {
52
+        this.crmImportStatus = ''
53
+      }
54
+    },
55
+
56
+    /**
57
+     * 第二步查询数量
58
+     */
59
+    lockrSecondQueryNum(messageId) {
60
+      crmQueryImportNumAPI({ messageId: messageId })
61
+        .then(res => {
62
+          if (res.data === null) { // 结束 否则 进行中
63
+            this.crmImportStatus = 'finish'
64
+            this.cacheDone = true
65
+          } else {
66
+            this.cacheDone = false
67
+            this.crmImportStatus = 'process'
68
+          }
69
+          this.showCRMImport = false
70
+        })
71
+        .catch(() => {
72
+          Lockr.rm('crmImportInfo')
73
+        })
74
+    }
75
+  }
76
+
77
+}

+ 91
- 0
src/components/WkImport/XrImport.vue Wyświetl plik

@@ -0,0 +1,91 @@
1
+<template>
2
+  <flexbox class="xr-import">
3
+    <div
4
+      v-loading="loading"
5
+      v-if="loading"
6
+      class="xr-import__icon" />
7
+    <i
8
+      v-else
9
+      class="wk wk-success xr-import__icon" />
10
+    <p
11
+      :class="{ 'is-loading': loading }"
12
+      class="xr-import__label">{{ processLabel }}</p>
13
+  </flexbox>
14
+</template>
15
+
16
+<script>
17
+export default {
18
+  // 导入缩小框子
19
+  name: 'XrImport',
20
+  components: {},
21
+  props: {
22
+    // wait / process / finish / error / success
23
+    processStatus: {
24
+      type: String,
25
+      default: 'wait'
26
+    }
27
+  },
28
+  data() {
29
+    return {
30
+    }
31
+  },
32
+  computed: {
33
+    loading() {
34
+      return this.processStatus == 'process'
35
+    },
36
+
37
+    processLabel() {
38
+      return this.loading ? '导入中' : '导入完成'
39
+    }
40
+  },
41
+  watch: {},
42
+  mounted() {},
43
+
44
+  beforeDestroy() {},
45
+  methods: {}
46
+}
47
+</script>
48
+
49
+<style lang="scss" scoped>
50
+.xr-import {
51
+  position: fixed;
52
+  right: 50px;
53
+  bottom: 150px;
54
+  z-index: 999999;
55
+  cursor: pointer;
56
+
57
+  width: 130px;
58
+  height: 50px;
59
+  padding: 10px;
60
+  border-radius: 25px;
61
+  background-color: white;
62
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
63
+
64
+  &__icon {
65
+    font-size: 30px;
66
+    margin-right: 5px;
67
+  }
68
+
69
+  &__label {
70
+    font-size: 14px;
71
+    color: #333;
72
+  }
73
+
74
+  &__label.is-loading {
75
+    margin-left: 35px;
76
+  }
77
+
78
+  /deep/ .el-loading-spinner .circular {
79
+    width: 30px;
80
+    height: 30px;
81
+  }
82
+
83
+  /deep/ .el-loading-spinner {
84
+    margin-top: -15px;
85
+  }
86
+}
87
+
88
+.wk-success {
89
+  color: $xr-color-primary;
90
+}
91
+</style>

+ 868
- 0
src/components/WkImport/index.vue Wyświetl plik

@@ -0,0 +1,868 @@
1
+<template>
2
+  <div>
3
+    <el-dialog
4
+      :visible="showCRMImport"
5
+      :title="'导入'+crmTypeName"
6
+      :append-to-body="true"
7
+      :close-on-click-modal="false"
8
+      width="750px"
9
+      @close="closeView">
10
+      <div class="dialog-body">
11
+        <el-steps
12
+          :active="stepsActive"
13
+          simple>
14
+          <el-step
15
+            v-for="(item, index) in stepList"
16
+            :key="index"
17
+            :title="item.title"
18
+            :icon="item.icon"
19
+            :status="item.status" />
20
+        </el-steps>
21
+
22
+        <div v-if="stepsActive == 1" class="step-section">
23
+          <div class="sections">
24
+            <div class="sections__title">一、请按照数据模板的格式准备要导入的数据。<span
25
+              class="download"
26
+              @click="download">点击下载《{{ crmTypeName }}导入模板》</span></div>
27
+            <div class="sections__tips">导入文件请勿超过2MB(约10,000条数据)</div>
28
+          </div>
29
+          <div v-if="config.repeatHandleShow" class="sections">
30
+            <div class="sections__title">二、请选择数据重复时的处理方式{{ config.repeatRuleShow ? `(查重规则:【${ fieldUniqueInfo }】)` : '' }}</div>
31
+            <div v-if="config.repeatRuleShow" class="sections__tips">查重规则为:添加{{ crmTypeName }}时所需填写的所有唯一字段,当前设置唯一字段为:{{ fieldUniqueInfo }}</div>
32
+            <div class="content">
33
+              <el-select
34
+                v-model="repeatHandling"
35
+                placeholder="请选择">
36
+                <el-option
37
+                  v-for="(item, index) in [{name: '覆盖系统原有数据',value: 1},{name: '跳过',value: 2}]"
38
+                  :key="index"
39
+                  :label="item.name"
40
+                  :value="item.value"/>
41
+              </el-select>
42
+            </div>
43
+          </div>
44
+          <div class="sections">
45
+            <div class="sections__title">{{ config.repeatHandleShow ? '三' : '二' }}、请选择需要导入的文件</div>
46
+            <div class="content">
47
+              <flexbox class="file-select">
48
+                <el-input
49
+                  v-model="file.name"
50
+                  :disabled="true"/>
51
+                <el-button
52
+                  type="primary"
53
+                  @click="selectFile">选择文件</el-button>
54
+              </flexbox>
55
+            </div>
56
+          </div>
57
+          <div v-if="config.ownerSelectShow" class="sections">
58
+            <div class="sections__title">四、请选择负责人(负责人为必填字段,若不填写,则会导致导入失败)</div>
59
+            <div class="content">
60
+              <div class="user-cell">
61
+                <xh-user-cell
62
+                  :value="user"
63
+                  @value-change="userSelect"/>
64
+              </div>
65
+            </div>
66
+          </div>
67
+
68
+          <div v-if="config.poolSelectShow" class="sections">
69
+            <div class="sections__title">四、请选择公海</div>
70
+            <div class="content">
71
+              <div class="user-cell">
72
+                <el-select v-model="poolId" placeholder="请选择">
73
+                  <el-option
74
+                    v-for="item in poolList"
75
+                    :key="item.poolId"
76
+                    :label="item.poolName"
77
+                    :value="item.poolId"/>
78
+                </el-select>
79
+              </div>
80
+            </div>
81
+          </div>
82
+        </div>
83
+
84
+        <div
85
+          v-loading="loading"
86
+          v-else-if="stepsActive == 2"
87
+          :element-loading-text="importLoadingText"
88
+          element-loading-spinner="el-icon-loading"
89
+          class="step-section">
90
+          <div class="step-section__tips">当前数据正在导入,您可以点击【最小化】隐藏该页面,数据导入不受影响。</div>
91
+        </div>
92
+
93
+        <div
94
+          v-loading="loading"
95
+          v-else-if="stepsActive == 3"
96
+          class="step-section">
97
+          <div class="result-info">
98
+            <i class="wk wk-success result-info__icon" />
99
+            <p class="result-info__des">数据导入完成</p>
100
+            <p v-if="resultData" class="result-info__detail">导入总数据<span class="result-info__detail--all">{{ resultData.totalSize }}</span>条,<template v-if="resultData.updateSize">覆盖<span class="result-info__detail--suc">{{ resultData.updateSize }}</span>条,</template>导入成功<span class="result-info__detail--suc">{{ resultData.totalSize - resultData.errSize }}</span>条,导入失败<span class="result-info__detail--err">{{ resultData.errSize }}</span>条</p>
101
+            <el-button
102
+              v-if="resultData && resultData.errSize > 0"
103
+              class="result-info__btn--err"
104
+              type="text"
105
+              @click="downloadErrData(resultData)">下载错误数据</el-button>
106
+          </div>
107
+        </div>
108
+
109
+        <input
110
+          id="importInputFile"
111
+          type="file"
112
+          @change="uploadFile">
113
+      </div>
114
+      <span
115
+        slot="footer"
116
+        class="dialog-footer">
117
+        <el-popover
118
+          v-if="config.historyShow"
119
+          v-model="historyPopoverShow"
120
+          placement="top"
121
+          width="800"
122
+          popper-class="no-padding-popover"
123
+          trigger="click">
124
+          <import-history
125
+            :show="historyPopoverShow"
126
+            :crm-type="crmType"
127
+            :props="crmProps"
128
+            @close="historyPopoverShow = false" />
129
+          <el-button
130
+            slot="reference"
131
+            class="history-btn"
132
+            type="text">查看历史导入记录</el-button>
133
+        </el-popover>
134
+        <el-button
135
+          :class="{ 'is-hidden': !showCancel }"
136
+          @click="closeView">取消</el-button>
137
+        <el-button
138
+          v-if="sureTitle"
139
+          type="primary"
140
+          @click="sureClick">{{ sureTitle }}</el-button>
141
+      </span>
142
+    </el-dialog>
143
+
144
+    <xr-import
145
+      v-if="showFixImport"
146
+      :process-status="crmImportStatus"
147
+      @click.native="fixImportClick"/>
148
+
149
+  </div>
150
+</template>
151
+
152
+<script>
153
+import {
154
+  crmQueryImportNumAPI,
155
+  crmQueryImportInfoAPI,
156
+  crmDownImportErrorAPI,
157
+  filedGetFieldAPI
158
+} from '@/api/crm/common'
159
+import {
160
+  crmCustomerExcelImportAPI,
161
+  crmCustomerDownloadExcelAPI,
162
+  crmCustomerPoolDownloadExcelAPI,
163
+  crmCustomerPoolNameListAPI,
164
+  crmCustomerPoolExcelImportAPI
165
+} from '@/api/crm/customer'
166
+import {
167
+  crmLeadsExcelImportAPI,
168
+  crmLeadsDownloadExcelAPI
169
+} from '@/api/crm/leads'
170
+import {
171
+  crmContactsExcelImportAPI,
172
+  crmContactsDownloadExcelAPI
173
+} from '@/api/crm/contacts'
174
+import {
175
+  crmProductExcelImportAPI,
176
+  crmProductDownloadExcelAPI
177
+} from '@/api/crm/product'
178
+
179
+import { XhUserCell } from '@/components/CreateCom'
180
+import ImportHistory from './ImportHistory'
181
+import XrImport from './XrImport'
182
+
183
+// import { mapGetters } from 'vuex'
184
+import crmTypeModel from '@/views/crm/model/crmTypeModel'
185
+import { downloadExcelWithResData, verifyFileTypeWithFileName } from '@/utils'
186
+import Lockr from 'lockr'
187
+import merge from '@/utils/merge'
188
+import ImportMixins from './ImportMixins'
189
+
190
+const DefaultProps = {
191
+  typeName: '', // 模块名称
192
+  ownerSelectShow: false,
193
+  poolSelectShow: false, // 代表是公海 导入模板也不一样
194
+  historyShow: true,
195
+  repeatHandleShow: true,
196
+  repeatRuleShow: true, // 步骤二的重复规则是否展示
197
+  importRequest: null, // 导入请求
198
+  importParams: null, // 导入参数
199
+  templateRequest: null, // 模板请求
200
+  templateParams: null, // 导入模板参数
201
+  noImportProcess: false, // 无导入过程,上传完附件直接出结果
202
+  downloadErrFuc: null, // 自定义下载错误的方法
203
+  userInfo: null // 当前登录人信息
204
+}
205
+
206
+export default {
207
+  name: 'WkCRMImport', // 文件导入
208
+  components: {
209
+    XhUserCell,
210
+    ImportHistory,
211
+    XrImport
212
+  },
213
+  mixins: [ImportMixins],
214
+  props: {
215
+    // show: {
216
+    //   type: Boolean,
217
+    //   default: false
218
+    // }
219
+    // // CRM类型
220
+    // crmType: {
221
+    //   type: String,
222
+    //   default: ''
223
+    // },
224
+    // props: {
225
+    //   type: Object,
226
+    //   default: () => {
227
+    //     return {}
228
+    //   }
229
+    // },
230
+    // 是有缓存的信息展示的页面 是否完成状态
231
+    // cacheShow: {
232
+    //   type: Boolean,
233
+    //   default: false
234
+    // },
235
+    // cacheDone: {
236
+    //   type: Boolean,
237
+    //   default: false
238
+    // }
239
+  },
240
+  data() {
241
+    return {
242
+      loading: false,
243
+      fieldList: [],
244
+      repeatHandling: 1, // 	1 覆盖,2跳过
245
+      file: { name: '' },
246
+      user: [],
247
+
248
+      // 公海数据
249
+      poolId: '',
250
+      poolList: [],
251
+
252
+      stepsActive: 1,
253
+      stepList: [
254
+        {
255
+          icon: 'wk wk-upload',
256
+          title: '上传文件',
257
+          status: 'wait'
258
+        },
259
+        {
260
+          icon: 'wk wk-data-import',
261
+          title: '导入数据',
262
+          status: 'wait'
263
+        },
264
+        {
265
+          icon: 'wk wk-success',
266
+          title: '导入完成',
267
+          status: 'wait'
268
+        }
269
+      ],
270
+      resultData: null,
271
+      processData: {
272
+        count: 0,
273
+        status: ''
274
+      },
275
+      messageId: null,
276
+      intervalTimer: null,
277
+
278
+      historyPopoverShow: false
279
+    }
280
+  },
281
+  computed: {
282
+    // ...mapGetters(['userInfo']),
283
+    crmTypeName() {
284
+      if (this.crmProps && this.crmProps.typeName) {
285
+        return this.crmProps.typeName
286
+      }
287
+
288
+      return (
289
+        {
290
+          customer: '客户',
291
+          leads: '线索',
292
+          contacts: '联系人',
293
+          product: '产品'
294
+        }[this.crmType] || ''
295
+      )
296
+    },
297
+
298
+    importLoadingText() {
299
+      if (this.processData.count) {
300
+        return `数据导入中(当前已导入${this.processData.count}条)`
301
+      }
302
+
303
+      return '数据导入中'
304
+    },
305
+
306
+    sureTitle() {
307
+      return {
308
+        1: '立即导入',
309
+        2: '最小化',
310
+        3: '确定'
311
+      }[this.stepsActive]
312
+    },
313
+
314
+    showCancel() {
315
+      return this.stepsActive != 2
316
+    },
317
+
318
+    fieldUniqueInfo() {
319
+      const uniqueList = this.fieldList.filter(item => item.isUnique == 1)
320
+
321
+      return uniqueList.map(item => item.name).join('/') || '无'
322
+    },
323
+
324
+    config() {
325
+      return merge({ ...DefaultProps }, this.crmProps || {})
326
+    }
327
+  },
328
+  watch: {
329
+    cacheShow: {
330
+      handler(val) {
331
+        // 展示缓存
332
+        if (val) {
333
+          // this.$emit('update:cacheShow', false)
334
+          this.cacheShow = false
335
+          const beforeImportInfo = Lockr.get('crmImportInfo')
336
+          if (beforeImportInfo && beforeImportInfo.messageId) {
337
+            this.loading = true
338
+            this.messageId = beforeImportInfo.messageId
339
+            this.stepList[0].status = 'finish'
340
+
341
+            if (this.cacheDone) {
342
+              this.stepList[1].status = 'finish'
343
+              this.stepsActive = 3
344
+              this.thirdQueryResult()
345
+            } else {
346
+              this.stepsActive = 2
347
+              this.stepList[1].status = 'process'
348
+            }
349
+
350
+            this.loopSecondQueryNum()
351
+          }
352
+        }
353
+      },
354
+      immediate: true
355
+    },
356
+
357
+    showCRMImport: {
358
+      handler(val) {
359
+        if (val) {
360
+          // 阶段一展示 需要获取的信息
361
+          if (this.stepsActive == 1) {
362
+            if (this.config.userInfo) {
363
+              this.user = [this.config.userInfo]
364
+            }
365
+
366
+            if (this.config.poolSelectShow) {
367
+              this.getPoolList()
368
+            }
369
+
370
+            if (this.config.repeatRuleShow && this.config.repeatHandleShow) {
371
+              this.getField()
372
+            }
373
+          }
374
+        } else {
375
+          if (this.stepsActive == 3) {
376
+            this.resetData()
377
+          }
378
+
379
+          this.fieldList = []
380
+        }
381
+      },
382
+      immediate: true
383
+    },
384
+
385
+    stepsActive() {
386
+      // this.$emit('status', {
387
+      //   1: 'wait',
388
+      //   2: 'process',
389
+      //   3: 'finish'
390
+      // }[this.stepsActive])
391
+
392
+      this.crmImportChange({
393
+        1: 'wait',
394
+        2: 'process',
395
+        3: 'finish'
396
+      }[this.stepsActive])
397
+    }
398
+
399
+    // file() {
400
+    //   this.getFirstStepStatus()
401
+    // },
402
+
403
+    // user() {
404
+    //   this.getFirstStepStatus()
405
+    // }
406
+
407
+  },
408
+  created() {},
409
+  methods: {
410
+    import(crmType, props) {
411
+      if (this.crmType != crmType && this.showFixImport) {
412
+        this.$message.error('请先处理当前导入的数据')
413
+      } else {
414
+        this.crmType = crmType
415
+        this.crmProps = props
416
+        this.showCRMImport = true
417
+      }
418
+    },
419
+    sureClick() {
420
+      if (this.stepsActive == 1) {
421
+        if (this.stepList[0].status == 'finish') {
422
+          this.stepList[1].status = 'process'
423
+          this.stepsActive = 2
424
+          this.firstUpdateFile(res => {
425
+            const resData = res.data || {}
426
+            if (this.config.noImportProcess) {
427
+              this.loading = false
428
+              this.stepList[1].status = 'finish'
429
+              this.stepsActive = 3
430
+              this.$emit('status', 'finish')
431
+              this.resultData = resData
432
+              if (resData.errSize > 0) {
433
+                this.stepList[2].status = 'error'
434
+              } else {
435
+                this.stepList[2].status = 'finish'
436
+              }
437
+            } else {
438
+              this.messageId = resData
439
+
440
+              // 写入本次缓存
441
+              Lockr.set('crmImportInfo', {
442
+                messageId: this.messageId,
443
+                crmProps: this.config,
444
+                crmType: this.crmType
445
+              })
446
+
447
+              this.loopSecondQueryNum()
448
+            }
449
+          })
450
+        } else {
451
+          if (!this.file.name) {
452
+            this.$message.error('请选择导入文件')
453
+          } else if (
454
+            this.config.ownerSelectShow &&
455
+            (!this.user || this.user.length == 0)
456
+          ) {
457
+            this.$message.error('请选择负责人')
458
+          }
459
+        }
460
+      } else {
461
+        this.closeView()
462
+      }
463
+    },
464
+
465
+
466
+    /**
467
+     * 第一步上传
468
+     */
469
+    firstUpdateFile(result) {
470
+      let params = {}
471
+      params.repeatHandling = this.repeatHandling
472
+      params.file = this.file
473
+      if (this.config.ownerSelectShow) {
474
+        params.ownerUserId = this.user.length > 0 ? this.user[0].userId : ''
475
+      }
476
+      if (this.config.poolSelectShow) {
477
+        params.poolId = this.poolId
478
+      }
479
+
480
+      const request = this.config.importRequest || {
481
+        customer: this.config.poolSelectShow ? crmCustomerPoolExcelImportAPI : crmCustomerExcelImportAPI,
482
+        leads: crmLeadsExcelImportAPI,
483
+        contacts: crmContactsExcelImportAPI,
484
+        product: crmProductExcelImportAPI
485
+      }[this.crmType]
486
+      this.loading = true
487
+      if (this.config.importParams) {
488
+        params = {
489
+          ...params,
490
+          ...this.config.importParams
491
+        }
492
+      }
493
+      request(params)
494
+        .then(res => {
495
+          if (result) {
496
+            result(res)
497
+          }
498
+        })
499
+        .catch(() => {
500
+          if (result) {
501
+            result(false)
502
+          }
503
+          this.loading = false
504
+        })
505
+    },
506
+
507
+    /**
508
+     * 第二步查询数量
509
+     */
510
+    loopSecondQueryNum() {
511
+      this.secondQueryNum()
512
+      this.intervalTimer = setInterval(() => {
513
+        if (this.processData.status == 'end') {
514
+          clearInterval(this.intervalTimer)
515
+          this.intervalTimer = null
516
+          this.thirdQueryResult()
517
+        } else {
518
+          this.secondQueryNum()
519
+        }
520
+      }, 2000)
521
+    },
522
+
523
+    secondQueryNum() {
524
+      crmQueryImportNumAPI({ messageId: this.messageId })
525
+        .then(res => {
526
+          if (res.data === null) {
527
+            this.processData.status = 'end'
528
+          } else {
529
+            this.processData.status = ''
530
+            this.processData.count = res.data
531
+          }
532
+        })
533
+        .catch(() => {
534
+          // this.processData.status = 'err'
535
+        })
536
+    },
537
+
538
+    /**
539
+     * 第三部 查询结果
540
+     */
541
+    thirdQueryResult() {
542
+      crmQueryImportInfoAPI({ messageId: this.messageId })
543
+        .then(res => {
544
+          this.loading = false
545
+          this.stepList[1].status = 'finish'
546
+          this.stepsActive = 3
547
+          this.$emit('status', 'finish')
548
+          if (res) {
549
+            this.resultData = res.data
550
+            if (res.data.errSize > 0) {
551
+              this.stepList[2].status = 'error'
552
+            } else {
553
+              this.stepList[2].status = 'finish'
554
+            }
555
+          }
556
+        })
557
+        .catch(() => {})
558
+    },
559
+
560
+    /**
561
+     * 下载错误模板
562
+     */
563
+    downloadErrData(result) {
564
+      this.loading = true
565
+      if (this.config.downloadErrFuc) {
566
+        this.config.downloadErrFuc(result).then(res => {
567
+          downloadExcelWithResData(res)
568
+          this.loading = false
569
+        })
570
+          .catch(() => {
571
+            this.loading = false
572
+          })
573
+      } else {
574
+        crmDownImportErrorAPI({ messageId: this.messageId })
575
+          .then(res => {
576
+            downloadExcelWithResData(res)
577
+            this.loading = false
578
+          })
579
+          .catch(() => {
580
+            this.loading = false
581
+          })
582
+      }
583
+    },
584
+
585
+    // 下载模板操作
586
+    download() {
587
+      const request = this.config.templateRequest || {
588
+        customer: this.config.poolSelectShow ? crmCustomerPoolDownloadExcelAPI : crmCustomerDownloadExcelAPI,
589
+        leads: crmLeadsDownloadExcelAPI,
590
+        contacts: crmContactsDownloadExcelAPI,
591
+        product: crmProductDownloadExcelAPI
592
+      }[this.crmType]
593
+      request(this.config.templateParams || {})
594
+        .then(res => {
595
+          downloadExcelWithResData(res)
596
+        })
597
+        .catch(() => {})
598
+    },
599
+    // 选择文件
600
+    selectFile() {
601
+      document.getElementById('importInputFile').click()
602
+    },
603
+    /** 图片选择出发 */
604
+    uploadFile(event) {
605
+      var files = event.target.files
606
+      const file = files[0]
607
+      if (verifyFileTypeWithFileName(file.name)) {
608
+        this.file = file
609
+        // 阶段一状态
610
+        this.getFirstStepStatus()
611
+      }
612
+      event.target.value = ''
613
+    },
614
+    // 用户选择
615
+    userSelect(data) {
616
+      if (data.value && data.value.length > 0) {
617
+        this.user = data.value
618
+      } else {
619
+        this.user = []
620
+      }
621
+
622
+      // 阶段一状态
623
+      this.getFirstStepStatus()
624
+    },
625
+
626
+    getFirstStepStatus() {
627
+      // 阶段一状态
628
+      const hasFile = this.file && this.file.size
629
+      const hasUser = this.user && this.user.length > 0
630
+
631
+      if (this.config.ownerSelectShow) {
632
+        this.stepList[0].status = hasFile && hasUser ? 'finish' : 'wait'
633
+      } else {
634
+        this.stepList[0].status = hasFile ? 'finish' : 'wait'
635
+      }
636
+    },
637
+
638
+    /**
639
+     * 公海数据
640
+     */
641
+    getPoolList() {
642
+      crmCustomerPoolNameListAPI()
643
+        .then(res => {
644
+          this.poolList = res.data || []
645
+          this.poolId = this.poolList.length > 0 ? this.poolList[0].poolId : ''
646
+        })
647
+        .catch(() => {
648
+        })
649
+    },
650
+
651
+    // 关闭操作
652
+    closeView() {
653
+      // this.$emit('update:show', false)
654
+      this.showCRMImport = false
655
+      if (this.stepsActive == 3) {
656
+        this.$bus.emit('import-crm-done-bus', this.crmType)
657
+        Lockr.rm('crmImportInfo')
658
+      }
659
+      // this.$emit('close', this.stepsActive == 3 ? 'finish' : '')
660
+      this.crmImportClose(this.stepsActive == 3 ? 'finish' : '')
661
+    },
662
+
663
+    /**
664
+     * 重置页面数据
665
+     */
666
+    resetData() {
667
+      this.repeatHandling = 1
668
+      this.file = { name: '' }
669
+      this.user = []
670
+
671
+      this.stepsActive = 1
672
+      this.stepList = [
673
+        {
674
+          icon: 'wk wk-upload',
675
+          title: '上传文件',
676
+          status: 'wait'
677
+        },
678
+        {
679
+          icon: 'wk wk-data-import',
680
+          title: '导入数据',
681
+          status: 'wait'
682
+        },
683
+        {
684
+          icon: 'wk wk-success',
685
+          title: '导入完成',
686
+          status: 'wait'
687
+        }
688
+      ]
689
+      this.resultData = null
690
+      this.processData = {
691
+        count: 0,
692
+        status: ''
693
+      }
694
+      this.messageId = null
695
+    },
696
+
697
+    /**
698
+     * 获取验证字段
699
+     */
700
+    getField() {
701
+      var params = {
702
+        label: crmTypeModel[this.crmType],
703
+        type: 1
704
+      }
705
+
706
+      filedGetFieldAPI(params)
707
+        .then(res => {
708
+          this.fieldList = res.data
709
+        })
710
+        .catch(() => {
711
+        })
712
+    }
713
+  }
714
+}
715
+</script>
716
+
717
+<style scoped lang="scss">
718
+.el-steps {
719
+  margin-bottom: 15px;
720
+
721
+  /deep/ .el-step__title {
722
+    font-size: 14px;
723
+  }
724
+
725
+  /deep/ .el-step.is-simple .el-step__arrow::before,
726
+  /deep/ .el-step.is-simple .el-step__arrow::after {
727
+    height: 10px;
728
+    width: 2px;
729
+  }
730
+
731
+  /deep/ .el-step.is-simple .el-step__arrow::after {
732
+    transform: rotate(45deg) translateY(3px);
733
+  }
734
+  /deep/ .el-step.is-simple .el-step__arrow::before {
735
+    transform: rotate(-45deg) translateY(-2px);
736
+  }
737
+}
738
+
739
+.step-section {
740
+  min-height: 300px;
741
+  position: relative;
742
+
743
+  /deep/ .el-loading-spinner {
744
+    top: 45%;
745
+    .el-icon-loading {
746
+      font-size: 40px;
747
+      color: #999;
748
+    }
749
+
750
+    .el-loading-text {
751
+      color: #333;
752
+      margin: 8px 0;
753
+    }
754
+  }
755
+
756
+  &__tips {
757
+    color: #999;
758
+    font-size: 12px;
759
+    position: absolute;
760
+    left: 0;
761
+    bottom: 0;
762
+    right: 0;
763
+    text-align: center;
764
+    z-index: 3000;
765
+  }
766
+}
767
+
768
+.sections {
769
+  font-size: 14px;
770
+  color: #333;
771
+
772
+  &__title {
773
+    font-weight: 600;
774
+  }
775
+
776
+  &__tips {
777
+    padding-left: 30px;
778
+    margin: 8px 0 15px;
779
+    color: #999;
780
+    font-size: 12px;
781
+    line-height: 1.4;
782
+  }
783
+
784
+  .download {
785
+    cursor: pointer;
786
+    color: #2362FB;
787
+  }
788
+
789
+}
790
+
791
+.sections__tips + .content {
792
+  padding-top: 0;
793
+}
794
+
795
+.content {
796
+  padding: 10px 10px 10px 30px;
797
+  .el-select {
798
+    width: 400px;
799
+  }
800
+  .user-cell {
801
+    width: 400px;
802
+  }
803
+}
804
+
805
+#importInputFile {
806
+  display: none;
807
+}
808
+
809
+.file-select {
810
+  .el-input {
811
+    width: 400px;
812
+  }
813
+  button {
814
+    margin-left: 20px;
815
+  }
816
+}
817
+
818
+.is-hidden {
819
+  visibility: hidden;
820
+}
821
+
822
+.history-btn {
823
+  float: left;
824
+  margin-left: 15px;
825
+}
826
+
827
+
828
+// 结果信息
829
+.result-info {
830
+  text-align: center;
831
+  padding-top: 80px;
832
+
833
+  &__icon {
834
+    font-size: 40px;
835
+    color: $xr-color-primary;
836
+  }
837
+
838
+  &__des {
839
+    margin-top: 15px;
840
+    color: #333;
841
+    font-size: 14px;
842
+  }
843
+
844
+  &__detail {
845
+    margin-top: 15px;
846
+    font-size: 12px;
847
+    color: #666;
848
+    &--all {
849
+      color: #333;
850
+      font-weight: 600;
851
+    }
852
+
853
+    &--suc {
854
+      color: $xr-color-primary;
855
+      font-weight: 600;
856
+    }
857
+
858
+    &--err {
859
+      color: #f94e4e;
860
+      font-weight: 600;
861
+    }
862
+  }
863
+
864
+  &__btn--err {
865
+    margin-top: 10px;
866
+  }
867
+}
868
+</style>

+ 15
- 0
src/components/WkImport/main.js Wyświetl plik

@@ -0,0 +1,15 @@
1
+import WkImportVue from './index.vue'
2
+
3
+const WkImport = {}
4
+
5
+WkImport.install = (Vue) => {
6
+  const WkCRMImportConstructor = Vue.extend(WkImportVue)
7
+  const instance = new WkCRMImportConstructor({
8
+    el: document.createElement('div')
9
+  })
10
+  document.body.appendChild(instance.$el)
11
+
12
+  Vue.prototype.$wkImport = instance
13
+}
14
+
15
+export default WkImport

+ 1
- 0
src/components/XrMenu/XrMenuItem.vue Wyświetl plik

@@ -3,6 +3,7 @@
3 3
     :class="{'is-select':select}"
4 4
     class="xr-menu-item">
5 5
     <i
6
+      :style="{backgroundColor: select ? iconColor || '#2362FB' : '#edf2f6'}"
6 7
       :class="['xr-menu-item__icon', iconClass]" />
7 8
     <span class="xr-menu-item__label">{{ label }}</span>
8 9
     <el-badge

+ 17
- 7
src/components/XrUpgradeDialog.vue Wyświetl plik

@@ -34,13 +34,23 @@ export default {
34 34
   data() {
35 35
     return {
36 36
       message: `新增:
37
-  1、客户管理模块字段授权
38
-  2、客户管理模块打印模板
39
-  3、客户管理多公海模块
40
-  4、数据操作日志
41
-  5、系统操作日志
42
-  6、登录日志
43
-  7、员工管理增加批量重设部门的功能`
37
+1、增加多种自定义字段类型,支持自定义字段占比配置及布局调整;
38
+2、客户管理:新增团队成员有效时间;优化团队成员权限;联系人和回款模块增加团队成员功能;增加相关团队字段,支持通过相关团队对团队成员进行筛选;
39
+3、新增日志点赞互动功能;
40
+4、新增发票模块自定义字段、发票导出功能;
41
+5、增加市场活动自定义表单
42
+6、跟进记录增加导入导出功能
43
+7、导入数据时,增加“负责人”字段
44
+8、角色权限:系统管理角色新增权限"角色权限查看",控制在新建员工选择角色和编辑员工角色时,可查看和选择角色的范围;
45
+
46
+优化:
47
+1、优化客户管理仪表盘,图表展示和统计数据等;
48
+2、优化导出,支持操作一万条以上数据;
49
+3、高级筛选判断符优化调整;时间筛选增加固定时间段(例如今日、本月、本年等);
50
+4、权限优化
51
+
52
+修复:
53
+1、修复其他已知bug。`
44 54
     }
45 55
   },
46 56
   computed: {},

+ 1
- 1
src/config.js Wyświetl plik

@@ -3,7 +3,7 @@ const getLocationOrigin = () => {
3 3
 }
4 4
 
5 5
 const companyName = '悟空CRM'
6
-const version = 'V11.0.3'
6
+const version = 'V11.1.0'
7 7
 const baiduKey = 'lcuOQ71SCZhqpxsr1vL2mXoplWEoVctL'
8 8
 
9 9
 export default {

+ 3
- 0
src/main.js Wyświetl plik

@@ -68,6 +68,9 @@ Vue.use(FileUpload)
68 68
 import WkFileSelect from '@/components/NewCom/WkFile/Select/main.js'
69 69
 Vue.use(WkFileSelect)
70 70
 
71
+import WkImport from '@/components/WkImport/main.js'
72
+Vue.use(WkImport)
73
+
71 74
 /** 懒加载图片 */
72 75
 import VueSrc from './directives/src'
73 76
 Vue.directive('src', VueSrc)

+ 69
- 47
src/mixins/AdvancedFilter.js Wyświetl plik

@@ -4,97 +4,119 @@ export default {
4 4
      * 根据类型获取条件
5 5
      * @param {*} formType
6 6
      */
7
-    getAdvancedFilterOptions(formType) {
8
-      if (
9
-        formType == 'check_status' ||
10
-        formType == 'deal_status'
11
-      ) {
7
+    getAdvancedFilterOptions(formType, fieldName) {
8
+      // 单行文本、多行文本、网址、手机、邮箱
9
+      // 等于、不等于、包含、不包含、开始于、结束于、为空、不为空
10
+      if (formType == 'text' ||
11
+      formType == 'textarea' ||
12
+      formType == 'website' ||
13
+      formType == 'mobile' ||
14
+      formType == 'email' ||
15
+      formType == 'module') {
12 16
         return [
13 17
           { value: 'is', label: '等于', disabled: false, type: 1 },
14
-          { value: 'isNot', label: '不等于', disabled: false, type: 2 }
18
+          { value: 'isNot', label: '不等于', disabled: false, type: 2 },
19
+          { value: 'contains', label: '包含', disabled: false, type: 3 },
20
+          { value: 'notContains', label: '不包含', disabled: false, type: 4 },
21
+          { value: 'startWith', label: '开始于', disabled: false, type: 12 },
22
+          { value: 'endWith', label: '结束于', disabled: false, type: 13 },
23
+          { value: 'isNull', label: '为空', disabled: false, type: 5 },
24
+          { value: 'isNotNull', label: '不为空', disabled: false, type: 6 }
15 25
         ]
16 26
       } else if (
17
-        formType == 'user' ||
18
-        formType == 'single_user' ||
19
-        formType == 'structure'
27
+        fieldName === 'invoice_status' ||
28
+        formType == 'check_status' ||
29
+        formType == 'deal_status'
20 30
       ) {
21 31
         return [
22
-          // { value: 'contains', label: '包含', disabled: false, type: 3 },
23
-          // { value: 'notContains', label: '不包含', disabled: false, type: 4 }
24 32
           { value: 'is', label: '等于', disabled: false, type: 1 },
25 33
           { value: 'isNot', label: '不等于', disabled: false, type: 2 }
26 34
         ]
27 35
       } else if (
36
+        // 下拉框 等于、不等于、为空、不为空
28 37
         formType == 'select'
29 38
       ) {
30 39
         return [
31
-          { value: 'in', label: '等于', disabled: false, type: 1 },
32
-          { value: 'isNot', label: '不等于', disabled: false, type: 2 }
40
+          { value: 'is', label: '等于', disabled: false, type: 1 },
41
+          { value: 'isNot', label: '不等于', disabled: false, type: 2 },
42
+          { value: 'isNull', label: '为空', disabled: false, type: 5 },
43
+          { value: 'isNotNull', label: '不为空', disabled: false, type: 6 }
33 44
         ]
34 45
       } else if (
35
-        formType == 'checkbox'
46
+        // 布尔值 等于、不等于
47
+        formType == 'boolean_value'
36 48
       ) {
37 49
         return [
38 50
           { value: 'is', label: '等于', disabled: false, type: 1 },
39
-          { value: 'contains', label: '包含', disabled: false, type: 3 }
51
+          { value: 'isNot', label: '不等于', disabled: false, type: 2 }
40 52
         ]
41 53
       } else if (
42
-        formType == 'module' ||
43
-        formType == 'text' ||
44
-        formType == 'textarea'
54
+        formType == 'checkbox' ||
55
+        formType == 'location'
45 56
       ) {
46 57
         return [
47 58
           { value: 'is', label: '等于', disabled: false, type: 1 },
48 59
           { value: 'isNot', label: '不等于', disabled: false, type: 2 },
49 60
           { value: 'contains', label: '包含', disabled: false, type: 3 },
50
-          { value: 'notContains', label: '不包含', disabled: false, type: 4 }
61
+          { value: 'notContains', label: '不包含', disabled: false, type: 4 },
62
+          { value: 'isNull', label: '为空', disabled: false, type: 5 },
63
+          { value: 'isNotNull', label: '不为空', disabled: false, type: 6 }
51 64
         ]
52
-      } else if (formType == 'floatnumber' || formType == 'number') {
65
+        // 数字、货币、百分数
66
+        // 等于、不等于、大于、大于等于、小于、小于等于、区间(范围)、为空、不为空
67
+      } else if (
68
+        formType == 'number' ||
69
+        formType == 'floatnumber' ||
70
+        formType == 'percent'
71
+      ) {
53 72
         return [
54 73
           { value: 'is', label: '等于', disabled: false, type: 1 },
55 74
           { value: 'isNot', label: '不等于', disabled: false, type: 2 },
56
-          { value: 'contains', label: '包含', disabled: false, type: 3 },
57
-          { value: 'notContains', label: '不包含', disabled: false, type: 4 },
58
-          { value: 'isNull', label: '为空', disabled: false, type: 5 },
59
-          { value: 'isNotNull', label: '不为空', disabled: false, type: 6 },
60 75
           { value: 'gt', label: '大于', disabled: false, type: 7 },
61 76
           { value: 'egt', label: '大于等于', disabled: false, type: 8 },
62 77
           { value: 'lt', label: '小于', disabled: false, type: 9 },
63
-          { value: 'elt', label: '小于等于', disabled: false, type: 10 }
64
-        ]
65
-      } else if (formType == 'category') {
66
-        return [
67
-          { value: 'is', label: '等于', disabled: false, type: 1 },
68
-          { value: 'isNot', label: '不等于', disabled: false, type: 2 },
69
-          { value: 'contains', label: '包含', disabled: false, type: 3 },
70
-          { value: 'not_contain', label: '不包含', disabled: false, type: 4 }
78
+          { value: 'elt', label: '小于等于', disabled: false, type: 10 },
79
+          { value: 'range', label: '等于(范围)', disabled: false, type: 14 },
80
+          { value: 'isNull', label: '为空', disabled: false, type: 5 },
81
+          { value: 'isNotNull', label: '不为空', disabled: false, type: 6 }
71 82
         ]
72
-      } else if (formType == 'mobile' || formType == 'email') {
83
+        // 日期、日期时间
84
+        // 等于、不等于、早于(大于)、晚于(小于)、不早于(小于等于)、不晚于(大于等于)、等于(时间段)、为空、不为空
85
+      } else if (
86
+        formType == 'date' ||
87
+        formType == 'datetime'
88
+      ) {
73 89
         return [
74 90
           { value: 'is', label: '等于', disabled: false, type: 1 },
75 91
           { value: 'isNot', label: '不等于', disabled: false, type: 2 },
76
-          { value: 'contains', label: '包含', disabled: false, type: 3 },
77
-          { value: 'notContains', label: '不包含', disabled: false, type: 4 },
78
-          { value: 'startWith', label: '开始于', disabled: false, type: 8 },
79
-          { value: 'endWith', label: '结束于', disabled: false, type: 10 },
92
+          { value: 'lt', label: '早于', disabled: false, type: 9 },
93
+          { value: 'gt', label: '晚于', disabled: false, type: 7 },
94
+          { value: 'egt', label: '不早于', disabled: false, type: 8 },
95
+          { value: 'elt', label: '不晚于', disabled: false, type: 10 },
96
+          { value: 'range', label: '等于(时间段)', disabled: false, type: 14 },
80 97
           { value: 'isNull', label: '为空', disabled: false, type: 5 },
81 98
           { value: 'isNotNull', label: '不为空', disabled: false, type: 6 }
82 99
         ]
83
-      } else {
100
+        // 日期区间 地址 无判断符
101
+      } else if (
102
+        formType == 'date_interval' ||
103
+        formType == 'position'
104
+      ) {
105
+        return []
106
+        // 人员、部门 包含、不包含、为空、不为空
107
+      } else if (
108
+        formType == 'user' ||
109
+        formType == 'structure' ||
110
+        formType == 'single_user'
111
+      ) {
84 112
         return [
85
-          { value: 'is', label: '等于', disabled: false, type: 1 },
86
-          { value: 'isNot', label: '不等于', disabled: false, type: 2 },
87 113
           { value: 'contains', label: '包含', disabled: false, type: 3 },
88 114
           { value: 'notContains', label: '不包含', disabled: false, type: 4 },
89
-          { value: 'startWith', label: '开始于', disabled: false, type: 8 },
90
-          { value: 'endWith', label: '结束于', disabled: false, type: 10 },
91 115
           { value: 'isNull', label: '为空', disabled: false, type: 5 },
92
-          { value: 'isNotNull', label: '不为空', disabled: false, type: 6 },
93
-          { value: 'gt', label: '大于', disabled: false, type: 7 },
94
-          { value: 'egt', label: '大于等于', disabled: false, type: 8 },
95
-          { value: 'lt', label: '小于', disabled: false, type: 9 },
96
-          { value: 'elt', label: '小于等于', disabled: false, type: 10 }
116
+          { value: 'isNotNull', label: '不为空', disabled: false, type: 6 }
97 117
         ]
118
+      } else {
119
+        return []
98 120
       }
99 121
     }
100 122
   }

+ 1
- 1
src/mixins/CheckStatusMixin.js Wyświetl plik

@@ -170,7 +170,7 @@ export default {
170 170
       } else if (status == 2) {
171 171
         return '通过'
172 172
       } else if (status == 3) {
173
-        return '失败'
173
+        return '拒绝'
174 174
       } else if (status == 1) {
175 175
         return '审核中'
176 176
       } else if (status == 4) {

+ 385
- 53
src/mixins/CustomFields.js Wyświetl plik

@@ -17,10 +17,10 @@ export default {
17 17
      */
18 18
     getItemValue(item, detail, type) {
19 19
       detail = detail || {}
20
-      if ((item.formType || item.form_type) == 'contacts' ||
21
-          (item.formType || item.form_type) == 'customer' ||
22
-          (item.formType || item.form_type) == 'contract' ||
23
-          (item.formType || item.form_type) == 'business'
20
+      if ((item.form_type || item.form_type) == 'contacts' ||
21
+          (item.form_type || item.form_type) == 'customer' ||
22
+          (item.form_type || item.form_type) == 'contract' ||
23
+          (item.form_type || item.form_type) == 'business'
24 24
       ) {
25 25
         // crm相关信息特殊处理
26 26
         if (type === 'update') {
@@ -58,9 +58,9 @@ export default {
58 58
       } else if (item.form_type == 'user' ||
59 59
             item.form_type == 'structure' ||
60 60
             item.form_type == 'file' ||
61
-            item.formType == 'user' ||
62
-            item.formType == 'structure' ||
63
-            item.formType == 'file'
61
+            item.form_type == 'user' ||
62
+            item.form_type == 'structure' ||
63
+            item.form_type == 'file'
64 64
       ) {
65 65
         if (type === 'update') {
66 66
           return item.value ? objDeepCopy(item.value) : []
@@ -70,10 +70,44 @@ export default {
70 70
             : []
71 71
         }
72 72
       } else {
73
-        if (type == 'update') {
74
-          return item.value || ''
73
+        if (
74
+          item.form_type == 'number' ||
75
+        item.form_type == 'floatnumber' ||
76
+        item.form_type == 'percent' ||
77
+        item.form_type == 'number' ||
78
+        item.form_type == 'floatnumber' ||
79
+        item.form_type == 'percent'
80
+        ) {
81
+          if (type == 'update') {
82
+            return isEmpty(item.value) ? undefined : item.value
83
+          } else {
84
+            return isEmpty(item.default_value) ? undefined : item.default_value
85
+          }
86
+        } else if (item.form_type == 'detail_table' || item.form_type == 'detail_table') {
87
+          const baseFieldList = item.value || [item.fieldExtendList]
88
+          // 二维数组
89
+          const tableValue = []
90
+          baseFieldList.forEach(bigItem => {
91
+            const subForm = {}
92
+            tableValue.push(subForm)
93
+            bigItem.forEach(subItem => {
94
+              // 未转换 取 field 值
95
+              if ([
96
+                'select',
97
+                'checkbox'
98
+              ].includes(subItem.form_type)) {
99
+                subItem.optionsData = null
100
+              }
101
+              subForm[subItem.field] = this.getItemValue(subItem, null, type)
102
+            })
103
+          })
104
+          return tableValue
75 105
         } else {
76
-          return item.default_value || ''
106
+          if (type == 'update') {
107
+            return item.value || ''
108
+          } else {
109
+            return item.default_value || ''
110
+          }
77 111
         }
78 112
       }
79 113
     },
@@ -83,42 +117,49 @@ export default {
83 117
      */
84 118
     getUniquePromise(field, value, detail) {
85 119
       return new Promise((resolve, reject) => {
120
+        // var validatesParams = {}
121
+        // validatesParams.field_id = field.field_id
122
+        // if (isArray(value)) {
123
+        //   let postValue = ''
124
+        //   if (value.length > 0) {
125
+        //     if (
126
+        //       field.form_type == 'user' ||
127
+        //           field.form_type == 'structure'
128
+        //     ) {
129
+        //       postValue = value
130
+        //         .map(valueItem => {
131
+        //           return field.form_type == 'user'
132
+        //             ? valueItem.userId
133
+        //             : valueItem.id
134
+        //         })
135
+        //         .join(',')
136
+        //     } else if (field.field == 'categoryId') {
137
+        //       if (value && value.length) {
138
+        //         postValue = value[value.length - 1]
139
+        //       } else {
140
+        //         postValue = ''
141
+        //       }
142
+        //     } else if (field.form_type == 'checkbox') {
143
+        //       postValue = value.join(',')
144
+        //     }
145
+        //   }
146
+        //   validatesParams.val = postValue
147
+        // } else {
148
+        //   validatesParams.val = value
149
+        //   validatesParams.types = field.types
150
+        //   validatesParams.field = field.field
151
+        // }
152
+        // if (detail.type == 'update') {
153
+        //   validatesParams.id = detail.action_id || detail.id
154
+        // }
86 155
         var validatesParams = {}
87
-        validatesParams.fieldId = field.fieldId
88
-        if (isArray(value)) {
89
-          let postValue = ''
90
-          if (value.length > 0) {
91
-            if (
92
-              field.formType == 'user' ||
93
-                  field.formType == 'structure'
94
-            ) {
95
-              postValue = value
96
-                .map(valueItem => {
97
-                  return field.formType == 'user'
98
-                    ? valueItem.userId
99
-                    : valueItem.id
100
-                })
101
-                .join(',')
102
-            } else if (field.fieldName == 'categoryId') {
103
-              if (value && value.length) {
104
-                postValue = value[value.length - 1]
105
-              } else {
106
-                postValue = ''
107
-              }
108
-            } else if (field.formType == 'checkbox') {
109
-              postValue = value.join(',')
110
-            }
111
-          }
112
-          validatesParams.val = postValue
113
-        } else {
114
-          validatesParams.val = value
115
-          validatesParams.types = field.types
116
-          validatesParams.field = field.field
117
-        }
156
+        validatesParams.field_id = field.field_id
157
+        validatesParams.val = this.getRealParams(field, value)
158
+        validatesParams.types = field.types
159
+        validatesParams.field = field.field
118 160
         if (detail.type == 'update') {
119 161
           validatesParams.id = detail.action_id || detail.id
120 162
         }
121
-
122 163
         filedValidatesAPI(validatesParams).then(res => {
123 164
           // status 1 通过 0
124 165
           const resData = res || {}
@@ -138,7 +179,109 @@ export default {
138 179
         })
139 180
       })
140 181
     },
182
+    /**
183
+     * 获取字段默认内容
184
+     */
185
+    getFormItemDefaultProperty(item, isCopy = true) {
186
+      const temp = isCopy ? objDeepCopy(item) : item
187
+      temp.field = item.field
188
+      temp.field = item.field
189
+
190
+      // 细化 precisions
191
+      if ((item.form_type || item.form_type) === 'date_interval') {
192
+        // 1 日期  2 时间日期
193
+        if (item.precisions === 2) {
194
+          temp.dateType = 'datetimerange'
195
+          temp.dateValueFormat = 'yyyy-MM-dd HH:mm:ss'
196
+        } else {
197
+          temp.dateType = 'daterange'
198
+          temp.dateValueFormat = 'yyyy-MM-dd'
199
+        }
200
+      } else if ((item.form_type || item.form_type) === 'position') {
201
+        // 1 省/地区、市、区/县、详细地址 2 省/地区、市、区/县
202
+        // 3 省/地区、市 4 省/地区
203
+        temp.showDetail = item.precisions === 1
204
+        temp.hideArea = item.precisions === 3 || item.precisions === 4
205
+        temp.onlyProvince = item.precisions === 4
206
+      } else if ((item.form_type || item.form_type) === 'detail_table') {
207
+        // 校准默认单元数据
208
+        // authLevel 1 不能查看不能编辑 2可查看  3 可编辑可查看
209
+        const fieldForm = {}
210
+        temp.fieldExtendList.forEach(extendItem => {
211
+          const copyExtendItem = objDeepCopy(extendItem)
212
+          this.getFormItemDefaultProperty(extendItem, false)
213
+          extendItem.show = extendItem.is_hidden !== 1
214
+          if (extendItem.show) {
215
+            extendItem.rules = this.getRules(copyExtendItem)
216
+          }
217
+          this.getItemRadio(extendItem, extendItem)
218
+          fieldForm[extendItem.field] = this.getItemValue(extendItem)
219
+        })
220
+        temp.fieldForm = fieldForm // 用于追加新事项
141 221
 
222
+        const fieldExtendList = objDeepCopy(item.fieldExtendList)
223
+        const baseFieldList = isEmpty(item.value) ? [fieldExtendList] : item.value
224
+        // 二维数组
225
+        const tableFieldList = []
226
+        baseFieldList.forEach(bigItem => {
227
+          const subValue = []
228
+          bigItem.forEach(subItem => {
229
+            const copySubItem = objDeepCopy(subItem)
230
+            const subTemp = this.getFormItemDefaultProperty(subItem, false)
231
+            // 特殊字段允许多选
232
+            this.getItemRadio(subItem, subTemp)
233
+            subTemp.show = subTemp.is_hidden !== 1
234
+            if (subTemp.show) {
235
+              subTemp.rules = this.getRules(copySubItem)
236
+            }
237
+            subValue.push(subTemp)
238
+          })
239
+          tableFieldList.push(subValue)
240
+        })
241
+        temp.fieldList = tableFieldList
242
+      }
243
+      return temp
244
+    },
245
+
246
+    /**
247
+     * 获取二维数组字段
248
+     * @param {*} array
249
+     * @param {*} form_type
250
+     */
251
+    getItemWithFromType(array, form_type) {
252
+      let item = null
253
+      for (let index = 0; index < array.length; index++) {
254
+        const children = array[index]
255
+        for (let childIndex = 0; childIndex < children.length; childIndex++) {
256
+          const element = children[childIndex]
257
+          if (element.form_type === form_type) {
258
+            item = element
259
+            break
260
+          }
261
+        }
262
+
263
+        if (item) {
264
+          break
265
+        }
266
+      }
267
+
268
+      return item
269
+    },
270
+
271
+    /**
272
+     * 循环二维数组
273
+     * @param {*} array
274
+     * @param {*} result
275
+     */
276
+    itemsForEach(array, callback) {
277
+      for (let index = 0; index < array.length; index++) {
278
+        const children = array[index]
279
+        for (let childIndex = 0; childIndex < children.length; childIndex++) {
280
+          const element = children[childIndex]
281
+          callback(element, index, childIndex, children)
282
+        }
283
+      }
284
+    },
142 285
     /**
143 286
      * 获取字段是否可编辑
144 287
      */
@@ -153,9 +296,9 @@ export default {
153 296
      * user single_user structure
154 297
      */
155 298
     getItemRadio(field, data) {
156
-      if (field.formType == 'single_user') {
299
+      if (field.form_type == 'single_user') {
157 300
         data.radio = true
158
-      } else if (field.formType == 'user' || field.formType == 'structure' || field.form_type == 'user' || field.form_type == 'structure') {
301
+      } else if (field.form_type == 'user' || field.form_type == 'structure' || field.form_type == 'user' || field.form_type == 'structure') {
159 302
         data.radio = false
160 303
       }
161 304
     },
@@ -188,7 +331,8 @@ export default {
188 331
       if (this.action.type == 'relative') {
189 332
         crmItem = this.action.data[fromType]
190 333
       } else {
191
-        const crmObj = list.find(listItem => {
334
+        const childIsArray = list.length > 0 && isArray(list[0])
335
+        const crmObj = childIsArray ? this.getItemWithFromType(list, fromType) : list.find(listItem => {
192 336
           return listItem.form_type === fromType
193 337
         })
194 338
         if (crmObj && crmObj.value && crmObj.value.length > 0) {
@@ -202,20 +346,39 @@ export default {
202 346
      * 获取提交参数
203 347
      */
204 348
     getSubmiteParams(array, data) {
205
-      var params = { address: [] }
349
+      // var params = { address: [] }
350
+      var params = {}
206 351
       for (let index = 0; index < array.length; index++) {
207 352
         const field = array[index]
353
+        let dataValue = null
354
+        if (field.hasOwnProperty('show')) {
355
+          dataValue = field.show ? data[field.field] : null
356
+        } else {
357
+          dataValue = data[field.field]
358
+        }
208 359
         if (field.form_type == 'product') {
209
-          this.getProductParams(params, data[field.field])
360
+          this.getProductParams(params, dataValue)
210 361
         } else if (field.form_type == 'map_address') {
211
-          this.getCustomerAddressParams(params, data[field.field])
362
+          this.getCustomerAddressParams(params, dataValue)
212 363
         } else if (field.field_type == 1) {
213
-          const fieldValue = this.getRealParams(field, data[field.field])
364
+          const fieldValue = this.getRealParams(field, dataValue)
214 365
           params[field.field] = isEmpty(fieldValue) ? '' : fieldValue
215
-        } else {
216
-          field.value = this.getRealParams(field, data[field.field], data)
366
+        } else if (field.form_type !== 'desc_text') { //  描述文字忽略
367
+          field.value = this.getRealParams(field, dataValue, data)
217 368
           params[field.field] = field.value
369
+          // params.field.push({
370
+          //   field: field.field,
371
+          //   field_type: field.field_type,
372
+          //   name: field.name,
373
+          //   type: field.type,
374
+          //   field_id: field.field_id,
375
+          //   value: this.getRealParams(field, dataValue)
376
+          // })
218 377
         }
378
+        //  else {
379
+        //   field.value = this.getRealParams(field, dataValue, data)
380
+        //   params[field.field] = field.value
381
+        // }
219 382
       }
220 383
       return params
221 384
     },
@@ -290,10 +453,10 @@ export default {
290 453
         }
291 454
         return ''
292 455
       } else if (field.form_type == 'checkbox') {
293
-        if (dataValue && dataValue.length > 0) {
456
+        if (isArray(dataValue)) {
294 457
           return dataValue.join(',')
295 458
         }
296
-        return ''
459
+        return dataValue
297 460
       } else if (field.form_type == 'date') {
298 461
         if (dataValue) {
299 462
           return dataValue
@@ -311,9 +474,178 @@ export default {
311 474
           return dataValue
312 475
         }
313 476
         return ''
477
+      } else if (field.form_type == 'detail_table') {
478
+        const fieldExtendList = field.fieldExtendList || []
479
+        const values = []
480
+        const tableValues = Array.isArray(dataValue) ? dataValue : []
481
+
482
+        for (let tableValueIndex = 0; tableValueIndex < tableValues.length; tableValueIndex++) {
483
+          const tableValue = tableValues[tableValueIndex]
484
+          // 去除空值数据
485
+          if (!this.getFormValueIsEmpty(fieldExtendList, tableValue)) {
486
+            const valuesItems = []
487
+            fieldExtendList.forEach(tableField => {
488
+              const copyTableField = objDeepCopy(tableField)
489
+              delete copyTableField.companyId
490
+              copyTableField.value = this.getRealParams(copyTableField, tableValue[copyTableField.field])
491
+              valuesItems.push(copyTableField)
492
+            })
493
+            values.push(valuesItems)
494
+          }
495
+        }
496
+
497
+        return values
314 498
       }
315 499
 
316 500
       return dataValue
501
+    },
502
+    /**
503
+     * 判断对象值是否是空
504
+     */
505
+    getFormValueIsEmpty(fieldList, valueObj) {
506
+      for (let index = 0; index < fieldList.length; index++) {
507
+        const field = fieldList[index]
508
+        const value = valueObj[field.field]
509
+        if (field.form_type === 'select' || field.form_type === 'checkbox') {
510
+          if (isObject(value) && !isEmpty(value.select)) {
511
+            return false
512
+          }
513
+        } else if (field.form_type === 'location') {
514
+          if (isObject(value) && (!isEmpty(value.lat) || !isEmpty(value.lng) || !isEmpty(value.address))) {
515
+            return false
516
+          }
517
+        } else if (!isEmpty(value)) {
518
+          return false
519
+        }
520
+      }
521
+      return true
522
+    },
523
+    /**
524
+     * 获取逻辑表单隐藏id
525
+     */
526
+    getFormAssistIds(list, valueObj) {
527
+      let allIds = []
528
+      list.forEach(items => {
529
+        items.forEach(item => {
530
+          if ([
531
+            'select',
532
+            'checkbox'
533
+          ].includes((item.form_type || item.form_type)) &&
534
+          item.remark === 'options_type' &&
535
+          item.optionsData) {
536
+            for (const key in item.optionsData) {
537
+              allIds = allIds.concat(item.optionsData[key] || [])
538
+            }
539
+          }
540
+        })
541
+      })
542
+
543
+      allIds = allIds.filter(o => Boolean(o))
544
+      allIds = Array.from(new Set(allIds))
545
+
546
+      const ignoreIds = []
547
+      this.getFormAssistData(list, valueObj, allIds, ignoreIds)
548
+      return allIds.filter(o => !ignoreIds.includes(o))
549
+    },
550
+
551
+    /**
552
+     * 获取信息
553
+     */
554
+    getFormAssistData(list, valueObj, allIds, ignoreIds) {
555
+      // let ignorIds = []
556
+      const ignoreLength = ignoreIds.length
557
+      list.forEach(items => {
558
+        items.forEach(item => {
559
+          if ([
560
+            'select',
561
+            'checkbox'
562
+          ].includes((item.form_type || item.form_type)) &&
563
+          item.remark === 'options_type' &&
564
+          item.optionsData) {
565
+            let value = valueObj ? valueObj[item.field || item.field] : item.value
566
+            if (!allIds.includes(item.formAssistId)) {
567
+              if ((item.form_type || item.form_type) === 'select') {
568
+                if (isEmpty(value)) {
569
+                  value = []
570
+                } else {
571
+                  value = item.setting.includes(value) ? [value] : ['其他']
572
+                }
573
+              } else if ((item.form_type || item.form_type) === 'checkbox') {
574
+                if (isArray(value)) {
575
+                  const copyValue = objDeepCopy(value)
576
+                  const otherItem = copyValue.filter((name) => !item.setting.includes(name))
577
+                  if (otherItem.length > 0) {
578
+                    const newValue = copyValue.filter((name) => !otherItem.includes(name))
579
+                    newValue.push('其他')
580
+                    value = newValue
581
+                  }
582
+                } else {
583
+                  value = []
584
+                }
585
+              }
586
+
587
+              for (const key in item.optionsData) {
588
+                if (value && value.includes(key)) {
589
+                  const keyValue = item.optionsData[key] || []
590
+                  for (let index = 0; index < keyValue.length; index++) {
591
+                    const id = keyValue[index]
592
+                    if (!ignoreIds.includes(id)) {
593
+                      ignoreIds.push(id)
594
+                    }
595
+                  }
596
+                }
597
+              }
598
+            }
599
+          }
600
+        })
601
+      })
602
+
603
+      if (ignoreLength !== ignoreIds.length) {
604
+        const newAllIds = allIds.filter(o => !ignoreIds.includes(o))
605
+        this.getFormAssistData(list, valueObj, newAllIds, ignoreIds)
606
+      }
607
+    },
608
+
609
+    /**
610
+     * 获取表单展示内容
611
+     * data 用于相关模块新建填充模块值
612
+     * type 新建编辑
613
+     */
614
+    getFormContentByOptionsChange(fieldList, formObj, rules = {}, data = {}, type) {
615
+      const allFieldRules = this.fieldRules || rules
616
+      const actionData = this.action && this.action.data ? this.action.data : data
617
+      const actionType = this.action && this.action.type ? this.action.type : type
618
+
619
+      const fieldRules = {}
620
+      const fieldForm = {}
621
+
622
+      // 依据最新的值,获取隐藏的ids
623
+      const assistIds = this.getFormAssistIds(fieldList, formObj)
624
+      this.itemsForEach(fieldList, fieldItem => {
625
+        fieldItem.show = !assistIds.includes(fieldItem.formAssistId)
626
+        // 展示 并且 允许编辑,加入验证规则
627
+        if (fieldItem.show && !fieldItem.disabled) {
628
+          if (allFieldRules[fieldItem.field]) {
629
+            fieldRules[fieldItem.field] = allFieldRules[fieldItem.field]
630
+          } else {
631
+            fieldRules[fieldItem.field] = this.getRules(fieldItem)
632
+          }
633
+        }
634
+
635
+        // 值获取
636
+        if (fieldItem.show) {
637
+          if (formObj[fieldItem.field]) {
638
+            fieldForm[fieldItem.field] = formObj[fieldItem.field]
639
+          } else {
640
+            fieldForm[fieldItem.field] = this.getItemValue(fieldItem, actionData, actionType)
641
+          }
642
+        }
643
+      })
644
+
645
+      return {
646
+        fieldForm,
647
+        fieldRules
648
+      }
317 649
     }
318 650
 
319 651
   }

+ 11
- 1
src/router/modules/admin.js Wyświetl plik

@@ -113,7 +113,7 @@ export default [
113 113
     }, {
114 114
       name: 'workbenchHandlefield',
115 115
       path: 'workbench-custom-field/:type/:id/:label',
116
-      component: () => import('@/views/admin/crm/HandleField'),
116
+      component: () => import('@/views/admin/fields'),
117 117
       hidden: true,
118 118
       meta: {
119 119
         activeMenu: '/manage/system-workbench'
@@ -204,6 +204,16 @@ export default [
204 204
         requiresAuth: true,
205 205
         permissions: ['manage', 'crm', 'achievement']
206 206
       }
207
+    }, {
208
+      name: 'customField',
209
+      path: 'custom-field/:type/:label/:id',
210
+      component: () => import('@/views/admin/fields'),
211
+      hidden: true,
212
+      meta: {
213
+        activeMenu: '/manage/customer/custom-field',
214
+        requiresAuth: true,
215
+        permissionList: [['manage', 'crm', 'field'], ['manage', 'crm', 'activityForm']]
216
+      }
207 217
     }, {
208 218
       name: 'handlefield',
209 219
       path: 'custom-field/:type/:id/:label',

+ 6
- 0
src/styles/button.scss Wyświetl plik

@@ -18,6 +18,8 @@
18 18
 }
19 19
 
20 20
 .xr-btn--green:hover,
21
+.xr-btn--green.is-disabled,
22
+.xr-btn--green.is-disabled:hover,
21 23
 .xr-btn--green:focus {
22 24
   background: #4ca824;
23 25
   border-color: #4ca824;
@@ -36,6 +38,8 @@
36 38
 }
37 39
 
38 40
 .xr-btn--orange:hover,
41
+.xr-btn--orange.is-disabled,
42
+.xr-btn--orange.is-disabled:hover,
39 43
 .xr-btn--orange:focus {
40 44
   background: #fc7d63;
41 45
   border-color: #fc7d63;
@@ -54,6 +58,8 @@
54 58
 }
55 59
 
56 60
 .xr-btn--red:hover,
61
+.xr-btn--red.is-disabled,
62
+.xr-btn--red.is-disabled:hover,
57 63
 .xr-btn--red:focus {
58 64
   background: #fa6060;
59 65
   border-color: #fa6060;

+ 1
- 1
src/styles/emoji-sprite.scss Wyświetl plik

@@ -1,7 +1,7 @@
1 1
 [class*="sprite-"] {
2 2
   width:25px;
3 3
   height: 25px;
4
-  background: url('../assets/img/sprite/vue-emoji.png');
4
+  background: url('https://file.72crm.com/static/pc/images/emoji/sprite.png');
5 5
   background-repeat: no-repeat;
6 6
   vertical-align: top;
7 7
   display: inline-block;

+ 226
- 10
src/styles/iconfont/iconfont.css
Plik diff jest za duży
Wyświetl plik


BIN
src/styles/iconfont/iconfont.eot Wyświetl plik


+ 166
- 4
src/styles/iconfont/iconfont.svg
Plik diff jest za duży
Wyświetl plik


BIN
src/styles/iconfont/iconfont.ttf Wyświetl plik


BIN
src/styles/iconfont/iconfont.woff Wyświetl plik


BIN
src/styles/iconfont/iconfont.woff2 Wyświetl plik


+ 11
- 1
src/styles/index.scss Wyświetl plik

@@ -9,6 +9,8 @@
9 9
 @import './iconfont/iconfont.css';
10 10
 @import '../directives/style.scss';
11 11
 
12
+// 零碎样式
13
+@import './org-tree.scss';
12 14
 
13 15
 body {
14 16
   height: 100%;
@@ -165,7 +167,8 @@ div[lazy=loading] {
165 167
 .router-view {
166 168
   width: 100%;
167 169
   position: relative;
168
-  height: 100%;
170
+  // height: 100%;
171
+  flex: 1;
169 172
   overflow: hidden;
170 173
 }
171 174
 
@@ -362,3 +365,10 @@ div[lazy=loading] {
362 365
     }
363 366
   }
364 367
 }
368
+
369
+// 时间区间选择快捷键
370
+.el-picker-panel {
371
+  .el-picker-panel__sidebar {
372
+    padding-bottom: 40px;
373
+  }
374
+}

+ 30
- 0
src/styles/org-tree.scss Wyświetl plik

@@ -0,0 +1,30 @@
1
+.org-tree-company {
2
+  color: #42526E;
3
+  background-color: #ECEEF2;
4
+  box-shadow: none !important;
5
+  font-weight: 600;
6
+  font-size: 12px;
7
+}
8
+
9
+.org-tree-department {
10
+  color: #42526E;
11
+  font-weight: 600;
12
+  box-shadow: none !important;
13
+  font-size: 12px;
14
+  border: 2px solid #42526E;
15
+  padding: 8px 15px !important;
16
+}
17
+
18
+.org-tree-node-label {
19
+  cursor: pointer;
20
+}
21
+
22
+.org-tree-node:not(:last-child):after,
23
+.org-tree-node:not(:first-child):before {
24
+  border-top: 2px solid #42526E;
25
+}
26
+
27
+.org-tree-node:after,
28
+.org-tree-node-children:before {
29
+  border-left: 2px solid #42526E;
30
+}

+ 9
- 9
src/styles/side-detail.scss Wyświetl plik

@@ -83,34 +83,34 @@
83 83
 }
84 84
 
85 85
 // 详情
86
-.side-detail__tabs--default {
86
+/deep/ .side-detail__tabs--default {
87 87
   flex: 1;
88 88
   overflow: hidden;
89 89
 
90
-  /deep/ .el-tabs__header {
90
+  .el-tabs__header {
91 91
     padding: 0 20px;
92 92
   }
93 93
 
94
-  /deep/ .el-tabs__item {
94
+  .el-tabs__item {
95 95
     color: #333;
96 96
     font-size: 12px;
97 97
     top: 2px;
98 98
     margin-top: -2px;
99 99
   }
100 100
 
101
-  /deep/ .el-tabs__nav-scroll {
101
+  .el-tabs__nav-scroll {
102 102
     min-height: 39px;
103 103
   }
104 104
 
105
-  /deep/ .el-tabs__item.is-active {
105
+  .el-tabs__item.is-active {
106 106
     color: #333;
107 107
   }
108 108
 
109
-  /deep/ .el-tabs {
109
+  .el-tabs {
110 110
     height: calc(100% - 15px) !important;
111 111
   }
112 112
 
113
-  /deep/ .el-tabs__content {
113
+  .el-tabs__content {
114 114
     height: calc(100% - 55px) !important;
115 115
     padding: 0;
116 116
     overflow: hidden;
@@ -118,11 +118,11 @@
118 118
 
119 119
     .el-tab-pane {
120 120
       height: 100%;
121
-      overflow: hidden;
121
+      overflow-y: auto;
122 122
     }
123 123
   }
124 124
 
125
-  /deep/ .el-tabs__nav-wrap::after {
125
+  .el-tabs__nav-wrap::after {
126 126
     height: 1px;
127 127
   }
128 128
 

+ 1
- 1
src/styles/table.scss Wyświetl plik

@@ -99,7 +99,7 @@
99 99
   }
100 100
 }
101 101
 
102
-// 头部为白色 不加粗的表风格
102
+// 头部为白色 不加粗的表风格  人资模块
103 103
 .el-table-header--white {
104 104
   th {
105 105
     border-right-width: 0;

+ 1
- 1
src/views/admin/config/index.vue Wyświetl plik

@@ -173,7 +173,7 @@ export default {
173 173
         this.loading = true
174 174
         adminSystemSaveAPI({
175 175
           name: this.name,
176
-          logo: this.save_name
176
+          logo: this.save_name || this.logo && this.logo.split('uploads/')[1]
177 177
         })
178 178
           .then(res => {
179 179
             this.loading = false

+ 2
- 2
src/views/admin/crm/components/FieldInfo.vue Wyświetl plik

@@ -121,7 +121,7 @@
121 121
         字数上限
122 122
       </div>
123 123
       <el-input
124
-        v-model="field.maxLength"
124
+        v-model="field.max_length"
125 125
         :maxlength="4"
126 126
         :disabled="disabled"/>
127 127
       <div class="input-tips"><span>*</span>上限为2000字</div>
@@ -200,7 +200,7 @@ export default {
200 200
           is_null: false, // 是否必填
201 201
           is_hidden: false, // 是否隐藏字段
202 202
           input_tips: '', // 输入提示
203
-          maxLength: '', // textarea 多行文本有最大数量
203
+          max_length: '', // textarea 多行文本有最大数量
204 204
           default_value: '', // 默认值
205 205
           setting: '', // 接口返回setting数据
206 206
           showSetting: '', // 单选选项

+ 8
- 6
src/views/admin/crm/customField/index.vue Wyświetl plik

@@ -40,10 +40,6 @@
40 40
               type="text"
41 41
               size="small"
42 42
               @click="handleCustomField('edit', scope.row, scope.$index)">编辑</el-button>
43
-            <el-button
44
-              type="text"
45
-              size="small"
46
-              @click="handleCustomField('preview', scope.row, scope.$index)">预览</el-button>
47 43
           </template>
48 44
         </el-table-column>
49 45
       </el-table>
@@ -115,7 +111,7 @@ export default {
115 111
     handleCustomField(type, item, index) {
116 112
       if (type == 'edit') {
117 113
         this.$router.push({
118
-          name: 'handlefield',
114
+          name: 'customField',
119 115
           params: {
120 116
             // type: {
121 117
             //   1: 'crm_leads',
@@ -137,7 +133,9 @@ export default {
137 133
               'crm_business': 5,
138 134
               'crm_contract': 6,
139 135
               'crm_receivables': 7,
140
-              'crm_visit': 17
136
+              'crm_visit': 17,
137
+              'crm_invoice': 18,
138
+              'crm_receivables_plan': 19
141 139
             }[item.types]
142 140
           }
143 141
         })
@@ -167,6 +165,10 @@ export default {
167 165
         return require('@/assets/img/crm/receivables.png')
168 166
       } else if (types === 'crm_visit') {
169 167
         return require('@/assets/img/crm/visit.png')
168
+      } else if (types === 'crm_invoice') {
169
+        return require('@/assets/img/crm/invoice.png')
170
+      } else if (types === 'crm_receivables_plan') {
171
+        return require('@/assets/img/crm/receivables_plan.png')
170 172
       }
171 173
       return require('@/assets/img/crm/product.png')
172 174
     }

+ 39
- 0
src/views/admin/fields/components/FieldItem/FieldBoolean.vue Wyświetl plik

@@ -0,0 +1,39 @@
1
+<template>
2
+  <field-wrapper
3
+    :activate="activate"
4
+    :field="field"
5
+    :control-flag="controlFlag"
6
+    class="field-boolean"
7
+    @click="emitClick"
8
+    @action="handleAction">
9
+    <el-switch
10
+      v-model="field.default_value"
11
+      active-value="1"
12
+      inactive-value="0" />
13
+  </field-wrapper>
14
+</template>
15
+
16
+<script>
17
+import FieldWrapper from './FieldWrapper'
18
+import mixins from './mixins'
19
+
20
+export default {
21
+  name: 'FieldBoolean',
22
+  components: {
23
+    FieldWrapper
24
+  },
25
+  mixins: [mixins],
26
+  methods: {}
27
+}
28
+</script>
29
+
30
+<style scoped lang="scss">
31
+.box {
32
+  font-size: 14px;
33
+  padding: 10px 0;
34
+  border: 1px solid #e1e1e1;
35
+  border-radius: 3px;
36
+  background: white;
37
+  text-align: center;
38
+}
39
+</style>

+ 86
- 0
src/views/admin/fields/components/FieldItem/FieldCheckbox.vue Wyświetl plik

@@ -0,0 +1,86 @@
1
+<template>
2
+  <field-wrapper
3
+    :activate="activate"
4
+    :field="field"
5
+    :control-flag="controlFlag"
6
+    class="field-checkbox"
7
+    @click="emitClick"
8
+    @action="handleAction">
9
+
10
+    <el-checkbox-group
11
+      v-if="field.precisions === 1"
12
+      v-model="field.default_value"
13
+      :disabled="disabled">
14
+      <el-checkbox
15
+        v-for="(item, index) in field.setting"
16
+        :key="index"
17
+        :label="item"
18
+        class="checkbox" />
19
+    </el-checkbox-group>
20
+
21
+    <div
22
+      v-else
23
+      class="select-content">
24
+      <el-select
25
+        v-model="field.default_value"
26
+        multiple
27
+        placeholder="请选择">
28
+        <el-option
29
+          v-for="(item, index) in field.setting"
30
+          :key="index"
31
+          :label="item"
32
+          :value="item" />
33
+      </el-select>
34
+      <div class="mask" />
35
+    </div>
36
+
37
+  </field-wrapper>
38
+</template>
39
+
40
+<script>
41
+import FieldWrapper from './FieldWrapper'
42
+import mixins from './mixins'
43
+
44
+export default {
45
+  name: 'FieldCheckbox',
46
+  components: {
47
+    FieldWrapper
48
+  },
49
+  mixins: [mixins],
50
+  watch: {
51
+    field: {
52
+      handler() {
53
+        // 兼容老字段,确保状态不变
54
+        if (!this.field.precisions) {
55
+          this.$set(this.field, 'precisions', 1)
56
+        }
57
+      },
58
+      deep: true,
59
+      immediate: true
60
+    }
61
+  }
62
+}
63
+</script>
64
+
65
+<style scoped lang="scss">
66
+.select-content {
67
+  position: relative;
68
+  width: 100%;
69
+  color: #333;
70
+
71
+  .mask {
72
+    position: absolute;
73
+    top: 0;
74
+    left: 0;
75
+    z-index: 100;
76
+    width: 100%;
77
+    height: 100%;
78
+    background-color: transparent;
79
+    display: block;
80
+  }
81
+
82
+  .el-select {
83
+    width: 100%;
84
+  }
85
+}
86
+</style>

+ 56
- 0
src/views/admin/fields/components/FieldItem/FieldDateInterval.vue Wyświetl plik

@@ -0,0 +1,56 @@
1
+<template>
2
+  <field-wrapper
3
+    :activate="activate"
4
+    :field="field"
5
+    :control-flag="controlFlag"
6
+    class="field-date-interval"
7
+    @click="emitClick"
8
+    @action="handleAction">
9
+
10
+    <flexbox class="range-box">
11
+      <i class="el-icon-date icon" />
12
+      <flexbox-item :class="{ placeholder: !Boolean(field.default_value) }">
13
+        {{ field.default_value[0] || '开始时间' }}
14
+      </flexbox-item>
15
+      <span>至</span>
16
+      <flexbox-item :class="{ placeholder: !Boolean(field.default_value) }">
17
+        {{ field.default_value[1] || '结束时间' }}
18
+      </flexbox-item>
19
+    </flexbox>
20
+
21
+  </field-wrapper>
22
+</template>
23
+
24
+<script>
25
+import FieldWrapper from './FieldWrapper'
26
+import mixins from './mixins'
27
+
28
+export default {
29
+  name: 'FieldDateInterval',
30
+  components: {
31
+    FieldWrapper
32
+  },
33
+  mixins: [mixins],
34
+  methods: {}
35
+}
36
+</script>
37
+
38
+<style scoped lang="scss">
39
+.range-box {
40
+  width: 100%;
41
+  text-align: center;
42
+  font-size: 14px;
43
+  border: 1px solid #e1e1e1;
44
+  border-radius: 3px;
45
+  background: white;
46
+  padding: 10px;
47
+
48
+  .icon {
49
+    color: #999;
50
+  }
51
+  .placeholder {
52
+    flex: 1;
53
+    color: #999;
54
+  }
55
+}
56
+</style>

+ 77
- 0
src/views/admin/fields/components/FieldItem/FieldDescText.vue Wyświetl plik

@@ -0,0 +1,77 @@
1
+<template>
2
+  <field-wrapper
3
+    :activate="activate"
4
+    :field="field"
5
+    :control-flag="controlFlag"
6
+    hidden-title
7
+    class="field-desc-text"
8
+    @click="emitClick"
9
+    @action="handleAction">
10
+
11
+    <tinymce
12
+      :value="field.default_value"
13
+      :disabled="true"
14
+      :toolbar="[]"
15
+      :init="{
16
+        menubar: false,
17
+        toolbar_sticky: true,
18
+        statusbar: false,
19
+        placeholder: '描述文字内容',
20
+        quickbars_selection_toolbar: false,
21
+        contextmenu: '',
22
+        content_style: ' * {color: #262626; margin: 0;} body { font-size: 14px; }',
23
+        plugins: 'autoresize',
24
+        autoresize_bottom_margin: 0
25
+      }"
26
+      class="rich-txt" />
27
+    <div class="field-desc-text-cover"/>
28
+  </field-wrapper>
29
+</template>
30
+
31
+<script>
32
+import FieldWrapper from './FieldWrapper'
33
+import Tinymce from '@/components/Tinymce'
34
+import mixins from './mixins'
35
+
36
+export default {
37
+  name: 'FieldDescText',
38
+  components: {
39
+    FieldWrapper,
40
+    Tinymce
41
+  },
42
+  mixins: [mixins],
43
+  methods: {}
44
+}
45
+</script>
46
+
47
+<style lang="scss">
48
+.field-desc-text {
49
+  .tox-tinymce {
50
+    border: none;
51
+  }
52
+
53
+  .tox .tox-edit-area__iframe {
54
+    background-color: unset;
55
+  }
56
+}
57
+</style>
58
+<style scoped lang="scss">
59
+.field-desc-text {
60
+  padding: 15px 10px;
61
+
62
+  &-cover {
63
+    position: absolute;
64
+    top: 0;
65
+    right: 0;
66
+    bottom: 0;
67
+    left: 0;
68
+    z-index: 1;
69
+    cursor: move;
70
+  }
71
+}
72
+.rich-txt {
73
+  width: 100%;
74
+  background-color: white;
75
+  border: 0 none;
76
+}
77
+</style>

+ 224
- 0
src/views/admin/fields/components/FieldItem/FieldDetailTable.vue Wyświetl plik

@@ -0,0 +1,224 @@
1
+<template>
2
+  <field-wrapper
3
+    :activate="activate"
4
+    :field="field"
5
+    :control-flag="controlFlag"
6
+    class="field-detail-table"
7
+    @click="emitClick"
8
+    @action="handleAction">
9
+
10
+    <div
11
+      :class="{'is-empty': isEmpty}"
12
+      class="box">
13
+      <draggable
14
+        :list="list"
15
+        :options="dragListConfig"
16
+        :class="{'is-table': field.precisions === 2}"
17
+        class="field-list"
18
+        @end="dragListEnd"
19
+        @add="dragAdded">
20
+        <div v-if="isEmpty" class="empty-box">
21
+          <div class="empty-box-title">可拖拽添加多个字段</div>
22
+          <div class="empty-box-desc">(不支持明细中添加明细字段)</div>
23
+        </div>
24
+
25
+        <template v-if="!isEmpty && field.precisions === 1">
26
+          <component
27
+            v-for="(field, index) in list"
28
+            :key="index"
29
+            :is="field | typeToComponentName"
30
+            :field="field"
31
+            :point="[index, 0]"
32
+            :active-point="[null, null]"
33
+            class="draggable-hook"
34
+            @click="emitClick" />
35
+        </template>
36
+        <template v-if="!isEmpty && field.precisions === 2">
37
+          <el-table
38
+            :data="tableData"
39
+            border
40
+            style="width: 100%">
41
+            <el-table-column
42
+              v-for="(field, index) in list"
43
+              :key="index"
44
+              :prop="field.fieldName"
45
+              :label="field.name">
46
+              <template slot-scope="scope">
47
+                <div class="input-box" />
48
+              </template>
49
+            </el-table-column>
50
+            <el-table-column label="操作" fixed="right">
51
+              <template slot-scope="scope">
52
+                <el-button>删除</el-button>
53
+              </template>
54
+            </el-table-column>
55
+          </el-table>
56
+        </template>
57
+      </draggable>
58
+
59
+      <div v-if="!isEmpty" class="add-btn">
60
+        <el-button type="text">
61
+          <i class="wk wk-l-plus" />
62
+          {{ field.remark || '' }}
63
+        </el-button>
64
+      </div>
65
+    </div>
66
+
67
+  </field-wrapper>
68
+</template>
69
+
70
+<script>
71
+import FieldWrapper from './FieldWrapper'
72
+import draggable from 'vuedraggable'
73
+
74
+import mixins from './mixins'
75
+import { isEmpty } from '@/utils/types'
76
+import { typeToComponent } from '../../utils'
77
+
78
+export default {
79
+  name: 'FieldDetailTable',
80
+  components: {
81
+    draggable,
82
+    FieldWrapper,
83
+
84
+    'FieldInput': () => import('./FieldInput'),
85
+    'FieldTextarea': () => import('./FieldTextarea'),
86
+    'FieldSelect': () => import('./FieldSelect'),
87
+    'FieldCheckbox': () => import('./FieldCheckbox'),
88
+    'FieldFile': () => import('./FieldFile'),
89
+    'FieldBoolean': () => import('./FieldBoolean'),
90
+    'FieldPercent': () => import('./FieldPercent'),
91
+    'FieldPosition': () => import('./FieldPosition'),
92
+    'FieldLocation': () => import('./FieldLocation'),
93
+    'FieldWritingSign': () => import('./FieldWritingSign'),
94
+    'FieldDateInterval': () => import('./FieldDateInterval'),
95
+    'FieldDescText': () => import('./FieldDescText')
96
+  },
97
+  filters: {
98
+    /** 根据type 找到组件 */
99
+    typeToComponentName(item) {
100
+      return typeToComponent(item)
101
+    }
102
+  },
103
+  mixins: [mixins],
104
+  data() {
105
+    return {
106
+      dragListConfig: {
107
+        delay: 50,
108
+        group: {
109
+          name: 'childList',
110
+          put: ['libList'],
111
+          pull: false
112
+        },
113
+        sort: false,
114
+        forceFallback: true,
115
+        fallbackClass: 'draggingStyle',
116
+        filter: '.empty-box'
117
+      },
118
+      selectedPoint: [null, null],
119
+
120
+      tableData: [{}]
121
+    }
122
+  },
123
+  computed: {
124
+    isEmpty() {
125
+      return isEmpty(this.field.fieldExtendList)
126
+    },
127
+    isList() {
128
+      return true
129
+    },
130
+    list() {
131
+      return this.isEmpty ? [] : this.field.fieldExtendList
132
+    }
133
+  },
134
+  methods: {
135
+    dragListEnd(evt) {
136
+      // console.log('table drag list end', evt)
137
+    },
138
+    /**
139
+     * 拖拽派发新增事件
140
+     * @param evt
141
+     */
142
+    dragAdded(evt) {
143
+      this.$emit('child-drag-add', this.point, evt)
144
+      this.$nextTick(() => {
145
+        this.selectedPoint = [evt.newIndex, 0]
146
+      })
147
+    }
148
+  }
149
+}
150
+</script>
151
+
152
+<style scoped lang="scss">
153
+.box {
154
+  width: 100%;
155
+  font-size: 14px;
156
+  border-radius: 3px;
157
+  border: 1px solid #e1e1e1;
158
+  background-color: white;
159
+  padding: 5px;
160
+
161
+  &.is-empty {
162
+    border: unset;
163
+    background-color: #f7f8fa;
164
+  }
165
+
166
+  .add-btn {
167
+    display: flex;
168
+    align-items: center;
169
+    justify-content: flex-end;
170
+    padding-right: 10px;
171
+    .wk-l-plus {
172
+      font-size: 12px;
173
+    }
174
+  }
175
+
176
+  .empty-box {
177
+    width: 100%;
178
+    text-align: center;
179
+    background-color: #f7f8fa;
180
+    padding: 25px 0;
181
+    .empty-box-title {
182
+      color: $xr-color-text-primary;
183
+    }
184
+    .empty-box-desc {
185
+      color: $xr-color-text-placeholder;
186
+      font-size: 12px;
187
+      margin-top: 5px;
188
+    }
189
+  }
190
+
191
+  .field-list {
192
+    display: flex;
193
+    align-items: flex-start;
194
+    justify-content: flex-start;
195
+    flex-wrap: wrap;
196
+
197
+    &.is-table {
198
+      flex-wrap: nowrap;
199
+      overflow-x: auto;
200
+    }
201
+  }
202
+
203
+  .input-box {
204
+    width: 100%;
205
+    height: 30px;
206
+    border: 1px solid #dcdfe6;
207
+    padding: 3px 0;
208
+  }
209
+
210
+  .table-field {
211
+    width: 200px;
212
+    /deep/ .field-item {
213
+      padding-bottom: 0;
214
+      .field-item_title {
215
+        border: 1px solid #dcdfe6;
216
+        padding-left: 10px;
217
+      }
218
+      .field-item_body {
219
+        padding: 10px 5px;
220
+      }
221
+    }
222
+  }
223
+}
224
+</style>

+ 41
- 0
src/views/admin/fields/components/FieldItem/FieldFile.vue Wyświetl plik

@@ -0,0 +1,41 @@
1
+<template>
2
+  <field-wrapper
3
+    :activate="activate"
4
+    :field="field"
5
+    :control-flag="controlFlag"
6
+    class="field-file"
7
+    @click="emitClick"
8
+    @action="handleAction">
9
+
10
+    <div class="box">
11
+      请选择文件
12
+    </div>
13
+
14
+  </field-wrapper>
15
+</template>
16
+
17
+<script>
18
+import FieldWrapper from './FieldWrapper'
19
+import mixins from './mixins'
20
+
21
+export default {
22
+  name: 'FieldFile',
23
+  components: {
24
+    FieldWrapper
25
+  },
26
+  mixins: [mixins],
27
+  methods: {}
28
+}
29
+</script>
30
+
31
+<style scoped lang="scss">
32
+.box {
33
+  width: 100px;
34
+  text-align: center;
35
+  font-size: 14px;
36
+  border: 1px solid #e1e1e1;
37
+  border-radius: 3px;
38
+  background: white;
39
+  padding: 10px 0;
40
+}
41
+</style>

+ 47
- 0
src/views/admin/fields/components/FieldItem/FieldInput.vue Wyświetl plik

@@ -0,0 +1,47 @@
1
+<template>
2
+  <field-wrapper
3
+    :activate="activate"
4
+    :field="field"
5
+    :control-flag="controlFlag"
6
+    class="field-input"
7
+    @click="emitClick"
8
+    @action="handleAction">
9
+
10
+    <flexbox align="center" class="box">
11
+      <span class="default-val">
12
+        {{ typeof field.default_value == 'string' ? field.default_value : '' }}
13
+      </span>
14
+    </flexbox>
15
+
16
+  </field-wrapper>
17
+</template>
18
+
19
+<script>
20
+import FieldWrapper from './FieldWrapper'
21
+import mixins from './mixins'
22
+
23
+export default {
24
+  name: 'FieldInput',
25
+  components: {
26
+    FieldWrapper
27
+  },
28
+  mixins: [mixins],
29
+  methods: {}
30
+}
31
+</script>
32
+
33
+<style scoped lang="scss">
34
+.box {
35
+  width: 100%;
36
+  height: 32px;
37
+  font-size: 14px;
38
+  border: 1px solid #dcdfe6;
39
+  border-radius: $xr-border-radius-base;
40
+  background: white;
41
+  padding: 0 10px;
42
+
43
+  .default-val {
44
+    color: #333;
45
+  }
46
+}
47
+</style>

+ 45
- 0
src/views/admin/fields/components/FieldItem/FieldLocation.vue Wyświetl plik

@@ -0,0 +1,45 @@
1
+<template>
2
+  <field-wrapper
3
+    :activate="activate"
4
+    :field="field"
5
+    :control-flag="controlFlag"
6
+    class="field-current-position"
7
+    @click="emitClick"
8
+    @action="handleAction">
9
+
10
+    <flexbox align="center" class="box">
11
+      <flexbox-item class="default-val" />
12
+      <span class="wk wk-icon-location" />
13
+    </flexbox>
14
+
15
+  </field-wrapper>
16
+</template>
17
+
18
+<script>
19
+import FieldWrapper from './FieldWrapper'
20
+import mixins from './mixins'
21
+
22
+export default {
23
+  name: 'FieldCurrentPosition',
24
+  components: {
25
+    FieldWrapper
26
+  },
27
+  mixins: [mixins],
28
+  methods: {}
29
+}
30
+</script>
31
+
32
+<style scoped lang="scss">
33
+.box {
34
+  width: 100%;
35
+  height: 32px;
36
+  font-size: 14px;
37
+  border: 1px solid #dcdfe6;
38
+  border-radius: $xr-border-radius-base;
39
+  background: white;
40
+  padding: 0 10px;
41
+  .wk-icon-location {
42
+    color: #999999;
43
+  }
44
+}
45
+</style>

+ 52
- 0
src/views/admin/fields/components/FieldItem/FieldPercent.vue Wyświetl plik

@@ -0,0 +1,52 @@
1
+<template>
2
+  <field-wrapper
3
+    :activate="activate"
4
+    :field="field"
5
+    :control-flag="controlFlag"
6
+    class="field-percent"
7
+    @click="emitClick"
8
+    @action="handleAction">
9
+
10
+    <div class="box">
11
+      <span class="default-val">{{ field.default_value || '' }}</span>
12
+      <span class="rate">%</span>
13
+    </div>
14
+
15
+  </field-wrapper>
16
+</template>
17
+
18
+<script>
19
+import FieldWrapper from './FieldWrapper'
20
+import mixins from './mixins'
21
+
22
+export default {
23
+  name: 'FieldPercent',
24
+  components: {
25
+    FieldWrapper
26
+  },
27
+  mixins: [mixins],
28
+  methods: {}
29
+}
30
+</script>
31
+
32
+<style scoped lang="scss">
33
+.box {
34
+  position: relative;
35
+  width: 100%;
36
+  height: 32px;
37
+  line-height: 32px;
38
+  font-size: 14px;
39
+  color: #333;
40
+  border: 1px solid #dcdfe6;
41
+  border-radius: $xr-border-radius-base;
42
+  background: white;
43
+  padding: 0 30px 0 10px;
44
+
45
+  .rate {
46
+    position: absolute;
47
+    top: 50%;
48
+    right: 10px;
49
+    transform: translateY(-50%);
50
+  }
51
+}
52
+</style>

+ 79
- 0
src/views/admin/fields/components/FieldItem/FieldPosition.vue Wyświetl plik

@@ -0,0 +1,79 @@
1
+<template>
2
+  <field-wrapper
3
+    :activate="activate"
4
+    :field="field"
5
+    :control-flag="controlFlag"
6
+    class="field-map-position"
7
+    @click="emitClick"
8
+    @action="handleAction">
9
+
10
+    <flexbox class="box-select">
11
+      <div :class="{placeholder: !Boolean(areaText)}">
12
+        {{ areaText || '请选择' }}
13
+      </div>
14
+      <i class="el-icon-arrow-down el-icon--right"/>
15
+    </flexbox>
16
+    <div
17
+      v-if="field.precisions === 1"
18
+      class="box-textarea">
19
+      <div :class="{placeholder: !Boolean(detailAddress)}">
20
+        {{ detailAddress || '详细地址' }}
21
+      </div>
22
+    </div>
23
+
24
+  </field-wrapper>
25
+</template>
26
+
27
+<script>
28
+import FieldWrapper from './FieldWrapper'
29
+import mixins from './mixins'
30
+import { isEmpty } from '@/utils/types'
31
+
32
+export default {
33
+  name: 'FieldPosition',
34
+  components: {
35
+    FieldWrapper
36
+  },
37
+  mixins: [mixins],
38
+  computed: {
39
+    areaText() {
40
+      if (isEmpty(this.field.default_value)) return ''
41
+      return this.field.default_value
42
+        .filter(o => o.id !== 4)
43
+        .map(o => o.name)
44
+        .join('/')
45
+    },
46
+    detailAddress() {
47
+      if (isEmpty(this.field.default_value)) return ''
48
+      const findRes = this.field.default_value.find(o => o.id === 4)
49
+      if (!findRes) return ''
50
+      return findRes.name
51
+    }
52
+  },
53
+  methods: {}
54
+}
55
+</script>
56
+
57
+<style scoped lang="scss">
58
+.placeholder {
59
+  color: #999;
60
+}
61
+.box-select {
62
+  width: 100%;
63
+  color: #333;
64
+  border: 1px solid #dcdfe6;
65
+  border-radius: $xr-border-radius-base;
66
+  padding: 10px;
67
+  div {
68
+    flex: 1;
69
+  }
70
+}
71
+.box-textarea {
72
+  width: 100%;
73
+  height: 100px;
74
+  border: 1px solid #dcdfe6;
75
+  border-radius: $xr-border-radius-base;
76
+  margin-top: 10px;
77
+  padding: 10px;
78
+}
79
+</style>

+ 79
- 0
src/views/admin/fields/components/FieldItem/FieldSelect.vue Wyświetl plik

@@ -0,0 +1,79 @@
1
+<template>
2
+  <field-wrapper
3
+    :activate="activate"
4
+    :field="field"
5
+    :control-flag="controlFlag"
6
+    class="field-select"
7
+    @click="emitClick"
8
+    @action="handleAction">
9
+
10
+    <el-radio-group
11
+      v-if="field.precisions === 1"
12
+      v-model="field.default_value">
13
+      <el-radio
14
+        v-for="(item, index) in field.setting"
15
+        :key="index"
16
+        :label="item">
17
+        {{ item }}
18
+      </el-radio>
19
+    </el-radio-group>
20
+
21
+    <flexbox
22
+      v-else
23
+      class="select-box">
24
+      <div :class="{placeholder: !Boolean(field.default_value)}">
25
+        {{ field.default_value ? field.default_value :'请选择' }}
26
+      </div>
27
+      <i class="el-icon-arrow-down el-icon--right"/>
28
+    </flexbox>
29
+
30
+  </field-wrapper>
31
+</template>
32
+
33
+<script>
34
+import FieldWrapper from './FieldWrapper'
35
+import mixins from './mixins'
36
+
37
+export default {
38
+  name: 'FieldSelect',
39
+  components: {
40
+    FieldWrapper
41
+  },
42
+  mixins: [mixins],
43
+  watch: {
44
+    field: {
45
+      handler() {
46
+        // 兼容老字段,确保状态不变
47
+        if (!this.field.precisions) {
48
+          this.$set(this.field, 'precisions', 2)
49
+        }
50
+      },
51
+      deep: true,
52
+      immediate: true
53
+    }
54
+  }
55
+}
56
+</script>
57
+
58
+<style scoped lang="scss">
59
+.select-box {
60
+  width: 100%;
61
+  color: #333;
62
+  border: 1px solid #dcdfe6;
63
+  border-radius: $xr-border-radius-base;
64
+  padding: 8px 10px;
65
+  div {
66
+    flex: 1;
67
+  }
68
+  .placeholder {
69
+    color: #999;
70
+  }
71
+}
72
+
73
+.el-radio-group {
74
+  width: 100%;
75
+  .el-radio {
76
+    margin: 5px 30px 5px 0;
77
+  }
78
+}
79
+</style>

+ 63
- 0
src/views/admin/fields/components/FieldItem/FieldTextarea.vue Wyświetl plik

@@ -0,0 +1,63 @@
1
+<template>
2
+  <field-wrapper
3
+    :activate="activate"
4
+    :field="field"
5
+    :control-flag="controlFlag"
6
+    class="field-textarea"
7
+    @click="emitClick"
8
+    @action="handleAction">
9
+
10
+    <div class="box">
11
+      <div class="default-val">
12
+        {{ typeof field.default_value == 'string' ? field.default_value : '' }}
13
+      </div>
14
+      <div class="max-tips">
15
+        {{ field.default_value.length+'/'+(field.max_length || 800) }}
16
+      </div>
17
+    </div>
18
+
19
+  </field-wrapper>
20
+</template>
21
+
22
+<script>
23
+import FieldWrapper from './FieldWrapper'
24
+import mixins from './mixins'
25
+
26
+export default {
27
+  name: 'FieldTextarea',
28
+  components: {
29
+    FieldWrapper
30
+  },
31
+  mixins: [mixins],
32
+  methods: {}
33
+}
34
+</script>
35
+
36
+<style scoped lang="scss">
37
+.box {
38
+  position: relative;
39
+  width: 100%;
40
+  height: 80px;
41
+  font-size: 14px;
42
+  color: #333;
43
+  background: white;
44
+  border: 1px solid #dcdfe6;
45
+  border-radius: $xr-border-radius-base;
46
+  padding: 10px 10px 15px 10px;
47
+
48
+  .default-val {
49
+    width: 100%;
50
+    height: 48px;
51
+    word-break: break-all;
52
+    overflow: hidden;
53
+  }
54
+  .max-tips {
55
+    width: 100%;
56
+    text-align: right;
57
+    font-size: 12px;
58
+    color: #999;
59
+    word-break: break-all;
60
+    overflow: hidden;
61
+  }
62
+}
63
+</style>

+ 216
- 0
src/views/admin/fields/components/FieldItem/FieldWrapper.vue Wyświetl plik

@@ -0,0 +1,216 @@
1
+<template>
2
+  <div
3
+    :style="{width: fieldWidth}"
4
+    :class="{activate: activate}"
5
+    class="field-item"
6
+    @click.stop="emitClick">
7
+    <div v-if="!hiddenTitle" class="field-item_title">
8
+      <span class="required">{{ field.is_null ? '*' : '' }}</span>
9
+      <span>{{ field.name }}</span>
10
+      <span
11
+        v-if="field.input_tips"
12
+        class="input-tips">
13
+        ({{ field.input_tips }})
14
+      </span>
15
+    </div>
16
+
17
+    <div class="field-item_body">
18
+      <slot />
19
+    </div>
20
+
21
+    <template v-if="activate">
22
+      <div
23
+        v-if="controlFlag.top"
24
+        class="control-top control-btn"
25
+        @click.stop="handleControl('top', $event)">
26
+        <i class="wk wk-icon-top" />
27
+      </div>
28
+      <div
29
+        v-if="controlFlag.bottom"
30
+        class="control-bottom control-btn"
31
+        @click.stop="handleControl('bottom', $event)">
32
+        <i class="wk wk-icon-top bottom" />
33
+      </div>
34
+      <div
35
+        v-if="controlFlag.left"
36
+        class="control-left control-btn"
37
+        @click.stop="handleControl('left', $event)">
38
+        <i class="wk wk-transfer" />
39
+      </div>
40
+      <div
41
+        v-if="controlFlag.right"
42
+        class="control-right control-btn"
43
+        @click.stop="handleControl('right', $event)">
44
+        <i class="wk wk-transfer" />
45
+      </div>
46
+      <div class="edit-box">
47
+        <div
48
+          v-if="controlFlag.copy"
49
+          class="control-copy control-btn"
50
+          @click.stop="handleControl('copy', $event)">
51
+          <i class="wk wk-associated" />
52
+        </div>
53
+        <div
54
+          v-if="controlFlag.delete"
55
+          class="control-delete control-btn"
56
+          @click.stop="handleControl('delete', $event)">
57
+          <i class="wk wk-s-delete" />
58
+        </div>
59
+      </div>
60
+    </template>
61
+  </div>
62
+</template>
63
+
64
+<script>
65
+export default {
66
+  name: 'FieldWrapper',
67
+  props: {
68
+    field: { // 字段信息
69
+      type: Object,
70
+      required: true
71
+    },
72
+    activate: { // 当前字段是否已激活
73
+      type: Boolean,
74
+      default: false
75
+    },
76
+    controlFlag: { // 字段控制按钮状态
77
+      type: Object,
78
+      default: () => {
79
+        return {
80
+          top: false,
81
+          bottom: false,
82
+          left: false,
83
+          right: false,
84
+          delete: false,
85
+          copy: true
86
+        }
87
+      }
88
+    },
89
+    hiddenTitle: {
90
+      type: Boolean,
91
+      default: false
92
+    }
93
+  },
94
+  computed: {
95
+    fieldWidth() {
96
+      if (!this.field) return '100%'
97
+      return this.field.style_percent + '%'
98
+    }
99
+  },
100
+  watch: {
101
+    field: {
102
+      handler() {
103
+        if (this.field && !this.field.style_percent) {
104
+          this.field.style_percent = 100
105
+        }
106
+      },
107
+      deep: true,
108
+      immediate: true
109
+    }
110
+  },
111
+  methods: {
112
+    emitClick(evt) {
113
+      this.$emit('click', evt)
114
+    },
115
+    handleControl(action, evt) {
116
+      this.$emit('action', action, evt)
117
+    }
118
+  }
119
+}
120
+</script>
121
+
122
+<style scoped lang="scss">
123
+.field-item {
124
+  position: relative;
125
+  //border-left: 2px solid transparent;
126
+  padding: 0 10px 20px;
127
+  background-color: white;
128
+  cursor: move;
129
+
130
+  &.activate {
131
+    //border-left: 2px solid $xr-color-primary;
132
+    background-color: #f7f8fa;
133
+  }
134
+
135
+  .field-item_title {
136
+    min-height: 34px;
137
+    line-height: 1.5;
138
+    font-size: 13px;
139
+    width: 100%;
140
+    padding: 10px 0 8px;
141
+    word-wrap: break-word;
142
+    word-break: break-all;
143
+
144
+    .required {
145
+      color: #F56C6C;
146
+    }
147
+    .input-tips {
148
+      color: #999;
149
+    }
150
+  }
151
+
152
+  .field-item_body {}
153
+
154
+  .control-btn {
155
+    position: absolute;
156
+    z-index: 1;
157
+    width: 25px;
158
+    height: 25px;
159
+    border-radius: 50%;
160
+    box-shadow: 0 2px 4px 0 rgba(163,163,163,.5);
161
+    background-color: white;
162
+    cursor: pointer;
163
+    display: flex;
164
+    align-items: center;
165
+    justify-content: center;
166
+
167
+    .wk {
168
+      color: #555;
169
+      font-size: 14px;
170
+    }
171
+
172
+    .wk-icon-top {
173
+      font-size: 12px;
174
+      font-weight: bold;
175
+      &.bottom {
176
+        transform: rotate(180deg);
177
+      }
178
+    }
179
+
180
+    &.control-top {
181
+      top: -14px;
182
+      left: 50%;
183
+      transform: translateX(-50%);
184
+    }
185
+    &.control-bottom {
186
+      bottom: -14px;
187
+      left: 50%;
188
+      transform: translateX(-50%);
189
+    }
190
+    &.control-left {
191
+      left: -14px;
192
+      top: 50%;
193
+      transform: translateY(-50%);
194
+    }
195
+    &.control-right {
196
+      right: -14px;
197
+      top: 50%;
198
+      transform: translateY(-50%);
199
+    }
200
+  }
201
+
202
+  .edit-box {
203
+    position: absolute;
204
+    bottom: -14px;
205
+    right: 5%;
206
+    z-index: 1;
207
+    .control-btn {
208
+      position: unset;
209
+      display: inline-flex;
210
+      vertical-align: middle;
211
+      margin: 0 2px;
212
+    }
213
+  }
214
+}
215
+
216
+</style>

+ 38
- 0
src/views/admin/fields/components/FieldItem/FieldWritingSign.vue Wyświetl plik

@@ -0,0 +1,38 @@
1
+<template>
2
+  <field-wrapper
3
+    :activate="activate"
4
+    :field="field"
5
+    :control-flag="controlFlag"
6
+    class="field-writing-sign"
7
+    @click="emitClick"
8
+    @action="handleAction">
9
+
10
+    <div class="box" />
11
+
12
+  </field-wrapper>
13
+</template>
14
+
15
+<script>
16
+import FieldWrapper from './FieldWrapper'
17
+import mixins from './mixins'
18
+
19
+export default {
20
+  name: 'FieldWritingSign',
21
+  components: {
22
+    FieldWrapper
23
+  },
24
+  mixins: [mixins],
25
+  methods: {}
26
+}
27
+</script>
28
+
29
+<style scoped lang="scss">
30
+.box {
31
+  width: 100%;
32
+  height: 150px;
33
+  border: 1px solid #e1e1e1;
34
+  border-radius: 3px;
35
+  background: white;
36
+  padding: 10px 0;
37
+}
38
+</style>

+ 13
- 0
src/views/admin/fields/components/FieldItem/index.js Wyświetl plik

@@ -0,0 +1,13 @@
1
+export { default as FieldInput } from './FieldInput'
2
+export { default as FieldTextarea } from './FieldTextarea'
3
+export { default as FieldSelect } from './FieldSelect'
4
+export { default as FieldCheckbox } from './FieldCheckbox'
5
+export { default as FieldFile } from './FieldFile'
6
+export { default as FieldBoolean } from './FieldBoolean'
7
+export { default as FieldPercent } from './FieldPercent'
8
+export { default as FieldPosition } from './FieldPosition'
9
+export { default as FieldLocation } from './FieldLocation'
10
+export { default as FieldDetailTable } from './FieldDetailTable'
11
+export { default as FieldWritingSign } from './FieldWritingSign'
12
+export { default as FieldDateInterval } from './FieldDateInterval'
13
+export { default as FieldDescText } from './FieldDescText'

+ 132
- 0
src/views/admin/fields/components/FieldItem/mixins.js Wyświetl plik

@@ -0,0 +1,132 @@
1
+import { getFieldAuth } from '../../utils'
2
+import { isEmpty } from '@/utils/types'
3
+
4
+export default {
5
+  props: {
6
+    field: { // 当前字段信息
7
+      type: Object,
8
+      required: true
9
+    },
10
+    fieldArr: { // 全部字段数组,为空时则禁止点击改变位置
11
+      type: Array,
12
+      default: () => []
13
+    },
14
+    point: { // 当前字段坐标
15
+      type: Array
16
+    },
17
+    activePoint: { // 选中的字段坐标
18
+      type: Array,
19
+      default: () => []
20
+    }
21
+  },
22
+  data() {
23
+    return {
24
+      specialFormType: [
25
+        // 'detail_table' // 明细表格
26
+      ]
27
+    }
28
+  },
29
+  computed: {
30
+    // 当前字段是否激活
31
+    activate() {
32
+      return this.point[0] === this.activePoint[0] &&
33
+        this.point[1] === this.activePoint[1]
34
+    },
35
+    /** 只读 */
36
+    disabled() {
37
+      return !this.fieldAuth.defaultEdit
38
+    },
39
+
40
+    fieldAuth() {
41
+      return getFieldAuth(this.field.operating)
42
+    },
43
+    // 向上操作按钮
44
+    topFlag() {
45
+      if (isEmpty(this.fieldArr)) return false
46
+      // 第一行、上一行有4个、上一行为特殊字段类型不显示
47
+      const row = this.point[0]
48
+      if (row === 0) return false
49
+      const prevRow = this.fieldArr[row - 1]
50
+      if (prevRow.length === 4) return false
51
+      if (this.specialFormType.includes(prevRow[0].form_type)) return false
52
+      return true
53
+    },
54
+    // 向下操作按钮
55
+    bottomFlag() {
56
+      if (isEmpty(this.fieldArr)) return false
57
+      // 最后一行、当前行只有一个不显示
58
+      const row = this.point[0]
59
+      if (row === this.fieldArr.length - 1) return false
60
+      // if (this.fieldArr[row].length <= 1) return false
61
+      return true
62
+    },
63
+    // 左侧操作按钮
64
+    leftFlag() {
65
+      if (isEmpty(this.fieldArr)) return false
66
+      // 第一列不显示
67
+      const column = this.point[1]
68
+      if (column === 0) return false
69
+      return true
70
+    },
71
+    // 右侧操作按钮
72
+    rightFlag() {
73
+      if (isEmpty(this.fieldArr)) return false
74
+      // 最后一列不显示
75
+      const column = this.point[1]
76
+      const row = this.point[0]
77
+      if (column === this.fieldArr[row].length - 1) return false
78
+      return true
79
+    },
80
+    // 复制按钮
81
+    copyFlag() {
82
+      if (isEmpty(this.fieldArr)) return false
83
+      return ![
84
+        'customer',
85
+        'business',
86
+        'contacts',
87
+        'contract',
88
+        'receivables_plan',
89
+        'single_user'
90
+      ].includes(this.field.form_type)
91
+    },
92
+    controlFlag() {
93
+      return {
94
+        top: this.topFlag,
95
+        bottom: this.bottomFlag,
96
+        left: this.leftFlag,
97
+        right: this.rightFlag,
98
+        delete: this.fieldAuth.deleteEdit,
99
+        copy: this.copyFlag
100
+      }
101
+    }
102
+  },
103
+  // watch: {
104
+  //   field: {
105
+  //     handler() {
106
+  //       this.$nextTick(() => {
107
+  //         this.$forceUpdate()
108
+  //       })
109
+  //     },
110
+  //     deep: true,
111
+  //     immediate: true
112
+  //   }
113
+  // },
114
+  methods: {
115
+    /**
116
+     * click
117
+     * @param evt
118
+     */
119
+    emitClick(evt) {
120
+      this.$emit('click', evt)
121
+    },
122
+
123
+    /**
124
+     * 点击删除
125
+     * @param action
126
+     * @param evt
127
+     */
128
+    handleAction(action, evt) {
129
+      this.$emit('action', action, this.point, evt)
130
+    }
131
+  }
132
+}

+ 372
- 0
src/views/admin/fields/components/SettingField/SettingDefault.vue Wyświetl plik

@@ -0,0 +1,372 @@
1
+<template>
2
+  <div class="setting-default">
3
+    <el-input
4
+      v-if="type === 'text'"
5
+      v-model="field.default_value"
6
+      :maxlength="field.max_length || 100"
7
+      :disabled="disabled"
8
+      @blur="inputBlur" />
9
+
10
+    <el-input
11
+      v-else-if="type === 'textarea'"
12
+      v-model="field.default_value"
13
+      :maxlength="field.max_length || 800"
14
+      :disabled="disabled"
15
+      @blur="inputBlur" />
16
+
17
+    <el-date-picker
18
+      v-else-if="type === 'datePicker'"
19
+      v-model="field.default_value"
20
+      :disabled="disabled"
21
+      :type="field.form_type === 'date' ? 'date' : 'datetime'"
22
+      :value-format="field.form_type === 'date' ? 'yyyy-MM-dd' : 'yyyy-MM-dd HH:mm:ss'"
23
+      placeholder="请选择" />
24
+
25
+    <el-date-picker
26
+      v-else-if="type === 'date_interval'"
27
+      v-model="field.default_value"
28
+      :type="field.precisions === 1 ? 'daterange' : 'datetimerange'"
29
+      :value-format="field.precisions === 1 ? 'yyyy-MM-dd' : 'yyyy-MM-dd HH:mm:ss'"
30
+      :disabled="disabled"
31
+      start-placeholder="开始日期"
32
+      end-placeholder="结束日期" />
33
+
34
+    <el-select
35
+      v-else-if="type === 'select'"
36
+      v-model="field.default_value"
37
+      :clearable="canClearable"
38
+      :multiple="field.form_type === 'checkbox'"
39
+      :disabled="disabled"
40
+      placeholder="请选择">
41
+      <el-option
42
+        v-for="item in options"
43
+        :key="item.value"
44
+        :label="item.label"
45
+        :value="item.value" />
46
+    </el-select>
47
+
48
+    <template v-else-if="type === 'number'">
49
+      <el-input
50
+        v-model="field.default_value"
51
+        :disabled="disabled"
52
+        @blur="inputBlur">
53
+        <div
54
+          v-if="field.form_type === 'percent'"
55
+          slot="suffix"
56
+          class="el-input__icon">%</div>
57
+      </el-input>
58
+      <div class="input-tips">
59
+        <span>*</span>
60
+        数字的位数必须少于{{ field.form_type === 'percent' ? 10 : 15 }}位
61
+      </div>
62
+    </template>
63
+
64
+    <template v-else-if="type === 'position'">
65
+      <wk-distpicker
66
+        v-model="selectedMapValue"
67
+        :hide-area="field.precisions >= 3"
68
+        :only-province="field.precisions === 4"
69
+        :disabled="disabled"
70
+        clearable
71
+        @change="handleCascaderChange" />
72
+      <el-input
73
+        v-if="field.precisions === 1"
74
+        v-model="detailAddress"
75
+        :rows="3"
76
+        :maxlength="100"
77
+        :disabled="disabled"
78
+        type="textarea"
79
+        style="margin-top: 5px"
80
+        @change="inputPositionChange" />
81
+    </template>
82
+  </div>
83
+</template>
84
+
85
+<script>
86
+import WkDistpicker from '@/components/NewCom/WkDistpicker'
87
+
88
+import { isEmpty, isArray } from '@/utils/types'
89
+import { regexIsCRMMobile, regexIsCRMEmail, objDeepCopy } from '@/utils'
90
+import { getFieldAuth } from '../../utils'
91
+
92
+export default {
93
+  name: 'SettingDefault',
94
+  components: {
95
+    WkDistpicker
96
+  },
97
+  props: {
98
+    field: {
99
+      type: Object,
100
+      required: true
101
+    }
102
+  },
103
+  data() {
104
+    return {
105
+      selectedMapValue: [],
106
+      detailAddress: '',
107
+      oldPrecisions: null,
108
+
109
+      areaData: []
110
+    }
111
+  },
112
+  computed: {
113
+    // 只读
114
+    disabled() {
115
+      return !getFieldAuth(this.field.operating).defaultEdit
116
+    },
117
+    // 是否允许清除默认选项
118
+    canClearable() {
119
+      const form_type = this.field.form_type
120
+      if ([
121
+        'boolean_value'
122
+      ].includes(form_type)) return false
123
+      return true
124
+    },
125
+    // 类型
126
+    type() {
127
+      const form_type = this.field.form_type
128
+      if ([
129
+        'date',
130
+        'datetime'
131
+      ].includes(form_type)) return 'datePicker'
132
+      if ([
133
+        'number',
134
+        'floatnumber',
135
+        'percent'
136
+      ].includes(form_type)) return 'number'
137
+      if ([
138
+        'select',
139
+        'checkbox',
140
+        'boolean_value'
141
+      ].includes(form_type)) return 'select'
142
+      switch (this.field.form_type) {
143
+        case 'date_interval':
144
+          return 'date_interval'
145
+        case 'position':
146
+          return 'position'
147
+        case 'textarea':
148
+          return 'textarea'
149
+        default:
150
+          return 'text'
151
+      }
152
+    },
153
+    // 选项
154
+    options() {
155
+      if (this.type !== 'select') return []
156
+      const form_type = this.field.form_type
157
+      if ([
158
+        'select',
159
+        'checkbox'
160
+      ].includes(form_type)) {
161
+        return this.field.setting.map(o => {
162
+          return { label: o, value: o }
163
+        })
164
+      }
165
+      switch (form_type) {
166
+        case 'boolean_value':
167
+          return [
168
+            { label: '选中', value: '1' },
169
+            { label: '不选中', value: '0' }
170
+          ]
171
+        default:
172
+          return []
173
+      }
174
+    }
175
+  },
176
+  watch: {
177
+    field: {
178
+      handler() {
179
+        if (this.field.form_type === 'boolean_value') {
180
+          // 布尔值
181
+          this.field.default_value = isEmpty(this.field.default_value) ? '0' : this.field.default_value
182
+          return
183
+        }
184
+        if (
185
+          this.type === 'select' &&
186
+          isEmpty(this.field.setting) &&
187
+          !isEmpty(this.field.options)
188
+        ) {
189
+          this.$set(this.field, 'setting', this.field.options.split(','))
190
+        }
191
+        if (this.type === 'position') {
192
+          this.resetDefaultValue()
193
+          // 地址默认值
194
+          if (isEmpty(this.field.default_value)) {
195
+            this.selectedMapValue = []
196
+            if (!isArray(this.field.default_value)) {
197
+              this.field.default_value = []
198
+            }
199
+            this.detailAddress = ''
200
+          } else {
201
+            this.selectedMapValue = this.field.default_value.filter(o => o.id !== 4)
202
+            if (this.field.precisions === 1) {
203
+              const findRes = this.field.default_value.find(o => o.id === 4)
204
+              if (findRes) {
205
+                this.detailAddress = findRes.name
206
+              } else {
207
+                this.detailAddress = ''
208
+              }
209
+            }
210
+          }
211
+        }
212
+      },
213
+      deep: true,
214
+      immediate: true
215
+    }
216
+  },
217
+  methods: {
218
+    inputBlur() {
219
+      // this.$emit('input', this.value)
220
+      if (!this.field.default_value) return
221
+      if (this.field.form_type === 'mobile') {
222
+        // 校验手机号
223
+        if (!regexIsCRMMobile(this.field.default_value)) {
224
+          this.$message.error('输入的手机格式有误')
225
+          this.field.default_value = ''
226
+        }
227
+      } else if (this.field.form_type === 'email') {
228
+        // 校验邮箱
229
+        if (!regexIsCRMEmail(this.field.default_value)) {
230
+          this.$message.error('输入的邮箱格式有误')
231
+          this.field.default_value = ''
232
+        }
233
+      } else if (this.type === 'number') {
234
+        // 校验数字类型
235
+        const num = Number(this.field.default_value) // 去0
236
+        if (isNaN(num)) {
237
+          this.field.default_value = null
238
+          return
239
+        }
240
+        this.field.default_value = String(num)
241
+        const arr = String(num).split('.')
242
+
243
+        const len = String(num)
244
+          .replace('.', '')
245
+          .replace('-', '')
246
+          .length
247
+        const maxlength = this.field.form_type === 'percent' ? 10 : 15
248
+        if (len > maxlength) {
249
+          this.$message.error(`最多支持${maxlength}位数字(包含小数位)`)
250
+          this.field.default_value = null
251
+          return
252
+        }
253
+
254
+        const min = isEmpty(this.field.minNumRestrict) ? -Infinity : Number(this.field.minNumRestrict || -Infinity)
255
+        const max = isEmpty(this.field.maxNumRestrict) ? Infinity : Number(this.field.maxNumRestrict || Infinity)
256
+        if (num < min) {
257
+          this.$message.error('默认值不能小于最小值')
258
+          this.field.default_value = null
259
+          return
260
+        }
261
+        if (num > max) {
262
+          this.$message.error('默认值不能大于最大值')
263
+          this.field.default_value = null
264
+          return
265
+        }
266
+
267
+        // null 不支持小数  0 不限制小数位
268
+        if (isEmpty(this.field.precisions)) {
269
+          this.field.default_value = arr[0]
270
+          return
271
+        }
272
+        if (this.field.precisions === 0) return
273
+        if (arr.length > 1 && arr[1].length > Number(this.field.precisions)) {
274
+          this.$message.error(`默认值的小数位不能大于${this.field.precisions}`)
275
+          this.field.default_value = null
276
+        }
277
+      }
278
+    },
279
+
280
+    /**
281
+     * 修改精度重置默认值
282
+     */
283
+    resetDefaultValue() {
284
+      if (
285
+        !this.oldPrecisions ||
286
+        (this.oldPrecisions === this.field.precisions)
287
+      ) {
288
+        this.oldPrecisions = this.field.precisions
289
+        return
290
+      }
291
+      this.oldPrecisions = this.field.precisions
292
+      this.selectedMapValue = []
293
+      if (!isEmpty(this.field.default_value)) {
294
+        this.field.default_value = []
295
+      }
296
+    },
297
+
298
+    /**
299
+     * 修改详细地址
300
+     */
301
+    inputPositionChange() {
302
+      if (this.field.precisions !== 1) {
303
+        const findIndex = this.field.default_value.findIndex(o => o.id === 4)
304
+        if (findIndex === -1) return
305
+        this.field.default_value.splice(findIndex, 1)
306
+      } else {
307
+        const findRes = this.field.default_value.find(o => o.id === 4)
308
+        if (findRes) {
309
+          findRes.name = this.detailAddress
310
+        } else {
311
+          this.field.default_value.push({
312
+            code: '',
313
+            name: this.detailAddress,
314
+            id: 4
315
+          })
316
+        }
317
+      }
318
+    },
319
+
320
+    /**
321
+     * 选择省市区
322
+     */
323
+    handleCascaderChange() {
324
+      this.field.default_value = this.selectedMapValue
325
+      this.inputPositionChange()
326
+    },
327
+
328
+    getCascaderValArr(data, value) {
329
+      const res = []
330
+      if (value.length === 0) return res
331
+      let index = 0
332
+      let _data = objDeepCopy(data)
333
+      do {
334
+        const findRes = _data.find(o => o.code === value[index])
335
+        if (findRes) {
336
+          _data = findRes.children || []
337
+          res.push({
338
+            code: findRes.code,
339
+            name: findRes.name,
340
+            id: index + 1
341
+          })
342
+        }
343
+        index++
344
+      } while (index <= value.length)
345
+      return res
346
+    }
347
+  }
348
+}
349
+</script>
350
+
351
+<style scoped lang="scss">
352
+.el-date-editor {
353
+  width: 100%;
354
+}
355
+.el-select {
356
+  width: 100%;
357
+}
358
+.el-cascader {
359
+  width: 100%;
360
+}
361
+.el-input__icon {
362
+  color: #333333;
363
+}
364
+.input-tips {
365
+  font-size: 12px;
366
+  margin-top: 10px;
367
+  color: #999;
368
+  span {
369
+    color: red;
370
+  }
371
+}
372
+</style>

+ 88
- 0
src/views/admin/fields/components/SettingField/SettingDescText.vue Wyświetl plik

@@ -0,0 +1,88 @@
1
+<template>
2
+  <div class="setting-rich-text">
3
+    <tinymce
4
+      ref="createTinymce"
5
+      v-model="field.default_value"
6
+      :init="getEditConfig()"
7
+      :height="200"
8
+      toolbar="bold italic underline strikethrough | fontselect | forecolor backcolor | fontsizeselect | numlist bullist | alignleft aligncenter alignright | image link | removeformat"
9
+      class="rich-txt"
10
+      @input="debouncedEditorInput" />
11
+  </div>
12
+</template>
13
+
14
+<script>
15
+import Tinymce from '@/components/Tinymce'
16
+import { debounce } from 'throttle-debounce'
17
+
18
+export default {
19
+  name: 'SettingRichText',
20
+  components: {
21
+    Tinymce
22
+  },
23
+  props: {
24
+    field: {
25
+      type: Object,
26
+      required: true
27
+    }
28
+  },
29
+  data() {
30
+    return {
31
+      debouncedEditorInput: null
32
+    }
33
+  },
34
+  created() {
35
+    this.debouncedEditorInput = debounce(300, this.editInputChange)
36
+  },
37
+  methods: {
38
+    getEditConfig() {
39
+      return {
40
+        menubar: false,
41
+        statusbar: false,
42
+        paste_data_images: true, // 允许粘贴图片
43
+        paste_enable_default_filters: false,
44
+        placeholder: '描述文字内容',
45
+        content_style: ' * {color: #262626; margin: 0;} body { margin: 8px; font-size: 14px; }',
46
+        paste_retain_style_properties: 'border', // 粘贴内容时要保留的样式
47
+        toolbar_mode: 'scrolling',
48
+        paste_preprocess: function(plugin, args) {
49
+          // 删除部分标签
50
+          var delTag = ['b', 'strong', 'i', 'em']
51
+          delTag.forEach(tag => {
52
+            var reg = new RegExp(`(<${tag}>)|(</${tag}>)]`, 'g')
53
+            args.content = args.content.replace(reg, '')
54
+          })
55
+          // 替换部分标签
56
+          var replaceTag = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
57
+          replaceTag.forEach(tag => {
58
+            var reg1 = new RegExp(`<${tag}>`, 'g')
59
+            var reg2 = new RegExp(`</${tag}>`, 'g')
60
+            args.content = args.content.replace(reg1, '<p>')
61
+            args.content = args.content.replace(reg2, '</p>')
62
+          })
63
+          // 删除所有font标签
64
+          args.content = args.content.replace(/<\/font>/ig, '').replace(/<font[^>]+>/ig, '')
65
+          console.log(args.content)
66
+        },
67
+        paste_postprocess: function(plugin, args) {
68
+          var doms = Array.from(args.node.querySelectorAll('*'))
69
+          // 删除字体样式
70
+          doms.forEach(dom => {
71
+            dom.style.color = ''
72
+            dom.style.fontWeight = ''
73
+            dom.style.fontFamily = ''
74
+            dom.style.fontSize = ''
75
+            dom.style.background = ''
76
+            console.log(dom)
77
+          })
78
+        }
79
+      }
80
+    },
81
+    editInputChange() {}
82
+  }
83
+}
84
+</script>
85
+
86
+<style scoped>
87
+
88
+</style>

+ 164
- 0
src/views/admin/fields/components/SettingField/SettingDetailTable.vue Wyświetl plik

@@ -0,0 +1,164 @@
1
+<template>
2
+  <div class="setting-detail-table">
3
+    <div class="item-section">
4
+      <div class="name">表格字段</div>
5
+      <draggable
6
+        :list="field.fieldExtendList"
7
+        :options="dragConfig"
8
+        @sort="handleChange">
9
+        <flexbox
10
+          v-for="(item, index) in field.fieldExtendList"
11
+          :key="index"
12
+          align="center"
13
+          justify="flex-start"
14
+          class="option-item">
15
+          <i
16
+            :class="typeObj(item.form_type).icon"
17
+            class="type-icon" />
18
+          <div class="option-item__name">{{ item.name }}</div>
19
+          <el-button
20
+            type="text"
21
+            class="option-item__icon wk wk-write"
22
+            @click="handleEdit(index)" />
23
+          <el-button
24
+            type="text"
25
+            class="option-item__icon wk wk-icon-bin"
26
+            @click="handleDelete(index)" />
27
+          <div class="option-item__icon drag-hook wk wk-grid" />
28
+        </flexbox>
29
+      </draggable>
30
+    </div>
31
+
32
+    <div class="item-section">
33
+      <div class="name">动作名</div>
34
+      <div>
35
+        <el-input v-model="field.remark" :maxlength="10" />
36
+      </div>
37
+    </div>
38
+
39
+    <div class="item-section">
40
+      <div class="name">
41
+        填写方式
42
+        <el-tooltip
43
+          content="选择明细的填写方式"
44
+          effect="dark"
45
+          popper-class="setting-number-tooltip"
46
+          placement="top">
47
+          <i class="wk wk-help wk-help-tips" />
48
+        </el-tooltip>
49
+      </div>
50
+      <el-radio-group v-model="field.precisions">
51
+        <el-radio :label="1">列表</el-radio>
52
+        <el-radio :label="2">表格</el-radio>
53
+      </el-radio-group>
54
+    </div>
55
+  </div>
56
+</template>
57
+
58
+<script>
59
+import draggable from 'vuedraggable'
60
+import { guid } from '@/utils'
61
+import FieldTypeLib from '../../fieldTypeLib'
62
+
63
+export default {
64
+  name: 'SettingDetailTable',
65
+  components: {
66
+    draggable
67
+  },
68
+  props: {
69
+    field: {
70
+      type: Object,
71
+      required: true
72
+    }
73
+  },
74
+  data() {
75
+    return {
76
+      dragConfig: {
77
+        group: guid(),
78
+        forceFallback: false,
79
+        fallbackClass: 'draggingStyle',
80
+        handle: '.drag-hook',
81
+        filter: '.el-input__inner',
82
+        preventOnFilter: false
83
+      }
84
+    }
85
+  },
86
+  watch: {
87
+    field: {
88
+      handler() {
89
+        if (!this.field.precisions) {
90
+          this.$set(this.field, 'precisions', 1)
91
+        }
92
+        this.$set(this.field, 'precisions', this.field.precisions)
93
+        this.$set(this.field, 'remark', this.field.remark)
94
+      },
95
+      deep: true,
96
+      immediate: true
97
+    }
98
+  },
99
+  methods: {
100
+    typeObj(form_type) {
101
+      return FieldTypeLib.find(o => o.form_type === form_type)
102
+    },
103
+
104
+    handleChange() {
105
+      this.$set(this.field, 'fieldExtendList', this.field.fieldExtendList)
106
+    },
107
+
108
+    handleEdit(index) {
109
+      this.$emit('child-edit', this.field.fieldExtendList[index])
110
+    },
111
+
112
+    handleDelete(index) {
113
+      this.field.fieldExtendList.splice(index, 1)
114
+      this.$set(this.field, 'fieldExtendList', this.field.fieldExtendList)
115
+    }
116
+  }
117
+}
118
+</script>
119
+
120
+<style scoped lang="scss">
121
+.setting-detail-table {
122
+  .item-section {
123
+    padding: 10px 0;
124
+    border-bottom: 1px solid #e6e6e6;
125
+    .name {
126
+      font-size: 13px;
127
+      font-weight: 500;
128
+      color: #333;
129
+      margin: 10px 0;
130
+    }
131
+  }
132
+
133
+  .option-item {
134
+    width: 100%;
135
+    height: 34px;
136
+    border: 1px solid #e6e6e6;
137
+    border-radius: 3px;
138
+    padding: 0 8px;
139
+    margin: 5px 0;
140
+
141
+    .drag-hook {
142
+      cursor: move;
143
+    }
144
+
145
+    .type-icon {
146
+      font-size: 14px;
147
+      color: #777777;
148
+      margin-right: 5px;
149
+    }
150
+
151
+    .option-item__name {
152
+      width: 174px;
153
+      overflow: hidden;
154
+      white-space: nowrap;
155
+      text-overflow: ellipsis;
156
+    }
157
+    .option-item__icon {
158
+      font-size: 14px;
159
+      color: #999999;
160
+      margin: 0 2px;
161
+    }
162
+  }
163
+}
164
+</style>

+ 321
- 0
src/views/admin/fields/components/SettingField/SettingLogicForm.vue Wyświetl plik

@@ -0,0 +1,321 @@
1
+<template>
2
+  <div class="setting-logic-form">
3
+    <el-button
4
+      :disabled="!optionsEditAuth"
5
+      class="add-btn"
6
+      @click="handleToSet">
7
+      点击配置
8
+    </el-button>
9
+
10
+    <el-dialog
11
+      :visible.sync="dialogVisible"
12
+      :before-close="handleCloseDialog"
13
+      :close-on-click-modal="false"
14
+      title="添加逻辑表单规则"
15
+      width="500px"
16
+      class="edit-dialog">
17
+      <div>
18
+        <div class="edit-tips">
19
+          选择选项后,才会显示所设置的其他字段
20
+        </div>
21
+
22
+        <div class="edit-table">
23
+          <flexbox
24
+            align="center"
25
+            justify="flex-start"
26
+            class="edit-table__header row">
27
+            <div class="label">选项内容</div>
28
+            <flexbox-item class="content">显示字段</flexbox-item>
29
+          </flexbox>
30
+          <div
31
+            v-if="list.length > 0 && fieldLibArr.length > 0"
32
+            class="edit-table__body">
33
+            <flexbox
34
+              v-for="(item, index) in list"
35
+              :key="index"
36
+              align="center"
37
+              justify="flex-start"
38
+              class="row">
39
+              <div class="label">{{ item.name }}</div>
40
+              <flexbox-item class="content">
41
+                <el-select
42
+                  v-model="item.value"
43
+                  placeholder="请选择"
44
+                  multiple>
45
+                  <el-option
46
+                    v-for="(item, childIndex) in fieldLibArr"
47
+                    :key="childIndex"
48
+                    :label="item.form_type === 'desc_text' ? '描述文字' : (item.name || '未命名')"
49
+                    :value="item.formAssistId" />
50
+                </el-select>
51
+              </flexbox-item>
52
+            </flexbox>
53
+          </div>
54
+        </div>
55
+      </div>
56
+      <div slot="footer">
57
+        <el-button @click="handleCloseDialog">
58
+          取 消
59
+        </el-button>
60
+        <el-button
61
+          type="primary"
62
+          @click="handleDialogConfirm">
63
+          确 定
64
+        </el-button>
65
+      </div>
66
+    </el-dialog>
67
+  </div>
68
+</template>
69
+
70
+<script>
71
+// import { objDeepCopy } from '@/utils/index'
72
+import { isEmpty } from '@/utils/types'
73
+import { getFieldAuth } from '../../utils'
74
+
75
+export default {
76
+  name: 'SettingLogicForm',
77
+  props: {
78
+    field: {
79
+      type: Object,
80
+      required: true
81
+    },
82
+    fieldArr: { // 所有字段
83
+      type: Array,
84
+      required: true
85
+    },
86
+    point: { // 字段坐标
87
+      type: Array,
88
+      required: true
89
+    }
90
+  },
91
+  data() {
92
+    return {
93
+      list: [],
94
+      dialogVisible: false
95
+    }
96
+  },
97
+  computed: {
98
+    // 选项不能配置,逻辑表单也禁止配置
99
+    optionsEditAuth() {
100
+      return getFieldAuth(this.field.operating).optionsEdit
101
+    },
102
+    fieldLibArr() {
103
+      const arr = []
104
+      const ids = [] // 记录所有formAssistId防止重复
105
+      this.fieldArr.forEach(father => {
106
+        father.forEach(child => {
107
+          if (child.hasOwnProperty('formAssistId') && !isEmpty(child.formAssistId)) {
108
+            ids.push(child.formAssistId)
109
+          }
110
+        })
111
+      })
112
+      this.fieldArr.forEach((father, fatherIndex) => {
113
+        father.forEach((child, childIndex) => {
114
+          if (!child.hasOwnProperty('formAssistId') || isEmpty(child.formAssistId)) {
115
+            // 如果没有formAssistId则生成一个formAssistId
116
+            child.formAssistId = this.generateFormAssistId(ids)
117
+            ids.push(child.formAssistId)
118
+          }
119
+          if (
120
+            fatherIndex !== this.point[0] ||
121
+            childIndex !== this.point[1]) {
122
+            arr.push(child)
123
+          }
124
+        })
125
+      })
126
+      return arr.filter(item => item.form_type !== 'customer' &&
127
+      item.form_type !== 'business' &&
128
+      item.form_type !== 'contract')
129
+    },
130
+    allFormAssistId() {
131
+      return this.fieldLibArr.map(o => o.formAssistId)
132
+    }
133
+  },
134
+  watch: {
135
+    field: {
136
+      handler() {
137
+        // remark null 普通  options_type 逻辑表单
138
+        if (this.field.remark !== 'options_type') {
139
+          // 普通单选多选
140
+          this.list = this.field.setting.map(o => {
141
+            return {
142
+              name: o,
143
+              value: null
144
+            }
145
+          })
146
+        } else {
147
+          // 逻辑表单单选多选
148
+          let data = {}
149
+          if (this.field.optionsData) {
150
+            data = this.field.optionsData || {}
151
+          } else {
152
+            try {
153
+              data = JSON.parse(this.field.options) || {}
154
+            } catch (e) {
155
+              this.list = this.field.setting.map(o => {
156
+                return {
157
+                  name: o,
158
+                  value: null
159
+                }
160
+              })
161
+              // 如果异常
162
+              this.$set(this.field, 'remark', null)
163
+              this.$set(this.field, 'optionsData', null)
164
+              this.$set(this.field, 'options', this.field.setting.join(','))
165
+              return
166
+            }
167
+          }
168
+          this.list = Object.keys(data).map(key => {
169
+            return {
170
+              name: key,
171
+              value: isEmpty(data[key]) ? [] : data[key]
172
+            }
173
+          })
174
+        }
175
+      },
176
+      deep: true,
177
+      immediate: true
178
+    },
179
+    allFormAssistId: {
180
+      handler() {
181
+        if (this.field.remark === 'options_type') {
182
+          // 某个字段被删除后,则同时删除其对应的逻辑关联
183
+          this.list.forEach(item => {
184
+            const ids = []
185
+            if (item.value) {
186
+              item.value.forEach(id => {
187
+                if (this.allFormAssistId.includes(id)) {
188
+                  ids.push(id)
189
+                }
190
+              })
191
+            }
192
+            item.value = ids
193
+          })
194
+
195
+          const optionsData = {}
196
+          this.list.forEach(o => {
197
+            optionsData[o.name] = o.value
198
+          })
199
+          this.$set(this.field, 'optionsData', optionsData)
200
+        }
201
+      },
202
+      deep: true,
203
+      immediate: true
204
+    }
205
+  },
206
+  methods: {
207
+    /**
208
+     * 生成逻辑表单辅助id
209
+     */
210
+    generateFormAssistId(ids) {
211
+      const startNum = 1000
212
+      const generateFn = function(num) {
213
+        const id = num + 1
214
+        if (ids.includes(id)) {
215
+          return generateFn(id)
216
+        } else {
217
+          return id
218
+        }
219
+      }
220
+      return generateFn(startNum)
221
+    },
222
+    handleToSet() {
223
+      this.dialogVisible = true
224
+    },
225
+
226
+    handleCloseDialog() {
227
+      // 关闭弹窗,触发watch更新
228
+      this.$set(this.field, '_remark', '')
229
+      this.$nextTick(() => {
230
+        delete this.field._remark
231
+      })
232
+      this.dialogVisible = false
233
+    },
234
+
235
+    handleDialogConfirm() {
236
+      const optionsData = {}
237
+      this.list.forEach(o => {
238
+        optionsData[o.name] = o.value
239
+      })
240
+      const len = this.list.filter(o => !isEmpty(o.value)).length
241
+      if (len !== 0) {
242
+        // 保存逻辑表单关系
243
+        this.$set(this.field, 'remark', 'options_type')
244
+        this.$set(this.field, 'optionsData', optionsData)
245
+        const optionsStr = JSON.stringify(optionsData)
246
+        this.$set(this.field, 'options', optionsStr)
247
+      } else {
248
+        // 如果没有配置逻辑表单则置普通类型
249
+        this.$set(this.field, 'remark', null)
250
+        this.$set(this.field, 'optionsData', null)
251
+        this.$set(this.field, 'options', this.field.setting.join(','))
252
+      }
253
+
254
+      this.handleCloseDialog()
255
+    }
256
+  }
257
+}
258
+</script>
259
+
260
+<style scoped lang="scss">
261
+.add-btn {
262
+  width: 100%;
263
+  height: 34px;
264
+  font-size: 14px;
265
+  color: #666666;
266
+  border: 1px dashed $xr-border-color-base;
267
+  border-radius: $xr-border-radius-base;
268
+  background-color: #f8f8f8;
269
+  cursor: pointer;
270
+}
271
+
272
+.edit-dialog {
273
+  .edit-tips {
274
+    font-size: 14px;
275
+    color: #999999;
276
+    background-color: #fafbfb;
277
+    border-top: 1px solid #E6E6E6;
278
+    border-bottom: 1px solid #E6E6E6;
279
+    padding: 5px 20px;
280
+  }
281
+  /deep/ .el-dialog__body {
282
+    padding: 0;
283
+  }
284
+
285
+  .edit-table {
286
+    height: 300px;
287
+    padding: 0 15px;
288
+    overflow-y: auto;
289
+
290
+    .row {
291
+      border-bottom: 1px solid #E6E6E6;
292
+      &:last-child {
293
+        border-bottom: 0 none;
294
+      }
295
+      .label {
296
+        width: 180px;
297
+        padding: 0 15px;
298
+        overflow: hidden;
299
+        white-space: nowrap;
300
+        text-overflow: ellipsis;
301
+      }
302
+      .content {
303
+        padding-right: 15px;
304
+        .el-select {
305
+          width: 100%;
306
+        }
307
+      }
308
+    }
309
+    .edit-table__header {
310
+      width: 100%;
311
+      height: 36px;
312
+      color: #999999;
313
+    }
314
+    .edit-table__body {
315
+      .row {
316
+        padding: 6px 0;
317
+      }
318
+    }
319
+  }
320
+}
321
+</style>

+ 211
- 0
src/views/admin/fields/components/SettingField/SettingNumber.vue Wyświetl plik

@@ -0,0 +1,211 @@
1
+<template>
2
+  <div class="setting-number">
3
+    <flexbox
4
+      align="center"
5
+      justify="flex-start"
6
+      class="setting-number-item">
7
+      <el-checkbox v-model="checked" @change="checkedChange" />
8
+      <span style="font-size: 13px;">支持小数</span>
9
+      <el-tooltip
10
+        content="不选择只能输入整数,勾选后可规定小数位数"
11
+        effect="dark"
12
+        popper-class="setting-number-tooltip"
13
+        placement="top">
14
+        <i class="wk wk-help wk-help-tips" />
15
+      </el-tooltip>
16
+      <template v-if="checked">
17
+        <span>限制&nbsp;</span>
18
+        <el-select
19
+          v-model="field.precisions"
20
+          size="small"
21
+          placeholder=""
22
+          @change="handleSelectChange">
23
+          <el-option
24
+            v-for="item in precisionList"
25
+            :key="item.value"
26
+            :label="item.label"
27
+            :value="item.value" />
28
+        </el-select>
29
+        <span>&nbsp;位</span>
30
+      </template>
31
+    </flexbox>
32
+    <flexbox
33
+      align="flex-start"
34
+      justify="flex-start"
35
+      direction="column"
36
+      class="setting-number-item">
37
+      <el-checkbox v-model="limitChecked" @change="limitChange">
38
+        限制数值范围
39
+      </el-checkbox>
40
+      <flexbox
41
+        v-if="limitChecked"
42
+        align="center"
43
+        justify="flex-start"
44
+        class="number-range-body">
45
+        <flexbox-item>
46
+          <el-input-number
47
+            v-model="minNumRestrict"
48
+            :controls="false"
49
+            placeholder="最小值"
50
+            @change="handleChangeNumber('minNumRestrict')" />
51
+        </flexbox-item>
52
+        <div class="number-range-text">~</div>
53
+        <flexbox-item>
54
+          <el-input-number
55
+            v-model="maxNumRestrict"
56
+            :controls="false"
57
+            placeholder="最大值"
58
+            @change="handleChangeNumber('maxNumRestrict')" />
59
+        </flexbox-item>
60
+      </flexbox>
61
+    </flexbox>
62
+  </div>
63
+</template>
64
+
65
+<script>
66
+import { isEmpty } from '@/utils/types'
67
+
68
+export default {
69
+  name: 'SettingNumber',
70
+  props: {
71
+    field: {
72
+      type: Object,
73
+      required: true
74
+    }
75
+  },
76
+  data() {
77
+    return {
78
+      checked: false,
79
+      precisionList: [],
80
+
81
+      limitChecked: false,
82
+      minNumRestrict: undefined, // 定义为 undefined 防止input number自动填充0
83
+      maxNumRestrict: undefined
84
+    }
85
+  },
86
+  watch: {
87
+    field: {
88
+      handler() {
89
+        if ([
90
+          'number',
91
+          'floatnumber',
92
+          'percent'
93
+        ].includes(this.field.form_type)) {
94
+          if (!this.field.hasOwnProperty('minNumRestrict')) {
95
+            this.field.minNumRestrict = null
96
+          }
97
+          if (!this.field.hasOwnProperty('maxNumRestrict')) {
98
+            this.field.maxNumRestrict = null
99
+          }
100
+          this.minNumRestrict = isEmpty(this.field.minNumRestrict) ? undefined : Number(this.field.minNumRestrict)
101
+          this.maxNumRestrict = isEmpty(this.field.maxNumRestrict) ? undefined : Number(this.field.maxNumRestrict)
102
+
103
+          // 小数位
104
+          const max = this.field.form_type === 'percent' ? 5 : 14
105
+          this.precisionList = Array.from({ length: max })
106
+            .map((o, i) => {
107
+              return { label: i + 1, value: i + 1 }
108
+            })
109
+          // if (this.field.form_type !== 'percent') {
110
+          //   this.precisionList.unshift({ label: '不限', value: 0 })
111
+          // }
112
+          if (!this.field.hasOwnProperty('precisions')) {
113
+            this.field.precisions = this.field.form_type === 'number' ? 4 : 2
114
+          }
115
+          if (this.field.precisions > max) {
116
+            // 不能大于最大值
117
+            this.field.precisions = max
118
+          }
119
+          this.checked = !isEmpty(this.field.precisions)
120
+          this.limitChecked = !isEmpty(this.minNumRestrict) || !isEmpty(this.maxNumRestrict)
121
+        }
122
+      },
123
+      deep: true,
124
+      immediate: true
125
+    }
126
+  },
127
+  methods: {
128
+    checkedChange() {
129
+      if (!this.checked) {
130
+        this.field.precisions = null
131
+      } else {
132
+        this.field.precisions = 2
133
+      }
134
+    },
135
+    handleSelectChange() {
136
+      this.$set(this.field, 'precisions', this.field.precisions)
137
+      this.$forceUpdate()
138
+    },
139
+    limitChange() {
140
+      if (!this.limitChecked) {
141
+        this.minNumRestrict = undefined
142
+        this.maxNumRestrict = undefined
143
+
144
+        this.field.minNumRestrict = ''
145
+        this.field.maxNumRestrict = ''
146
+      }
147
+    },
148
+    handleChangeNumber(type) {
149
+      const current = this[type]
150
+      const len = String(current || '')
151
+        .replace('.', '')
152
+        .replace('-', '')
153
+        .length
154
+
155
+      const maxlength = this.field.form_type === 'percent' ? 10 : 15
156
+      if (len > maxlength) {
157
+        this.$message.error(`最多支持${maxlength}位数字`)
158
+        this.field[type] = null
159
+        return
160
+      }
161
+
162
+      const min = this.minNumRestrict
163
+      const max = this.maxNumRestrict
164
+
165
+      if (!isEmpty(min) && !isEmpty(max)) {
166
+        if (Number(min) > Number(max)) {
167
+          this.$message.error('请输入正确的数值范围')
168
+          this.field[type] = null
169
+        }
170
+      }
171
+      const minNum = isEmpty(min) ? '' : min
172
+      const maxNum = isEmpty(max) ? '' : max
173
+      this.field.minNumRestrict = this.minNumRestrict !== null ? String(minNum) : null
174
+      this.field.maxNumRestrict = this.maxNumRestrict !== null ? String(maxNum) : null
175
+    }
176
+  }
177
+}
178
+</script>
179
+
180
+<style>
181
+.setting-number-tooltip {
182
+  max-width: 250px;
183
+}
184
+</style>
185
+<style scoped lang="scss">
186
+.setting-number-item {
187
+  height: 32px;
188
+  &:nth-child(2) {
189
+    height: auto;
190
+    margin-top: 10px;
191
+  }
192
+  .el-checkbox {
193
+    margin-right: 8px;
194
+  }
195
+  .el-tooltip {
196
+    margin: 0 10px 0 5px;
197
+  }
198
+  .el-select {
199
+    width: 70px;
200
+  }
201
+  .el-input-number {
202
+    width: 100%;
203
+  }
204
+  .number-range-body {
205
+    margin-top: 10px;
206
+  }
207
+  .number-range-text {
208
+    padding: 0 10px;
209
+  }
210
+}
211
+</style>

+ 395
- 0
src/views/admin/fields/components/SettingField/SettingOptions.vue Wyświetl plik

@@ -0,0 +1,395 @@
1
+<template>
2
+  <div class="setting-options">
3
+    <draggable
4
+      :list="optionsList"
5
+      :options="dragConfig"
6
+      @sort="handleChange">
7
+      <div
8
+        v-for="(item, index) in optionsList"
9
+        :key="index"
10
+        class="option-item">
11
+        <el-input
12
+          :disabled="!optionsEditAuth"
13
+          v-model="item.value"
14
+          @change="handleChange">
15
+          <flexbox v-if="optionsEditAuth" slot="suffix">
16
+            <div class="el-input__icon drag-hook wk wk-grid" />
17
+            <el-button
18
+              type="text"
19
+              class="el-input__icon wk wk-icon-bin"
20
+              @click="handleDelete(index)" />
21
+          </flexbox>
22
+        </el-input>
23
+      </div>
24
+    </draggable>
25
+
26
+    <div
27
+      v-if="showOther"
28
+      class="option-item other-item">
29
+      <el-input
30
+        value="其他"
31
+        disabled>
32
+        <flexbox v-if="optionsEditAuth" slot="suffix">
33
+          <el-button
34
+            type="text"
35
+            class="el-input__icon wk wk-icon-bin"
36
+            @click="handleDelete(-1)" />
37
+        </flexbox>
38
+      </el-input>
39
+    </div>
40
+
41
+    <el-button
42
+      v-if="optionsEditAuth"
43
+      class="add-btn"
44
+      @click="handleAdd">
45
+      <i class="el-icon-plus" /> 添加新选项
46
+    </el-button>
47
+
48
+    <flexbox
49
+      v-if="optionsEditAuth"
50
+      align="center"
51
+      justify="center">
52
+      <div class="add-other-btn" @click="handleAddOther">
53
+        添加其他
54
+      </div>
55
+      <flexbox-item />
56
+      <div class="add-other-btn" @click="handleUpdateAll">批量编辑</div>
57
+    </flexbox>
58
+
59
+    <el-dialog
60
+      :visible.sync="dialogVisible"
61
+      :before-close="handleCloseDialog"
62
+      title="批量编辑"
63
+      width="500px"
64
+      class="edit-dialog">
65
+      <div>
66
+        <div class="edit-tips">
67
+          每行内容对应一个选项,点击完成后,逻辑表单设置将失效
68
+        </div>
69
+        <el-input
70
+          v-model="dialogContentVal"
71
+          :rows="10"
72
+          resize="none"
73
+          type="textarea" />
74
+      </div>
75
+      <div slot="footer">
76
+        <el-button @click="handleCloseDialog">
77
+          取 消
78
+        </el-button>
79
+        <el-button
80
+          type="primary"
81
+          @click="handleDialogConfirm">
82
+          确 定
83
+        </el-button>
84
+      </div>
85
+    </el-dialog>
86
+  </div>
87
+</template>
88
+
89
+<script>
90
+import draggable from 'vuedraggable'
91
+import { isEmpty, isArray } from '@/utils/types'
92
+import { getFieldAuth } from '../../utils'
93
+import { guid } from '@/utils'
94
+
95
+export default {
96
+  name: 'SettingOptions',
97
+  components: {
98
+    draggable
99
+  },
100
+  props: {
101
+    field: {
102
+      type: Object,
103
+      required: true
104
+    },
105
+    isTableChild: {
106
+      type: Boolean,
107
+      default: false
108
+    }
109
+  },
110
+  data() {
111
+    return {
112
+      optionsList: [],
113
+      dialogVisible: false,
114
+      dialogContentVal: ''
115
+    }
116
+  },
117
+  computed: {
118
+    optionsEditAuth() {
119
+      return getFieldAuth(this.field.operating).optionsEdit
120
+    },
121
+
122
+    showOther() {
123
+      return this.field.setting.includes('其他')
124
+    },
125
+
126
+    dragConfig() {
127
+      return {
128
+        group: guid(),
129
+        forceFallback: false,
130
+        disabled: !this.optionsEditAuth,
131
+        fallbackClass: 'draggingStyle',
132
+        handle: '.drag-hook',
133
+        filter: '.el-input__inner',
134
+        preventOnFilter: false
135
+      }
136
+    }
137
+  },
138
+  watch: {
139
+    field: {
140
+      handler(newVal, oldVal) {
141
+        if (isEmpty(this.field.options)) {
142
+          this.$set(this.field, 'options', '选1,选2,选3')
143
+          this.$set(this.field, 'setting', ['选1', '选2', '选3'])
144
+        }
145
+        if (!oldVal || newVal.options !== oldVal.options) {
146
+          this.optionsList = this.field.setting
147
+            .filter(o => o !== '其他')
148
+            .map(o => {
149
+              return { value: o }
150
+            })
151
+        }
152
+      },
153
+      deep: true,
154
+      immediate: true
155
+    }
156
+  },
157
+  methods: {
158
+    /**
159
+     * 修改、排序
160
+     */
161
+    handleChange() {
162
+      // 选项不允许重复或有空
163
+      let arr = this.optionsList
164
+        .map(o => o.value)
165
+        .filter(o => !isEmpty(o) && o !== '其他')
166
+      arr = Array.from(new Set(arr))
167
+      if (arr.length !== this.optionsList.length) {
168
+        this.optionsList = arr.map(o => {
169
+          return { value: o }
170
+        })
171
+      }
172
+      if (this.showOther) {
173
+        arr.push('其他')
174
+      }
175
+      this.field.setting = arr
176
+      if (this.field.remark === 'options_type') {
177
+        // 如果是逻辑表单
178
+        const optionsData = {}
179
+        const keys = Object.keys(this.field.optionsData)
180
+        this.optionsList.forEach(o => {
181
+          if (keys.includes(o.value)) {
182
+            optionsData[o.value] = this.field.optionsData[o.value]
183
+          } else {
184
+            optionsData[o.value] = []
185
+          }
186
+        })
187
+        Object.keys(optionsData).forEach(key => {
188
+          const findRes = this.optionsList.find(o => o.value === key && key !== '其他')
189
+          if (!findRes) {
190
+            delete optionsData[key]
191
+          }
192
+        })
193
+        if (!this.showOther) {
194
+          delete optionsData['其他']
195
+        } else if (keys.includes('其他')) {
196
+          optionsData['其他'] = this.field.optionsData['其他']
197
+        }
198
+        this.field.options = JSON.stringify(optionsData)
199
+        this.$set(this.field, 'optionsData', optionsData)
200
+      } else {
201
+        // 如果不是逻辑表单
202
+        this.field.options = arr.join(',')
203
+      }
204
+      this.$set(this.field, 'setting', this.field.setting)
205
+      this.$set(this.field, 'options', this.field.options)
206
+      this.$nextTick(() => {
207
+        this.checkDefaultValue()
208
+      })
209
+    },
210
+    /**
211
+     * 删除
212
+     * @param index
213
+     */
214
+    handleDelete(index) {
215
+      let delItem = null
216
+
217
+      if (index !== -1) {
218
+        delItem = this.field.setting[index]
219
+        this.optionsList.splice(index, 1)
220
+        this.field.setting.splice(index, 1)
221
+      } else {
222
+        const findIndex = this.field.setting.lastIndexOf('其他')
223
+        if (findIndex !== -1) {
224
+          this.field.setting.splice(findIndex, 1)
225
+          delItem = '其他'
226
+        }
227
+      }
228
+
229
+      if (this.field.remark === 'options_type') {
230
+        // 如果是逻辑表单
231
+        delete this.field.optionsData[delItem]
232
+        this.$set(this.field, 'options', JSON.stringify(this.field.optionsData))
233
+      } else {
234
+        // 如果不是逻辑表单
235
+        this.$set(this.field, 'options', this.field.setting.join(','))
236
+      }
237
+
238
+      this.$set(this.field, 'setting', this.field.setting)
239
+      this.checkDefaultValue()
240
+    },
241
+    /**
242
+     * 添加
243
+     */
244
+    handleAdd() {
245
+      const val = this.getAddValue(this.optionsList.length + 1)
246
+      this.optionsList.push({
247
+        value: val
248
+      })
249
+      this.handleChange()
250
+    },
251
+
252
+    /**
253
+     * 添加其他
254
+     */
255
+    handleAddOther() {
256
+      if (this.field.setting.indexOf('其他') === -1) {
257
+        this.field.setting.push('其他')
258
+      }
259
+      this.$set(this.field, 'setting', this.field.setting)
260
+      if (this.field.remark === 'options_type') {
261
+        // 如果是逻辑表单
262
+        this.field.optionsData['其他'] = []
263
+        this.$set(this.field, 'optionsData', this.field.optionsData)
264
+        this.$set(this.field, 'options', JSON.stringify(this.field.optionsData))
265
+      } else {
266
+        // 如果不是逻辑表单
267
+        this.$set(this.field, 'options', this.field.setting.join(','))
268
+      }
269
+    },
270
+
271
+    /**
272
+     * 点击批量编辑
273
+     */
274
+    handleUpdateAll() {
275
+      this.dialogContentVal = this.optionsList.map(o => o.value).join('\n')
276
+      this.dialogVisible = true
277
+    },
278
+
279
+    /**
280
+     * 关闭弹窗
281
+     */
282
+    handleCloseDialog() {
283
+      this.dialogVisible = false
284
+    },
285
+
286
+    /**
287
+     * 批量编辑
288
+     */
289
+    handleDialogConfirm() {
290
+      let arr = this.dialogContentVal.split(/\n|\r/)
291
+      arr = Array.from(new Set(arr))
292
+        .map(o => o.trim())
293
+        .filter(o => !isEmpty(o) && o !== '其他')
294
+      this.optionsList = arr.map(o => {
295
+        return { value: o }
296
+      })
297
+      this.$set(this.field, 'remark', null)
298
+      this.$set(this.field, 'optionsData', null)
299
+      this.handleChange()
300
+      this.handleCloseDialog()
301
+    },
302
+
303
+    getAddValue(index) {
304
+      const findRes = this.optionsList.find(o => o.value === `选${index}`)
305
+      if (findRes) {
306
+        return this.getAddValue(index + 1)
307
+      }
308
+      return `选${index}`
309
+    },
310
+
311
+    /**
312
+     * 选项变化后修改默认值
313
+     */
314
+    checkDefaultValue() {
315
+      if (!isEmpty(this.field.default_value)) {
316
+        if (isArray(this.field.default_value)) {
317
+          const arr = []
318
+          this.field.default_value.forEach(o => {
319
+            const findRes = this.optionsList.find(item => item.value === o)
320
+            if (findRes) arr.push(o)
321
+          })
322
+          this.$set(this.field, 'default_value', [...arr])
323
+        } else {
324
+          const findRes = this.optionsList.find(item => item.value === this.field.default_value)
325
+          if (!findRes) {
326
+            this.$set(this.field, 'default_value', null)
327
+          }
328
+        }
329
+      }
330
+    }
331
+  }
332
+}
333
+</script>
334
+
335
+<style scoped lang="scss">
336
+.option-item {
337
+  margin: 5px 0;
338
+
339
+  .el-input__icon {
340
+    font-size: 14px;
341
+    color: #999999;
342
+  }
343
+  .drag-hook {
344
+    cursor: move;
345
+  }
346
+
347
+  &.other-item {
348
+    .wk-icon-bin {
349
+      cursor: pointer;
350
+    }
351
+  }
352
+}
353
+
354
+.add-btn {
355
+  width: 100%;
356
+  height: 34px;
357
+  font-size: 14px;
358
+  color: #666666;
359
+  border: 1px dashed $xr-border-color-base;
360
+  border-radius: $xr-border-radius-base;
361
+  background-color: #f8f8f8;
362
+  cursor: pointer;
363
+}
364
+
365
+.add-other-btn {
366
+  font-size: 14px;
367
+  color: #666666;
368
+  cursor: pointer;
369
+  display: inline-block;
370
+  margin-top: 8px;
371
+}
372
+
373
+.edit-dialog {
374
+  .edit-tips {
375
+    font-size: 14px;
376
+    //color: #999999;
377
+    color: #ecb971;
378
+    background-color: #fafbfb;
379
+    border-top: 1px solid #E6E6E6;
380
+    border-bottom: 1px solid #E6E6E6;
381
+    padding: 5px 20px;
382
+  }
383
+  .el-textarea {
384
+    margin: 10px 0;
385
+  }
386
+  /deep/ .el-textarea__inner {
387
+    border: 0 none;
388
+    padding: 10px 20px;
389
+  }
390
+  /deep/ .el-dialog__body {
391
+    padding: 0;
392
+  }
393
+}
394
+
395
+</style>

+ 83
- 0
src/views/admin/fields/components/SettingField/SettingPrecisions.vue Wyświetl plik

@@ -0,0 +1,83 @@
1
+<template>
2
+  <div class="setting-precisions">
3
+    <el-select
4
+      :disabled="!optionsEditAuth"
5
+      v-model="field.precisions"
6
+      placeholder="请选择">
7
+      <el-option
8
+        v-for="item in options"
9
+        :key="item.value"
10
+        :label="item.label"
11
+        :value="item.value" />
12
+    </el-select>
13
+  </div>
14
+</template>
15
+
16
+<script>
17
+import { getFieldAuth } from '../../utils'
18
+
19
+export default {
20
+  name: 'SettingPrecisions',
21
+  props: {
22
+    field: {
23
+      type: Object,
24
+      required: true
25
+    }
26
+  },
27
+  data() {
28
+    return {
29
+      options: []
30
+    }
31
+  },
32
+  computed: {
33
+    // 选项不能配置
34
+    optionsEditAuth() {
35
+      return getFieldAuth(this.field.operating).optionsEdit
36
+    }
37
+  },
38
+  watch: {
39
+    field: {
40
+      handler() {
41
+        if (![
42
+          'date_interval',
43
+          'position',
44
+          'select',
45
+          'checkbox'
46
+        ].includes(this.field.form_type)) return
47
+        if (this.field.form_type === 'date_interval') {
48
+          this.options = [
49
+            { label: '日期', value: 1 },
50
+            { label: '日期时间', value: 2 }
51
+          ]
52
+        } else if (this.field.form_type === 'position') {
53
+          this.options = [
54
+            { label: '省/地区、市、区/县、详细地址', value: 1 },
55
+            { label: '省/地区、市、区/县', value: 2 },
56
+            { label: '省/地区、市', value: 3 },
57
+            { label: '省/地区', value: 4 }
58
+          ]
59
+        } else {
60
+          this.options = [
61
+            { label: '平铺', value: 1 },
62
+            { label: '下拉', value: 2 }
63
+          ]
64
+          if (!this.field.precisions) {
65
+            this.$set(this.field, 'precisions', this.field.form_type === 'checkbox' ? 1 : 2)
66
+          }
67
+        }
68
+        if (!this.field.precisions) {
69
+          this.$set(this.field, 'precisions', 1)
70
+        }
71
+      },
72
+      deep: true,
73
+      immediate: true
74
+    }
75
+  }
76
+}
77
+</script>
78
+
79
+<style scoped>
80
+.el-select {
81
+  width: 100%;
82
+}
83
+</style>

+ 325
- 0
src/views/admin/fields/components/SettingField/index.vue Wyświetl plik

@@ -0,0 +1,325 @@
1
+<template>
2
+  <div
3
+    v-clickoutside="clickOutSide"
4
+    v-if="typeObj"
5
+    class="field-setting">
6
+    <div class="setting-title">
7
+      {{ typeObj.name }}
8
+    </div>
9
+
10
+    <div class="setting-body">
11
+      <template v-if="!isDescText">
12
+        <div class="item-section">
13
+          <div class="name">标识名</div>
14
+          <el-input
15
+            v-model="field.name"
16
+            :disabled="!fieldAuth.nameEdit"/>
17
+          <div class="input-tips"><span>*</span>标识名不能为空</div>
18
+        </div>
19
+
20
+        <div class="item-section">
21
+          <div class="name">提示文字</div>
22
+          <el-input
23
+            v-model="field.input_tips"
24
+            :rows="3"
25
+            type="textarea"
26
+            resize="none"/>
27
+          <div class="input-tips"><span>*</span>显示在标识名右侧的说明文字</div>
28
+        </div>
29
+
30
+        <setting-detail-table
31
+          v-if="field.form_type === 'detail_table'"
32
+          :field="field"
33
+          @child-edit="emitChildEdit" />
34
+
35
+        <template v-if="canOptions">
36
+          <div class="item-section">
37
+            <div class="name">选项内容</div>
38
+            <div class="input-tips"><span>*</span>修改选项后该项设置的逻辑表单会失效</div>
39
+            <setting-options
40
+              :field="field"
41
+              :is-table-child="isTableChild" />
42
+          </div>
43
+
44
+          <div v-if="!isTableChild" class="item-section">
45
+            <div class="name">逻辑表单</div>
46
+            <setting-logic-form
47
+              :field="field"
48
+              :point="point"
49
+              :field-arr="fieldArr" />
50
+          </div>
51
+        </template>
52
+
53
+        <div v-if="canPrecisions" class="item-section">
54
+          <div class="name">
55
+            {{ precisionsTitle }}
56
+          </div>
57
+          <setting-precisions :field="field" />
58
+        </div>
59
+
60
+        <div v-if="canDefault" class="item-section">
61
+          <div class="name">默认值</div>
62
+          <setting-default :field="field" />
63
+        </div>
64
+
65
+        <div v-if="canNumber" class="item-section">
66
+          <setting-number :field="field" />
67
+        </div>
68
+      </template>
69
+
70
+      <div v-if="isDescText" class="item-section">
71
+        <div class="name">内容</div>
72
+        <setting-desc-text :field="field" />
73
+      </div>
74
+
75
+      <div v-if="fieldAuth.percentEdit" class="item-section">
76
+        <div class="name">
77
+          字段占比 %
78
+          <el-tooltip
79
+            content="配置表单布局,可以单行多字段排布"
80
+            effect="dark"
81
+            placement="top">
82
+            <i class="wk wk-help wk-help-tips" style="margin-left: 3px;"/>
83
+          </el-tooltip>
84
+        </div>
85
+        <el-radio-group
86
+          v-model="field.style_percent"
87
+          size="medium"
88
+          @change="emitUpdateWidth">
89
+          <el-radio-button
90
+            v-for="item in widthOptions"
91
+            :label="item.value"
92
+            :key="item.value">{{ item.value }}</el-radio-button>
93
+        </el-radio-group>
94
+      </div>
95
+
96
+      <div v-if="canTransform && transformData && transformData[field.form_type]" class="item-section">
97
+        <div class="name">转化客户字段</div>
98
+        <el-select
99
+          v-model="field.relevant"
100
+          clearable>
101
+          <el-option
102
+            v-for="item in transformData[field.form_type]"
103
+            :key="item.value"
104
+            :label="item.label"
105
+            :value="item.value"/>
106
+        </el-select>
107
+      </div>
108
+
109
+      <template v-if="!isDescText">
110
+        <div
111
+          v-if="fieldAuth.nullEdit"
112
+          class="item-check-section">
113
+          <el-checkbox
114
+            v-model="field.is_null"
115
+            :true-label="1"
116
+            :false-label="0">设为必填</el-checkbox>
117
+        </div>
118
+
119
+        <div
120
+          v-if="fieldAuth.uniqueEdit"
121
+          class="item-check-section">
122
+          <el-checkbox
123
+            v-model="field.is_unique"
124
+            :true-label="1"
125
+            :false-label="0">设为唯一</el-checkbox>
126
+        </div>
127
+
128
+        <div
129
+          v-if="fieldAuth.hiddenEdit"
130
+          class="item-check-section">
131
+          <el-checkbox
132
+            v-model="field.is_hidden"
133
+            :true-label="1"
134
+            :false-label="0">隐藏字段</el-checkbox>
135
+        </div>
136
+      </template>
137
+
138
+    </div>
139
+  </div>
140
+</template>
141
+
142
+<script>
143
+import SettingDefault from './SettingDefault'
144
+import SettingOptions from './SettingOptions'
145
+import SettingNumber from './SettingNumber'
146
+import SettingPrecisions from './SettingPrecisions'
147
+import SettingDescText from './SettingDescText'
148
+import SettingDetailTable from './SettingDetailTable'
149
+import SettingLogicForm from './SettingLogicForm'
150
+
151
+import FieldTypeLib from '../../fieldTypeLib'
152
+import { getFieldAuth } from '../../utils'
153
+
154
+export default {
155
+  name: 'FieldSetting',
156
+  components: {
157
+    SettingDefault,
158
+    SettingOptions,
159
+    SettingNumber,
160
+    SettingPrecisions,
161
+    SettingDescText,
162
+    SettingDetailTable,
163
+    SettingLogicForm
164
+  },
165
+  props: {
166
+    // 是否开启转移  转移对应数据
167
+    canTransform: Boolean,
168
+    transformData: Object,
169
+    field: { // 要编辑的字段信息
170
+      type: Object,
171
+      required: true
172
+    },
173
+    fieldArr: { // 所有字段
174
+      type: Array,
175
+      required: true
176
+    },
177
+    point: { // 被选中的字段坐标
178
+      type: Array,
179
+      required: true
180
+    }
181
+  },
182
+  data() {
183
+    return {
184
+      widthOptions: [
185
+        { value: 25 },
186
+        { value: 50 },
187
+        { value: 75 },
188
+        { value: 100 }
189
+      ],
190
+      stylePercentValue: []
191
+    }
192
+  },
193
+  computed: {
194
+    typeObj() {
195
+      const field = FieldTypeLib.find(o => o.form_type === this.field.form_type)
196
+      return field || this.field
197
+    },
198
+    fieldAuth() {
199
+      return getFieldAuth(this.field.operating)
200
+    },
201
+    // 是否允许设置字段默认值
202
+    canDefault() {
203
+      return ![
204
+        'user',
205
+        'structure',
206
+        'file',
207
+        'location',
208
+        'handwriting_sign',
209
+        'detail_table'
210
+      ].includes(this.field.form_type)
211
+    },
212
+    // 是否允许设置选项内容
213
+    canOptions() {
214
+      return [
215
+        'select',
216
+        'checkbox'
217
+      ].includes(this.field.form_type)
218
+    },
219
+    // 是否允许设置小数
220
+    canNumber() {
221
+      return [
222
+        'number',
223
+        'floatnumber',
224
+        'percent'
225
+      ].includes(this.field.form_type)
226
+    },
227
+    // 精度
228
+    canPrecisions() {
229
+      return [
230
+        'date_interval',
231
+        'position',
232
+        'select',
233
+        'checkbox'
234
+      ].includes(this.field.form_type)
235
+    },
236
+    // 精度标题
237
+    precisionsTitle() {
238
+      if (!this.canPrecisions) return ''
239
+      switch (this.field.form_type) {
240
+        case 'date_interval':
241
+          return '日期类型'
242
+        case 'position':
243
+          return '地址精度'
244
+        case 'select':
245
+          return '展示方式'
246
+        case 'checkbox':
247
+          return '展示方式'
248
+        default:
249
+          return '精度'
250
+      }
251
+    },
252
+    // 是否为描述文字类型
253
+    isDescText() {
254
+      return this.field.form_type === 'desc_text'
255
+    },
256
+
257
+    // 是否为明细表格内部字段
258
+    isTableChild() {
259
+      const fatherField = this.fieldArr[this.point[0]][this.point[1]]
260
+      return fatherField.form_type === 'detail_table'
261
+    }
262
+  },
263
+  watch: {
264
+    field: {
265
+      handler() {
266
+        this.stylePercentValue = [Number(this.field.style_percent) || 100]
267
+      },
268
+      deep: true,
269
+      immediate: true
270
+    }
271
+  },
272
+  methods: {
273
+    emitUpdateWidth() {
274
+      this.$emit('update-width')
275
+    },
276
+    emitChildEdit(field = null) {
277
+      this.$emit('child-edit', field)
278
+    },
279
+    clickOutSide() {
280
+      this.emitChildEdit()
281
+    }
282
+  }
283
+}
284
+</script>
285
+
286
+<style scoped lang="scss">
287
+.el-checkbox /deep/ .el-checkbox__label {
288
+  font-size: 13px;
289
+  color: #333333;
290
+}
291
+
292
+.field-setting {
293
+  .setting-title {
294
+    padding: 20px 15px 0;
295
+    font-weight: bold;
296
+  }
297
+
298
+  .setting-body {
299
+    padding: 0 15px 10px;
300
+    .input-tips {
301
+      font-size: 12px;
302
+      margin-top: 10px;
303
+      color: #999;
304
+      span {
305
+        color: red;
306
+      }
307
+    }
308
+
309
+    .item-section {
310
+      padding: 10px 0;
311
+      border-bottom: 1px solid #e6e6e6;
312
+      .name {
313
+        font-size: 13px;
314
+        font-weight: 500;
315
+        color: #333;
316
+        margin: 10px 0;
317
+      }
318
+    }
319
+
320
+    .item-check-section {
321
+      margin-top: 10px;
322
+    }
323
+  }
324
+}
325
+</style>

+ 39
- 0
src/views/admin/fields/field.js Wyświetl plik

@@ -0,0 +1,39 @@
1
+export default class Field {
2
+  constructor(obj) {
3
+    this.field_type = 0 // 新增字段默认加入0 1是系统字段 2 客户行业 级别 来源 等 3 特殊
4
+    this.field_id = obj.field_id || '' //  字段id  1
5
+    this.name = obj.name || '' //  标识名  1
6
+    this.form_type = obj.form_type || '' // 字段类型  1
7
+    this.is_unique = obj.is_unique || 0 // 是否唯一
8
+    this.is_null = obj.is_null || 0 // 是否必填
9
+    this.is_hidden = obj.is_hidden || 0 // 是否隐藏字段
10
+    this.input_tips = obj.input_tips || '' // 输入提示
11
+    if (this.form_type === 'textarea') {
12
+      this.max_length = obj.max_length || 800 // textarea 多行文本有最大数量
13
+    }
14
+
15
+    if (this.form_type === 'checkbox') {
16
+      this.default_value = obj.default_value || []
17
+    } else {
18
+      this.default_value = obj.default_value || ''
19
+    }
20
+
21
+    // 表格的特殊处理
22
+    if (this.form_type === 'form') {
23
+      this.formValue = obj.formValue || [] // 内部布局
24
+    }
25
+
26
+    this.setting = obj.setting || [] // 单选选项
27
+    // this.showSetting = obj.showSetting || [] // 单选选项
28
+    // this.componentName = '' // 组件名字
29
+    this.is_deleted = 0 // 是删除标示这个字段是无效的 1是无效的
30
+  }
31
+
32
+  // 校验数据
33
+  check() {
34
+    if (this.name === '') {
35
+      return '字段名称不能为空'
36
+    }
37
+    return ''
38
+  }
39
+}

+ 0
- 0
src/views/admin/fields/fieldTypeLib.js Wyświetl plik


Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików