@xuekl/cli-components 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ContextMenu.vue +117 -0
- package/PaddingBox.vue +19 -0
- package/PageContainer.vue +57 -0
- package/XklButton.vue +34 -0
- package/XklDatePicker.vue +32 -0
- package/XklDict.vue +23 -8
- package/XklForm.vue +164 -97
- package/XklLink.vue +34 -0
- package/XklSelect.vue +56 -18
- package/XklTable.vue +71 -10
- package/XklTree.vue +17 -0
- package/XklTreeSelect.vue +50 -0
- package/XklUpload.vue +62 -0
- package/index.ts +14 -0
- package/package.json +1 -1
package/ContextMenu.vue
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="my_menu" :style="{ left: position.x + 'px', top: position.y + 'px' }">
|
|
3
|
+
<div class="menu_item" v-for="item in menus" :key="item.value" @click.stop="resultSend(item)">
|
|
4
|
+
<span style="width: 100%;display: flex;justify-content: space-between;"
|
|
5
|
+
:class="{ 'menu-disabled': item.disabled }">{{ item.label }}<span v-if="item.children?.length">
|
|
6
|
+
<icon-svg name="more"></icon-svg>
|
|
7
|
+
</span></span>
|
|
8
|
+
<div class="my_sub_menu">
|
|
9
|
+
<div class="menu_item" v-for="sub in item.children" :key="sub.value" @click.stop="resultSend(sub)">
|
|
10
|
+
<span class="menu_name" :class="{ 'menu-disabled': item.disabled }">{{ sub.label }}</span>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
</template>
|
|
16
|
+
<script lang="ts">
|
|
17
|
+
export default {
|
|
18
|
+
name: 'ContextMenu'
|
|
19
|
+
}
|
|
20
|
+
</script>
|
|
21
|
+
<script setup lang="ts">
|
|
22
|
+
import { LocalMenu } from '@/utils/type'
|
|
23
|
+
import { Ref, ref } from 'vue';
|
|
24
|
+
const props = defineProps(['position', 'type'])
|
|
25
|
+
const emit = defineEmits(['result'])
|
|
26
|
+
const { position, type } = props
|
|
27
|
+
|
|
28
|
+
const newChildren: LocalMenu[] = []
|
|
29
|
+
if (type === 'folder' || type === 'root') {
|
|
30
|
+
newChildren.unshift({
|
|
31
|
+
value: 'folder', label: '目录'
|
|
32
|
+
}, {
|
|
33
|
+
value: 'page', label: '菜单'
|
|
34
|
+
})
|
|
35
|
+
} else {
|
|
36
|
+
newChildren.unshift(
|
|
37
|
+
{ value: 'entity', label: '实体' },
|
|
38
|
+
{ value: 'dialogEdit', label: '弹窗' },
|
|
39
|
+
{ value: 'subEidt', label: '页面' },
|
|
40
|
+
{ value: 'dialogDetail', label: '弹窗(详情)' },
|
|
41
|
+
{ value: 'subDetail', label: '页面(详情)' },)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const menus: Ref<LocalMenu[]> = ref([
|
|
45
|
+
{
|
|
46
|
+
value: 'new',
|
|
47
|
+
label: '新建',
|
|
48
|
+
children: newChildren
|
|
49
|
+
},
|
|
50
|
+
{ value: 'cut', label: '剪切', disabled: true },
|
|
51
|
+
{ value: 'copy', label: '复制', disabled: true },
|
|
52
|
+
{ value: 'paste', label: '粘贴', disabled: true },
|
|
53
|
+
{ value: 'rename', label: '重命名', disabled: type === 'root' },
|
|
54
|
+
{ value: 'delete', label: '删除', disabled: type === 'root' },
|
|
55
|
+
])
|
|
56
|
+
|
|
57
|
+
const resultSend = (item: LocalMenu) => {
|
|
58
|
+
if (item.children || item.disabled) {
|
|
59
|
+
return false
|
|
60
|
+
}
|
|
61
|
+
emit('result', item)
|
|
62
|
+
}
|
|
63
|
+
</script>
|
|
64
|
+
<style lang="scss" scoped>
|
|
65
|
+
.my_menu {
|
|
66
|
+
position: fixed;
|
|
67
|
+
z-index: 1000;
|
|
68
|
+
display: flex;
|
|
69
|
+
flex-direction: column;
|
|
70
|
+
background-color: #000;
|
|
71
|
+
color: #fff;
|
|
72
|
+
border-radius: 4px;
|
|
73
|
+
box-shadow: 2px 2px 2px 2px #f5f5ff;
|
|
74
|
+
|
|
75
|
+
.menu_item {
|
|
76
|
+
width: 110px;
|
|
77
|
+
height: 40px;
|
|
78
|
+
display: flex;
|
|
79
|
+
align-items: center;
|
|
80
|
+
padding: 0 10px;
|
|
81
|
+
cursor: pointer;
|
|
82
|
+
|
|
83
|
+
.menu_name {
|
|
84
|
+
display: inline-block;
|
|
85
|
+
width: 100%;
|
|
86
|
+
white-space: nowrap;
|
|
87
|
+
overflow: hidden;
|
|
88
|
+
text-overflow: ellipsis;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
&:hover {
|
|
92
|
+
background-color: #333;
|
|
93
|
+
|
|
94
|
+
.my_sub_menu {
|
|
95
|
+
display: block;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.my_sub_menu {
|
|
101
|
+
display: none;
|
|
102
|
+
background-color: #000;
|
|
103
|
+
color: #fff;
|
|
104
|
+
border-radius: 4px;
|
|
105
|
+
box-shadow: 2px 2px 2px 2px #f5f5ff;
|
|
106
|
+
position: absolute;
|
|
107
|
+
left: 100%;
|
|
108
|
+
top: 0;
|
|
109
|
+
border-left: 1px solid #f5f5f5;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.menu-disabled {
|
|
113
|
+
color: #aaa;
|
|
114
|
+
cursor: not-allowed;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
</style>
|
package/PaddingBox.vue
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="padding-box">
|
|
3
|
+
<slot></slot>
|
|
4
|
+
</div>
|
|
5
|
+
</template>
|
|
6
|
+
<script lang="ts">
|
|
7
|
+
import { Vue, Options } from "vue-class-component";
|
|
8
|
+
|
|
9
|
+
@Options({
|
|
10
|
+
name: 'PaddingBox'
|
|
11
|
+
})
|
|
12
|
+
export default class PaddingBox extends Vue { }
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<style lang="scss" scoped>
|
|
16
|
+
.padding-box {
|
|
17
|
+
padding-bottom: 22px;
|
|
18
|
+
}
|
|
19
|
+
</style>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<div v-if="queryVisible" class="page-query" :class="['mb-' + gutters]">
|
|
4
|
+
<slot name="query"></slot>
|
|
5
|
+
</div>
|
|
6
|
+
<div class="page-body">
|
|
7
|
+
<slot></slot>
|
|
8
|
+
</div>
|
|
9
|
+
<div v-if="footerVisible" class="page-footer" :class="['mt-' + gutters]">
|
|
10
|
+
<slot name="footer"></slot>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
</template>
|
|
14
|
+
<script lang="ts">
|
|
15
|
+
import { Vue, Options } from 'vue-class-component'
|
|
16
|
+
|
|
17
|
+
@Options({
|
|
18
|
+
name: "PageContainer",
|
|
19
|
+
props: {
|
|
20
|
+
gutters: {
|
|
21
|
+
type: Number,
|
|
22
|
+
default: 2
|
|
23
|
+
},
|
|
24
|
+
queryVisible: {
|
|
25
|
+
type: Boolean,
|
|
26
|
+
default: true
|
|
27
|
+
},
|
|
28
|
+
footerVisible: {
|
|
29
|
+
type: Boolean,
|
|
30
|
+
default: true
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
export default class PageContainer extends Vue {
|
|
35
|
+
gutters: unknown
|
|
36
|
+
queryVisible: unknown
|
|
37
|
+
footerVisible: unknown
|
|
38
|
+
}
|
|
39
|
+
</script>
|
|
40
|
+
<style lang="scss" scoped>
|
|
41
|
+
.page-query {
|
|
42
|
+
padding: 20px;
|
|
43
|
+
background-color: #fff;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.page-body {
|
|
47
|
+
padding: 20px;
|
|
48
|
+
background-color: #fff;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.page-footer {
|
|
52
|
+
display: flex;
|
|
53
|
+
justify-content: flex-end;
|
|
54
|
+
padding: 20px;
|
|
55
|
+
background-color: #fff;
|
|
56
|
+
}
|
|
57
|
+
</style>
|
package/XklButton.vue
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<real-el-button v-if="isAuth(auth)" @click.prevent="btnClick">
|
|
3
|
+
<slot></slot>
|
|
4
|
+
</real-el-button>
|
|
5
|
+
</template>
|
|
6
|
+
<script lang="ts">
|
|
7
|
+
export default {
|
|
8
|
+
name: 'ElButton'
|
|
9
|
+
}
|
|
10
|
+
</script>
|
|
11
|
+
<script setup lang="ts">
|
|
12
|
+
import { ElButton as RealElButton } from 'element-plus'
|
|
13
|
+
import { componentGlobal } from '@/utils/global'
|
|
14
|
+
import { isAuth } from '@/utils'
|
|
15
|
+
const props = defineProps(['auth'])
|
|
16
|
+
const emit = defineEmits(['click', 'async'])
|
|
17
|
+
|
|
18
|
+
const { auth } = props
|
|
19
|
+
let preventClick = false
|
|
20
|
+
|
|
21
|
+
const btnClick = () => {
|
|
22
|
+
emit('click')
|
|
23
|
+
if (componentGlobal.enableSubmit) {
|
|
24
|
+
preventClick = false
|
|
25
|
+
componentGlobal.enableSubmit = false
|
|
26
|
+
}
|
|
27
|
+
if (!preventClick) {
|
|
28
|
+
preventClick = true
|
|
29
|
+
emit('async', () => {
|
|
30
|
+
setTimeout(() => preventClick = false, 200)
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
</script>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-date-picker :type="type" :start-placeholder="startPlaceholder" :end-placeholder="endPlaceholder"
|
|
3
|
+
:value-format="valueFormat" />
|
|
4
|
+
</template>
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
export default {
|
|
7
|
+
name: 'XklDatePicker'
|
|
8
|
+
}
|
|
9
|
+
</script>
|
|
10
|
+
<script setup lang="ts">
|
|
11
|
+
import { ref } from 'vue';
|
|
12
|
+
|
|
13
|
+
const props = defineProps(['type'])
|
|
14
|
+
const { type } = props
|
|
15
|
+
|
|
16
|
+
console.log('type', type);
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
const startPlaceholder = ref('')
|
|
20
|
+
const endPlaceholder = ref('')
|
|
21
|
+
const valueFormat = ref('YYYY-MM-DD')
|
|
22
|
+
if (type === 'daterange') {
|
|
23
|
+
startPlaceholder.value = '开始日期'
|
|
24
|
+
endPlaceholder.value = '结束日期'
|
|
25
|
+
valueFormat.value = 'YYYY-MM-DD'
|
|
26
|
+
} else if (type === 'datetimerange') {
|
|
27
|
+
startPlaceholder.value = '开始时间'
|
|
28
|
+
endPlaceholder.value = '结束时间'
|
|
29
|
+
valueFormat.value = 'YYYY-MM-DD HH:mm:ss'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
</script>
|
package/XklDict.vue
CHANGED
|
@@ -1,15 +1,30 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<el-select style="width: 100%;">
|
|
3
|
-
<el-option v-for="item in
|
|
3
|
+
<el-option v-for="item in dataList" :key="item.dictCode" :label="item.dictLabel"
|
|
4
|
+
:value="item.dictValue"></el-option>
|
|
4
5
|
</el-select>
|
|
5
6
|
</template>
|
|
7
|
+
<script lang="ts">
|
|
8
|
+
export default {
|
|
9
|
+
name: 'XklDict'
|
|
10
|
+
}
|
|
11
|
+
</script>
|
|
6
12
|
<script setup lang="ts">
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
const props = defineProps(['
|
|
10
|
-
const {
|
|
11
|
-
const {
|
|
13
|
+
import { ref, onBeforeMount, Ref } from 'vue'
|
|
14
|
+
import http from "@/utils/httpRequest"
|
|
15
|
+
const props = defineProps(['config'])
|
|
16
|
+
const { config } = props
|
|
17
|
+
const dataList: Ref<{ dictCode: string, dictLabel: string, dictValue: string | number }[]> = ref([])
|
|
12
18
|
|
|
13
|
-
|
|
14
|
-
|
|
19
|
+
onBeforeMount(() => {
|
|
20
|
+
http({
|
|
21
|
+
url: http.adornUrl(`system/dict/data/type/${config.dict}`),
|
|
22
|
+
method: 'get',
|
|
23
|
+
params: http.adornParams({})
|
|
24
|
+
}).then(({ data }) => {
|
|
25
|
+
if (data.code === 200) {
|
|
26
|
+
dataList.value = data.data
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
})
|
|
15
30
|
</script>
|
package/XklForm.vue
CHANGED
|
@@ -1,133 +1,200 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<el-form ref="
|
|
3
|
-
<el-row
|
|
4
|
-
<template v-for="item in formList" :key="item.
|
|
5
|
-
<el-col v-if="item.show()"
|
|
6
|
-
<el-form-item :label="item.label" :label-width="item.labelWidth" :
|
|
7
|
-
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
2
|
+
<el-form ref="XklFormRef" :model="form" :label-width="opts.labelWidth">
|
|
3
|
+
<el-row>
|
|
4
|
+
<template v-for="item in formList" :key="item.prop">
|
|
5
|
+
<el-col :span="item.span" v-if="item.show()">
|
|
6
|
+
<el-form-item :label="item.label" :label-width="item.labelWidth" :prop="item.prop"
|
|
7
|
+
:rules="item.refRules.value">
|
|
8
|
+
<el-input v-if="item.type === 'input'" v-model="form[item.prop]" v-bind="item.element"
|
|
9
|
+
v-on="item.events" :disabled="item.disabled()"></el-input>
|
|
10
|
+
<el-input-number v-else-if="item.type === 'input-number'" v-model="form[item.prop]"
|
|
11
|
+
v-bind="item.element" v-on="item.events" :disabled="item.disabled()"
|
|
12
|
+
style="width: 100%;"></el-input-number>
|
|
13
|
+
<el-input v-else-if="item.type === 'textarea'" v-model="form[item.prop]" v-bind="item.element"
|
|
14
|
+
:disabled="item.disabled()" v-on="item.events" type="textarea"></el-input>
|
|
15
|
+
<XklSelect v-else-if="item.type === 'select'" v-model="form[item.prop]"
|
|
16
|
+
v-model:label="form[item.prop + 'Label']" :list="item.list" :config="{}" v-bind="item.element"
|
|
17
|
+
v-on="item.events" :disabled="item.disabled()">
|
|
18
|
+
</XklSelect>
|
|
19
|
+
<XklDict v-else-if="item.type === 'dict-select'" v-model="form[item.prop]"
|
|
20
|
+
v-model:label="form[item.prop + 'Label']" :config="item.config" v-bind="item.element"
|
|
21
|
+
v-on="item.events" :disabled="item.disabled()"></XklDict>
|
|
22
|
+
<XklSelect v-else-if="item.type === 'url-select'" v-model="form[item.prop]"
|
|
23
|
+
v-model:label="form[item.prop + 'Label']" :config="item.config" v-bind="item.element"
|
|
24
|
+
v-on="item.events" :disabled="item.disabled()">
|
|
25
|
+
</XklSelect>
|
|
26
|
+
<XklTreeSelect v-else-if="item.type === 'tree-select'" v-model="form[item.prop]"
|
|
27
|
+
:config="item.config" v-bind="item.element" v-on="item.events" :disabled="item.disabled()">
|
|
28
|
+
</XklTreeSelect>
|
|
29
|
+
<el-radio-group v-else-if="item.type === 'radio'" v-model="form[item.prop]" v-bind="item.element"
|
|
30
|
+
v-on="item.events" :disabled="item.disabled()">
|
|
31
|
+
<el-radio v-for="ra in item.list" :label="ra.value" :key="ra.value">{{ ra.label }}</el-radio>
|
|
32
|
+
</el-radio-group>
|
|
33
|
+
<el-checkbox-group v-else-if="item.type === 'checkbox'" v-model="form[item.prop]"
|
|
34
|
+
v-bind="item.element" v-on="item.events" :disabled="item.disabled()">
|
|
35
|
+
<el-checkbox v-for="ch in item.list" :label="ch.value" :key="ch.value">{{ ch.label
|
|
36
|
+
}}</el-checkbox>
|
|
37
|
+
</el-checkbox-group>
|
|
38
|
+
<XklDatePicker v-else-if="item.type === 'date-picker'" v-model="form[item.prop]"
|
|
39
|
+
v-bind="item.element" v-on="item.events" style="width: 100%;"></XklDatePicker>
|
|
40
|
+
<XklUpload v-else-if="item.type === 'upload'" :key="form[item.prop].length > 0"
|
|
41
|
+
v-model="form[item.prop]" :config="item.config" v-bind="item.element" v-on="item.events">
|
|
42
|
+
</XklUpload>
|
|
43
|
+
<slot v-else :name="item.prop"></slot>
|
|
30
44
|
</el-form-item>
|
|
31
45
|
</el-col>
|
|
32
46
|
</template>
|
|
33
|
-
<el-col :span="
|
|
34
|
-
<
|
|
47
|
+
<el-col :span="searchSpan">
|
|
48
|
+
<el-form-item label-width="0">
|
|
49
|
+
<div class="query-action">
|
|
50
|
+
<slot name="_action"></slot>
|
|
51
|
+
</div>
|
|
52
|
+
</el-form-item>
|
|
35
53
|
</el-col>
|
|
36
54
|
</el-row>
|
|
37
55
|
</el-form>
|
|
38
56
|
</template>
|
|
39
|
-
|
|
57
|
+
<script lang="ts">
|
|
58
|
+
export default {
|
|
59
|
+
name: "XklForm",
|
|
60
|
+
components: { XklTreeSelect, XklDict, XklDatePicker }
|
|
61
|
+
}
|
|
62
|
+
</script>
|
|
40
63
|
<script setup lang="ts">
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
43
|
-
import
|
|
64
|
+
import { setUpperFirst } from '@xuekl/cli-utils'
|
|
65
|
+
import { FormItem } from '@/utils/type'
|
|
66
|
+
import { ref, watch, onMounted } from 'vue'
|
|
67
|
+
import XklSelect from './XklSelect.vue'
|
|
68
|
+
import XklTreeSelect from './XklTreeSelect.vue'
|
|
44
69
|
import XklDict from './XklDict.vue'
|
|
45
|
-
import
|
|
70
|
+
import XklDatePicker from './XklDatePicker.vue'
|
|
46
71
|
|
|
47
72
|
const props = defineProps(['form'])
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
const formList:
|
|
51
|
-
|
|
52
|
-
const config = form.getConfig()
|
|
53
|
-
const options = form.getOptions()
|
|
54
|
-
const xklForm: Ref = ref(null)
|
|
55
|
-
|
|
56
|
-
Object.keys(form).forEach(key => {
|
|
57
|
-
const upperKey = setUpperFirst(key)
|
|
58
|
-
if (form['get' + upperKey]) {
|
|
59
|
-
const item = form['get' + upperKey]()
|
|
60
|
-
|
|
61
|
-
const placelabel = item.label.replace(':', '').replace(':', '').trim()
|
|
62
|
-
if (item.dict || item.url || item.list) {
|
|
63
|
-
item.placeholder = item.placeholder || '请选择' + placelabel
|
|
64
|
-
} else {
|
|
65
|
-
item.placeholder = item.placeholder || '请输入' + placelabel
|
|
66
|
-
}
|
|
73
|
+
const XklFormRef = ref(null)
|
|
74
|
+
const { form } = props
|
|
75
|
+
const formList: FormItem[] = []
|
|
76
|
+
const opts = form._opts || {}
|
|
67
77
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
// 获取符合mode的字段
|
|
79
|
+
const modeFields: any = {}
|
|
80
|
+
Object.keys(form).forEach(prop => {
|
|
81
|
+
if (form['get' + setUpperFirst(prop)]) {
|
|
82
|
+
const field: FormItem = form['get' + setUpperFirst(prop)]()
|
|
83
|
+
field.rules = field.rules || []
|
|
84
|
+
field.refRules = ref(field.rules)
|
|
85
|
+
field.show = field.show || (() => true)
|
|
86
|
+
field.disabled = field.disabled || (() => false)
|
|
87
|
+
if (!['query', 'search'].includes(opts.mode)) {
|
|
88
|
+
if (field.required) {
|
|
89
|
+
// 如果穿了required函数监听required的变化
|
|
90
|
+
watch(field.required, (val) => {
|
|
91
|
+
if (val) {
|
|
92
|
+
field.refRules.value.push({ required: true, message: '请输入' + field.label, trigger: 'blur' })
|
|
93
|
+
} else {
|
|
94
|
+
field.refRules.value = field.refRules.value.filter(res => !res.required)
|
|
95
|
+
}
|
|
78
96
|
})
|
|
79
97
|
}
|
|
98
|
+
field.required = field.required || (() => false)
|
|
99
|
+
if (field.required()) {
|
|
100
|
+
field.refRules.value.push({ required: true, message: '请输入' + field.label, trigger: 'blur' })
|
|
101
|
+
}
|
|
80
102
|
} else {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
103
|
+
field.refRules.value = field.refRules.value.filter(res => !res.required)
|
|
104
|
+
}
|
|
105
|
+
const placeholderStart = ['input', 'textarea'].includes(field.type) ? '请输入' : '请选择'
|
|
106
|
+
if (!field.element) {
|
|
107
|
+
field.element = {
|
|
108
|
+
placeholder: placeholderStart + field.label.replace(':', '').replace(':', ''),
|
|
109
|
+
clearable: true
|
|
85
110
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
111
|
+
} else {
|
|
112
|
+
field.element.placeholder = placeholderStart + field.label.replace(':', '').replace(':', '')
|
|
113
|
+
field.element.clearable = typeof field.element.clearable === 'undefined' ? true : field.element.clearable
|
|
114
|
+
}
|
|
115
|
+
const item = {
|
|
116
|
+
prop,
|
|
117
|
+
type: field.type,
|
|
118
|
+
label: field.label,
|
|
119
|
+
span: field.span || opts.span,
|
|
120
|
+
labelWidth: field.labelWidth || opts.labelWidth,
|
|
121
|
+
show: field.show,
|
|
122
|
+
disabled: field.disabled,
|
|
123
|
+
required: field.required,
|
|
124
|
+
refRules: field.refRules,
|
|
125
|
+
element: field.element,
|
|
126
|
+
events: field.events || {},
|
|
127
|
+
list: field.list,
|
|
128
|
+
config: field.config || {}
|
|
129
|
+
}
|
|
130
|
+
if (opts.mode) {
|
|
131
|
+
if (field.mode && field.mode.includes(opts.mode)) {
|
|
132
|
+
formList.push(item)
|
|
89
133
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (newVal) {
|
|
93
|
-
item.rules.value.push({ required: true, message: '请输入' + placelabel, trigger: 'blur' })
|
|
94
|
-
} else {
|
|
95
|
-
item.rules.value = item.rules.value.filter((item: RuleItem) => !item.required)
|
|
96
|
-
xklForm.value.clearValidate([key])
|
|
97
|
-
}
|
|
98
|
-
})
|
|
99
|
-
formList.value.push({
|
|
100
|
-
key: key,
|
|
101
|
-
...item
|
|
102
|
-
})
|
|
134
|
+
} else {
|
|
135
|
+
formList.push(item)
|
|
103
136
|
}
|
|
137
|
+
}
|
|
138
|
+
})
|
|
104
139
|
|
|
140
|
+
//计算按钮所占span
|
|
141
|
+
let toalSpan = 0
|
|
142
|
+
const searchSpan = ref(0)
|
|
143
|
+
formList.forEach(res => {
|
|
144
|
+
if (res.show && res.show()) {
|
|
145
|
+
if (res.span) {
|
|
146
|
+
toalSpan = toalSpan + res.span
|
|
147
|
+
} else {
|
|
148
|
+
toalSpan = toalSpan + opts.span
|
|
149
|
+
}
|
|
105
150
|
}
|
|
106
151
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
152
|
+
})
|
|
153
|
+
searchSpan.value = 24 - toalSpan % 24
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
const regProto = () => {
|
|
157
|
+
if (XklFormRef.value) {
|
|
158
|
+
let enableSubmit = true;
|
|
159
|
+
form.setSubmit((callback: (call: () => void, feils: any) => void) => {
|
|
110
160
|
if (enableSubmit) {
|
|
111
|
-
enableSubmit = false
|
|
112
|
-
|
|
161
|
+
enableSubmit = false;
|
|
162
|
+
formList.forEach(field => {
|
|
163
|
+
modeFields[field.prop] = (form as any)[field.prop]
|
|
164
|
+
});
|
|
165
|
+
(XklFormRef.value as any).validate((valid: boolean) => {
|
|
113
166
|
if (valid) {
|
|
114
|
-
|
|
167
|
+
callback(() => {
|
|
115
168
|
setTimeout(() => {
|
|
116
169
|
enableSubmit = true
|
|
117
170
|
}, 300)
|
|
118
|
-
})
|
|
171
|
+
}, modeFields)
|
|
119
172
|
} else {
|
|
120
173
|
enableSubmit = true
|
|
121
174
|
}
|
|
122
175
|
})
|
|
123
176
|
}
|
|
124
|
-
})
|
|
125
|
-
form.setResetFields(xklForm.value.resetFields)
|
|
177
|
+
}, form)
|
|
126
178
|
}
|
|
127
179
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
180
|
+
form.setBinds(() => {
|
|
181
|
+
formList.forEach(field => {
|
|
182
|
+
modeFields[field.prop] = (form as any)[field.prop]
|
|
183
|
+
});
|
|
184
|
+
return modeFields
|
|
185
|
+
}, form)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
onMounted(() => {
|
|
189
|
+
regProto()
|
|
131
190
|
})
|
|
132
191
|
|
|
133
|
-
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
</script>
|
|
195
|
+
<style lang="scss" scoped>
|
|
196
|
+
.query-action {
|
|
197
|
+
width: 100%;
|
|
198
|
+
text-align: right;
|
|
199
|
+
}
|
|
200
|
+
</style>
|
package/XklLink.vue
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<real-el-link v-if="isAuth(auth)" @click.prevent="btnClick">
|
|
3
|
+
<slot></slot>
|
|
4
|
+
</real-el-link>
|
|
5
|
+
</template>
|
|
6
|
+
<script lang="ts">
|
|
7
|
+
export default {
|
|
8
|
+
name: 'ElLink'
|
|
9
|
+
}
|
|
10
|
+
</script>
|
|
11
|
+
<script setup lang="ts">
|
|
12
|
+
import { ElLink as RealElLink } from 'element-plus'
|
|
13
|
+
import { componentGlobal } from '@/utils/global'
|
|
14
|
+
import { isAuth } from '@/utils'
|
|
15
|
+
const props = defineProps(['auth'])
|
|
16
|
+
const emit = defineEmits(['click', 'async'])
|
|
17
|
+
|
|
18
|
+
const { auth } = props
|
|
19
|
+
let preventClick = false
|
|
20
|
+
|
|
21
|
+
const btnClick = () => {
|
|
22
|
+
emit('click')
|
|
23
|
+
if (componentGlobal.enableSubmit) {
|
|
24
|
+
preventClick = false
|
|
25
|
+
componentGlobal.enableSubmit = false
|
|
26
|
+
}
|
|
27
|
+
if (!preventClick) {
|
|
28
|
+
preventClick = true
|
|
29
|
+
emit('async', () => {
|
|
30
|
+
setTimeout(() => preventClick = false, 200)
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
</script>
|
package/XklSelect.vue
CHANGED
|
@@ -1,28 +1,66 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<el-select style="width: 100%;">
|
|
3
|
-
<el-option v-for="item in dataList" :key="item.
|
|
4
|
-
:value="item[config.valueTarget] || item.value"></el-option>
|
|
2
|
+
<el-select style="width: 100%;" @change="changeHandle">
|
|
3
|
+
<el-option v-for="item in dataList" :key="item.id" :label="item.label" :value="item.value"></el-option>
|
|
5
4
|
</el-select>
|
|
6
5
|
</template>
|
|
6
|
+
<script lang="ts">
|
|
7
|
+
export default {
|
|
8
|
+
name: 'XklSelect'
|
|
9
|
+
}
|
|
10
|
+
</script>
|
|
7
11
|
<script setup lang="ts">
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
10
|
-
|
|
12
|
+
import { onBeforeMount, ref, Ref, watch } from 'vue'
|
|
13
|
+
import { SelectItem } from '@/utils/type'
|
|
14
|
+
import http from "@/utils/httpRequest"
|
|
15
|
+
const props = defineProps(['config', 'list'])
|
|
16
|
+
const emit = defineEmits(['update:label'])
|
|
17
|
+
const { config, list } = props
|
|
18
|
+
|
|
19
|
+
const dataList: Ref<SelectItem[]> = ref([])
|
|
11
20
|
|
|
12
|
-
const
|
|
21
|
+
const changeHandle = (val: string) => {
|
|
22
|
+
const item = dataList.value.find(res => res.value === val)
|
|
23
|
+
if (item) {
|
|
24
|
+
emit('update:label', item.label)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
13
27
|
|
|
14
|
-
const
|
|
28
|
+
const handleData = (data) => {
|
|
29
|
+
data.forEach(inner => {
|
|
30
|
+
inner.value = inner[config?.valueTarget || 'value']
|
|
31
|
+
inner.label = inner[config?.labelTarget || 'label']
|
|
32
|
+
})
|
|
33
|
+
return data
|
|
34
|
+
}
|
|
15
35
|
|
|
16
|
-
if (
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
http({
|
|
20
|
-
url: http.adornUrl(config.url),
|
|
21
|
-
method: 'get',
|
|
22
|
-
params: http.adornParams({})
|
|
23
|
-
}).then(({ data }) => {
|
|
24
|
-
dataList.value = data.data
|
|
36
|
+
if (typeof list === 'function') {
|
|
37
|
+
watch(list, (val: SelectItem[]) => {
|
|
38
|
+
dataList.value = val
|
|
25
39
|
})
|
|
26
40
|
}
|
|
27
41
|
|
|
28
|
-
|
|
42
|
+
onBeforeMount(() => {
|
|
43
|
+
if (list) {
|
|
44
|
+
if (typeof list === 'function') {
|
|
45
|
+
return false
|
|
46
|
+
}
|
|
47
|
+
dataList.value = handleData(list)
|
|
48
|
+
} else {
|
|
49
|
+
if (!config) { return }
|
|
50
|
+
http({
|
|
51
|
+
url: http.adornUrl(config.url),
|
|
52
|
+
method: 'get',
|
|
53
|
+
params: http.adornParams(config.params)
|
|
54
|
+
}).then(({ data }) => {
|
|
55
|
+
dataList.value = handleData(data.data)
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
</script>
|
|
60
|
+
<style lang="scss">
|
|
61
|
+
.readony-input {
|
|
62
|
+
.el-input__inner {
|
|
63
|
+
cursor: not-allowed;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
</style>
|
package/XklTable.vue
CHANGED
|
@@ -1,9 +1,30 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<el-table :data="table.list" style="width: 100%">
|
|
3
|
-
<el-table-column v-if="table.selection" type="selection"></el-table-column>
|
|
4
|
-
<el-table-column v-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
<el-table ref="XklTable" :data="table.list" style="width: 100%" :header-cell-style="{ background: '#F1F4F9' }">
|
|
3
|
+
<el-table-column v-if="table.selection" type="selection" align="center"></el-table-column>
|
|
4
|
+
<el-table-column v-if="table.index" type="index" align="center" v-bind="table.indexConfig"></el-table-column>
|
|
5
|
+
<el-table-column v-for="item in table.columns" :key="item.prop" :prop="item.prop" :label="item.label"
|
|
6
|
+
:width="item.width" :align="item.align || 'center'" :sortable="item.sortable">
|
|
7
|
+
<template v-if="item.reflect" v-slot="{ row }">
|
|
8
|
+
{{ row[item.reflect] }}
|
|
9
|
+
</template>
|
|
10
|
+
<template v-if="item.dict" v-slot="{ row }">
|
|
11
|
+
<el-tag v-if="item.tags"
|
|
12
|
+
:style="{ backgroundColor: tagColor(item, row) + '22', borderColor: tagColor(item, row) + '22', color: tagColor(item, row) }">
|
|
13
|
+
{{ dictLabel(item, row) }}
|
|
14
|
+
</el-tag>
|
|
15
|
+
<span v-else>{{ dictLabel(item, row) }}</span>
|
|
16
|
+
</template>
|
|
17
|
+
<template v-else-if="item.tags" v-slot="{ row }">
|
|
18
|
+
<el-tag
|
|
19
|
+
:style="{ backgroundColor: tagColor(item, row) + '22', borderColor: tagColor(item, row) + '22', color: tagColor(item, row) }">
|
|
20
|
+
{{ row[item.prop] }}
|
|
21
|
+
</el-tag>
|
|
22
|
+
</template>
|
|
23
|
+
<template v-else-if="item.slot" v-slot="{ row }">
|
|
24
|
+
<slot :name="item.prop" :row="row"></slot>
|
|
25
|
+
</template>
|
|
26
|
+
</el-table-column>
|
|
27
|
+
<el-table-column v-if="table.operation.operation !== false" align="center" v-bind="table.operation">
|
|
7
28
|
<template #default="{ row }">
|
|
8
29
|
<slot name="_operation" :row="row"></slot>
|
|
9
30
|
</template>
|
|
@@ -13,13 +34,53 @@
|
|
|
13
34
|
</template>
|
|
14
35
|
</el-table>
|
|
15
36
|
</template>
|
|
37
|
+
<script lang="ts">
|
|
38
|
+
export default {
|
|
39
|
+
name: 'XklTable'
|
|
40
|
+
}
|
|
41
|
+
</script>
|
|
16
42
|
<script setup lang="ts">
|
|
17
|
-
import
|
|
18
|
-
import {
|
|
43
|
+
import http from "@/utils/httpRequest"
|
|
44
|
+
import { computed } from "vue";
|
|
19
45
|
const props = defineProps(['table'])
|
|
20
|
-
const { table } =
|
|
46
|
+
const { table } = props
|
|
47
|
+
const dictTypes = table.columns.map(res => res.dict).filter(res => !!res)
|
|
48
|
+
const dictStore = {}
|
|
49
|
+
|
|
50
|
+
dictTypes.forEach((dict: string) => {
|
|
51
|
+
http({
|
|
52
|
+
url: http.adornUrl(`system/dict/data/type/${dict}`),
|
|
53
|
+
method: 'get',
|
|
54
|
+
params: http.adornParams({})
|
|
55
|
+
}).then(({ data }) => {
|
|
56
|
+
if (data.code === 200) {
|
|
57
|
+
dictStore[dict] = data.data
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const dictLabel = computed(() => {
|
|
63
|
+
return function (col: any, row: any) {
|
|
64
|
+
if (dictStore[col.dict]) {
|
|
65
|
+
const item = dictStore[col.dict].find(res => res.dictValue == row[col.prop]) || {}
|
|
66
|
+
return item.dictLabel
|
|
67
|
+
}
|
|
68
|
+
return ''
|
|
69
|
+
}
|
|
70
|
+
})
|
|
21
71
|
|
|
22
|
-
const
|
|
23
|
-
|
|
72
|
+
const tagColor = computed(() => {
|
|
73
|
+
return function (col, row) {
|
|
74
|
+
if (col.tags) {
|
|
75
|
+
if (Array.isArray(col.tags)) {
|
|
76
|
+
const item = col.tags.find(res => res.value == row[col.prop]) || {}
|
|
77
|
+
return item.color
|
|
78
|
+
} else {
|
|
79
|
+
return col.tags.color
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return ''
|
|
83
|
+
}
|
|
84
|
+
})
|
|
24
85
|
|
|
25
86
|
</script>
|
package/XklTree.vue
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-tree :props="props" :load="loadNode" lazy show-checkbox @check-change="handleCheckChange" />
|
|
3
|
+
</template>
|
|
4
|
+
<script lang="ts">
|
|
5
|
+
export default {
|
|
6
|
+
name: 'XklTree'
|
|
7
|
+
}
|
|
8
|
+
</script>
|
|
9
|
+
<script setup lang="ts">
|
|
10
|
+
const props = {
|
|
11
|
+
label: 'name',
|
|
12
|
+
children: 'zones',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const loadNode = () => { }
|
|
16
|
+
const handleCheckChange = () => { }
|
|
17
|
+
</script>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-tree-select style="width: 100%;" :data="dataList" />
|
|
3
|
+
</template>
|
|
4
|
+
<script lang="ts">
|
|
5
|
+
export default {
|
|
6
|
+
name: 'XklTreeSelect'
|
|
7
|
+
}
|
|
8
|
+
</script>
|
|
9
|
+
<script setup lang="ts">
|
|
10
|
+
import { onBeforeMount, ref } from 'vue'
|
|
11
|
+
import http from "@/utils/httpRequest";
|
|
12
|
+
import { treeData } from '@/utils'
|
|
13
|
+
const props = defineProps(['config'])
|
|
14
|
+
const { config } = props
|
|
15
|
+
|
|
16
|
+
const dataList: any = ref([])
|
|
17
|
+
|
|
18
|
+
const handleData = (data) => {
|
|
19
|
+
data.forEach(inner => {
|
|
20
|
+
inner.value = inner[config.valueTarget || 'value']
|
|
21
|
+
inner.label = inner[config.labelTarget || 'label']
|
|
22
|
+
if (inner.children) {
|
|
23
|
+
handleData(inner.children)
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
return data
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
onBeforeMount(() => {
|
|
30
|
+
http({
|
|
31
|
+
url: http.adornUrl(config.url),
|
|
32
|
+
method: 'get',
|
|
33
|
+
params: http.adornParams(config.params)
|
|
34
|
+
}).then(({ data }) => {
|
|
35
|
+
if (data.code === 200) {
|
|
36
|
+
if (config.toTree) {
|
|
37
|
+
data.data = treeData(data.data, config.toTree.id, config.toTree.pId)
|
|
38
|
+
if (config.toTree.incorporate) {
|
|
39
|
+
dataList.value = [{ value: 0, label: config.toTree.incorporate, children: handleData(data.data) }]
|
|
40
|
+
} else {
|
|
41
|
+
dataList.value = handleData(data.data)
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
dataList.value = handleData(data.data)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
</script>
|
package/XklUpload.vue
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-upload v-model:file-list="fileList" class="upload-demo" :headers="headers" :action="actionUrl"
|
|
3
|
+
:on-change="handleChange" :before-upload="beforeUpload">
|
|
4
|
+
<el-button type="primary">点击上传</el-button>
|
|
5
|
+
<template #tip>
|
|
6
|
+
<div v-if="config.maxSize" class="el-upload__tip">
|
|
7
|
+
文件大小不得超过{{ config.maxSize }}M
|
|
8
|
+
</div>
|
|
9
|
+
</template>
|
|
10
|
+
</el-upload>
|
|
11
|
+
</template>
|
|
12
|
+
<script lang="ts">
|
|
13
|
+
export default {
|
|
14
|
+
name: 'XklUpload'
|
|
15
|
+
}
|
|
16
|
+
</script>
|
|
17
|
+
<script setup lang="ts">
|
|
18
|
+
import { ref, onMounted } from 'vue'
|
|
19
|
+
import { ElMessage } from 'element-plus'
|
|
20
|
+
import http from '@/utils/httpRequest';
|
|
21
|
+
import { getToken } from '@/utils'
|
|
22
|
+
const props = defineProps(['config', 'modelValue'])
|
|
23
|
+
const emit = defineEmits(['update:modelValue'])
|
|
24
|
+
const { config, modelValue } = props
|
|
25
|
+
|
|
26
|
+
const headers = {
|
|
27
|
+
Authorization: getToken()
|
|
28
|
+
}
|
|
29
|
+
const actionUrl = http.adornUrl(config.url || '/common/upload')
|
|
30
|
+
|
|
31
|
+
const fileList = ref([])
|
|
32
|
+
|
|
33
|
+
const beforeUpload = (file) => {
|
|
34
|
+
if (config.maxSize) {
|
|
35
|
+
if (file.size > config.maxSize * 1024 * 1024) {
|
|
36
|
+
ElMessage.warning(`文件大小不得超过${config.maxSize}M`)
|
|
37
|
+
return false
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return true
|
|
41
|
+
}
|
|
42
|
+
const handleChange = (_file, _files) => {
|
|
43
|
+
const upFiles = fileList.value.map((res: any) => {
|
|
44
|
+
const j = {
|
|
45
|
+
name: res.name,
|
|
46
|
+
url: res.url
|
|
47
|
+
}
|
|
48
|
+
if (res.response) {
|
|
49
|
+
j.name = res.response.originalFilename
|
|
50
|
+
j.url = res.response.url
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return j
|
|
54
|
+
})
|
|
55
|
+
emit('update:modelValue', upFiles)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
onMounted(() => {
|
|
60
|
+
fileList.value = modelValue
|
|
61
|
+
})
|
|
62
|
+
</script>
|
package/index.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { App } from "vue";
|
|
2
|
+
|
|
3
|
+
const files = import.meta.glob('./*.vue', { eager: true })
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export default function useComponent(app: App<Element>) {
|
|
8
|
+
Object.keys(files).forEach((component) => {
|
|
9
|
+
const file = (files[component] as any).default
|
|
10
|
+
|
|
11
|
+
// 使用内置的组件名称 进行全局组件注册
|
|
12
|
+
app.component(file.name, file)
|
|
13
|
+
})
|
|
14
|
+
}
|