Ver código fonte

first commit

wangxx 4 meses atrás
pai
commit
d05944743d

+ 15 - 0
src/utils/auth.js

@@ -0,0 +1,15 @@
1
+import Cookies from 'js-cookie'
2
+
3
+const TokenKey = 'Admin-Token'
4
+
5
+export function getToken() {
6
+  return Cookies.get(TokenKey)
7
+}
8
+
9
+export function setToken(token) {
10
+  return Cookies.set(TokenKey, token)
11
+}
12
+
13
+export function removeToken() {
14
+  return Cookies.remove(TokenKey)
15
+}

+ 24 - 0
src/utils/dict.js

@@ -0,0 +1,24 @@
1
+import useDictStore from '@/store/modules/dict'
2
+import { getDicts } from '@/api/system/dict/data'
3
+
4
+/**
5
+ * 获取字典数据
6
+ */
7
+export function useDict(...args) {
8
+  const res = ref({})
9
+  return (() => {
10
+    args.forEach((dictType, index) => {
11
+      res.value[dictType] = []
12
+      const dicts = useDictStore().getDict(dictType)
13
+      if (dicts) {
14
+        res.value[dictType] = dicts
15
+      } else {
16
+        getDicts(dictType).then(resp => {
17
+          res.value[dictType] = resp.data.map(p => ({ label: p.dictLabel, value: p.dictValue, elTagType: p.listClass, elTagClass: p.cssClass }))
18
+          useDictStore().setDict(dictType, res.value[dictType])
19
+        })
20
+      }
21
+    })
22
+    return toRefs(res.value)
23
+  })()
24
+}

+ 14 - 0
src/utils/dynamicTitle.js

@@ -0,0 +1,14 @@
1
+import defaultSettings from '@/settings'
2
+import useSettingsStore from '@/store/modules/settings'
3
+
4
+/**
5
+ * 动态修改标题
6
+ */
7
+export function useDynamicTitle() {
8
+  const settingsStore = useSettingsStore()
9
+  if (settingsStore.dynamicTitle) {
10
+    document.title = settingsStore.title + ' - ' + defaultSettings.title
11
+  } else {
12
+    document.title = defaultSettings.title
13
+  }
14
+}

+ 6 - 0
src/utils/errorCode.js

@@ -0,0 +1,6 @@
1
+export default {
2
+  '401': '认证失败,无法访问系统资源',
3
+  '403': '当前操作没有权限',
4
+  '404': '访问资源不存在',
5
+  'default': '系统未知错误,请反馈给管理员'
6
+}

+ 452 - 0
src/utils/generator/config.js

@@ -0,0 +1,452 @@
1
+export const formConf = {
2
+  formRef: 'formRef',
3
+  formModel: 'formData',
4
+  size: 'default',
5
+  labelPosition: 'right',
6
+  labelWidth: 100,
7
+  formRules: 'rules',
8
+  gutter: 15,
9
+  disabled: false,
10
+  span: 24,
11
+  formBtns: true,
12
+}
13
+
14
+export const inputComponents = [
15
+  {
16
+    label: '单行文本',
17
+    tag: 'el-input',
18
+    tagIcon: 'input',
19
+    type: 'text',
20
+    placeholder: '请输入',
21
+    defaultValue: undefined,
22
+    span: 24,
23
+    labelWidth: null,
24
+    style: { width: '100%' },
25
+    clearable: true,
26
+    prepend: '',
27
+    append: '',
28
+    'prefix-icon': '',
29
+    'suffix-icon': '',
30
+    maxlength: null,
31
+    'show-word-limit': false,
32
+    readonly: false,
33
+    disabled: false,
34
+    required: true,
35
+    regList: [],
36
+    changeTag: true,
37
+    document: 'https://element-plus.org/zh-CN/component/input',
38
+  },
39
+  {
40
+    label: '多行文本',
41
+    tag: 'el-input',
42
+    tagIcon: 'textarea',
43
+    type: 'textarea',
44
+    placeholder: '请输入',
45
+    defaultValue: undefined,
46
+    span: 24,
47
+    labelWidth: null,
48
+    autosize: {
49
+      minRows: 4,
50
+      maxRows: 4,
51
+    },
52
+    style: { width: '100%' },
53
+    maxlength: null,
54
+    'show-word-limit': false,
55
+    readonly: false,
56
+    disabled: false,
57
+    required: true,
58
+    regList: [],
59
+    changeTag: true,
60
+    document: 'https://element-plus.org/zh-CN/component/input',
61
+  },
62
+  {
63
+    label: '密码',
64
+    tag: 'el-input',
65
+    tagIcon: 'password',
66
+    type: 'password',
67
+    placeholder: '请输入',
68
+    defaultValue: undefined,
69
+    span: 24,
70
+    'show-password': true,
71
+    labelWidth: null,
72
+    style: { width: '100%' },
73
+    clearable: true,
74
+    prepend: '',
75
+    append: '',
76
+    'prefix-icon': '',
77
+    'suffix-icon': '',
78
+    maxlength: null,
79
+    'show-word-limit': false,
80
+    readonly: false,
81
+    disabled: false,
82
+    required: true,
83
+    regList: [],
84
+    changeTag: true,
85
+    document: 'https://element-plus.org/zh-CN/component/input',
86
+  },
87
+  {
88
+    label: '计数器',
89
+    tag: 'el-input-number',
90
+    tagIcon: 'number',
91
+    placeholder: '',
92
+    defaultValue: undefined,
93
+    span: 24,
94
+    labelWidth: null,
95
+    min: undefined,
96
+    max: undefined,
97
+    step: undefined,
98
+    'step-strictly': false,
99
+    precision: undefined,
100
+    'controls-position': '',
101
+    disabled: false,
102
+    required: true,
103
+    regList: [],
104
+    changeTag: true,
105
+    document: 'https://element-plus.org/zh-CN/component/input-number',
106
+  },
107
+]
108
+
109
+export const selectComponents = [
110
+  {
111
+    label: '下拉选择',
112
+    tag: 'el-select',
113
+    tagIcon: 'select',
114
+    placeholder: '请选择',
115
+    defaultValue: undefined,
116
+    span: 24,
117
+    labelWidth: null,
118
+    style: { width: '100%' },
119
+    clearable: true,
120
+    disabled: false,
121
+    required: true,
122
+    filterable: false,
123
+    multiple: false,
124
+    options: [
125
+      {
126
+        label: '选项一',
127
+        value: 1,
128
+      },
129
+      {
130
+        label: '选项二',
131
+        value: 2,
132
+      },
133
+    ],
134
+    regList: [],
135
+    changeTag: true,
136
+    document: 'https://element-plus.org/zh-CN/component/select',
137
+  },
138
+  {
139
+    label: '级联选择',
140
+    tag: 'el-cascader',
141
+    tagIcon: 'cascader',
142
+    placeholder: '请选择',
143
+    defaultValue: [],
144
+    span: 24,
145
+    labelWidth: null,
146
+    style: { width: '100%' },
147
+    props: {
148
+      props: {
149
+        multiple: false,
150
+      },
151
+    },
152
+    'show-all-levels': true,
153
+    disabled: false,
154
+    clearable: true,
155
+    filterable: false,
156
+    required: true,
157
+    options: [
158
+      {
159
+        id: 1,
160
+        value: 1,
161
+        label: '选项1',
162
+        children: [
163
+          {
164
+            id: 2,
165
+            value: 2,
166
+            label: '选项1-1',
167
+          },
168
+        ],
169
+      },
170
+    ],
171
+    dataType: 'dynamic',
172
+    labelKey: 'label',
173
+    valueKey: 'value',
174
+    childrenKey: 'children',
175
+    separator: '/',
176
+    regList: [],
177
+    changeTag: true,
178
+    document: 'https://element-plus.org/zh-CN/component/cascader',
179
+  },
180
+  {
181
+    label: '单选框组',
182
+    tag: 'el-radio-group',
183
+    tagIcon: 'radio',
184
+    defaultValue: 0,
185
+    span: 24,
186
+    labelWidth: null,
187
+    style: {},
188
+    optionType: 'default',
189
+    border: false,
190
+    size: 'default',
191
+    disabled: false,
192
+    required: true,
193
+    options: [
194
+      {
195
+        label: '选项一',
196
+        value: 1,
197
+      },
198
+      {
199
+        label: '选项二',
200
+        value: 2,
201
+      },
202
+    ],
203
+    regList: [],
204
+    changeTag: true,
205
+    document: 'https://element-plus.org/zh-CN/component/radio',
206
+  },
207
+  {
208
+    label: '多选框组',
209
+    tag: 'el-checkbox-group',
210
+    tagIcon: 'checkbox',
211
+    defaultValue: [],
212
+    span: 24,
213
+    labelWidth: null,
214
+    style: {},
215
+    optionType: 'default',
216
+    border: false,
217
+    size: 'default',
218
+    disabled: false,
219
+    required: true,
220
+    options: [
221
+      {
222
+        label: '选项一',
223
+        value: 1,
224
+      },
225
+      {
226
+        label: '选项二',
227
+        value: 2,
228
+      },
229
+    ],
230
+    regList: [],
231
+    changeTag: true,
232
+    document: 'https://element-plus.org/zh-CN/component/checkbox',
233
+  },
234
+  {
235
+    label: '开关',
236
+    tag: 'el-switch',
237
+    tagIcon: 'switch',
238
+    defaultValue: false,
239
+    span: 24,
240
+    labelWidth: null,
241
+    style: {},
242
+    disabled: false,
243
+    required: true,
244
+    'active-text': '',
245
+    'inactive-text': '',
246
+    'active-color': null,
247
+    'inactive-color': null,
248
+    'active-value': true,
249
+    'inactive-value': false,
250
+    regList: [],
251
+    changeTag: true,
252
+    document: 'https://element-plus.org/zh-CN/component/switch',
253
+  },
254
+  {
255
+    label: '滑块',
256
+    tag: 'el-slider',
257
+    tagIcon: 'slider',
258
+    defaultValue: null,
259
+    span: 24,
260
+    labelWidth: null,
261
+    disabled: false,
262
+    required: true,
263
+    min: 0,
264
+    max: 100,
265
+    step: 1,
266
+    'show-stops': false,
267
+    range: false,
268
+    regList: [],
269
+    changeTag: true,
270
+    document: 'https://element-plus.org/zh-CN/component/slider',
271
+  },
272
+  {
273
+    label: '时间选择',
274
+    tag: 'el-time-picker',
275
+    tagIcon: 'time',
276
+    placeholder: '请选择',
277
+    defaultValue: '',
278
+    span: 24,
279
+    labelWidth: null,
280
+    style: { width: '100%' },
281
+    disabled: false,
282
+    clearable: true,
283
+    required: true,
284
+    format: 'HH:mm:ss',
285
+    'value-format': 'HH:mm:ss',
286
+    regList: [],
287
+    changeTag: true,
288
+    document: 'https://element-plus.org/zh-CN/component/time-picker',
289
+  },
290
+  {
291
+    label: '时间范围',
292
+    tag: 'el-time-picker',
293
+    tagIcon: 'time-range',
294
+    defaultValue: null,
295
+    span: 24,
296
+    labelWidth: null,
297
+    style: { width: '100%' },
298
+    disabled: false,
299
+    clearable: true,
300
+    required: true,
301
+    'is-range': true,
302
+    'range-separator': '至',
303
+    'start-placeholder': '开始时间',
304
+    'end-placeholder': '结束时间',
305
+    format: 'HH:mm:ss',
306
+    'value-format': 'HH:mm:ss',
307
+    regList: [],
308
+    changeTag: true,
309
+    document: 'https://element-plus.org/zh-CN/component/time-picker',
310
+  },
311
+  {
312
+    label: '日期选择',
313
+    tag: 'el-date-picker',
314
+    tagIcon: 'date',
315
+    placeholder: '请选择',
316
+    defaultValue: null,
317
+    type: 'date',
318
+    span: 24,
319
+    labelWidth: null,
320
+    style: { width: '100%' },
321
+    disabled: false,
322
+    clearable: true,
323
+    required: true,
324
+    format: 'YYYY-MM-DD',
325
+    'value-format': 'YYYY-MM-DD',
326
+    readonly: false,
327
+    regList: [],
328
+    changeTag: true,
329
+    document: 'https://element-plus.org/zh-CN/component/date-picker',
330
+  },
331
+  {
332
+    label: '日期范围',
333
+    tag: 'el-date-picker',
334
+    tagIcon: 'date-range',
335
+    defaultValue: null,
336
+    span: 24,
337
+    labelWidth: null,
338
+    style: { width: '100%' },
339
+    type: 'daterange',
340
+    'range-separator': '至',
341
+    'start-placeholder': '开始日期',
342
+    'end-placeholder': '结束日期',
343
+    disabled: false,
344
+    clearable: true,
345
+    required: true,
346
+    format: 'YYYY-MM-DD',
347
+    'value-format': 'YYYY-MM-DD',
348
+    readonly: false,
349
+    regList: [],
350
+    changeTag: true,
351
+    document: 'https://element-plus.org/zh-CN/component/date-picker',
352
+  },
353
+  {
354
+    label: '评分',
355
+    tag: 'el-rate',
356
+    tagIcon: 'rate',
357
+    defaultValue: 0,
358
+    span: 24,
359
+    labelWidth: null,
360
+    style: {},
361
+    max: 5,
362
+    'allow-half': false,
363
+    'show-text': false,
364
+    'show-score': false,
365
+    disabled: false,
366
+    required: true,
367
+    regList: [],
368
+    changeTag: true,
369
+    document: 'https://element-plus.org/zh-CN/component/rate',
370
+  },
371
+  {
372
+    label: '颜色选择',
373
+    tag: 'el-color-picker',
374
+    tagIcon: 'color',
375
+    defaultValue: null,
376
+    labelWidth: null,
377
+    'show-alpha': false,
378
+    'color-format': '',
379
+    disabled: false,
380
+    required: true,
381
+    size: 'default',
382
+    regList: [],
383
+    changeTag: true,
384
+    document: 'https://element-plus.org/zh-CN/component/color-picker',
385
+  },
386
+  {
387
+    label: '上传',
388
+    tag: 'el-upload',
389
+    tagIcon: 'upload',
390
+    action: 'https://jsonplaceholder.typicode.com/posts/',
391
+    defaultValue: null,
392
+    labelWidth: null,
393
+    disabled: false,
394
+    required: true,
395
+    accept: '',
396
+    name: 'file',
397
+    'auto-upload': true,
398
+    showTip: false,
399
+    buttonText: '点击上传',
400
+    fileSize: 2,
401
+    sizeUnit: 'MB',
402
+    'list-type': 'text',
403
+    multiple: false,
404
+    regList: [],
405
+    changeTag: true,
406
+    document: 'https://element-plus.org/zh-CN/component/upload',
407
+    tip: '只能上传不超过 2MB 的文件',
408
+    style: { width: '100%' },
409
+  },
410
+]
411
+
412
+export const layoutComponents = [
413
+  {
414
+    layout: 'rowFormItem',
415
+    tagIcon: 'row',
416
+    type: 'default',
417
+    justify: 'start',
418
+    align: 'top',
419
+    label: '行容器',
420
+    layoutTree: true,
421
+    children: [],
422
+    document: 'https://element-plus.org/zh-CN/component/layout',
423
+  },
424
+  {
425
+    layout: 'colFormItem',
426
+    label: '按钮',
427
+    changeTag: true,
428
+    labelWidth: null,
429
+    tag: 'el-button',
430
+    tagIcon: 'button',
431
+    span: 24,
432
+    default: '主要按钮',
433
+    type: 'primary',
434
+    icon: 'Search',
435
+    size: 'default',
436
+    disabled: false,
437
+    document: 'https://element-plus.org/zh-CN/component/button',
438
+  },
439
+]
440
+
441
+// 组件rule的触发方式,无触发方式的组件不生成rule
442
+export const trigger = {
443
+  'el-input': 'blur',
444
+  'el-input-number': 'blur',
445
+  'el-select': 'change',
446
+  'el-radio-group': 'change',
447
+  'el-checkbox-group': 'change',
448
+  'el-cascader': 'change',
449
+  'el-time-picker': 'change',
450
+  'el-date-picker': 'change',
451
+  'el-rate': 'change',
452
+}

+ 18 - 0
src/utils/generator/css.js

@@ -0,0 +1,18 @@
1
+const styles = {
2
+  'el-rate': '.el-rate{display: inline-block; vertical-align: text-top;}',
3
+  'el-upload': '.el-upload__tip{line-height: 1.2;}'
4
+}
5
+
6
+function addCss(cssList, el) {
7
+  const css = styles[el.tag]
8
+  css && cssList.indexOf(css) === -1 && cssList.push(css)
9
+  if (el.children) {
10
+    el.children.forEach(el2 => addCss(cssList, el2))
11
+  }
12
+}
13
+
14
+export function makeUpCss(conf) {
15
+  const cssList = []
16
+  conf.fields.forEach(el => addCss(cssList, el))
17
+  return cssList.join('\n')
18
+}

+ 29 - 0
src/utils/generator/drawingDefalut.js

@@ -0,0 +1,29 @@
1
+export default [
2
+  {
3
+    layout: 'colFormItem',
4
+    tagIcon: 'input',
5
+    label: '手机号',
6
+    vModel: 'mobile',
7
+    formId: 6,
8
+    tag: 'el-input',
9
+    placeholder: '请输入手机号',
10
+    defaultValue: '',
11
+    span: 24,
12
+    style: { width: '100%' },
13
+    clearable: true,
14
+    prepend: '',
15
+    append: '',
16
+    'prefix-icon': 'Cellphone',
17
+    'suffix-icon': '',
18
+    maxlength: 11,
19
+    'show-word-limit': true,
20
+    readonly: false,
21
+    disabled: false,
22
+    required: true,
23
+    changeTag: true,
24
+    regList: [{
25
+      pattern: '/^1(3|4|5|7|8|9)\\d{9}$/',
26
+      message: '手机号格式错误'
27
+    }]
28
+  }
29
+]

+ 359 - 0
src/utils/generator/html.js

@@ -0,0 +1,359 @@
1
+/* eslint-disable max-len */
2
+import { trigger } from './config'
3
+
4
+let confGlobal
5
+let someSpanIsNot24
6
+
7
+export function dialogWrapper(str) {
8
+  return `<el-dialog v-model="dialogVisible"  @open="onOpen" @close="onClose" title="Dialog Titile">
9
+    ${str}
10
+    <template #footer>
11
+      <el-button @click="close">取消</el-button>
12
+	  <el-button type="primary" @click="handelConfirm">确定</el-button>
13
+    </template>
14
+  </el-dialog>`
15
+}
16
+
17
+export function vueTemplate(str) {
18
+  return `<template>
19
+    <div class="app-container">
20
+      ${str}
21
+    </div>
22
+  </template>`
23
+}
24
+
25
+export function vueScript(str) {
26
+  return `<script setup>
27
+    ${str}
28
+  </script>`
29
+}
30
+
31
+export function cssStyle(cssStr) {
32
+  return `<style>
33
+    ${cssStr}
34
+  </style>`
35
+}
36
+
37
+function buildFormTemplate(conf, child, type) {
38
+  let labelPosition = ''
39
+  if (conf.labelPosition !== 'right') {
40
+    labelPosition = `label-position="${conf.labelPosition}"`
41
+  }
42
+  const disabled = conf.disabled ? `:disabled="${conf.disabled}"` : ''
43
+  let str = `<el-form ref="${conf.formRef}" :model="${conf.formModel}" :rules="${conf.formRules}" size="${conf.size}" ${disabled} label-width="${conf.labelWidth}px" ${labelPosition}>
44
+      ${child}
45
+      ${buildFromBtns(conf, type)}
46
+    </el-form>`
47
+  if (someSpanIsNot24) {
48
+    str = `<el-row :gutter="${conf.gutter}">
49
+        ${str}
50
+      </el-row>`
51
+  }
52
+  return str
53
+}
54
+
55
+function buildFromBtns(conf, type) {
56
+  let str = ''
57
+  if (conf.formBtns && type === 'file') {
58
+    str = `<el-form-item>
59
+          <el-button type="primary" @click="submitForm">提交</el-button>
60
+          <el-button @click="resetForm">重置</el-button>
61
+        </el-form-item>`
62
+    if (someSpanIsNot24) {
63
+      str = `<el-col :span="24">
64
+          ${str}
65
+        </el-col>`
66
+    }
67
+  }
68
+  return str
69
+}
70
+
71
+// span不为24的用el-col包裹
72
+function colWrapper(element, str) {
73
+  if (someSpanIsNot24 || element.span !== 24) {
74
+    return `<el-col :span="${element.span}">
75
+      ${str}
76
+    </el-col>`
77
+  }
78
+  return str
79
+}
80
+
81
+const layouts = {
82
+  colFormItem(element) {
83
+    let labelWidth = ''
84
+    if (element.labelWidth && element.labelWidth !== confGlobal.labelWidth) {
85
+      labelWidth = `label-width="${element.labelWidth}px"`
86
+    }
87
+    const required = !trigger[element.tag] && element.required ? 'required' : ''
88
+    const tagDom = tags[element.tag] ? tags[element.tag](element) : null
89
+    let str = `<el-form-item ${labelWidth} label="${element.label}" prop="${element.vModel}" ${required}>
90
+        ${tagDom}
91
+      </el-form-item>`
92
+    str = colWrapper(element, str)
93
+    return str
94
+  },
95
+  rowFormItem(element) {
96
+    const type = element.type === 'default' ? '' : `type="${element.type}"`
97
+    const justify = element.type === 'default' ? '' : `justify="${element.justify}"`
98
+    const align = element.type === 'default' ? '' : `align="${element.align}"`
99
+    const gutter = element.gutter ? `gutter="${element.gutter}"` : ''
100
+    const children = element.children.map(el => layouts[el.layout](el))
101
+    let str = `<el-row ${type} ${justify} ${align} ${gutter}>
102
+      ${children.join('\n')}
103
+    </el-row>`
104
+    str = colWrapper(element, str)
105
+    return str
106
+  }
107
+}
108
+
109
+const tags = {
110
+  'el-button': el => {
111
+    const {
112
+      tag, disabled
113
+    } = attrBuilder(el)
114
+    const type = el.type ? `type="${el.type}"` : ''
115
+    const icon = el.icon ? `icon="${el.icon}"` : ''
116
+    const size = el.size ? `size="${el.size}"` : ''
117
+    let child = buildElButtonChild(el)
118
+
119
+    if (child) child = `\n${child}\n` // 换行
120
+    return `<${el.tag} ${type} ${icon} ${size} ${disabled}>${child}</${el.tag}>`
121
+  },
122
+  'el-input': el => {
123
+    const {
124
+      disabled, vModel, clearable, placeholder, width
125
+    } = attrBuilder(el)
126
+    const maxlength = el.maxlength ? `:maxlength="${el.maxlength}"` : ''
127
+    const showWordLimit = el['show-word-limit'] ? 'show-word-limit' : ''
128
+    const readonly = el.readonly ? 'readonly' : ''
129
+    const prefixIcon = el['prefix-icon'] ? `prefix-icon='${el['prefix-icon']}'` : ''
130
+    const suffixIcon = el['suffix-icon'] ? `suffix-icon='${el['suffix-icon']}'` : ''
131
+    const showPassword = el['show-password'] ? 'show-password' : ''
132
+    const type = el.type ? `type="${el.type}"` : ''
133
+    const autosize = el.autosize && el.autosize.minRows
134
+      ? `:autosize="{minRows: ${el.autosize.minRows}, maxRows: ${el.autosize.maxRows}}"`
135
+      : ''
136
+    let child = buildElInputChild(el)
137
+
138
+    if (child) child = `\n${child}\n` // 换行
139
+    return `<${el.tag} ${vModel} ${type} ${placeholder} ${maxlength} ${showWordLimit} ${readonly} ${disabled} ${clearable} ${prefixIcon} ${suffixIcon} ${showPassword} ${autosize} ${width}>${child}</${el.tag}>`
140
+  },
141
+  'el-input-number': el => {
142
+    const { disabled, vModel, placeholder } = attrBuilder(el)
143
+    const controlsPosition = el['controls-position'] ? `controls-position=${el['controls-position']}` : ''
144
+    const min = el.min ? `:min='${el.min}'` : ''
145
+    const max = el.max ? `:max='${el.max}'` : ''
146
+    const step = el.step ? `:step='${el.step}'` : ''
147
+    const stepStrictly = el['step-strictly'] ? 'step-strictly' : ''
148
+    const precision = el.precision ? `:precision='${el.precision}'` : ''
149
+
150
+    return `<${el.tag} ${vModel} ${placeholder} ${step} ${stepStrictly} ${precision} ${controlsPosition} ${min} ${max} ${disabled}></${el.tag}>`
151
+  },
152
+  'el-select': el => {
153
+    const {
154
+      disabled, vModel, clearable, placeholder, width
155
+    } = attrBuilder(el)
156
+    const filterable = el.filterable ? 'filterable' : ''
157
+    const multiple = el.multiple ? 'multiple' : ''
158
+    let child = buildElSelectChild(el)
159
+
160
+    if (child) child = `\n${child}\n` // 换行
161
+    return `<${el.tag} ${vModel} ${placeholder} ${disabled} ${multiple} ${filterable} ${clearable} ${width}>${child}</${el.tag}>`
162
+  },
163
+  'el-radio-group': el => {
164
+    const { disabled, vModel } = attrBuilder(el)
165
+    const size = `size="${el.size}"`
166
+    let child = buildElRadioGroupChild(el)
167
+
168
+    if (child) child = `\n${child}\n` // 换行
169
+    return `<${el.tag} ${vModel} ${size} ${disabled}>${child}</${el.tag}>`
170
+  },
171
+  'el-checkbox-group': el => {
172
+    const { disabled, vModel } = attrBuilder(el)
173
+    const size = `size="${el.size}"`
174
+    const min = el.min ? `:min="${el.min}"` : ''
175
+    const max = el.max ? `:max="${el.max}"` : ''
176
+    let child = buildElCheckboxGroupChild(el)
177
+
178
+    if (child) child = `\n${child}\n` // 换行
179
+    return `<${el.tag} ${vModel} ${min} ${max} ${size} ${disabled}>${child}</${el.tag}>`
180
+  },
181
+  'el-switch': el => {
182
+    const { disabled, vModel } = attrBuilder(el)
183
+    const activeText = el['active-text'] ? `active-text="${el['active-text']}"` : ''
184
+    const inactiveText = el['inactive-text'] ? `inactive-text="${el['inactive-text']}"` : ''
185
+    const activeColor = el['active-color'] ? `active-color="${el['active-color']}"` : ''
186
+    const inactiveColor = el['inactive-color'] ? `inactive-color="${el['inactive-color']}"` : ''
187
+    const activeValue = el['active-value'] !== true ? `:active-value='${JSON.stringify(el['active-value'])}'` : ''
188
+    const inactiveValue = el['inactive-value'] !== false ? `:inactive-value='${JSON.stringify(el['inactive-value'])}'` : ''
189
+
190
+    return `<${el.tag} ${vModel} ${activeText} ${inactiveText} ${activeColor} ${inactiveColor} ${activeValue} ${inactiveValue} ${disabled}></${el.tag}>`
191
+  },
192
+  'el-cascader': el => {
193
+    const {
194
+      disabled, vModel, clearable, placeholder, width
195
+    } = attrBuilder(el)
196
+    const options = el.options ? `:options="${el.vModel}Options"` : ''
197
+    const props = el.props ? `:props="${el.vModel}Props"` : ''
198
+    const showAllLevels = el['show-all-levels'] ? '' : ':show-all-levels="false"'
199
+    const filterable = el.filterable ? 'filterable' : ''
200
+    const separator = el.separator === '/' ? '' : `separator="${el.separator}"`
201
+
202
+    return `<${el.tag} ${vModel} ${options} ${props} ${width} ${showAllLevels} ${placeholder} ${separator} ${filterable} ${clearable} ${disabled}></${el.tag}>`
203
+  },
204
+  'el-slider': el => {
205
+    const { disabled, vModel } = attrBuilder(el)
206
+    const min = el.min ? `:min='${el.min}'` : ''
207
+    const max = el.max ? `:max='${el.max}'` : ''
208
+    const step = el.step ? `:step='${el.step}'` : ''
209
+    const range = el.range ? 'range' : ''
210
+    const showStops = el['show-stops'] ? `:show-stops="${el['show-stops']}"` : ''
211
+
212
+    return `<${el.tag} ${min} ${max} ${step} ${vModel} ${range} ${showStops} ${disabled}></${el.tag}>`
213
+  },
214
+  'el-time-picker': el => {
215
+    const {
216
+      disabled, vModel, clearable, placeholder, width
217
+    } = attrBuilder(el)
218
+    const startPlaceholder = el['start-placeholder'] ? `start-placeholder="${el['start-placeholder']}"` : ''
219
+    const endPlaceholder = el['end-placeholder'] ? `end-placeholder="${el['end-placeholder']}"` : ''
220
+    const rangeSeparator = el['range-separator'] ? `range-separator="${el['range-separator']}"` : ''
221
+    const isRange = el['is-range'] ? 'is-range' : ''
222
+    const format = el.format ? `format="${el.format}"` : ''
223
+    const valueFormat = el['value-format'] ? `value-format="${el['value-format']}"` : ''
224
+    const pickerOptions = el['picker-options'] ? `:picker-options='${JSON.stringify(el['picker-options'])}'` : ''
225
+
226
+    return `<${el.tag} ${vModel} ${isRange} ${format} ${valueFormat} ${pickerOptions} ${width} ${placeholder} ${startPlaceholder} ${endPlaceholder} ${rangeSeparator} ${clearable} ${disabled}></${el.tag}>`
227
+  },
228
+  'el-date-picker': el => {
229
+    const {
230
+      disabled, vModel, clearable, placeholder, width
231
+    } = attrBuilder(el)
232
+    const startPlaceholder = el['start-placeholder'] ? `start-placeholder="${el['start-placeholder']}"` : ''
233
+    const endPlaceholder = el['end-placeholder'] ? `end-placeholder="${el['end-placeholder']}"` : ''
234
+    const rangeSeparator = el['range-separator'] ? `range-separator="${el['range-separator']}"` : ''
235
+    const format = el.format ? `format="${el.format}"` : ''
236
+    const valueFormat = el['value-format'] ? `value-format="${el['value-format']}"` : ''
237
+    const type = el.type === 'date' ? '' : `type="${el.type}"`
238
+    const readonly = el.readonly ? 'readonly' : ''
239
+
240
+    return `<${el.tag} ${type} ${vModel} ${format} ${valueFormat} ${width} ${placeholder} ${startPlaceholder} ${endPlaceholder} ${rangeSeparator} ${clearable} ${readonly} ${disabled}></${el.tag}>`
241
+  },
242
+  'el-rate': el => {
243
+    const { disabled, vModel } = attrBuilder(el)
244
+    const max = el.max ? `:max='${el.max}'` : ''
245
+    const allowHalf = el['allow-half'] ? 'allow-half' : ''
246
+    const showText = el['show-text'] ? 'show-text' : ''
247
+    const showScore = el['show-score'] ? 'show-score' : ''
248
+
249
+    return `<${el.tag} ${vModel} ${allowHalf} ${showText} ${showScore} ${disabled}></${el.tag}>`
250
+  },
251
+  'el-color-picker': el => {
252
+    const { disabled, vModel } = attrBuilder(el)
253
+    const size = `size="${el.size}"`
254
+    const showAlpha = el['show-alpha'] ? 'show-alpha' : ''
255
+    const colorFormat = el['color-format'] ? `color-format="${el['color-format']}"` : ''
256
+
257
+    return `<${el.tag} ${vModel} ${size} ${showAlpha} ${colorFormat} ${disabled}></${el.tag}>`
258
+  },
259
+  'el-upload': el => {
260
+    const disabled = el.disabled ? ':disabled=\'true\'' : ''
261
+    const action = el.action ? `:action="${el.vModel}Action"` : ''
262
+    const multiple = el.multiple ? 'multiple' : ''
263
+    const listType = el['list-type'] !== 'text' ? `list-type="${el['list-type']}"` : ''
264
+    const accept = el.accept ? `accept="${el.accept}"` : ''
265
+    const name = el.name !== 'file' ? `name="${el.name}"` : ''
266
+    const autoUpload = el['auto-upload'] === false ? ':auto-upload="false"' : ''
267
+    const beforeUpload = `:before-upload="${el.vModel}BeforeUpload"`
268
+    const fileList = `:file-list="${el.vModel}fileList"`
269
+    const ref = `ref="${el.vModel}"`
270
+    let child = buildElUploadChild(el)
271
+
272
+    if (child) child = `\n${child}\n` // 换行
273
+    return `<${el.tag} ${ref} ${fileList} ${action} ${autoUpload} ${multiple} ${beforeUpload} ${listType} ${accept} ${name} ${disabled}>${child}</${el.tag}>`
274
+  }
275
+}
276
+
277
+function attrBuilder(el) {
278
+  return {
279
+    vModel: `v-model="${confGlobal.formModel}.${el.vModel}"`,
280
+    clearable: el.clearable ? 'clearable' : '',
281
+    placeholder: el.placeholder ? `placeholder="${el.placeholder}"` : '',
282
+    width: el.style && el.style.width ? ':style="{width: \'100%\'}"' : '',
283
+    disabled: el.disabled ? ':disabled=\'true\'' : ''
284
+  }
285
+}
286
+
287
+// el-buttin 子级
288
+function buildElButtonChild(conf) {
289
+  const children = []
290
+  if (conf.default) {
291
+    children.push(conf.default)
292
+  }
293
+  return children.join('\n')
294
+}
295
+
296
+// el-input innerHTML
297
+function buildElInputChild(conf) {
298
+  const children = []
299
+  if (conf.prepend) {
300
+    children.push(`<template slot="prepend">${conf.prepend}</template>`)
301
+  }
302
+  if (conf.append) {
303
+    children.push(`<template slot="append">${conf.append}</template>`)
304
+  }
305
+  return children.join('\n')
306
+}
307
+
308
+function buildElSelectChild(conf) {
309
+  const children = []
310
+  if (conf.options && conf.options.length) {
311
+    children.push(`<el-option v-for="(item, index) in ${conf.vModel}Options" :key="index" :label="item.label" :value="item.value" :disabled="item.disabled"></el-option>`)
312
+  }
313
+  return children.join('\n')
314
+}
315
+
316
+function buildElRadioGroupChild(conf) {
317
+  const children = []
318
+  if (conf.options && conf.options.length) {
319
+    const tag = conf.optionType === 'button' ? 'el-radio-button' : 'el-radio'
320
+    const border = conf.border ? 'border' : ''
321
+    children.push(`<${tag} v-for="(item, index) in ${conf.vModel}Options" :key="index" :value="item.value" :disabled="item.disabled" ${border}>{{item.label}}</${tag}>`)
322
+  }
323
+  return children.join('\n')
324
+}
325
+
326
+function buildElCheckboxGroupChild(conf) {
327
+  const children = []
328
+  if (conf.options && conf.options.length) {
329
+    const tag = conf.optionType === 'button' ? 'el-checkbox-button' : 'el-checkbox'
330
+    const border = conf.border ? 'border' : ''
331
+    children.push(`<${tag} v-for="(item, index) in ${conf.vModel}Options" :key="index" :label="item.value" :value="item.label" :disabled="item.disabled" ${border} />`)
332
+  }
333
+  return children.join('\n')
334
+}
335
+
336
+function buildElUploadChild(conf) {
337
+  const list = []
338
+  if (conf['list-type'] === 'picture-card') list.push('<i class="el-icon-plus"></i>')
339
+  else list.push(`<el-button size="small" type="primary" icon="el-icon-upload">${conf.buttonText}</el-button>`)
340
+  if (conf.showTip) list.push(`<div slot="tip" class="el-upload__tip">只能上传不超过 ${conf.fileSize}${conf.sizeUnit} 的${conf.accept}文件</div>`)
341
+  return list.join('\n')
342
+}
343
+
344
+export function makeUpHtml(conf, type) {
345
+  const htmlList = []
346
+  confGlobal = conf
347
+  someSpanIsNot24 = conf.fields.some(item => item.span !== 24)
348
+  conf.fields.forEach(el => {
349
+    htmlList.push(layouts[el.layout](el))
350
+  })
351
+  const htmlStr = htmlList.join('\n')
352
+
353
+  let temp = buildFormTemplate(conf, htmlStr, type)
354
+  if (type === 'dialog') {
355
+    temp = dialogWrapper(temp)
356
+  }
357
+  confGlobal = null
358
+  return temp
359
+}

Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/utils/generator/icon.json


+ 370 - 0
src/utils/generator/js.js

@@ -0,0 +1,370 @@
1
+import { titleCase } from '@/utils/index'
2
+import { trigger } from './config'
3
+// 文件大小设置
4
+const units = {
5
+  KB: '1024',
6
+  MB: '1024 / 1024',
7
+  GB: '1024 / 1024 / 1024',
8
+}
9
+/**
10
+ * @name: 生成js需要的数据
11
+ * @description: 生成js需要的数据
12
+ * @param {*} conf
13
+ * @param {*} type 弹窗或表单
14
+ * @return {*}
15
+ */
16
+export function makeUpJs(conf, type) {
17
+  conf = JSON.parse(JSON.stringify(conf))
18
+  const dataList = []
19
+  const ruleList = []
20
+  const optionsList = []
21
+  const propsList = []
22
+  const methodList = []
23
+  const uploadVarList = []
24
+
25
+  conf.fields.forEach((el) => {
26
+    buildAttributes(
27
+      el,
28
+      dataList,
29
+      ruleList,
30
+      optionsList,
31
+      methodList,
32
+      propsList,
33
+      uploadVarList
34
+    )
35
+  })
36
+
37
+  const script = buildexport(
38
+    conf,
39
+    type,
40
+    dataList.join('\n'),
41
+    ruleList.join('\n'),
42
+    optionsList.join('\n'),
43
+    uploadVarList.join('\n'),
44
+    propsList.join('\n'),
45
+    methodList.join('\n')
46
+  )
47
+  
48
+  return script
49
+}
50
+/**
51
+ * @name: 生成参数
52
+ * @description: 生成参数,包括表单数据表单验证数据,多选选项数据,上传数据等
53
+ * @return {*}
54
+ */
55
+function buildAttributes(
56
+  el,
57
+  dataList,
58
+  ruleList,
59
+  optionsList,
60
+  methodList,
61
+  propsList,
62
+  uploadVarList
63
+){
64
+  buildData(el, dataList)
65
+  buildRules(el, ruleList)
66
+
67
+  if (el.options && el.options.length) {
68
+    buildOptions(el, optionsList)
69
+    if (el.dataType === 'dynamic') {
70
+      const model = `${el.vModel}Options`
71
+      const options = titleCase(model)
72
+      buildOptionMethod(`get${options}`, model, methodList)
73
+    }
74
+  }
75
+
76
+  if (el.props && el.props.props) {
77
+    buildProps(el, propsList)
78
+  }
79
+
80
+  if (el.action && el.tag === 'el-upload') {
81
+    uploadVarList.push(
82
+      `
83
+      // 上传请求路径
84
+      const ${el.vModel}Action = ref('${el.action}')
85
+      // 上传文件列表
86
+      const ${el.vModel}fileList =  ref([])`
87
+    )
88
+    methodList.push(buildBeforeUpload(el))
89
+    if (!el['auto-upload']) {
90
+      methodList.push(buildSubmitUpload(el))
91
+    }
92
+  }
93
+
94
+  if (el.children) {
95
+    el.children.forEach((el2) => {
96
+      buildAttributes(
97
+        el2,
98
+        dataList,
99
+        ruleList,
100
+        optionsList,
101
+        methodList,
102
+        propsList,
103
+        uploadVarList
104
+      )
105
+    })
106
+  }
107
+}
108
+/**
109
+ * @name: 生成表单数据formData
110
+ * @description: 生成表单数据formData
111
+ * @param {*} conf
112
+ * @param {*} dataList 数据列表
113
+ * @return {*}
114
+ */
115
+function buildData(conf, dataList) {
116
+  if (conf.vModel === undefined) return
117
+  let defaultValue
118
+  if (typeof conf.defaultValue === 'string' && !conf.multiple) {
119
+    defaultValue = `'${conf.defaultValue}'`
120
+  } else {
121
+    defaultValue = `${JSON.stringify(conf.defaultValue)}`
122
+  }
123
+  dataList.push(`${conf.vModel}: ${defaultValue},`)
124
+}
125
+/**
126
+ * @name: 生成表单验证数据rule
127
+ * @description: 生成表单验证数据rule
128
+ * @param {*} conf
129
+ * @param {*} ruleList 验证数据列表
130
+ * @return {*}
131
+ */
132
+function buildRules(conf, ruleList) {
133
+  if (conf.vModel === undefined) return
134
+  const rules = []
135
+  if (trigger[conf.tag]) {
136
+    if (conf.required) {
137
+      const type = Array.isArray(conf.defaultValue) ? "type: 'array'," : ''
138
+      let message = Array.isArray(conf.defaultValue)
139
+        ? `请至少选择一个${conf.vModel}`
140
+        : conf.placeholder
141
+      if (message === undefined) message = `${conf.label}不能为空`
142
+      rules.push(
143
+        `{ required: true, ${type} message: '${message}', trigger: '${
144
+          trigger[conf.tag]
145
+        }' }`
146
+      )
147
+    }
148
+    if (conf.regList && Array.isArray(conf.regList)) {
149
+      conf.regList.forEach((item) => {
150
+        if (item.pattern) {
151
+          rules.push(
152
+            `{ pattern: new RegExp(${item.pattern}), message: '${
153
+              item.message
154
+            }', trigger: '${trigger[conf.tag]}' }`
155
+          )
156
+        }
157
+      })
158
+    }
159
+    ruleList.push(`${conf.vModel}: [${rules.join(',')}],`)
160
+  }
161
+}
162
+/**
163
+ * @name: 生成选项数据
164
+ * @description: 生成选项数据,单选多选下拉等
165
+ * @param {*} conf
166
+ * @param {*} optionsList 选项数据列表
167
+ * @return {*}
168
+ */
169
+function buildOptions(conf, optionsList) {
170
+  if (conf.vModel === undefined) return
171
+  if (conf.dataType === 'dynamic') {
172
+    conf.options = []
173
+  }
174
+  const str = `const ${conf.vModel}Options = ref(${JSON.stringify(conf.options)})`
175
+  optionsList.push(str)
176
+}
177
+/**
178
+ * @name: 生成方法
179
+ * @description: 生成方法
180
+ * @param {*} methodName 方法名
181
+ * @param {*} model
182
+ * @param {*} methodList 方法列表
183
+ * @return {*}
184
+ */
185
+function buildOptionMethod(methodName, model, methodList) {
186
+  const str = `function ${methodName}() {
187
+    // TODO 发起请求获取数据
188
+    ${model}.value
189
+  }`
190
+  methodList.push(str)
191
+}
192
+/**
193
+ * @name: 生成表单组件需要的props设置
194
+ * @description: 生成表单组件需要的props设置,如;级联组件
195
+ * @param {*} conf
196
+ * @param {*} propsList
197
+ * @return {*}
198
+ */
199
+function buildProps(conf, propsList) {
200
+  if (conf.dataType === 'dynamic') {
201
+    conf.valueKey !== 'value' && (conf.props.props.value = conf.valueKey)
202
+    conf.labelKey !== 'label' && (conf.props.props.label = conf.labelKey)
203
+    conf.childrenKey !== 'children' &&
204
+      (conf.props.props.children = conf.childrenKey)
205
+  }
206
+  const str = `
207
+  // props设置
208
+  const ${conf.vModel}Props = ref(${JSON.stringify(conf.props.props)})`
209
+  propsList.push(str)
210
+}
211
+/**
212
+ * @name: 生成上传组件的相关内容
213
+ * @description: 生成上传组件的相关内容
214
+ * @param {*} conf
215
+ * @return {*}
216
+ */
217
+function buildBeforeUpload(conf) {
218
+  const unitNum = units[conf.sizeUnit]
219
+  let rightSizeCode = ''
220
+  let acceptCode = ''
221
+  const returnList = []
222
+  if (conf.fileSize) {
223
+    rightSizeCode = `let isRightSize = file.size / ${unitNum} < ${conf.fileSize}
224
+    if(!isRightSize){
225
+      proxy.$modal.msgError('文件大小超过 ${conf.fileSize}${conf.sizeUnit}')
226
+    }`
227
+    returnList.push('isRightSize')
228
+  }
229
+  if (conf.accept) {
230
+    acceptCode = `let isAccept = new RegExp('${conf.accept}').test(file.type)
231
+    if(!isAccept){
232
+      proxy.$modal.msgError('应该选择${conf.accept}类型的文件')
233
+    }`
234
+    returnList.push('isAccept')
235
+  }
236
+  const str = `
237
+  /**
238
+   * @name: 上传之前的文件判断
239
+   * @description: 上传之前的文件判断,判断文件大小文件类型等
240
+   * @param {*} file
241
+   * @return {*}
242
+   */  
243
+  function ${conf.vModel}BeforeUpload(file) {
244
+    ${rightSizeCode}
245
+    ${acceptCode}
246
+    return ${returnList.join('&&')}
247
+  }`
248
+  return returnList.length ? str : ''
249
+}
250
+/**
251
+ * @name: 生成提交表单方法
252
+ * @description: 生成提交表单方法
253
+ * @param {Object} conf vModel 表单ref
254
+ * @return {*}
255
+ */
256
+function buildSubmitUpload(conf) {
257
+  const str = `function submitUpload() {
258
+    this.$refs['${conf.vModel}'].submit()
259
+  }`
260
+  return str
261
+}
262
+/**
263
+ * @name: 组装js代码
264
+ * @description: 组装js代码方法
265
+ * @return {*}
266
+ */
267
+function buildexport(
268
+  conf,
269
+  type,
270
+  data,
271
+  rules,
272
+  selectOptions,
273
+  uploadVar,
274
+  props,
275
+  methods
276
+) {
277
+  let str = `
278
+    const { proxy } = getCurrentInstance()
279
+    const ${conf.formRef} = ref()
280
+    const data = reactive({
281
+      ${conf.formModel}: {
282
+        ${data}
283
+      },
284
+      ${conf.formRules}: {
285
+        ${rules}
286
+      }
287
+    })
288
+
289
+    const {${conf.formModel}, ${conf.formRules}} = toRefs(data)
290
+
291
+    ${selectOptions}
292
+
293
+    ${uploadVar}
294
+
295
+    ${props}
296
+
297
+    ${methods}
298
+  `
299
+  
300
+  if(type === 'dialog') {
301
+    str += `
302
+      // 弹窗设置
303
+      const dialogVisible = defineModel()
304
+      // 弹窗确认回调
305
+      const emit = defineEmits(['confirm'])
306
+      /**
307
+       * @name: 弹窗打开后执行
308
+       * @description: 弹窗打开后执行方法
309
+       * @return {*}
310
+       */
311
+      function onOpen(){
312
+
313
+      }
314
+      /**
315
+       * @name: 弹窗关闭时执行
316
+       * @description: 弹窗关闭方法,重置表单
317
+       * @return {*}
318
+       */
319
+      function onClose(){
320
+        ${conf.formRef}.value.resetFields()
321
+      }
322
+      /**
323
+       * @name: 弹窗取消
324
+       * @description: 弹窗取消方法
325
+       * @return {*}
326
+       */
327
+      function close(){
328
+        dialogVisible.value = false
329
+      }
330
+      /**
331
+       * @name: 弹窗表单提交
332
+       * @description: 弹窗表单提交方法
333
+       * @return {*}
334
+       */
335
+      function handelConfirm(){
336
+        ${conf.formRef}.value.validate((valid) => {
337
+          if (!valid) return
338
+          // TODO 提交表单
339
+
340
+          close()
341
+          // 回调父级组件
342
+          emit('confirm')
343
+        })
344
+      }
345
+    `
346
+  } else {
347
+    str += `
348
+    /**
349
+     * @name: 表单提交
350
+     * @description: 表单提交方法
351
+     * @return {*}
352
+     */
353
+    function submitForm() {
354
+      ${conf.formRef}.value.validate((valid) => {
355
+        if (!valid) return
356
+        // TODO 提交表单
357
+      })
358
+    }
359
+    /**
360
+     * @name: 表单重置
361
+     * @description: 表单重置方法
362
+     * @return {*}
363
+     */
364
+    function resetForm() {
365
+      ${conf.formRef}.value.resetFields()
366
+    }
367
+    `
368
+  }
369
+  return str
370
+}

+ 156 - 0
src/utils/generator/render.js

@@ -0,0 +1,156 @@
1
+import { defineComponent, h } from 'vue'
2
+import { makeMap } from '@/utils/index'
3
+
4
+const isAttr = makeMap(
5
+  'accept,accept-charset,accesskey,action,align,alt,async,autocomplete,' +
6
+  'autofocus,autoplay,autosave,bgcolor,border,buffered,challenge,charset,' +
7
+  'checked,cite,class,code,codebase,color,cols,colspan,content,http-equiv,' +
8
+  'name,contenteditable,contextmenu,controls,coords,data,datetime,default,' +
9
+  'defer,dir,dirname,disabled,download,draggable,dropzone,enctype,method,for,' +
10
+  'form,formaction,headers,height,hidden,high,href,hreflang,http-equiv,' +
11
+  'icon,id,ismap,itemprop,keytype,kind,label,lang,language,list,loop,low,' +
12
+  'manifest,max,maxlength,media,method,GET,POST,min,multiple,email,file,' +
13
+  'muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,' +
14
+  'preload,radiogroup,readonly,rel,required,reversed,rows,rowspan,sandbox,' +
15
+  'scope,scoped,seamless,selected,shape,size,type,text,password,sizes,span,' +
16
+  'spellcheck,src,srcdoc,srclang,srcset,start,step,style,summary,tabindex,' +
17
+  'target,title,type,usemap,value,width,wrap' + 'prefix-icon'
18
+)
19
+const isNotProps = makeMap(
20
+  'layout,prepend,regList,tag,document,changeTag,defaultValue'
21
+)
22
+
23
+function useVModel(props, emit) {
24
+  return {
25
+    modelValue: props.defaultValue,
26
+    'onUpdate:modelValue': (val) => emit('update:modelValue', val),
27
+  }
28
+}
29
+const componentChild = {
30
+  'el-button': {
31
+    default(h, conf, key) {
32
+      return conf[key]
33
+    },
34
+  },
35
+  'el-select': {
36
+    options(h, conf, key) {
37
+      return conf.options.map(item => h(resolveComponent('el-option'), {
38
+        label: item.label,
39
+        value: item.value,
40
+      }))
41
+    }
42
+  },
43
+  'el-radio-group': {
44
+    options(h, conf, key) {
45
+      return conf.optionType === 'button' ? conf.options.map(item => h(resolveComponent('el-checkbox-button'), {
46
+        label: item.value,
47
+      }, () => item.label)) : conf.options.map(item => h(resolveComponent('el-radio'), {
48
+        label: item.value,
49
+        border: conf.border,
50
+      }, () => item.label))
51
+    }
52
+  },
53
+  'el-checkbox-group': {
54
+    options(h, conf, key) {
55
+      return conf.optionType === 'button' ? conf.options.map(item => h(resolveComponent('el-checkbox-button'), {
56
+        label: item.value,
57
+      }, () => item.label)) : conf.options.map(item => h(resolveComponent('el-checkbox'), {
58
+        label: item.value,
59
+        border: conf.border,
60
+      }, () => item.label))
61
+    }
62
+  },
63
+  'el-upload': {
64
+    'list-type': (h, conf, key) => {
65
+      const option = {}
66
+      // if (conf.showTip) {
67
+      //   tip = h('div', {
68
+      //     class: "el-upload__tip"
69
+      //   }, () => '只能上传不超过' + conf.fileSize + conf.sizeUnit + '的' + conf.accept + '文件')
70
+      // }
71
+      if (conf['list-type'] === 'picture-card') {
72
+        return h(resolveComponent('el-icon'), option, () => h(resolveComponent('Plus')))
73
+      } else {
74
+        // option.size = "small"
75
+        option.type = "primary"
76
+        option.icon = "Upload"
77
+        return h(resolveComponent('el-button'), option, () => conf.buttonText)
78
+      }
79
+    },
80
+
81
+  }
82
+}
83
+const componentSlot = {
84
+  'el-upload': {
85
+    'tip': (h, conf, key) => {
86
+      if (conf.showTip) {
87
+        return () => h('div', {
88
+          class: "el-upload__tip"
89
+        }, '只能上传不超过' + conf.fileSize + conf.sizeUnit + '的' + conf.accept + '文件')
90
+      }
91
+    },
92
+  }
93
+}
94
+export default defineComponent({
95
+
96
+  // 使用 render 函数
97
+  render() {
98
+    const dataObject = {
99
+      attrs: {},
100
+      props: {},
101
+      on: {},
102
+      style: {}
103
+    }
104
+    const confClone = JSON.parse(JSON.stringify(this.conf))
105
+    const children = []
106
+    const slot = {}
107
+    const childObjs = componentChild[confClone.tag]
108
+    if (childObjs) {
109
+      Object.keys(childObjs).forEach(key => {
110
+        const childFunc = childObjs[key]
111
+        if (confClone[key]) {
112
+          children.push(childFunc(h, confClone, key))
113
+        }
114
+      })
115
+    }
116
+    const slotObjs = componentSlot[confClone.tag]
117
+    if (slotObjs) {
118
+      Object.keys(slotObjs).forEach(key => {
119
+        const childFunc = slotObjs[key]
120
+        if (confClone[key]) {
121
+          slot[key] = childFunc(h, confClone, key)
122
+        }
123
+      })
124
+    }
125
+    Object.keys(confClone).forEach(key => {
126
+      const val = confClone[key]
127
+      if (dataObject[key]) {
128
+        dataObject[key] = val
129
+      } else if (isAttr(key)) {
130
+        dataObject.attrs[key] = val
131
+      } else if (!isNotProps(key)) {
132
+        dataObject.props[key] = val
133
+      }
134
+    })
135
+    if(children.length > 0){
136
+      slot.default = () => children
137
+    }
138
+    
139
+    return h(resolveComponent(this.conf.tag),
140
+      {
141
+        modelValue: this.$attrs.modelValue,
142
+        ...dataObject.props,
143
+        ...dataObject.attrs,
144
+        style: {
145
+          ...dataObject.style
146
+        },
147
+      }
148
+      , slot ?? null)
149
+  },
150
+  props: {
151
+    conf: {
152
+      type: Object,
153
+      required: true,
154
+    },
155
+  }
156
+})

+ 390 - 0
src/utils/index.js

@@ -0,0 +1,390 @@
1
+import { parseTime } from './ruoyi'
2
+
3
+/**
4
+ * 表格时间格式化
5
+ */
6
+export function formatDate(cellValue) {
7
+  if (cellValue == null || cellValue == "") return ""
8
+  var date = new Date(cellValue) 
9
+  var year = date.getFullYear()
10
+  var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1
11
+  var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate() 
12
+  var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours() 
13
+  var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes() 
14
+  var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
15
+  return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds
16
+}
17
+
18
+/**
19
+ * @param {number} time
20
+ * @param {string} option
21
+ * @returns {string}
22
+ */
23
+export function formatTime(time, option) {
24
+  if (('' + time).length === 10) {
25
+    time = parseInt(time) * 1000
26
+  } else {
27
+    time = +time
28
+  }
29
+  const d = new Date(time)
30
+  const now = Date.now()
31
+
32
+  const diff = (now - d) / 1000
33
+
34
+  if (diff < 30) {
35
+    return '刚刚'
36
+  } else if (diff < 3600) {
37
+    // less 1 hour
38
+    return Math.ceil(diff / 60) + '分钟前'
39
+  } else if (diff < 3600 * 24) {
40
+    return Math.ceil(diff / 3600) + '小时前'
41
+  } else if (diff < 3600 * 24 * 2) {
42
+    return '1天前'
43
+  }
44
+  if (option) {
45
+    return parseTime(time, option)
46
+  } else {
47
+    return (
48
+      d.getMonth() +
49
+      1 +
50
+      '月' +
51
+      d.getDate() +
52
+      '日' +
53
+      d.getHours() +
54
+      '时' +
55
+      d.getMinutes() +
56
+      '分'
57
+    )
58
+  }
59
+}
60
+
61
+/**
62
+ * @param {string} url
63
+ * @returns {Object}
64
+ */
65
+export function getQueryObject(url) {
66
+  url = url == null ? window.location.href : url
67
+  const search = url.substring(url.lastIndexOf('?') + 1)
68
+  const obj = {}
69
+  const reg = /([^?&=]+)=([^?&=]*)/g
70
+  search.replace(reg, (rs, $1, $2) => {
71
+    const name = decodeURIComponent($1)
72
+    let val = decodeURIComponent($2)
73
+    val = String(val)
74
+    obj[name] = val
75
+    return rs
76
+  })
77
+  return obj
78
+}
79
+
80
+/**
81
+ * @param {string} input value
82
+ * @returns {number} output value
83
+ */
84
+export function byteLength(str) {
85
+  // returns the byte length of an utf8 string
86
+  let s = str.length
87
+  for (var i = str.length - 1; i >= 0; i--) {
88
+    const code = str.charCodeAt(i)
89
+    if (code > 0x7f && code <= 0x7ff) s++
90
+    else if (code > 0x7ff && code <= 0xffff) s += 2
91
+    if (code >= 0xDC00 && code <= 0xDFFF) i--
92
+  }
93
+  return s
94
+}
95
+
96
+/**
97
+ * @param {Array} actual
98
+ * @returns {Array}
99
+ */
100
+export function cleanArray(actual) {
101
+  const newArray = []
102
+  for (let i = 0; i < actual.length; i++) {
103
+    if (actual[i]) {
104
+      newArray.push(actual[i])
105
+    }
106
+  }
107
+  return newArray
108
+}
109
+
110
+/**
111
+ * @param {Object} json
112
+ * @returns {Array}
113
+ */
114
+export function param(json) {
115
+  if (!json) return ''
116
+  return cleanArray(
117
+    Object.keys(json).map(key => {
118
+      if (json[key] === undefined) return ''
119
+      return encodeURIComponent(key) + '=' + encodeURIComponent(json[key])
120
+    })
121
+  ).join('&')
122
+}
123
+
124
+/**
125
+ * @param {string} url
126
+ * @returns {Object}
127
+ */
128
+export function param2Obj(url) {
129
+  const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
130
+  if (!search) {
131
+    return {}
132
+  }
133
+  const obj = {}
134
+  const searchArr = search.split('&')
135
+  searchArr.forEach(v => {
136
+    const index = v.indexOf('=')
137
+    if (index !== -1) {
138
+      const name = v.substring(0, index)
139
+      const val = v.substring(index + 1, v.length)
140
+      obj[name] = val
141
+    }
142
+  })
143
+  return obj
144
+}
145
+
146
+/**
147
+ * @param {string} val
148
+ * @returns {string}
149
+ */
150
+export function html2Text(val) {
151
+  const div = document.createElement('div')
152
+  div.innerHTML = val
153
+  return div.textContent || div.innerText
154
+}
155
+
156
+/**
157
+ * Merges two objects, giving the last one precedence
158
+ * @param {Object} target
159
+ * @param {(Object|Array)} source
160
+ * @returns {Object}
161
+ */
162
+export function objectMerge(target, source) {
163
+  if (typeof target !== 'object') {
164
+    target = {}
165
+  }
166
+  if (Array.isArray(source)) {
167
+    return source.slice()
168
+  }
169
+  Object.keys(source).forEach(property => {
170
+    const sourceProperty = source[property]
171
+    if (typeof sourceProperty === 'object') {
172
+      target[property] = objectMerge(target[property], sourceProperty)
173
+    } else {
174
+      target[property] = sourceProperty
175
+    }
176
+  })
177
+  return target
178
+}
179
+
180
+/**
181
+ * @param {HTMLElement} element
182
+ * @param {string} className
183
+ */
184
+export function toggleClass(element, className) {
185
+  if (!element || !className) {
186
+    return
187
+  }
188
+  let classString = element.className
189
+  const nameIndex = classString.indexOf(className)
190
+  if (nameIndex === -1) {
191
+    classString += '' + className
192
+  } else {
193
+    classString =
194
+      classString.substr(0, nameIndex) +
195
+      classString.substr(nameIndex + className.length)
196
+  }
197
+  element.className = classString
198
+}
199
+
200
+/**
201
+ * @param {string} type
202
+ * @returns {Date}
203
+ */
204
+export function getTime(type) {
205
+  if (type === 'start') {
206
+    return new Date().getTime() - 3600 * 1000 * 24 * 90
207
+  } else {
208
+    return new Date(new Date().toDateString())
209
+  }
210
+}
211
+
212
+/**
213
+ * @param {Function} func
214
+ * @param {number} wait
215
+ * @param {boolean} immediate
216
+ * @return {*}
217
+ */
218
+export function debounce(func, wait, immediate) {
219
+  let timeout, args, context, timestamp, result
220
+
221
+  const later = function() {
222
+    // 据上一次触发时间间隔
223
+    const last = +new Date() - timestamp
224
+
225
+    // 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait
226
+    if (last < wait && last > 0) {
227
+      timeout = setTimeout(later, wait - last)
228
+    } else {
229
+      timeout = null
230
+      // 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用
231
+      if (!immediate) {
232
+        result = func.apply(context, args)
233
+        if (!timeout) context = args = null
234
+      }
235
+    }
236
+  }
237
+
238
+  return function(...args) {
239
+    context = this
240
+    timestamp = +new Date()
241
+    const callNow = immediate && !timeout
242
+    // 如果延时不存在,重新设定延时
243
+    if (!timeout) timeout = setTimeout(later, wait)
244
+    if (callNow) {
245
+      result = func.apply(context, args)
246
+      context = args = null
247
+    }
248
+
249
+    return result
250
+  }
251
+}
252
+
253
+/**
254
+ * This is just a simple version of deep copy
255
+ * Has a lot of edge cases bug
256
+ * If you want to use a perfect deep copy, use lodash's _.cloneDeep
257
+ * @param {Object} source
258
+ * @returns {Object}
259
+ */
260
+export function deepClone(source) {
261
+  if (!source && typeof source !== 'object') {
262
+    throw new Error('error arguments', 'deepClone')
263
+  }
264
+  const targetObj = source.constructor === Array ? [] : {}
265
+  Object.keys(source).forEach(keys => {
266
+    if (source[keys] && typeof source[keys] === 'object') {
267
+      targetObj[keys] = deepClone(source[keys])
268
+    } else {
269
+      targetObj[keys] = source[keys]
270
+    }
271
+  })
272
+  return targetObj
273
+}
274
+
275
+/**
276
+ * @param {Array} arr
277
+ * @returns {Array}
278
+ */
279
+export function uniqueArr(arr) {
280
+  return Array.from(new Set(arr))
281
+}
282
+
283
+/**
284
+ * @returns {string}
285
+ */
286
+export function createUniqueString() {
287
+  const timestamp = +new Date() + ''
288
+  const randomNum = parseInt((1 + Math.random()) * 65536) + ''
289
+  return (+(randomNum + timestamp)).toString(32)
290
+}
291
+
292
+/**
293
+ * Check if an element has a class
294
+ * @param {HTMLElement} elm
295
+ * @param {string} cls
296
+ * @returns {boolean}
297
+ */
298
+export function hasClass(ele, cls) {
299
+  return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'))
300
+}
301
+
302
+/**
303
+ * Add class to element
304
+ * @param {HTMLElement} elm
305
+ * @param {string} cls
306
+ */
307
+export function addClass(ele, cls) {
308
+  if (!hasClass(ele, cls)) ele.className += ' ' + cls
309
+}
310
+
311
+/**
312
+ * Remove class from element
313
+ * @param {HTMLElement} elm
314
+ * @param {string} cls
315
+ */
316
+export function removeClass(ele, cls) {
317
+  if (hasClass(ele, cls)) {
318
+    const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)')
319
+    ele.className = ele.className.replace(reg, ' ')
320
+  }
321
+}
322
+
323
+export function makeMap(str, expectsLowerCase) {
324
+  const map = Object.create(null)
325
+  const list = str.split(',')
326
+  for (let i = 0; i < list.length; i++) {
327
+    map[list[i]] = true
328
+  }
329
+  return expectsLowerCase
330
+    ? val => map[val.toLowerCase()]
331
+    : val => map[val]
332
+}
333
+ 
334
+export const exportDefault = 'export default '
335
+
336
+export const beautifierConf = {
337
+  html: {
338
+    indent_size: '2',
339
+    indent_char: ' ',
340
+    max_preserve_newlines: '-1',
341
+    preserve_newlines: false,
342
+    keep_array_indentation: false,
343
+    break_chained_methods: false,
344
+    indent_scripts: 'separate',
345
+    brace_style: 'end-expand',
346
+    space_before_conditional: true,
347
+    unescape_strings: false,
348
+    jslint_happy: false,
349
+    end_with_newline: true,
350
+    wrap_line_length: '110',
351
+    indent_inner_html: true,
352
+    comma_first: false,
353
+    e4x: true,
354
+    indent_empty_lines: true
355
+  },
356
+  js: {
357
+    indent_size: '2',
358
+    indent_char: ' ',
359
+    max_preserve_newlines: '-1',
360
+    preserve_newlines: false,
361
+    keep_array_indentation: false,
362
+    break_chained_methods: false,
363
+    indent_scripts: 'normal',
364
+    brace_style: 'end-expand',
365
+    space_before_conditional: true,
366
+    unescape_strings: false,
367
+    jslint_happy: true,
368
+    end_with_newline: true,
369
+    wrap_line_length: '110',
370
+    indent_inner_html: true,
371
+    comma_first: false,
372
+    e4x: true,
373
+    indent_empty_lines: true
374
+  }
375
+}
376
+
377
+// 首字母大小
378
+export function titleCase(str) {
379
+  return str.replace(/( |^)[a-z]/g, L => L.toUpperCase())
380
+}
381
+
382
+// 下划转驼峰
383
+export function camelCase(str) {
384
+  return str.replace(/_[a-z]/g, str1 => str1.substr(-1).toUpperCase())
385
+}
386
+
387
+export function isNumberStr(str) {
388
+  return /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(str)
389
+}
390
+ 

+ 30 - 0
src/utils/jsencrypt.js

@@ -0,0 +1,30 @@
1
+import JSEncrypt from 'jsencrypt/bin/jsencrypt.min'
2
+
3
+// 密钥对生成 http://web.chacuo.net/netrsakeypair
4
+
5
+const publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdH\n' +
6
+  'nzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
7
+
8
+const privateKey = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY\n' +
9
+  '7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKN\n' +
10
+  'PuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gA\n' +
11
+  'kM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWow\n' +
12
+  'cSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99Ecv\n' +
13
+  'DQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthh\n' +
14
+  'YhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3\n' +
15
+  'UP8iWi1Qw0Y='
16
+
17
+// 加密
18
+export function encrypt(txt) {
19
+  const encryptor = new JSEncrypt()
20
+  encryptor.setPublicKey(publicKey) // 设置公钥
21
+  return encryptor.encrypt(txt) // 对数据进行加密
22
+}
23
+
24
+// 解密
25
+export function decrypt(txt) {
26
+  const encryptor = new JSEncrypt()
27
+  encryptor.setPrivateKey(privateKey) // 设置私钥
28
+  return encryptor.decrypt(txt) // 对数据进行解密
29
+}
30
+

+ 51 - 0
src/utils/permission.js

@@ -0,0 +1,51 @@
1
+import useUserStore from '@/store/modules/user'
2
+
3
+/**
4
+ * 字符权限校验
5
+ * @param {Array} value 校验值
6
+ * @returns {Boolean}
7
+ */
8
+export function checkPermi(value) {
9
+  if (value && value instanceof Array && value.length > 0) {
10
+    const permissions = useUserStore().permissions
11
+    const permissionDatas = value
12
+    const all_permission = "*:*:*"
13
+
14
+    const hasPermission = permissions.some(permission => {
15
+      return all_permission === permission || permissionDatas.includes(permission)
16
+    })
17
+
18
+    if (!hasPermission) {
19
+      return false
20
+    }
21
+    return true
22
+  } else {
23
+    console.error(`need roles! Like checkPermi="['system:user:add','system:user:edit']"`)
24
+    return false
25
+  }
26
+}
27
+
28
+/**
29
+ * 角色权限校验
30
+ * @param {Array} value 校验值
31
+ * @returns {Boolean}
32
+ */
33
+export function checkRole(value) {
34
+  if (value && value instanceof Array && value.length > 0) {
35
+    const roles = useUserStore().roles
36
+    const permissionRoles = value
37
+    const super_admin = "admin"
38
+
39
+    const hasRole = roles.some(role => {
40
+      return super_admin === role || permissionRoles.includes(role)
41
+    })
42
+
43
+    if (!hasRole) {
44
+      return false
45
+    }
46
+    return true
47
+  } else {
48
+    console.error(`need roles! Like checkRole="['admin','editor']"`)
49
+    return false
50
+  }
51
+}

+ 152 - 0
src/utils/request.js

@@ -0,0 +1,152 @@
1
+import axios from 'axios'
2
+import { ElNotification , ElMessageBox, ElMessage, ElLoading } from 'element-plus'
3
+import { getToken } from '@/utils/auth'
4
+import errorCode from '@/utils/errorCode'
5
+import { tansParams, blobValidate } from '@/utils/ruoyi'
6
+import cache from '@/plugins/cache'
7
+import { saveAs } from 'file-saver'
8
+import useUserStore from '@/store/modules/user'
9
+
10
+let downloadLoadingInstance
11
+// 是否显示重新登录
12
+export let isRelogin = { show: false }
13
+
14
+axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
15
+// 创建axios实例
16
+const service = axios.create({
17
+  // axios中请求配置有baseURL选项,表示请求URL公共部分
18
+  baseURL: import.meta.env.VITE_APP_BASE_API,
19
+  // 超时
20
+  timeout: 10000
21
+})
22
+
23
+// request拦截器
24
+service.interceptors.request.use(config => {
25
+  // 是否需要设置 token
26
+  const isToken = (config.headers || {}).isToken === false
27
+  // 是否需要防止数据重复提交
28
+  const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
29
+  if (getToken() && !isToken) {
30
+    config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
31
+  }
32
+  // get请求映射params参数
33
+  if (config.method === 'get' && config.params) {
34
+    let url = config.url + '?' + tansParams(config.params)
35
+    url = url.slice(0, -1)
36
+    config.params = {}
37
+    config.url = url
38
+  }
39
+  if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
40
+    const requestObj = {
41
+      url: config.url,
42
+      data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
43
+      time: new Date().getTime()
44
+    }
45
+    const requestSize = Object.keys(JSON.stringify(requestObj)).length // 请求数据大小
46
+    const limitSize = 5 * 1024 * 1024 // 限制存放数据5M
47
+    if (requestSize >= limitSize) {
48
+      console.warn(`[${config.url}]: ` + '请求数据大小超出允许的5M限制,无法进行防重复提交验证。')
49
+      return config
50
+    }
51
+    const sessionObj = cache.session.getJSON('sessionObj')
52
+    if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
53
+      cache.session.setJSON('sessionObj', requestObj)
54
+    } else {
55
+      const s_url = sessionObj.url                // 请求地址
56
+      const s_data = sessionObj.data              // 请求数据
57
+      const s_time = sessionObj.time              // 请求时间
58
+      const interval = 1000                       // 间隔时间(ms),小于此时间视为重复提交
59
+      if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
60
+        const message = '数据正在处理,请勿重复提交'
61
+        console.warn(`[${s_url}]: ` + message)
62
+        return Promise.reject(new Error(message))
63
+      } else {
64
+        cache.session.setJSON('sessionObj', requestObj)
65
+      }
66
+    }
67
+  }
68
+  return config
69
+}, error => {
70
+    console.log(error)
71
+    Promise.reject(error)
72
+})
73
+
74
+// 响应拦截器
75
+service.interceptors.response.use(res => {
76
+    // 未设置状态码则默认成功状态
77
+    const code = res.data.code || 200
78
+    // 获取错误信息
79
+    const msg = errorCode[code] || res.data.msg || errorCode['default']
80
+    // 二进制数据则直接返回
81
+    if (res.request.responseType ===  'blob' || res.request.responseType ===  'arraybuffer') {
82
+      return res.data
83
+    }
84
+    if (code === 401) {
85
+      if (!isRelogin.show) {
86
+        isRelogin.show = true
87
+        ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {
88
+          isRelogin.show = false
89
+          useUserStore().logOut().then(() => {
90
+            location.href = '/index'
91
+          })
92
+      }).catch(() => {
93
+        isRelogin.show = false
94
+      })
95
+    }
96
+      return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
97
+    } else if (code === 500) {
98
+      ElMessage({ message: msg, type: 'error' })
99
+      return Promise.reject(new Error(msg))
100
+    } else if (code === 601) {
101
+      ElMessage({ message: msg, type: 'warning' })
102
+      return Promise.reject(new Error(msg))
103
+    } else if (code !== 200) {
104
+      ElNotification.error({ title: msg })
105
+      return Promise.reject('error')
106
+    } else {
107
+      return  Promise.resolve(res.data)
108
+    }
109
+  },
110
+  error => {
111
+    console.log('err' + error)
112
+    let { message } = error
113
+    if (message == "Network Error") {
114
+      message = "后端接口连接异常"
115
+    } else if (message.includes("timeout")) {
116
+      message = "系统接口请求超时"
117
+    } else if (message.includes("Request failed with status code")) {
118
+      message = "系统接口" + message.substr(message.length - 3) + "异常"
119
+    }
120
+    ElMessage({ message: message, type: 'error', duration: 5 * 1000 })
121
+    return Promise.reject(error)
122
+  }
123
+)
124
+
125
+// 通用下载方法
126
+export function download(url, params, filename, config) {
127
+  downloadLoadingInstance = ElLoading.service({ text: "正在下载数据,请稍候", background: "rgba(0, 0, 0, 0.7)", })
128
+  return service.post(url, params, {
129
+    transformRequest: [(params) => { return tansParams(params) }],
130
+    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
131
+    responseType: 'blob',
132
+    ...config
133
+  }).then(async (data) => {
134
+    const isBlob = blobValidate(data)
135
+    if (isBlob) {
136
+      const blob = new Blob([data])
137
+      saveAs(blob, filename)
138
+    } else {
139
+      const resText = await data.text()
140
+      const rspObj = JSON.parse(resText)
141
+      const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']
142
+      ElMessage.error(errMsg)
143
+    }
144
+    downloadLoadingInstance.close()
145
+  }).catch((r) => {
146
+    console.error(r)
147
+    ElMessage.error('下载文件出现错误,请联系管理员!')
148
+    downloadLoadingInstance.close()
149
+  })
150
+}
151
+
152
+export default service

+ 228 - 0
src/utils/ruoyi.js

@@ -0,0 +1,228 @@
1
+/**
2
+ * 通用js方法封装处理
3
+ * Copyright (c) 2019 ruoyi
4
+ */
5
+
6
+// 日期格式化
7
+export function parseTime(time, pattern) {
8
+  if (arguments.length === 0 || !time) {
9
+    return null
10
+  }
11
+  const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
12
+  let date
13
+  if (typeof time === 'object') {
14
+    date = time
15
+  } else {
16
+    if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
17
+      time = parseInt(time)
18
+    } else if (typeof time === 'string') {
19
+      time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), '')
20
+    }
21
+    if ((typeof time === 'number') && (time.toString().length === 10)) {
22
+      time = time * 1000
23
+    }
24
+    date = new Date(time)
25
+  }
26
+  const formatObj = {
27
+    y: date.getFullYear(),
28
+    m: date.getMonth() + 1,
29
+    d: date.getDate(),
30
+    h: date.getHours(),
31
+    i: date.getMinutes(),
32
+    s: date.getSeconds(),
33
+    a: date.getDay()
34
+  }
35
+  const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
36
+    let value = formatObj[key]
37
+    // Note: getDay() returns 0 on Sunday
38
+    if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }
39
+    if (result.length > 0 && value < 10) {
40
+      value = '0' + value
41
+    }
42
+    return value || 0
43
+  })
44
+  return time_str
45
+}
46
+
47
+// 表单重置
48
+export function resetForm(refName) {
49
+  if (this.$refs[refName]) {
50
+    this.$refs[refName].resetFields()
51
+  }
52
+}
53
+
54
+// 添加日期范围
55
+export function addDateRange(params, dateRange, propName) {
56
+  let search = params
57
+  search.params = typeof (search.params) === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {}
58
+  dateRange = Array.isArray(dateRange) ? dateRange : []
59
+  if (typeof (propName) === 'undefined') {
60
+    search.params['beginTime'] = dateRange[0]
61
+    search.params['endTime'] = dateRange[1]
62
+  } else {
63
+    search.params['begin' + propName] = dateRange[0]
64
+    search.params['end' + propName] = dateRange[1]
65
+  }
66
+  return search
67
+}
68
+
69
+// 回显数据字典
70
+export function selectDictLabel(datas, value) {
71
+  if (value === undefined) {
72
+    return ""
73
+  }
74
+  var actions = []
75
+  Object.keys(datas).some((key) => {
76
+    if (datas[key].value == ('' + value)) {
77
+      actions.push(datas[key].label)
78
+      return true
79
+    }
80
+  })
81
+  if (actions.length === 0) {
82
+    actions.push(value)
83
+  }
84
+  return actions.join('')
85
+}
86
+
87
+// 回显数据字典(字符串、数组)
88
+export function selectDictLabels(datas, value, separator) {
89
+  if (value === undefined || value.length ===0) {
90
+    return ""
91
+  }
92
+  if (Array.isArray(value)) {
93
+    value = value.join(",")
94
+  }
95
+  var actions = []
96
+  var currentSeparator = undefined === separator ? "," : separator
97
+  var temp = value.split(currentSeparator)
98
+  Object.keys(value.split(currentSeparator)).some((val) => {
99
+    var match = false
100
+    Object.keys(datas).some((key) => {
101
+      if (datas[key].value == ('' + temp[val])) {
102
+        actions.push(datas[key].label + currentSeparator)
103
+        match = true
104
+      }
105
+    })
106
+    if (!match) {
107
+      actions.push(temp[val] + currentSeparator)
108
+    }
109
+  })
110
+  return actions.join('').substring(0, actions.join('').length - 1)
111
+}
112
+
113
+// 字符串格式化(%s )
114
+export function sprintf(str) {
115
+  var args = arguments, flag = true, i = 1
116
+  str = str.replace(/%s/g, function () {
117
+    var arg = args[i++]
118
+    if (typeof arg === 'undefined') {
119
+      flag = false
120
+      return ''
121
+    }
122
+    return arg
123
+  })
124
+  return flag ? str : ''
125
+}
126
+
127
+// 转换字符串,undefined,null等转化为""
128
+export function parseStrEmpty(str) {
129
+  if (!str || str == "undefined" || str == "null") {
130
+    return ""
131
+  }
132
+  return str
133
+}
134
+
135
+// 数据合并
136
+export function mergeRecursive(source, target) {
137
+  for (var p in target) {
138
+    try {
139
+      if (target[p].constructor == Object) {
140
+        source[p] = mergeRecursive(source[p], target[p])
141
+      } else {
142
+        source[p] = target[p]
143
+      }
144
+    } catch (e) {
145
+      source[p] = target[p]
146
+    }
147
+  }
148
+  return source
149
+}
150
+
151
+/**
152
+ * 构造树型结构数据
153
+ * @param {*} data 数据源
154
+ * @param {*} id id字段 默认 'id'
155
+ * @param {*} parentId 父节点字段 默认 'parentId'
156
+ * @param {*} children 孩子节点字段 默认 'children'
157
+ */
158
+export function handleTree(data, id, parentId, children) {
159
+  let config = {
160
+    id: id || 'id',
161
+    parentId: parentId || 'parentId',
162
+    childrenList: children || 'children'
163
+  }
164
+
165
+  var childrenListMap = {}
166
+  var tree = []
167
+  for (let d of data) {
168
+    let id = d[config.id]
169
+    childrenListMap[id] = d
170
+    if (!d[config.childrenList]) {
171
+      d[config.childrenList] = []
172
+    }
173
+  }
174
+
175
+  for (let d of data) {
176
+    let parentId = d[config.parentId]
177
+    let parentObj = childrenListMap[parentId]
178
+    if (!parentObj) {
179
+      tree.push(d)
180
+    } else {
181
+      parentObj[config.childrenList].push(d)
182
+    }
183
+  }
184
+  return tree
185
+}
186
+
187
+/**
188
+* 参数处理
189
+* @param {*} params  参数
190
+*/
191
+export function tansParams(params) {
192
+  let result = ''
193
+  for (const propName of Object.keys(params)) {
194
+    const value = params[propName]
195
+    var part = encodeURIComponent(propName) + "="
196
+    if (value !== null && value !== "" && typeof (value) !== "undefined") {
197
+      if (typeof value === 'object') {
198
+        for (const key of Object.keys(value)) {
199
+          if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') {
200
+            let params = propName + '[' + key + ']'
201
+            var subPart = encodeURIComponent(params) + "="
202
+            result += subPart + encodeURIComponent(value[key]) + "&"
203
+          }
204
+        }
205
+      } else {
206
+        result += part + encodeURIComponent(value) + "&"
207
+      }
208
+    }
209
+  }
210
+  return result
211
+}
212
+
213
+// 返回项目路径
214
+export function getNormalPath(p) {
215
+  if (p.length === 0 || !p || p == 'undefined') {
216
+    return p
217
+  }
218
+  let res = p.replace('//', '/')
219
+  if (res[res.length - 1] === '/') {
220
+    return res.slice(0, res.length - 1)
221
+  }
222
+  return res
223
+}
224
+
225
+// 验证是否为blob格式
226
+export function blobValidate(data) {
227
+  return data.type !== 'application/json'
228
+}

+ 58 - 0
src/utils/scroll-to.js

@@ -0,0 +1,58 @@
1
+Math.easeInOutQuad = function(t, b, c, d) {
2
+  t /= d / 2
3
+  if (t < 1) {
4
+    return c / 2 * t * t + b
5
+  }
6
+  t--
7
+  return -c / 2 * (t * (t - 2) - 1) + b
8
+}
9
+
10
+// requestAnimationFrame for Smart Animating http://goo.gl/sx5sts
11
+var requestAnimFrame = (function() {
12
+  return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60) }
13
+})()
14
+
15
+/**
16
+ * Because it's so fucking difficult to detect the scrolling element, just move them all
17
+ * @param {number} amount
18
+ */
19
+function move(amount) {
20
+  document.documentElement.scrollTop = amount
21
+  document.body.parentNode.scrollTop = amount
22
+  document.body.scrollTop = amount
23
+}
24
+
25
+function position() {
26
+  return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop
27
+}
28
+
29
+/**
30
+ * @param {number} to
31
+ * @param {number} duration
32
+ * @param {Function} callback
33
+ */
34
+export function scrollTo(to, duration, callback) {
35
+  const start = position()
36
+  const change = to - start
37
+  const increment = 20
38
+  let currentTime = 0
39
+  duration = (typeof (duration) === 'undefined') ? 500 : duration
40
+  var animateScroll = function() {
41
+    // increment the time
42
+    currentTime += increment
43
+    // find the value with the quadratic in-out easing function
44
+    var val = Math.easeInOutQuad(currentTime, start, change, duration)
45
+    // move the document.body
46
+    move(val)
47
+    // do the animation unless its over
48
+    if (currentTime < duration) {
49
+      requestAnimFrame(animateScroll)
50
+    } else {
51
+      if (callback && typeof (callback) === 'function') {
52
+        // the animation is done so lets callback
53
+        callback()
54
+      }
55
+    }
56
+  }
57
+  animateScroll()
58
+}

+ 49 - 0
src/utils/theme.js

@@ -0,0 +1,49 @@
1
+// 处理主题样式
2
+export function handleThemeStyle(theme) {
3
+	document.documentElement.style.setProperty('--el-color-primary', theme)
4
+	for (let i = 1; i <= 9; i++) {
5
+		document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, `${getLightColor(theme, i / 10)}`)
6
+	}
7
+	for (let i = 1; i <= 9; i++) {
8
+		document.documentElement.style.setProperty(`--el-color-primary-dark-${i}`, `${getDarkColor(theme, i / 10)}`)
9
+	}
10
+}
11
+
12
+// hex颜色转rgb颜色
13
+export function hexToRgb(str) {
14
+	str = str.replace('#', '')
15
+	let hexs = str.match(/../g)
16
+	for (let i = 0; i < 3; i++) {
17
+		hexs[i] = parseInt(hexs[i], 16)
18
+	}
19
+	return hexs
20
+}
21
+
22
+// rgb颜色转Hex颜色
23
+export function rgbToHex(r, g, b) {
24
+	let hexs = [r.toString(16), g.toString(16), b.toString(16)]
25
+	for (let i = 0; i < 3; i++) {
26
+		if (hexs[i].length == 1) {
27
+			hexs[i] = `0${hexs[i]}`
28
+		}
29
+	}
30
+	return `#${hexs.join('')}`
31
+}
32
+
33
+// 变浅颜色值
34
+export function getLightColor(color, level) {
35
+	let rgb = hexToRgb(color)
36
+	for (let i = 0; i < 3; i++) {
37
+		rgb[i] = Math.floor((255 - rgb[i]) * level + rgb[i])
38
+	}
39
+	return rgbToHex(rgb[0], rgb[1], rgb[2])
40
+}
41
+
42
+// 变深颜色值
43
+export function getDarkColor(color, level) {
44
+	let rgb = hexToRgb(color)
45
+	for (let i = 0; i < 3; i++) {
46
+		rgb[i] = Math.floor(rgb[i] * (1 - level))
47
+	}
48
+	return rgbToHex(rgb[0], rgb[1], rgb[2])
49
+}

+ 114 - 0
src/utils/validate.js

@@ -0,0 +1,114 @@
1
+/**
2
+ * 路径匹配器
3
+ * @param {string} pattern
4
+ * @param {string} path
5
+ * @returns {Boolean}
6
+ */
7
+export function isPathMatch(pattern, path) {
8
+  const regexPattern = pattern.replace(/\//g, '\\/').replace(/\*\*/g, '.*').replace(/\*/g, '[^\\/]*')
9
+  const regex = new RegExp(`^${regexPattern}$`)
10
+  return regex.test(path)
11
+}
12
+
13
+/**
14
+ * 判断value字符串是否为空 
15
+ * @param {string} value
16
+ * @returns {Boolean}
17
+ */
18
+export function isEmpty(value) {
19
+  if (value == null || value == "" || value == undefined || value == "undefined") {
20
+    return true
21
+  }
22
+  return false
23
+}
24
+
25
+/**
26
+ * 判断url是否是http或https 
27
+ * @param {string} url
28
+ * @returns {Boolean}
29
+ */
30
+export function isHttp(url) {
31
+  return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1
32
+}
33
+
34
+/**
35
+ * 判断path是否为外链
36
+ * @param {string} path
37
+ * @returns {Boolean}
38
+ */
39
+export function isExternal(path) {
40
+  return /^(https?:|mailto:|tel:)/.test(path)
41
+}
42
+
43
+/**
44
+ * @param {string} str
45
+ * @returns {Boolean}
46
+ */
47
+export function validUsername(str) {
48
+  const valid_map = ['admin', 'editor']
49
+  return valid_map.indexOf(str.trim()) >= 0
50
+}
51
+
52
+/**
53
+ * @param {string} url
54
+ * @returns {Boolean}
55
+ */
56
+export function validURL(url) {
57
+  const reg = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
58
+  return reg.test(url)
59
+}
60
+
61
+/**
62
+ * @param {string} str
63
+ * @returns {Boolean}
64
+ */
65
+export function validLowerCase(str) {
66
+  const reg = /^[a-z]+$/
67
+  return reg.test(str)
68
+}
69
+
70
+/**
71
+ * @param {string} str
72
+ * @returns {Boolean}
73
+ */
74
+export function validUpperCase(str) {
75
+  const reg = /^[A-Z]+$/
76
+  return reg.test(str)
77
+}
78
+
79
+/**
80
+ * @param {string} str
81
+ * @returns {Boolean}
82
+ */
83
+export function validAlphabets(str) {
84
+  const reg = /^[A-Za-z]+$/
85
+  return reg.test(str)
86
+}
87
+
88
+/**
89
+ * @param {string} email
90
+ * @returns {Boolean}
91
+ */
92
+export function validEmail(email) {
93
+  const reg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
94
+  return reg.test(email)
95
+}
96
+
97
+/**
98
+ * @param {string} str
99
+ * @returns {Boolean}
100
+ */
101
+export function isString(str) {
102
+  return typeof str === 'string' || str instanceof String
103
+}
104
+
105
+/**
106
+ * @param {Array} arg
107
+ * @returns {Boolean}
108
+ */
109
+export function isArray(arg) {
110
+  if (typeof Array.isArray === 'undefined') {
111
+    return Object.prototype.toString.call(arg) === '[object Array]'
112
+  }
113
+  return Array.isArray(arg)
114
+}