@xuekl/cli-components 1.0.0 → 1.0.2
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 +29 -0
- package/package.json +2 -2
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,29 @@
|
|
|
1
|
+
import ContextMenu from './ContextMenu.vue'
|
|
2
|
+
import PaddingBox from './PaddingBox.vue'
|
|
3
|
+
import PageContainer from './PageContainer.vue'
|
|
4
|
+
import XklButton from './XklButton.vue'
|
|
5
|
+
import XklDatePicker from './XklDatePicker.vue'
|
|
6
|
+
import XklDict from './XklDict.vue'
|
|
7
|
+
import XklForm from './XklForm.vue'
|
|
8
|
+
import XklLink from './XklLink.vue'
|
|
9
|
+
import XklSelect from './XklSelect.vue'
|
|
10
|
+
import XklTable from './XklTable.vue'
|
|
11
|
+
import XklTree from './XklTree.vue'
|
|
12
|
+
import XklTreeSelect from './XklTreeSelect.vue'
|
|
13
|
+
import XklUpload from './XklUpload.vue'
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
ContextMenu,
|
|
17
|
+
PaddingBox,
|
|
18
|
+
PageContainer,
|
|
19
|
+
XklButton,
|
|
20
|
+
XklDatePicker,
|
|
21
|
+
XklDict,
|
|
22
|
+
XklForm,
|
|
23
|
+
XklLink,
|
|
24
|
+
XklSelect,
|
|
25
|
+
XklTable,
|
|
26
|
+
XklTree,
|
|
27
|
+
XklTreeSelect,
|
|
28
|
+
XklUpload,
|
|
29
|
+
}
|