@jetlinks-web/components 1.0.0
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/index.ts +25 -0
- package/package.json +35 -0
- package/src/AMap/Map.vue +107 -0
- package/src/AMap/index.ts +1 -0
- package/src/AMap/util.ts +56 -0
- package/src/BadgeStatus/Badge.vue +40 -0
- package/src/BadgeStatus/color.ts +23 -0
- package/src/BadgeStatus/index.ts +4 -0
- package/src/Echarts/Echarts.vue +43 -0
- package/src/Echarts/index.ts +3 -0
- package/src/FullPage/index.vue +27 -0
- package/src/GeoComponent/GeoComponent.vue +139 -0
- package/src/GeoComponent/index.ts +3 -0
- package/src/PermissionButton/hooks.ts +20 -0
- package/src/PermissionButton/index.tsx +107 -0
- package/src/ValueItem/ValueItem.vue +177 -0
- package/src/ValueItem/index.ts +3 -0
- package/src/ValueItem/util.ts +15 -0
package/index.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { App } from 'vue'
|
|
2
|
+
|
|
3
|
+
import FullPage from './src/FullPage/index.vue'
|
|
4
|
+
import PermissionButton from './src/PermissionButton/index'
|
|
5
|
+
import BadgeStatus from './src/BadgeStatus'
|
|
6
|
+
import GeoComponent from './src/GeoComponent'
|
|
7
|
+
import ValueItem from './src/ValueItem'
|
|
8
|
+
import Echarts from './src/Echarts'
|
|
9
|
+
|
|
10
|
+
import { PageContainer, AIcon } from 'jetlinks-ui-components'
|
|
11
|
+
import { InitAMap } from './src/AMap'
|
|
12
|
+
|
|
13
|
+
export default {
|
|
14
|
+
install(app: App) {
|
|
15
|
+
app.component('PageContainer', PageContainer)
|
|
16
|
+
.component('AIcon', AIcon)
|
|
17
|
+
.component('PermissionButton', PermissionButton)
|
|
18
|
+
.component('FullPage', FullPage)
|
|
19
|
+
.component('BadgeStatus', BadgeStatus)
|
|
20
|
+
.component('InitAmap', InitAMap)
|
|
21
|
+
.component('GeoComponent', GeoComponent)
|
|
22
|
+
.component('ValueItem', ValueItem)
|
|
23
|
+
.component('Echarts', Echarts)
|
|
24
|
+
}
|
|
25
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jetlinks-web/components",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.ts",
|
|
6
|
+
"module": "index.ts",
|
|
7
|
+
"keywords": [],
|
|
8
|
+
"files": [
|
|
9
|
+
"src",
|
|
10
|
+
"index.ts"
|
|
11
|
+
],
|
|
12
|
+
"private": false,
|
|
13
|
+
"author": "",
|
|
14
|
+
"license": "ISC",
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@vuemap/vue-amap": "^2.0.17",
|
|
17
|
+
"@vueuse/core": "^10.2.1",
|
|
18
|
+
"ant-design-vue": "^3.2.20",
|
|
19
|
+
"jetlinks-ui-components": "^1.0.27",
|
|
20
|
+
"lodash-es": "^4.17.21",
|
|
21
|
+
"pinia": "^2.1.6",
|
|
22
|
+
"vue": "^3.3.4",
|
|
23
|
+
"vue-router": "^4.2.4",
|
|
24
|
+
"@jetlinks/hooks": "npm:@jetlinks-web/hooks@^1.0.0",
|
|
25
|
+
"@jetlinks/stores": "npm:@jetlinks-web/stores@^1.0.0",
|
|
26
|
+
"@jetlinks/utils": "npm:@jetlinks-web/utils@^1.0.0"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"registry": "https://registry.npmjs.org/",
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"clean": "pnpm rimraf node_modules && pnpm rimraf .turbo"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/AMap/Map.vue
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
:style="props.style || { width: '100%', height: '100%' }"
|
|
4
|
+
:class="props.class"
|
|
5
|
+
>
|
|
6
|
+
<el-amap
|
|
7
|
+
v-if="amapKey"
|
|
8
|
+
v-bind="{
|
|
9
|
+
...props,
|
|
10
|
+
...$attrs
|
|
11
|
+
}"
|
|
12
|
+
:map-style="_mapStyle"
|
|
13
|
+
@init="initMap"
|
|
14
|
+
@click="(e) => emit('click', e)"
|
|
15
|
+
@dblclick="(e) => emit('dblclick', e)"
|
|
16
|
+
@movestart="(e) => emit('movestart', e)"
|
|
17
|
+
@moveend="(e) => emit('moveend', e)"
|
|
18
|
+
@rightclick="(e) => emit('rightclick', e)"
|
|
19
|
+
>
|
|
20
|
+
<template v-if="isOpenUi">
|
|
21
|
+
<template v-if="uiLoading">
|
|
22
|
+
<slot></slot>
|
|
23
|
+
</template>
|
|
24
|
+
</template>
|
|
25
|
+
<template v-else><slot></slot></template>
|
|
26
|
+
</el-amap>
|
|
27
|
+
<JEmpty v-else description="请配置高德地图key" style="padding: 20%" />
|
|
28
|
+
</div>
|
|
29
|
+
</template>
|
|
30
|
+
|
|
31
|
+
<script setup name="InitAMap" lang="ts">
|
|
32
|
+
import type { CSSProperties, PropType } from 'vue';
|
|
33
|
+
import { ref, computed } from 'vue'
|
|
34
|
+
import { initAMapApiLoader, ElAmap } from '@vuemap/vue-amap';
|
|
35
|
+
import '@vuemap/vue-amap/dist/style.css';
|
|
36
|
+
import { store } from '@jetlinks/stores'
|
|
37
|
+
import { getAMapUiPromise } from '@jetlinks/utils'
|
|
38
|
+
import { MapProps } from './util'
|
|
39
|
+
interface AMapProps {
|
|
40
|
+
style?: CSSProperties;
|
|
41
|
+
class?: string;
|
|
42
|
+
AMapUI?: string | boolean;
|
|
43
|
+
plugins?: string[]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const systemStore = store.SystemStore
|
|
47
|
+
|
|
48
|
+
const amapKey = systemStore.systemInfo.apiKey
|
|
49
|
+
|
|
50
|
+
const emit = defineEmits([
|
|
51
|
+
'initMap',
|
|
52
|
+
'click',
|
|
53
|
+
'dblclick',
|
|
54
|
+
'movestart',
|
|
55
|
+
'moveend',
|
|
56
|
+
'rightclick',
|
|
57
|
+
])
|
|
58
|
+
|
|
59
|
+
const props = defineProps({
|
|
60
|
+
...MapProps(),
|
|
61
|
+
style: Object as PropType<AMapProps['style']>,
|
|
62
|
+
class: String as PropType<AMapProps['class']>,
|
|
63
|
+
AMapUI: [String, Boolean],
|
|
64
|
+
center: Array,
|
|
65
|
+
plugin: Array,
|
|
66
|
+
zooms: {
|
|
67
|
+
type: Array,
|
|
68
|
+
default: [3, 20]
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const _mapStyle = computed(() => {
|
|
73
|
+
return props.mapStyle ? `amap://styles/${props.mapStyle}` : undefined
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
initAMapApiLoader({
|
|
77
|
+
key: amapKey || '',
|
|
78
|
+
plugin: props.plugin
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const uiLoading = ref<boolean>(false);
|
|
82
|
+
|
|
83
|
+
const map = ref<any>(null);
|
|
84
|
+
|
|
85
|
+
const isOpenUi = computed(() => {
|
|
86
|
+
return 'AMapUI' in props || props.AMapUI;
|
|
87
|
+
});
|
|
88
|
+
const getAMapUI = () => {
|
|
89
|
+
const version = typeof props.AMapUI === 'string' ? props.AMapUI : '1.1';
|
|
90
|
+
getAMapUiPromise(version).then(() => {
|
|
91
|
+
uiLoading.value = true;
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const initMap = (e: any) => {
|
|
96
|
+
map.value = e;
|
|
97
|
+
emit('initMap', e)
|
|
98
|
+
if (isOpenUi.value) {
|
|
99
|
+
getAMapUI();
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
</script>
|
|
104
|
+
|
|
105
|
+
<style scoped>
|
|
106
|
+
|
|
107
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as InitAMap } from './Map.vue'
|
package/src/AMap/util.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export const MapProps = () => ({
|
|
2
|
+
// 响应式
|
|
3
|
+
center: Array,
|
|
4
|
+
labelzIndex: Number,
|
|
5
|
+
lang: String,
|
|
6
|
+
mapStyle: String,
|
|
7
|
+
|
|
8
|
+
// 静态属性
|
|
9
|
+
vid: String,
|
|
10
|
+
amapManager: Object as any,
|
|
11
|
+
defaultCursor: String,
|
|
12
|
+
animateEnable: {
|
|
13
|
+
type: Boolean,
|
|
14
|
+
default: true
|
|
15
|
+
},
|
|
16
|
+
isHotspot: Boolean,
|
|
17
|
+
rotateEnable: {
|
|
18
|
+
type: Boolean,
|
|
19
|
+
default: true
|
|
20
|
+
},
|
|
21
|
+
resizeEnable: {
|
|
22
|
+
type: Boolean,
|
|
23
|
+
default: true
|
|
24
|
+
},
|
|
25
|
+
showIndoorMap: Boolean,
|
|
26
|
+
expandZoomRange: Boolean,
|
|
27
|
+
dragEnable: {
|
|
28
|
+
type: Boolean,
|
|
29
|
+
default: true
|
|
30
|
+
},
|
|
31
|
+
zoomEnable: {
|
|
32
|
+
type: Boolean,
|
|
33
|
+
default: true
|
|
34
|
+
},
|
|
35
|
+
doubleClickZoom: {
|
|
36
|
+
type: Boolean,
|
|
37
|
+
default: true
|
|
38
|
+
},
|
|
39
|
+
keyboardEnable: {
|
|
40
|
+
type: Boolean,
|
|
41
|
+
default: true
|
|
42
|
+
},
|
|
43
|
+
jogEnable: {
|
|
44
|
+
type: Boolean,
|
|
45
|
+
default: true
|
|
46
|
+
},
|
|
47
|
+
scrollWheel: {
|
|
48
|
+
type: Boolean,
|
|
49
|
+
default: true
|
|
50
|
+
},
|
|
51
|
+
touchZoom: {
|
|
52
|
+
type: Boolean,
|
|
53
|
+
default: true
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
})
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<j-badge
|
|
3
|
+
:color="_color"
|
|
4
|
+
:text="text"
|
|
5
|
+
></j-badge>
|
|
6
|
+
</template>
|
|
7
|
+
|
|
8
|
+
<script setup lang="ts" name="BadgeStatus">
|
|
9
|
+
import { defineProps, computed } from 'vue'
|
|
10
|
+
import { getHexColor } from './color'
|
|
11
|
+
const props = defineProps({
|
|
12
|
+
text: {
|
|
13
|
+
type: String,
|
|
14
|
+
},
|
|
15
|
+
status: {
|
|
16
|
+
type: [String, Number],
|
|
17
|
+
default: 'default',
|
|
18
|
+
},
|
|
19
|
+
/**
|
|
20
|
+
* 自定义status值颜色
|
|
21
|
+
* @example {
|
|
22
|
+
* 1: 'success',
|
|
23
|
+
* 0: 'error'
|
|
24
|
+
* }
|
|
25
|
+
*/
|
|
26
|
+
statusNames: {
|
|
27
|
+
type: Object,
|
|
28
|
+
default: () => ({
|
|
29
|
+
'success': 'success',
|
|
30
|
+
'warning': 'warning',
|
|
31
|
+
'error': 'error',
|
|
32
|
+
'default': 'default',
|
|
33
|
+
})
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const _color = computed(() => {
|
|
38
|
+
return getHexColor(props.statusNames[props.status], 1)
|
|
39
|
+
})
|
|
40
|
+
</script>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
|
|
2
|
+
const color = {
|
|
3
|
+
'processing': '9, 46, 231',
|
|
4
|
+
'error': '229, 0, 18',
|
|
5
|
+
'success': '36, 178, 118',
|
|
6
|
+
'warning': '255, 144, 0',
|
|
7
|
+
'default': '102, 102, 102',
|
|
8
|
+
//告警颜色
|
|
9
|
+
'level1': '229, 0, 18',
|
|
10
|
+
'level2': '255, 148, 87',
|
|
11
|
+
'level3': '250, 189, 71',
|
|
12
|
+
'level4': '153, 153, 153',
|
|
13
|
+
'level5': '196, 196, 196'
|
|
14
|
+
}
|
|
15
|
+
export const getHexColor = (code: string, pe: number = 0.1) => {
|
|
16
|
+
const _color = color[code] || color.default
|
|
17
|
+
if (code === 'default') {
|
|
18
|
+
pe = 0.1
|
|
19
|
+
}
|
|
20
|
+
return `rgba(${_color}, ${pe})`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default color
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div ref="echartsDom" class="echarts-warp" :style="style"></div>
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script lang="ts" name="Echarts" setup>
|
|
6
|
+
import { CSSProperties, nextTick, ref, watch, Ref } from 'vue'
|
|
7
|
+
import { useECharts } from '@jetlinks/hooks'
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
style?: CSSProperties
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const props = defineProps({
|
|
14
|
+
options: {
|
|
15
|
+
type: Object,
|
|
16
|
+
default: undefined,
|
|
17
|
+
},
|
|
18
|
+
style: Object as PropType<Props['style']>,
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const echartsDom = ref<Ref<HTMLDivElement> | HTMLDivElement>()
|
|
22
|
+
|
|
23
|
+
const { setOptions } = useECharts(echartsDom.value)
|
|
24
|
+
|
|
25
|
+
watch(
|
|
26
|
+
() => props.options,
|
|
27
|
+
() => {
|
|
28
|
+
if (props.options) {
|
|
29
|
+
nextTick(() => {
|
|
30
|
+
setOptions(props.options)
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
{ immediate: true, deep: true },
|
|
35
|
+
)
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<style scoped>
|
|
39
|
+
.echarts-warp {
|
|
40
|
+
width: 100%;
|
|
41
|
+
height: 100%;
|
|
42
|
+
}
|
|
43
|
+
</style>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class='full-page-warp' ref='fullPage' :style='{ minHeight: `calc(100vh - ${y + 24}px)`}'>
|
|
3
|
+
<div class="full-page-warp-content">
|
|
4
|
+
<slot></slot>
|
|
5
|
+
</div>
|
|
6
|
+
</div>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<script setup lang='ts' name='FullPage'>
|
|
10
|
+
import { ref } from 'vue'
|
|
11
|
+
import { useElementBounding } from '@vueuse/core'
|
|
12
|
+
|
|
13
|
+
const fullPage = ref(null)
|
|
14
|
+
const { y } = useElementBounding(fullPage)
|
|
15
|
+
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<style scoped lang="less">
|
|
19
|
+
.full-page-warp {
|
|
20
|
+
background: #fff;
|
|
21
|
+
display: flex;
|
|
22
|
+
.full-page-warp-content {
|
|
23
|
+
width: 100%;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
</style>
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
<!-- 坐标点拾取组件 -->
|
|
2
|
+
<template>
|
|
3
|
+
<div class="page-container">
|
|
4
|
+
<j-input allowClear v-bind="props" v-model:value="inputPoint">
|
|
5
|
+
<template #addonAfter>
|
|
6
|
+
<AIcon type="EnvironmentOutlined" @click="modalVisible = true" />
|
|
7
|
+
</template>
|
|
8
|
+
</j-input>
|
|
9
|
+
<j-modal
|
|
10
|
+
title="地理位置"
|
|
11
|
+
ok-text="确认"
|
|
12
|
+
cancel-text="取消"
|
|
13
|
+
v-model:visible="modalVisible"
|
|
14
|
+
width="700px"
|
|
15
|
+
@cancel="modalVisible = false"
|
|
16
|
+
@ok="handleModalSubmit"
|
|
17
|
+
destroyOnClose
|
|
18
|
+
>
|
|
19
|
+
<div style="width: 100%; height: 400px">
|
|
20
|
+
<div style="margin-bottom: 10px;">地理位置:{{ mapPoint }}</div>
|
|
21
|
+
<el-amap
|
|
22
|
+
:center="_center"
|
|
23
|
+
:zoom="_zoom"
|
|
24
|
+
@init="initMap"
|
|
25
|
+
@click="clickMap"
|
|
26
|
+
>
|
|
27
|
+
<el-amap-search-box visible @select="selectPosition" />
|
|
28
|
+
<el-amap-marker :position="position" />
|
|
29
|
+
</el-amap>
|
|
30
|
+
</div>
|
|
31
|
+
</j-modal>
|
|
32
|
+
</div>
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<script setup lang="ts">
|
|
36
|
+
import { store } from '@jetlinks/stores'
|
|
37
|
+
import { initAMapApiLoader } from '@vuemap/vue-amap'
|
|
38
|
+
import '@vuemap/vue-amap/dist/style.css'
|
|
39
|
+
import { computed, CSSProperties, PropType, ref } from 'vue'
|
|
40
|
+
|
|
41
|
+
interface AMapProps {
|
|
42
|
+
style?: CSSProperties
|
|
43
|
+
class?: string
|
|
44
|
+
AMapUI?: string | boolean
|
|
45
|
+
plugins?: string[]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const systemStore = store.SystemStore
|
|
49
|
+
|
|
50
|
+
const amapKey = systemStore.systemInfo.apiKey
|
|
51
|
+
|
|
52
|
+
interface EmitProps {
|
|
53
|
+
(e: 'update:point', data: string): void
|
|
54
|
+
(e: 'change', data: string): void
|
|
55
|
+
}
|
|
56
|
+
const props = defineProps({
|
|
57
|
+
style: Object as PropType<AMapProps['style']>,
|
|
58
|
+
class: String as PropType<AMapProps['class']>,
|
|
59
|
+
AMapUI: [String, Boolean],
|
|
60
|
+
center: Array as PropType<number[]>,
|
|
61
|
+
plugin: Array,
|
|
62
|
+
zoom: {
|
|
63
|
+
type: Number,
|
|
64
|
+
default: 12,
|
|
65
|
+
},
|
|
66
|
+
securityJsCode: String,
|
|
67
|
+
point: { type: [Number, String], default: '' },
|
|
68
|
+
})
|
|
69
|
+
const emit = defineEmits<EmitProps>()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
initAMapApiLoader({
|
|
73
|
+
key: amapKey,
|
|
74
|
+
securityJsCode: props.securityJsCode
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
// 手动输入的坐标点(经纬度字符串)
|
|
78
|
+
const inputPoint = computed({
|
|
79
|
+
get: () => {
|
|
80
|
+
return props.point
|
|
81
|
+
},
|
|
82
|
+
set: (val: any) => {
|
|
83
|
+
mapPoint.value = val
|
|
84
|
+
emit('update:point', val)
|
|
85
|
+
emit('change', val)
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
// 地图弹窗
|
|
90
|
+
const modalVisible = ref<boolean>(false)
|
|
91
|
+
|
|
92
|
+
const handleModalSubmit = () => {
|
|
93
|
+
inputPoint.value = mapPoint.value
|
|
94
|
+
modalVisible.value = false
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 地图拾取的坐标点(经纬度字符串)
|
|
98
|
+
const mapPoint = ref('')
|
|
99
|
+
|
|
100
|
+
const _zoom = ref<number>(props.zoom)
|
|
101
|
+
const _center = ref<number[]>(props?.center || [])
|
|
102
|
+
let map: any = null
|
|
103
|
+
|
|
104
|
+
// 地图经纬度
|
|
105
|
+
const position = ref<number[] | string[]>([])
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 地图初始化
|
|
109
|
+
* @param e
|
|
110
|
+
*/
|
|
111
|
+
const initMap = (e: any) => {
|
|
112
|
+
map = e
|
|
113
|
+
|
|
114
|
+
const pointStr = mapPoint.value as string
|
|
115
|
+
position.value = pointStr ? pointStr.split(',') : _center.value
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* 地图点击
|
|
120
|
+
* @param e
|
|
121
|
+
*/
|
|
122
|
+
const clickMap = (e: any) => {
|
|
123
|
+
mapPoint.value = `${e.lnglat.lng},${e.lnglat.lat}`
|
|
124
|
+
position.value = [e.lnglat.lng, e.lnglat.lat]
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* 选择搜索结果
|
|
129
|
+
* @param e
|
|
130
|
+
*/
|
|
131
|
+
const selectPosition = (e: any) => {
|
|
132
|
+
const selectPoint = [e.poi.location.lng, e.poi.location.lat]
|
|
133
|
+
map.setCenter(selectPoint)
|
|
134
|
+
mapPoint.value = selectPoint.join(',')
|
|
135
|
+
position.value = selectPoint
|
|
136
|
+
}
|
|
137
|
+
</script>
|
|
138
|
+
|
|
139
|
+
<style lang="less" scoped></style>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {watch, ref} from 'vue'
|
|
2
|
+
import type { Ref } from 'vue'
|
|
3
|
+
import { store } from '@jetlinks/stores'
|
|
4
|
+
import {isBoolean} from "lodash-es";
|
|
5
|
+
|
|
6
|
+
export const usePermission = (code?: string | string[] | boolean): {
|
|
7
|
+
hasPerm: Ref<boolean>
|
|
8
|
+
} => {
|
|
9
|
+
const hasPerm = ref(false)
|
|
10
|
+
|
|
11
|
+
watch(() => code, () => {
|
|
12
|
+
if (code) {
|
|
13
|
+
hasPerm.value = isBoolean(code) ? code : store.AuthStore.hasPermission(code)
|
|
14
|
+
}
|
|
15
|
+
}, { immediate: true })
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
hasPerm
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import {h, defineComponent, computed} from "vue";
|
|
2
|
+
import type { PropType, CSSProperties, ExtractPropTypes } from 'vue'
|
|
3
|
+
import { Button, Tooltip, Popconfirm } from 'jetlinks-ui-components'
|
|
4
|
+
import {PopconfirmProps, TooltipProps} from "ant-design-vue/es";
|
|
5
|
+
import {omit} from "lodash-es";
|
|
6
|
+
import {buttonProps} from "ant-design-vue/es/button/button";
|
|
7
|
+
import { usePermission } from './hooks'
|
|
8
|
+
|
|
9
|
+
type PermissionType = string | Array<string> | boolean
|
|
10
|
+
|
|
11
|
+
const definedProps = {
|
|
12
|
+
tooltip: {
|
|
13
|
+
type: Object as PropType<TooltipProps>,
|
|
14
|
+
},
|
|
15
|
+
popConfirm: {
|
|
16
|
+
type: Object as PropType<PopconfirmProps>,
|
|
17
|
+
},
|
|
18
|
+
hasPermission: {
|
|
19
|
+
type: [String, Array, Boolean] as PropType<PermissionType>,
|
|
20
|
+
default: undefined
|
|
21
|
+
},
|
|
22
|
+
style: {
|
|
23
|
+
type: Object as PropType<CSSProperties>
|
|
24
|
+
},
|
|
25
|
+
noPermissionTitle: {
|
|
26
|
+
type: String
|
|
27
|
+
},
|
|
28
|
+
...omit(buttonProps(), 'icon')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type DefinedPropsType = Partial<ExtractPropTypes<typeof definedProps>>
|
|
32
|
+
|
|
33
|
+
const PermissionButton = defineComponent({
|
|
34
|
+
name: 'PermissionButton',
|
|
35
|
+
// @ts-ignore
|
|
36
|
+
slots: ['button', 'icon'],
|
|
37
|
+
props: definedProps,
|
|
38
|
+
setup(props, { slots }) {
|
|
39
|
+
|
|
40
|
+
const { popConfirm, tooltip } = props
|
|
41
|
+
const { hasPerm } = usePermission(props.hasPermission as PermissionType)
|
|
42
|
+
|
|
43
|
+
const permission = computed(() => {
|
|
44
|
+
if (!props.hasPermission || props.hasPermission === true) {
|
|
45
|
+
return true
|
|
46
|
+
}
|
|
47
|
+
return hasPerm.value
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const isPermission = computed(() => {
|
|
51
|
+
if ('hasPermission' in props && permission.value) {
|
|
52
|
+
return 'disabled' in props ? props.disabled : false
|
|
53
|
+
}
|
|
54
|
+
return true
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const hasPopConfirm = computed(() => !!popConfirm) // 是否包含确认弹窗
|
|
58
|
+
const hasTooltip = computed(() => !!tooltip) // 是否包含文字提示
|
|
59
|
+
|
|
60
|
+
return () => {
|
|
61
|
+
const { popConfirm, tooltip, hasPermission, noPermissionTitle, ...buttonProps } = props
|
|
62
|
+
|
|
63
|
+
const button = !slots.button ?
|
|
64
|
+
h(Button,
|
|
65
|
+
{
|
|
66
|
+
...buttonProps,
|
|
67
|
+
disabled: isPermission.value,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
default: () => slots?.default?.(),
|
|
71
|
+
icon: () => slots?.icon?.()
|
|
72
|
+
}) :
|
|
73
|
+
slots.button()
|
|
74
|
+
|
|
75
|
+
// 文字提示
|
|
76
|
+
const _tooltip = tooltip ? h(Tooltip, { ...tooltip, disabled: isPermission.value }, { default: () => button}) : undefined
|
|
77
|
+
|
|
78
|
+
// 无权限
|
|
79
|
+
const noPermissionButton = !permission.value ? h(Tooltip, { title: noPermissionTitle || '暂无权限,请联系管理员' }, { default: () => button}) : undefined
|
|
80
|
+
|
|
81
|
+
// 二次确认
|
|
82
|
+
const _popConfirm = popConfirm ?
|
|
83
|
+
h(Popconfirm,
|
|
84
|
+
{
|
|
85
|
+
...popConfirm,
|
|
86
|
+
disabled: !permission.value || buttonProps.disabled,
|
|
87
|
+
overlayStyle: { width: '220px' }
|
|
88
|
+
}, { default: () => tooltip ? _tooltip : button })
|
|
89
|
+
: undefined
|
|
90
|
+
|
|
91
|
+
if (permission.value) {
|
|
92
|
+
if(hasPopConfirm.value) {
|
|
93
|
+
return _popConfirm
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (hasTooltip.value) {
|
|
97
|
+
return _tooltip
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return button
|
|
101
|
+
}
|
|
102
|
+
return noPermissionButton
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
export default PermissionButton
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
<!-- 参数类型输入组件 -->
|
|
2
|
+
<template>
|
|
3
|
+
<div class="value-item-warp">
|
|
4
|
+
<j-select
|
|
5
|
+
v-if="typeMap.get(itemType) === 'select'"
|
|
6
|
+
v-model:value="myValue"
|
|
7
|
+
allowClear
|
|
8
|
+
@change="(_, options) => onChange(options)"
|
|
9
|
+
v-bind="props"
|
|
10
|
+
/>
|
|
11
|
+
<j-date-picker
|
|
12
|
+
v-else-if="typeMap.get(itemType) === 'date'"
|
|
13
|
+
:valueFormat="valueFormat || 'YYYY-MM-DD HH:mm:ss'"
|
|
14
|
+
v-model:value="myValue"
|
|
15
|
+
allowClear
|
|
16
|
+
showTime
|
|
17
|
+
@change="onChange"
|
|
18
|
+
/>
|
|
19
|
+
<j-input-number
|
|
20
|
+
v-else-if="typeMap.get(itemType) === 'inputNumber'"
|
|
21
|
+
v-bind="props"
|
|
22
|
+
v-model:value="myValue"
|
|
23
|
+
allowClear
|
|
24
|
+
@change="onChange"
|
|
25
|
+
/>
|
|
26
|
+
<j-input
|
|
27
|
+
allowClear
|
|
28
|
+
v-bind="props"
|
|
29
|
+
v-else-if="typeMap.get(itemType) === 'object'"
|
|
30
|
+
v-model:value="myValue"
|
|
31
|
+
@change="onChange"
|
|
32
|
+
>
|
|
33
|
+
<template #addonAfter>
|
|
34
|
+
<AIcon type="FormOutlined" @click="modalVisible = true" />
|
|
35
|
+
</template>
|
|
36
|
+
</j-input>
|
|
37
|
+
<GeoComponent
|
|
38
|
+
v-else-if="typeMap.get(itemType) === 'geoPoint'"
|
|
39
|
+
v-model:point="myValue"
|
|
40
|
+
@change="onChange"
|
|
41
|
+
v-bind="props"
|
|
42
|
+
/>
|
|
43
|
+
<j-input
|
|
44
|
+
v-else-if="typeMap.get(itemType) === 'file'"
|
|
45
|
+
v-model:value="myValue"
|
|
46
|
+
placeholder="请输入链接"
|
|
47
|
+
allowClear
|
|
48
|
+
@change="onChange"
|
|
49
|
+
>
|
|
50
|
+
<template #addonAfter>
|
|
51
|
+
<j-upload
|
|
52
|
+
name="file"
|
|
53
|
+
:action="action"
|
|
54
|
+
:headers="headers"
|
|
55
|
+
:showUploadList="false"
|
|
56
|
+
@change="handleFileChange"
|
|
57
|
+
>
|
|
58
|
+
<AIcon type="UploadOutlined" />
|
|
59
|
+
</j-upload>
|
|
60
|
+
</template>
|
|
61
|
+
</j-input>
|
|
62
|
+
<j-input-password
|
|
63
|
+
v-else-if="typeMap.get(itemType) === 'password'"
|
|
64
|
+
allowClear
|
|
65
|
+
v-bind="props"
|
|
66
|
+
type="password"
|
|
67
|
+
v-model:value="myValue"
|
|
68
|
+
@change="onChange"
|
|
69
|
+
/>
|
|
70
|
+
<j-input
|
|
71
|
+
v-else
|
|
72
|
+
allowClear
|
|
73
|
+
v-bind="props"
|
|
74
|
+
type="text"
|
|
75
|
+
v-model:value="myValue"
|
|
76
|
+
@change="onChange"
|
|
77
|
+
/>
|
|
78
|
+
|
|
79
|
+
<!-- 代码编辑器弹窗 -->
|
|
80
|
+
<j-modal
|
|
81
|
+
title="编辑"
|
|
82
|
+
ok-text="确认"
|
|
83
|
+
cancel-text="取消"
|
|
84
|
+
v-model:visible="modalVisible"
|
|
85
|
+
width="700px"
|
|
86
|
+
@cancel="modalVisible = false"
|
|
87
|
+
@ok="handleItemModalSubmit"
|
|
88
|
+
:zIndex="1100"
|
|
89
|
+
>
|
|
90
|
+
<div style="width: 100%; height: 300px">
|
|
91
|
+
<JMonacoEditor v-model:modelValue="objectValue" />
|
|
92
|
+
</div>
|
|
93
|
+
</j-modal>
|
|
94
|
+
</div>
|
|
95
|
+
</template>
|
|
96
|
+
|
|
97
|
+
<script setup lang="ts">
|
|
98
|
+
import { CSSProperties, PropType, ref, watch } from 'vue'
|
|
99
|
+
import { componentsType } from './util'
|
|
100
|
+
|
|
101
|
+
type Emits = {
|
|
102
|
+
(e: 'update:modelValue', data: string | number | boolean): void
|
|
103
|
+
(e: 'change', data: any, item?: any): void
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
interface ItemProps {
|
|
107
|
+
style?: CSSProperties
|
|
108
|
+
class?: string
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const emit = defineEmits<Emits>()
|
|
112
|
+
|
|
113
|
+
const props = defineProps({
|
|
114
|
+
// 组件双向绑定的值
|
|
115
|
+
modelValue: {
|
|
116
|
+
type: [Number, String],
|
|
117
|
+
default: '',
|
|
118
|
+
},
|
|
119
|
+
// 组件类型
|
|
120
|
+
itemType: {
|
|
121
|
+
type: String,
|
|
122
|
+
default: () => 'string',
|
|
123
|
+
},
|
|
124
|
+
// 多选框
|
|
125
|
+
mode: {
|
|
126
|
+
type: String as PropType<'multiple' | 'tags' | 'combobox' | ''>,
|
|
127
|
+
default: '',
|
|
128
|
+
},
|
|
129
|
+
placeholder: String,
|
|
130
|
+
options: Array, // 下拉选择框下拉数据
|
|
131
|
+
style: Object as PropType<ItemProps['style']>,
|
|
132
|
+
class: String,
|
|
133
|
+
valueFormat: String,
|
|
134
|
+
action: [String, Promise],
|
|
135
|
+
headers: Object,
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
const typeMap = new Map(Object.entries(componentsType))
|
|
139
|
+
|
|
140
|
+
const myValue = ref<any>(undefined)
|
|
141
|
+
const modalVisible = ref<boolean>(false)
|
|
142
|
+
const objectValue = ref<string>('')
|
|
143
|
+
|
|
144
|
+
const handleItemModalSubmit = () => {
|
|
145
|
+
myValue.value = objectValue.value.replace(/[\r\n]\s*/g, '')
|
|
146
|
+
modalVisible.value = false
|
|
147
|
+
emit('update:modelValue', objectValue.value)
|
|
148
|
+
emit('change', objectValue.value)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const onChange = (e) => {
|
|
152
|
+
emit('update:modelValue', myValue.value)
|
|
153
|
+
emit('change', e)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const handleFileChange = (info: any) => {
|
|
157
|
+
if (info.file.status === 'done') {
|
|
158
|
+
const url = info.file.response?.result
|
|
159
|
+
myValue.value = url
|
|
160
|
+
emit('update:modelValue', url)
|
|
161
|
+
emit('change', url)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
watch(
|
|
166
|
+
() => props.modelValue,
|
|
167
|
+
() => {
|
|
168
|
+
myValue.value = props.modelValue
|
|
169
|
+
if (props.itemType === 'object') {
|
|
170
|
+
objectValue.value = props.modelValue as string
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
{ immediate: true },
|
|
174
|
+
)
|
|
175
|
+
</script>
|
|
176
|
+
|
|
177
|
+
<style lang="less" scoped></style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const componentsType = {
|
|
2
|
+
int: 'inputNumber',
|
|
3
|
+
long: 'inputNumber',
|
|
4
|
+
float: 'inputNumber',
|
|
5
|
+
double: 'inputNumber',
|
|
6
|
+
string: 'input',
|
|
7
|
+
array: 'input',
|
|
8
|
+
password: 'password',
|
|
9
|
+
enum: 'select',
|
|
10
|
+
boolean: 'select',
|
|
11
|
+
date: 'date',
|
|
12
|
+
object: 'object',
|
|
13
|
+
geoPoint: 'geoPoint',
|
|
14
|
+
file: 'file',
|
|
15
|
+
}
|