Showing
16 changed files
with
1566 additions
and
22 deletions
src/views/data/board/cpns/ColorPicker.vue
0 → 100644
1 | +<script lang="ts" setup> | |
2 | +import '@simonwep/pickr/dist/themes/monolith.min.css' | |
3 | +import ColorPicker from '@simonwep/pickr' | |
4 | +import type { PropType } from 'vue' | |
5 | +import { computed, onMounted, onUnmounted, ref, unref } from 'vue' | |
6 | + | |
7 | +type Format = Exclude<keyof ColorPicker.HSVaColor, 'clone'> | |
8 | + | |
9 | +const props = defineProps({ | |
10 | + value: { | |
11 | + type: String, | |
12 | + default: '#377dff', | |
13 | + }, | |
14 | + format: { | |
15 | + type: String as PropType<ColorPicker.Representation>, | |
16 | + default: 'HEXA' as ColorPicker.Representation, | |
17 | + }, | |
18 | + config: { | |
19 | + type: Object as PropType<ColorPicker.Options>, | |
20 | + }, | |
21 | +}) | |
22 | + | |
23 | +const emit = defineEmits(['update:value']) | |
24 | + | |
25 | +const prefixCls = 'thingskit' | |
26 | + | |
27 | +const colorPickerCls = 'color-picker' | |
28 | + | |
29 | +const picker = ref<Nullable<ColorPicker>>(null) | |
30 | + | |
31 | +const getFormat = computed<Format>(() => { | |
32 | + return `to${props.format}` | |
33 | +}) | |
34 | + | |
35 | +const getColor = () => { | |
36 | + const value = unref(picker)?.getColor() | |
37 | + return value && value[unref(getFormat)]().toString() | |
38 | +} | |
39 | + | |
40 | +const onInit = () => { | |
41 | + unref(picker)?.setColor(props.value) | |
42 | +} | |
43 | + | |
44 | +const onSave = () => { | |
45 | + const value = getColor() | |
46 | + emit('update:value', value) | |
47 | + unref(picker)?.hide() | |
48 | +} | |
49 | + | |
50 | +const getOption = computed<ColorPicker.Options>(() => { | |
51 | + const { config = {} } = props | |
52 | + return { | |
53 | + theme: 'monolith', | |
54 | + components: { | |
55 | + // Main components | |
56 | + preview: true, | |
57 | + opacity: true, | |
58 | + hue: true, | |
59 | + | |
60 | + // Input / output Options | |
61 | + interaction: { | |
62 | + hex: true, | |
63 | + rgba: true, | |
64 | + input: true, | |
65 | + clear: true, | |
66 | + save: true, | |
67 | + }, | |
68 | + }, | |
69 | + i18n: { | |
70 | + 'ui:dialog': 'color picker dialog', | |
71 | + 'btn:toggle': 'toggle color picker dialog', | |
72 | + 'btn:swatch': 'color swatch', | |
73 | + 'btn:last-color': '使用以前的颜色', | |
74 | + 'btn:save': '保存', | |
75 | + 'btn:cancel': '取消', | |
76 | + 'btn:clear': '清除', | |
77 | + | |
78 | + // 用于 aria-labels 的字符串 | |
79 | + 'aria:btn:save': 'save and close', | |
80 | + 'aria:btn:cancel': 'cancel and close', | |
81 | + 'aria:btn:clear': 'clear and close', | |
82 | + 'aria:input': '颜色输入字段', | |
83 | + 'aria:palette': '颜色选择区域', | |
84 | + 'aria:hue': '色调选择滑块', | |
85 | + 'aria:opacity': '选择滑块', | |
86 | + }, | |
87 | + ...config, | |
88 | + | |
89 | + el: `.${prefixCls}-${colorPickerCls}`, | |
90 | + } | |
91 | +}) | |
92 | + | |
93 | +onMounted(() => { | |
94 | + picker.value = ColorPicker.create(unref(getOption)) | |
95 | + unref(picker)?.on('init', onInit) | |
96 | + unref(picker)?.on('save', onSave) | |
97 | +}) | |
98 | + | |
99 | +onUnmounted(() => { | |
100 | + unref(picker)?.off('init', onInit) | |
101 | + unref(picker)?.off('save', onSave) | |
102 | + | |
103 | + unref(picker)?.destroyAndRemove() | |
104 | +}) | |
105 | +</script> | |
106 | + | |
107 | +<template> | |
108 | + <div :class="`${prefixCls}-${colorPickerCls}`" /> | |
109 | +</template> | ... | ... |
1 | +<script lang="ts" setup> | |
2 | +import icon from '../assets/command.svg' | |
3 | + | |
4 | +const props = defineProps<{ | |
5 | + value?: boolean | |
6 | +}>() | |
7 | + | |
8 | +const emit = defineEmits(['update:value', 'change']) | |
9 | + | |
10 | +const handleChange = (event: Event) => { | |
11 | + const _value = (event.target as HTMLInputElement).checked | |
12 | + emit('update:value', _value) | |
13 | + emit('change', _value) | |
14 | +} | |
15 | +</script> | |
16 | + | |
17 | +<template> | |
18 | + <div class="command-send-button"> | |
19 | + 123 | |
20 | + <a href="javascript:;"> | |
21 | + <img :src="icon" alt=""> | |
22 | + </a> | |
23 | + </div> | |
24 | +</template> | |
25 | + | |
26 | +<style scoped lang="less"> | |
27 | +.command-send-button { | |
28 | + width: 80px; | |
29 | + height: 60px; | |
30 | + position: relative; | |
31 | + | |
32 | + a { | |
33 | + position: absolute; | |
34 | + box-sizing: border-box; | |
35 | + cursor: pointer; | |
36 | + text-decoration: none; | |
37 | + color: #fff; | |
38 | + background: #f2385a; | |
39 | + top: 0; | |
40 | + padding: 0 16px; | |
41 | + height: 60px; | |
42 | + width: 80px; | |
43 | + overflow: hidden; | |
44 | + font-size: 20px; | |
45 | + line-height: 60px; | |
46 | + border-radius: 10px; | |
47 | + box-shadow: 0 15px 0 0 #f02046, 0 0 20px 0 #bbb; | |
48 | + transition: all 0.2s; | |
49 | + } | |
50 | + | |
51 | +} | |
52 | +</style> | ... | ... |
1 | +<script lang="ts" setup> | |
2 | + import type { ECharts, EChartsOption } from 'echarts'; | |
3 | + import type { PropType } from 'vue'; | |
4 | + import { nextTick, onMounted, onUnmounted, ref, unref } from 'vue'; | |
5 | + import { init } from 'echarts'; | |
6 | + | |
7 | + interface DataSource { | |
8 | + id: string | number; | |
9 | + } | |
10 | + | |
11 | + const props = defineProps({ | |
12 | + dataSource: { | |
13 | + type: Object as PropType<DataSource>, | |
14 | + required: true, | |
15 | + }, | |
16 | + chartOption: { | |
17 | + type: Object as PropType<EChartsOption>, | |
18 | + // required: true, | |
19 | + }, | |
20 | + add: { | |
21 | + type: Function, | |
22 | + required: true, | |
23 | + }, | |
24 | + }); | |
25 | + | |
26 | + const getControlsWidgetId = () => `widget-chart-${props.dataSource.id}`; | |
27 | + | |
28 | + const chartRef = ref<Nullable<ECharts>>(null); | |
29 | + | |
30 | + function initChart() { | |
31 | + const chartDom = document.getElementById(getControlsWidgetId())!; | |
32 | + chartRef.value = init(chartDom); | |
33 | + const option: EChartsOption = props.chartOption || { | |
34 | + tooltip: { | |
35 | + trigger: 'item', | |
36 | + // confine: true, | |
37 | + extraCssText: 'position: fixed;', | |
38 | + position: (point, params, dom, rect, size) => { | |
39 | + const parentEl = (dom as HTMLDivElement).parentElement!; | |
40 | + | |
41 | + const { top = 0, left = 0 } = parentEl.getBoundingClientRect()!; | |
42 | + return [left, top]; | |
43 | + }, | |
44 | + }, | |
45 | + series: [ | |
46 | + { | |
47 | + name: 'Access From', | |
48 | + type: 'pie', | |
49 | + radius: '50%', | |
50 | + data: [ | |
51 | + { value: 1048, name: 'Search Engine' }, | |
52 | + { value: 735, name: 'Direct' }, | |
53 | + { value: 580, name: 'Email' }, | |
54 | + { value: 484, name: 'Union Ads' }, | |
55 | + { value: 300, name: 'Video Ads' }, | |
56 | + ], | |
57 | + emphasis: { | |
58 | + itemStyle: { | |
59 | + shadowBlur: 10, | |
60 | + shadowOffsetX: 0, | |
61 | + shadowColor: 'rgba(0, 0, 0, 0.5)', | |
62 | + }, | |
63 | + }, | |
64 | + }, | |
65 | + ], | |
66 | + }; | |
67 | + | |
68 | + nextTick(() => { | |
69 | + option && unref(chartRef)?.setOption(option); | |
70 | + }); | |
71 | + } | |
72 | + | |
73 | + function update() { | |
74 | + unref(chartRef)?.resize(); | |
75 | + } | |
76 | + | |
77 | + onMounted(() => { | |
78 | + initChart(); | |
79 | + props.add(props.dataSource.id, update); | |
80 | + }); | |
81 | + | |
82 | + onUnmounted(() => { | |
83 | + unref(chartRef)?.clear(); | |
84 | + }); | |
85 | + | |
86 | + defineExpose({ update }); | |
87 | +</script> | |
88 | + | |
89 | +<template> | |
90 | + <div :id="getControlsWidgetId()" class="widget-charts"></div> | |
91 | +</template> | |
92 | + | |
93 | +<style scoped> | |
94 | + .widget-charts { | |
95 | + min-width: 10px; | |
96 | + min-height: 10px; | |
97 | + width: 100%; | |
98 | + height: 100%; | |
99 | + } | |
100 | + | |
101 | + .widget-charts > div { | |
102 | + width: 100%; | |
103 | + height: 100%; | |
104 | + } | |
105 | +</style> | ... | ... |
src/views/data/board/cpns/HelloWorld.vue
0 → 100644
src/views/data/board/cpns/IndicatorLight.vue
0 → 100644
1 | +<script lang="ts" setup> | |
2 | +const props = defineProps<{ | |
3 | + value?: boolean | |
4 | +}>() | |
5 | + | |
6 | +const emit = defineEmits(['update:value', 'change']) | |
7 | + | |
8 | +const handleChange = (event: Event) => { | |
9 | + const _value = (event.target as HTMLInputElement).checked | |
10 | + emit('update:value', _value) | |
11 | + emit('change', _value) | |
12 | +} | |
13 | +</script> | |
14 | + | |
15 | +<template> | |
16 | + <label class="indicator-light-switch"> | |
17 | + <input :value="props.value" :checked="props.value" type="checkbox" @change="handleChange"> | |
18 | + <div class="panel" /> | |
19 | + </label> | |
20 | +</template> | |
21 | + | |
22 | +<style scoped lang="less"> | |
23 | +.indicator-light-switch { | |
24 | + input[type='checkbox'] { | |
25 | + display: none; | |
26 | + } | |
27 | + .panel { | |
28 | + position: relative; | |
29 | + width: 60px; | |
30 | + height: 60px; | |
31 | + cursor: pointer; | |
32 | + border-radius: 50%; | |
33 | + background: linear-gradient(#dedede, #fdfdfd); | |
34 | + box-shadow: 0 3px 5px rgb(0 0 0 / 25%), inset 0 1px 0 hsl(0deg 0% 100% / 30%), inset 0 -5px 5px hsl(0deg 0% 39% / 10%), inset 0 5px 5px hsl(0deg 0% 100% / 30%); | |
35 | + } | |
36 | + | |
37 | + .panel::after, | |
38 | + .panel::before { | |
39 | + content: ''; | |
40 | + position: absolute; | |
41 | + width: 20%; | |
42 | + height: 20%; | |
43 | + border-radius: 50%; | |
44 | + left: 40%; | |
45 | + top: 40%; | |
46 | + } | |
47 | + | |
48 | + input:checked ~ .panel::after { | |
49 | + display: none; | |
50 | + } | |
51 | + | |
52 | + input:not(:checked) ~ .panel::before { | |
53 | + display: none; | |
54 | + } | |
55 | + | |
56 | + input:not(:checked) ~ .panel { | |
57 | + background: linear-gradient(#EAEAEA, #eaeaea); | |
58 | + } | |
59 | + | |
60 | + .panel::after { | |
61 | + background-color: #ddd; | |
62 | + background: radial-gradient(40% 35%, #ccc, #969696 60%); | |
63 | + box-shadow: inset 0 2px 1px rgb(0 0 0 / 15%), 0 2px 5px hsl(0deg 0% 78% / 10%); | |
64 | + } | |
65 | + | |
66 | + .panel::before { | |
67 | + background-color: #25d025; | |
68 | + box-shadow: inset 0 3px 5px 1px rgb(0 0 0 / 10%), 0 1px 0 hsl(0deg 0% 100% / 40%), 0 0 10px 2px rgb(0 210 0 / 50%); | |
69 | + } | |
70 | +} | |
71 | +</style> | ... | ... |
1 | +<script lang="ts" setup> | |
2 | +import on from '../assets/light-bulb-on.svg' | |
3 | +import off from '../assets/light-bulb-off.svg' | |
4 | + | |
5 | +const props = defineProps<{ | |
6 | + value?: boolean | |
7 | +}>() | |
8 | + | |
9 | +const emit = defineEmits(['update:value', 'change']) | |
10 | + | |
11 | +const handleChange = (event: Event) => { | |
12 | + const _value = (event.target as HTMLInputElement).checked | |
13 | + emit('update:value', _value) | |
14 | + emit('change', _value) | |
15 | +} | |
16 | +</script> | |
17 | + | |
18 | +<template> | |
19 | + <label class="light-bulb-switch"> | |
20 | + <input :value="props.value" :checked="props.value" type="checkbox" @change="handleChange"> | |
21 | + <div class="light-bulb__on"> | |
22 | + <img :src="on" alt="开"> | |
23 | + </div> | |
24 | + <div class="light-bulb__off"> | |
25 | + <img :src="off" alt="关"> | |
26 | + </div> | |
27 | + </label> | |
28 | +</template> | |
29 | + | |
30 | +<style scoped lang="less"> | |
31 | +.light-bulb-switch { | |
32 | + input[type='checkbox'] { | |
33 | + display: none; | |
34 | + } | |
35 | + | |
36 | + input:checked ~ .light-bulb__off { | |
37 | + display: none; | |
38 | + } | |
39 | + | |
40 | + input:not(:checked) ~ .light-bulb__on { | |
41 | + display: none; | |
42 | + } | |
43 | + | |
44 | + .light-bulb__on > img, | |
45 | + .light-bulb__off > img { | |
46 | + width: 48px; | |
47 | + height: 48px; | |
48 | + cursor: pointer; | |
49 | + } | |
50 | +} | |
51 | +</style> | ... | ... |
src/views/data/board/cpns/RockerSwitch.vue
0 → 100644
1 | +<script lang="ts" setup> | |
2 | +const props = defineProps<{ | |
3 | + value?: boolean | |
4 | +}>() | |
5 | + | |
6 | +const emit = defineEmits(['update:value', 'change']) | |
7 | + | |
8 | +const handleChange = (event: Event) => { | |
9 | + const _value = (event.target as HTMLInputElement).checked | |
10 | + emit('update:value', _value) | |
11 | + emit('change', _value) | |
12 | +} | |
13 | +</script> | |
14 | + | |
15 | +<template> | |
16 | + <label class="rocker-switch"> | |
17 | + <input :value="props.value" type="checkbox" :checked="props.value" @change="handleChange"> | |
18 | + <span class="switch-left">ON</span> | |
19 | + <span class="switch-right">OFF</span> | |
20 | + </label> | |
21 | +</template> | |
22 | + | |
23 | +<style scoped lang="less"> | |
24 | +.rocker-switch { | |
25 | + display: flex; | |
26 | + width: 72px; | |
27 | + font-size: 14px; | |
28 | + color: #fff; | |
29 | + font-weight: 700; | |
30 | + letter-spacing: 1px; | |
31 | + border: 0.5em solid #eee; | |
32 | + background-color: #999; | |
33 | + user-select: none; | |
34 | + | |
35 | + input[type='checkbox'] { | |
36 | + display: none; | |
37 | + } | |
38 | + | |
39 | + input:checked + .switch-left + .switch-right { | |
40 | + transform: rotate(-15deg) skew(-15deg) translate(-1px, -5px); | |
41 | + } | |
42 | + input:checked + .switch-left + .switch-right::before { | |
43 | + background-color: #ccc; | |
44 | + content: ''; | |
45 | + position: absolute; | |
46 | + width: 3px; | |
47 | + height: 30px; | |
48 | + transform: skewY(75deg) skewX(4deg) rotate(5deg) translateX(1px); | |
49 | + top: 1.5px; | |
50 | + right: -1.5px; | |
51 | + } | |
52 | + | |
53 | + input + .switch-left { | |
54 | + transform: rotate(15deg) skew(17deg) translate(1px, -5px); | |
55 | + } | |
56 | + | |
57 | + input:checked + .switch-left { | |
58 | + background-color: #0084d0; | |
59 | + transform: none; | |
60 | + } | |
61 | + | |
62 | + input:not(:checked) + .switch-left { | |
63 | + background-color: #ccc; | |
64 | + } | |
65 | + | |
66 | + input:not(:checked) + .switch-left + .switch-right { | |
67 | + background-color: #bd5757; | |
68 | + color: #fff; | |
69 | + } | |
70 | + | |
71 | + input + .switch-left::before { | |
72 | + background-color: #ccc; | |
73 | + content: ''; | |
74 | + position: absolute; | |
75 | + width: 3px; | |
76 | + height: 30px; | |
77 | + background-color: #ccc; | |
78 | + transform: skewY(-75deg) skewX(-4deg) rotate(-5deg) translateX(-1.5px); | |
79 | + top: -1.5px; | |
80 | + left: -1.5px; | |
81 | + } | |
82 | + | |
83 | + .switch-left { | |
84 | + width: 36px; | |
85 | + height: 30px; | |
86 | + text-align: center; | |
87 | + line-height: 30px; | |
88 | + cursor: pointer; | |
89 | + } | |
90 | + | |
91 | + .switch-right { | |
92 | + color: #888; | |
93 | + width: 36px; | |
94 | + height: 30px; | |
95 | + text-align: center; | |
96 | + line-height: 30px; | |
97 | + background-color: #ddd; | |
98 | + cursor: pointer; | |
99 | + } | |
100 | +} | |
101 | +</style> | ... | ... |
src/views/data/board/cpns/SlidingSwitch.vue
0 → 100644
1 | +<script lang="ts" setup> | |
2 | +const props = defineProps<{ | |
3 | + value?: boolean | |
4 | +}>() | |
5 | + | |
6 | +const emit = defineEmits(['update:value', 'change']) | |
7 | + | |
8 | +const handleChange = (event: Event) => { | |
9 | + const _value = (event.target as HTMLInputElement).checked | |
10 | + emit('update:value', _value) | |
11 | + emit('change', _value) | |
12 | +} | |
13 | +</script> | |
14 | + | |
15 | +<template> | |
16 | + <label class="sliding-switch"> | |
17 | + <input :value="props.value" type="checkbox" :checked="props.value" @change="handleChange"> | |
18 | + <span class="slider" /> | |
19 | + <span class="on">ON</span> | |
20 | + <span class="off">OFF</span> | |
21 | + </label> | |
22 | +</template> | |
23 | + | |
24 | +<style scoped lang="less"> | |
25 | +.sliding-switch { | |
26 | + position: relative; | |
27 | + display: block; | |
28 | + font-weight: 700; | |
29 | + line-height: 40px; | |
30 | + width: 80px; | |
31 | + height: 40px; | |
32 | + font-size: 14px; | |
33 | + cursor: pointer; | |
34 | + user-select: none; | |
35 | + | |
36 | + input[type='checkbox'] { | |
37 | + display: none; | |
38 | + } | |
39 | + | |
40 | + .slider { | |
41 | + width: 80px; | |
42 | + height: 40px; | |
43 | + box-sizing: border-box; | |
44 | + position: absolute; | |
45 | + top: 0; | |
46 | + left: 0; | |
47 | + right: 0; | |
48 | + bottom: 0; | |
49 | + border: 2px solid #ecf0f3; | |
50 | + border-radius: 20px; | |
51 | + box-shadow: -2px -2px 8px #fff, -2px -2px 12px hsl(0deg 0% 100% / 50%), inset -2px -2px 8px #fff, inset -2px -2px 12px hsl(0deg 0% 100% / 50%), inset 2px 2px 4px hsl(0deg 0% 100% / 10%), | |
52 | + inset 2px 2px 8px rgb(0 0 0 / 30%), 2px 2px 8px rgb(0 0 0 / 30%); | |
53 | + background-color: #ecf0f3; | |
54 | + z-index: -1; | |
55 | + } | |
56 | + | |
57 | + .slider::after { | |
58 | + position: absolute; | |
59 | + cursor: pointer; | |
60 | + display: block; | |
61 | + content: ''; | |
62 | + width: 24px; | |
63 | + height: 24px; | |
64 | + border-radius: 50%; | |
65 | + top: 6px; | |
66 | + left: 6px; | |
67 | + background-color: #ecf0f3; | |
68 | + box-shadow: -2px -2px 8px #fff, -2px -2px 12px hsl(0deg 0% 100% / 50%), inset 2px 2px 4px hsl(0deg 0% 100% / 10%), 2px 2px 8px rgb(0 0 0 / 30%); | |
69 | + z-index: 999; | |
70 | + transition: 0.5s; | |
71 | + } | |
72 | + | |
73 | + input:checked ~ .off { | |
74 | + opacity: 0; | |
75 | + } | |
76 | + | |
77 | + input:checked ~ .slider::after { | |
78 | + transform: translateX(35px); | |
79 | + } | |
80 | + input:not(:checked) ~ .on { | |
81 | + opacity: 0; | |
82 | + transform: translateX(0px); | |
83 | + } | |
84 | + | |
85 | + .on, | |
86 | + .off { | |
87 | + display: inline-block; | |
88 | + margin-left: 3px; | |
89 | + width: 34px; | |
90 | + text-align: center; | |
91 | + transition: 0.2s; | |
92 | + } | |
93 | + | |
94 | + .on { | |
95 | + color: #039be5; | |
96 | + } | |
97 | + .off { | |
98 | + color: #999; | |
99 | + } | |
100 | +} | |
101 | +</style> | ... | ... |
src/views/data/board/cpns/ToggleSwitch.vue
0 → 100644
1 | +<script lang="ts" setup> | |
2 | +const props = defineProps<{ | |
3 | + value?: boolean | |
4 | +}>() | |
5 | + | |
6 | +const emit = defineEmits(['update:value', 'change']) | |
7 | + | |
8 | +const handleChange = (event: Event) => { | |
9 | + const _value = (event.target as HTMLInputElement).checked | |
10 | + emit('update:value', _value) | |
11 | + emit('change', _value) | |
12 | +} | |
13 | +</script> | |
14 | + | |
15 | +<template> | |
16 | + <div class="toggle-switch"> | |
17 | + <label class="switch"> | |
18 | + <input :value="props.value" type="checkbox" :checked="props.value" @change="handleChange"> | |
19 | + <div class="button"> | |
20 | + <div class="light" /> | |
21 | + <div class="dots" /> | |
22 | + <div class="characters" /> | |
23 | + <div class="shine" /> | |
24 | + <div class="shadow" /> | |
25 | + </div> | |
26 | + </label> | |
27 | + </div> | |
28 | +</template> | |
29 | + | |
30 | +<style scoped> | |
31 | +.toggle-switch { | |
32 | + flex: 1 1 auto; | |
33 | + max-width: 75px; | |
34 | + height: 97.5px; | |
35 | + display: flex; | |
36 | +} | |
37 | + | |
38 | +.switch { | |
39 | + background-color: black; | |
40 | + box-sizing: border-box; | |
41 | + width: 100%; | |
42 | + height: 100%; | |
43 | + box-shadow: 0 0 10px 2px rgba(0, 0, 0, 0.2), 0 0 1px 2px black, inset 0 2px 2px -2px white, inset 0 0 2px 15px #47434c, inset 0 0 2px 22px black; | |
44 | + border-radius: 5px; | |
45 | + padding: 10px; | |
46 | + perspective: 700px; | |
47 | +} | |
48 | + | |
49 | +.switch input { | |
50 | + display: none; | |
51 | +} | |
52 | + | |
53 | +.switch input:checked + .button { | |
54 | + transform: translateZ(20px) rotateX(25deg); | |
55 | + box-shadow: 0 -5px 10px #ff1818; | |
56 | +} | |
57 | + | |
58 | +.switch input:checked + .button .light { | |
59 | + animation: flicker 0.2s infinite 0.3s; | |
60 | +} | |
61 | + | |
62 | +.switch input:checked + .button .shine { | |
63 | + opacity: 1; | |
64 | +} | |
65 | + | |
66 | +.switch input:checked + .button .shadow { | |
67 | + opacity: 0; | |
68 | +} | |
69 | + | |
70 | +.switch .button { | |
71 | + display: flex; | |
72 | + justify-content: center; | |
73 | + align-items: center; | |
74 | + transition: all 0.3s cubic-bezier(1, 0, 1, 1); | |
75 | + transform-origin: center center -20px; | |
76 | + transform: translateZ(20px) rotateX(-25deg); | |
77 | + transform-style: preserve-3d; | |
78 | + background-color: #9b0621; | |
79 | + width: 100%; | |
80 | + height: 100%; | |
81 | + position: relative; | |
82 | + cursor: pointer; | |
83 | + background: linear-gradient(#980000 0%, #6f0000 30%, #6f0000 70%, #980000 100%); | |
84 | + background-repeat: no-repeat; | |
85 | +} | |
86 | + | |
87 | +.switch .button::before { | |
88 | + content: ''; | |
89 | + background: linear-gradient(rgba(255, 255, 255, 0.8) 10%, rgba(255, 255, 255, 0.3) 30%, #650000 75%, #320000) 50% 50%/97% 97%, #b10000; | |
90 | + background-repeat: no-repeat; | |
91 | + width: 100%; | |
92 | + height: 30px; | |
93 | + transform-origin: top; | |
94 | + transform: rotateX(-90deg); | |
95 | + position: absolute; | |
96 | + top: 0; | |
97 | +} | |
98 | + | |
99 | +.switch .button::after { | |
100 | + content: ''; | |
101 | + background-image: linear-gradient(#650000, #320000); | |
102 | + width: 100%; | |
103 | + height: 30px; | |
104 | + transform-origin: top; | |
105 | + transform: translateY(30px) rotateX(-90deg); | |
106 | + position: absolute; | |
107 | + bottom: 0; | |
108 | + box-shadow: 0 30px 8px 0px black, 0 60px 20px 0px rgb(0 0 0 / 50%); | |
109 | +} | |
110 | + | |
111 | +.switch .light { | |
112 | + opacity: 0; | |
113 | + animation: light-off 1s; | |
114 | + position: absolute; | |
115 | + width: 80%; | |
116 | + height: 80%; | |
117 | + background-image: radial-gradient(#ffc97e, transparent 40%), radial-gradient(circle, #ff1818 50%, transparent 80%); | |
118 | +} | |
119 | + | |
120 | +.switch .dots { | |
121 | + position: absolute; | |
122 | + width: 100%; | |
123 | + height: 100%; | |
124 | + background-image: radial-gradient(transparent 30%, rgba(101, 0, 0, 0.7) 70%); | |
125 | + background-size: 10px 10px; | |
126 | +} | |
127 | + | |
128 | +.switch .characters { | |
129 | + position: absolute; | |
130 | + width: 100%; | |
131 | + height: 100%; | |
132 | + background: linear-gradient(white, white) 50% 20%/5% 20%, radial-gradient(circle, transparent 50%, white 52%, white 70%, transparent 72%) 50% 80%/33% 25%; | |
133 | + background-repeat: no-repeat; | |
134 | +} | |
135 | + | |
136 | +.switch .shine { | |
137 | + transition: all 0.3s cubic-bezier(1, 0, 1, 1); | |
138 | + opacity: 0.3; | |
139 | + position: absolute; | |
140 | + width: 100%; | |
141 | + height: 100%; | |
142 | + background: linear-gradient(white, transparent 3%) 50% 50%/97% 97%, linear-gradient(rgba(255, 255, 255, 0.5), transparent 50%, transparent 80%, rgba(255, 255, 255, 0.5)) 50% 50%/97% 97%; | |
143 | + background-repeat: no-repeat; | |
144 | +} | |
145 | + | |
146 | +.switch .shadow { | |
147 | + transition: all 0.3s cubic-bezier(1, 0, 1, 1); | |
148 | + opacity: 1; | |
149 | + position: absolute; | |
150 | + width: 100%; | |
151 | + height: 100%; | |
152 | + background: linear-gradient(transparent 70%, rgba(0, 0, 0, 0.8)); | |
153 | + background-repeat: no-repeat; | |
154 | +} | |
155 | + | |
156 | +@keyframes flicker { | |
157 | + 0% { | |
158 | + opacity: 1; | |
159 | + } | |
160 | + | |
161 | + 80% { | |
162 | + opacity: 0.8; | |
163 | + } | |
164 | + | |
165 | + 100% { | |
166 | + opacity: 1; | |
167 | + } | |
168 | +} | |
169 | + | |
170 | +@keyframes light-off { | |
171 | + 0% { | |
172 | + opacity: 1; | |
173 | + } | |
174 | + | |
175 | + 80% { | |
176 | + opacity: 0; | |
177 | + } | |
178 | +} | |
179 | +</style> | ... | ... |
1 | +<script lang="ts" setup> | |
2 | + import { MoreOutlined, LineChartOutlined } from '@ant-design/icons-vue'; | |
3 | + import { DropMenu } from '/@/components/Dropdown'; | |
4 | + import Dropdown from '/@/components/Dropdown/src/Dropdown.vue'; | |
5 | + import { Tooltip } from 'ant-design-vue'; | |
6 | + import Icon from '/@/components/Icon/src/Icon.vue'; | |
7 | + enum MoreEvent { | |
8 | + EDIT = 'edit', | |
9 | + COPY = 'copy', | |
10 | + DELETE = 'delete', | |
11 | + } | |
12 | + | |
13 | + const dropMenuList: DropMenu[] = [ | |
14 | + { | |
15 | + text: '编辑组件', | |
16 | + event: MoreEvent.EDIT, | |
17 | + icon: 'ant-design:edit-outlined', | |
18 | + }, | |
19 | + { | |
20 | + text: '复制组件', | |
21 | + event: MoreEvent.COPY, | |
22 | + icon: 'ant-design:copy-outlined', | |
23 | + }, | |
24 | + { | |
25 | + text: '删除组件', | |
26 | + event: MoreEvent.DELETE, | |
27 | + icon: 'ant-design:delete-outlined', | |
28 | + }, | |
29 | + ]; | |
30 | + | |
31 | + const handleMenuEvent = (event: DropMenu) => { | |
32 | + console.log(event); | |
33 | + }; | |
34 | +</script> | |
35 | + | |
36 | +<template> | |
37 | + <div class="flex justify-between"> | |
38 | + <div class="flex flex-auto"> | |
39 | + <div v-for="item in 3" class="flex mx-2" :key="item"> | |
40 | + <div class="flex items-center"> | |
41 | + <Tooltip> | |
42 | + <Icon icon="ant-design:edit-outlined" /> | |
43 | + </Tooltip> | |
44 | + <span class="truncate max-w-25">设备名称{{ item }}</span> | |
45 | + </div> | |
46 | + </div> | |
47 | + </div> | |
48 | + <div class="flex items-center wx-9"> | |
49 | + <Tooltip title="趋势"> | |
50 | + <LineChartOutlined class="cursor-pointer mx-2" /> | |
51 | + </Tooltip> | |
52 | + <Dropdown :drop-menu-list="dropMenuList" :trigger="['click']" @menu-event="handleMenuEvent"> | |
53 | + <Tooltip title="更多"> | |
54 | + <MoreOutlined class="transform rotate-90 cursor-pointer" /> | |
55 | + </Tooltip> | |
56 | + </Dropdown> | |
57 | + </div> | |
58 | + </div> | |
59 | +</template> | ... | ... |
1 | +<script lang="ts" setup> | |
2 | + import { onMounted } from 'vue'; | |
3 | + import { useUpdateCenter } from '../../hook/useUpdateCenter'; | |
4 | + import type { DataSource, WidgetWrapperRegister } from './type'; | |
5 | + | |
6 | + const props = defineProps<{ | |
7 | + dataSource: DataSource[]; | |
8 | + register?: WidgetWrapperRegister; | |
9 | + }>(); | |
10 | + | |
11 | + const { update, add, remove } = useUpdateCenter(); | |
12 | + | |
13 | + onMounted(() => { | |
14 | + props.register && props.register(props.dataSource); | |
15 | + }); | |
16 | + | |
17 | + defineExpose({ update }); | |
18 | +</script> | |
19 | + | |
20 | +<template> | |
21 | + <section class="widget"> | |
22 | + <slot name="header"></slot> | |
23 | + | |
24 | + <div class="widget-content"> | |
25 | + <div | |
26 | + v-for="item in props.dataSource" | |
27 | + :key="item.id" | |
28 | + :style="{ width: `${item.width}%`, height: `${item.height}%` }" | |
29 | + class="widget-item" | |
30 | + > | |
31 | + <div class="widget-box"> | |
32 | + <div class="widget-controls-container"> | |
33 | + <slot | |
34 | + name="controls" | |
35 | + :record="item" | |
36 | + :add="add" | |
37 | + :remove="remove" | |
38 | + :update="update" | |
39 | + ></slot> | |
40 | + </div> | |
41 | + <div class="widget-value"> | |
42 | + <slot name="value" :record="item"></slot> | |
43 | + </div> | |
44 | + <div class="widget-label"> | |
45 | + <slot name="label" :record="item"></slot> | |
46 | + </div> | |
47 | + </div> | |
48 | + </div> | |
49 | + </div> | |
50 | + <slot name="footer"></slot> | |
51 | + </section> | |
52 | +</template> | |
53 | + | |
54 | +<style scoped> | |
55 | + .widget { | |
56 | + display: flex; | |
57 | + flex-direction: column; | |
58 | + width: 100%; | |
59 | + height: 100%; | |
60 | + } | |
61 | + .widget-content { | |
62 | + display: flex; | |
63 | + flex-wrap: wrap; | |
64 | + justify-content: center; | |
65 | + align-items: center; | |
66 | + | |
67 | + width: 100%; | |
68 | + height: 100%; | |
69 | + } | |
70 | + | |
71 | + .widget-box .widget-charts { | |
72 | + display: flex; | |
73 | + } | |
74 | + | |
75 | + .widget-item { | |
76 | + display: flex; | |
77 | + flex-direction: column; | |
78 | + overflow: hidden; | |
79 | + justify-content: center; | |
80 | + align-items: center; | |
81 | + } | |
82 | + | |
83 | + .widget-box { | |
84 | + position: relative; | |
85 | + width: 100%; | |
86 | + height: 100%; | |
87 | + } | |
88 | + | |
89 | + .widget-controls-container { | |
90 | + flex: 1 1 auto; | |
91 | + width: 100%; | |
92 | + height: calc(100% - 20px); | |
93 | + display: flex; | |
94 | + align-items: center; | |
95 | + justify-content: center; | |
96 | + } | |
97 | + | |
98 | + .widget-controls-container > div { | |
99 | + width: 100%; | |
100 | + height: 100%; | |
101 | + background-color: red; | |
102 | + } | |
103 | + | |
104 | + .widget-value { | |
105 | + font-size: 14px; | |
106 | + position: absolute; | |
107 | + width: 100%; | |
108 | + top: 0; | |
109 | + text-align: center; | |
110 | + overflow: hidden; | |
111 | + text-overflow: ellipsis; | |
112 | + } | |
113 | + | |
114 | + .widget-label { | |
115 | + font-size: 14px; | |
116 | + line-height: 1.2; | |
117 | + text-align: center; | |
118 | + overflow: hidden; | |
119 | + text-overflow: ellipsis; | |
120 | + } | |
121 | +</style> | ... | ... |
1 | 1 | <script lang="ts" setup> |
2 | - import { PageWrapper } from '/@/components/Page'; | |
3 | - import { Button } from 'ant-design-vue'; | |
2 | + import { Button, PageHeader } from 'ant-design-vue'; | |
3 | + import { GridItem, GridLayout, Layout } from 'vue3-grid-layout'; | |
4 | + import { nextTick, ref } from 'vue'; | |
5 | + import BaseDashboard from '../cpns/Dashboard/BaseDashboard.vue'; | |
6 | + import WidgetWrapper from '../cpns/WidgetWrapper/WidgetWrapper.vue'; | |
7 | + import BaseWidgetHeader from '../cpns/WidgetHeader/BaseWidgetHeader.vue'; | |
4 | 8 | const handleBack = () => {}; |
9 | + | |
10 | + interface ChartAttr { | |
11 | + id: string | number; | |
12 | + width: number; | |
13 | + height: number; | |
14 | + } | |
15 | + | |
16 | + interface ChartSetting extends Layout { | |
17 | + chart: ChartAttr[]; | |
18 | + } | |
19 | + | |
20 | + const widgetEl = new Map<string, Fn>(); | |
21 | + | |
22 | + const id = '296Charts'; | |
23 | + // GridItem. | |
24 | + const layout = ref<ChartSetting[]>([ | |
25 | + { | |
26 | + x: 0, | |
27 | + y: 0, | |
28 | + w: 6, | |
29 | + h: 6, | |
30 | + i: id, | |
31 | + static: false, | |
32 | + chart: [ | |
33 | + { id: 'a', width: 100, height: 100 }, | |
34 | + { id: 'b', width: 100, height: 100 }, | |
35 | + { id: 'c', width: 100, height: 100 }, | |
36 | + { id: 'd', width: 100, height: 100 }, | |
37 | + { id: 'e', width: 100, height: 100 }, | |
38 | + { id: 'f', width: 100, height: 100 }, | |
39 | + { id: 'g', width: 100, height: 100 }, | |
40 | + { id: 'h', width: 100, height: 100 }, | |
41 | + { id: 'i', width: 100, height: 100 }, | |
42 | + ], | |
43 | + }, | |
44 | + { | |
45 | + x: 0, | |
46 | + y: 0, | |
47 | + w: 6, | |
48 | + h: 6, | |
49 | + i: 'sdasdf', | |
50 | + static: false, | |
51 | + chart: [ | |
52 | + { id: 'j', width: 100, height: 100 }, | |
53 | + { id: 'k', width: 100, height: 100 }, | |
54 | + { id: 'l', width: 100, height: 100 }, | |
55 | + { id: 'm', width: 100, height: 100 }, | |
56 | + { id: 'n', width: 100, height: 100 }, | |
57 | + { id: 'o', width: 100, height: 100 }, | |
58 | + { id: 'p', width: 100, height: 100 }, | |
59 | + { id: 'q', width: 100, height: 100 }, | |
60 | + { id: 'r', width: 100, height: 100 }, | |
61 | + ], | |
62 | + }, | |
63 | + ]); | |
64 | + const draggable = ref(true); | |
65 | + const resizable = ref(true); | |
66 | + | |
67 | + const GirdLayoutColNum = 24; | |
68 | + | |
69 | + const GridLayoutMargin = 10; | |
70 | + | |
71 | + function updateSize(i: string, newH: number, newW: number, newHPx: number, newWPx: number) { | |
72 | + newWPx = Number(newWPx); | |
73 | + newHPx = Number(newHPx); | |
74 | + | |
75 | + const data = layout.value.find((item) => item.i === i)!; | |
76 | + const border = 2; | |
77 | + const length = data.chart.length || 0; | |
78 | + | |
79 | + const row = Math.floor(Math.pow(length, 0.5)); | |
80 | + const col = Math.floor(length / row); | |
81 | + let width = Math.floor(100 / col); | |
82 | + let height = Math.floor(100 / row); | |
83 | + | |
84 | + const WHRatio = newWPx / newHPx; | |
85 | + const HWRatio = newHPx / newWPx; | |
86 | + | |
87 | + if (WHRatio > 1.6) { | |
88 | + width = Math.floor(100 / length); | |
89 | + height = 100; | |
90 | + } | |
91 | + | |
92 | + if (HWRatio > 1.6) { | |
93 | + height = Math.floor(100 / length); | |
94 | + width = 100; | |
95 | + } | |
96 | + | |
97 | + data.chart = data?.chart.map((item) => { | |
98 | + return { | |
99 | + ...item, | |
100 | + width, | |
101 | + height, | |
102 | + }; | |
103 | + }); | |
104 | + nextTick(() => { | |
105 | + const updateFn = widgetEl.get(i); | |
106 | + if (updateFn) updateFn(); | |
107 | + }); | |
108 | + } | |
109 | + | |
110 | + const itemResized = (i: string, newH: number, newW: number, newHPx: number, newWPx: number) => { | |
111 | + updateSize(i, newH, newW, newHPx, newWPx); | |
112 | + }; | |
113 | + | |
114 | + const itemContainerResized = ( | |
115 | + i: string, | |
116 | + newH: number, | |
117 | + newW: number, | |
118 | + newHPx: number, | |
119 | + newWPx: number | |
120 | + ) => { | |
121 | + updateSize(i, newH, newW, newHPx, newWPx); | |
122 | + }; | |
123 | + | |
124 | + const updateCharts = (i: string) => { | |
125 | + nextTick(() => { | |
126 | + const updateFn = widgetEl.get(i); | |
127 | + if (updateFn) updateFn(); | |
128 | + }); | |
129 | + }; | |
130 | + | |
131 | + const setComponentRef = (el: Element, record: ChartSetting) => { | |
132 | + if (widgetEl.has(record.i)) widgetEl.delete(record.i); | |
133 | + if (el && (el as unknown as { update: Fn }).update) | |
134 | + widgetEl.set(record.i, (el as unknown as { update: Fn }).update); | |
135 | + }; | |
5 | 136 | </script> |
6 | 137 | |
7 | 138 | <template> |
8 | - <PageWrapper title="水电表看板" @back="handleBack" content="已创建组件: 3个"> | |
139 | + <!-- <PageWrapper title="水电表看板" @back="handleBack" content="已创建组件: 3个"> | |
9 | 140 | <template #extra> |
10 | 141 | <Button type="primary">创建组件</Button> |
11 | 142 | </template> |
12 | - <section class="bg-light-50 h-full w-full"> </section> | |
13 | - </PageWrapper> | |
143 | + | |
144 | + </PageWrapper> --> | |
145 | + <section class="bg-light-50 flex flex-col overflow-hidden h-full w-full"> | |
146 | + <PageHeader title="水电表看板" @back="handleBack"> | |
147 | + <template #extra> | |
148 | + <Button type="primary">创建组件</Button> | |
149 | + </template> | |
150 | + <div>已创建组件: 3个</div> | |
151 | + </PageHeader> | |
152 | + <section class="flex-1"> | |
153 | + <GridLayout | |
154 | + v-model:layout="layout" | |
155 | + :col-num="GirdLayoutColNum" | |
156 | + :row-height="30" | |
157 | + :margin="[GridLayoutMargin, GridLayoutMargin]" | |
158 | + :is-draggable="draggable" | |
159 | + :is-resizable="resizable" | |
160 | + :vertical-compact="true" | |
161 | + :use-css-transforms="true" | |
162 | + style="width: 100%" | |
163 | + > | |
164 | + <GridItem | |
165 | + v-for="item in layout" | |
166 | + :key="item.i" | |
167 | + :static="item.static" | |
168 | + :x="item.x" | |
169 | + :y="item.y" | |
170 | + :w="item.w" | |
171 | + :h="item.h" | |
172 | + :i="item.i" | |
173 | + style="display: flex; flex-wrap: wrap" | |
174 | + class="grid-item-layout" | |
175 | + @resized="itemResized" | |
176 | + @resize="itemResized" | |
177 | + @moved="updateCharts" | |
178 | + @container-resized="itemContainerResized" | |
179 | + > | |
180 | + <WidgetWrapper | |
181 | + :key="item.i" | |
182 | + :ref="(el) => setComponentRef(el, item)" | |
183 | + :data-source="item.chart" | |
184 | + > | |
185 | + <template #header> | |
186 | + <!-- <div>header</div> --> | |
187 | + <BaseWidgetHeader /> | |
188 | + </template> | |
189 | + <template #controls="{ record, add }"> | |
190 | + <!-- <ToggleSwitch /> --> | |
191 | + <!-- <SlidingSwitch @change="handleChange" /> --> | |
192 | + <!-- <LightBulbSwitch @change="handleChange" /> --> | |
193 | + <!-- <div :id="getControlsWidgetId(record.id)" class="widget-charts" /> --> | |
194 | + <BaseDashboard :data-source="record" :add="add" /> | |
195 | + </template> | |
196 | + <template #value="{ record }"> | |
197 | + <span>{{ record.id }}</span> | |
198 | + </template> | |
199 | + <template #label="{ record }"> | |
200 | + <span>{{ record.id }}</span> | |
201 | + </template> | |
202 | + </WidgetWrapper> | |
203 | + </GridItem> | |
204 | + </GridLayout> | |
205 | + </section> | |
206 | + </section> | |
14 | 207 | </template> |
208 | + | |
209 | +<style> | |
210 | + .vue-grid-item:not(.vue-grid-placeholder) { | |
211 | + background: #fcfcfc; | |
212 | + border: 1px solid black; | |
213 | + } | |
214 | + .vue-grid-item .resizing { | |
215 | + opacity: 0.9; | |
216 | + } | |
217 | + .vue-grid-item .static { | |
218 | + background: #cce; | |
219 | + } | |
220 | + .vue-grid-item .text { | |
221 | + font-size: 24px; | |
222 | + text-align: center; | |
223 | + position: absolute; | |
224 | + top: 0; | |
225 | + bottom: 0; | |
226 | + left: 0; | |
227 | + right: 0; | |
228 | + margin: auto; | |
229 | + height: 100%; | |
230 | + width: 100%; | |
231 | + } | |
232 | + .vue-grid-item .no-drag { | |
233 | + height: 100%; | |
234 | + width: 100%; | |
235 | + } | |
236 | + .vue-grid-item .minMax { | |
237 | + font-size: 12px; | |
238 | + } | |
239 | + .vue-grid-item .add { | |
240 | + cursor: pointer; | |
241 | + } | |
242 | + .vue-draggable-handle { | |
243 | + position: absolute; | |
244 | + width: 20px; | |
245 | + height: 20px; | |
246 | + top: 0; | |
247 | + left: 0; | |
248 | + background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10'><circle cx='5' cy='5' r='5' fill='#999999'/></svg>") | |
249 | + no-repeat; | |
250 | + background-position: bottom right; | |
251 | + padding: 0 8px 8px 0; | |
252 | + background-repeat: no-repeat; | |
253 | + background-origin: content-box; | |
254 | + box-sizing: border-box; | |
255 | + cursor: pointer; | |
256 | + } | |
257 | + | |
258 | + .container { | |
259 | + display: grid; | |
260 | + grid-template-columns: 3; | |
261 | + grid-row: 3; | |
262 | + } | |
263 | + | |
264 | + .grid-item-layout { | |
265 | + overflow: hidden; | |
266 | + border: 1px solid #eee !important; | |
267 | + background-color: #fcfcfc !important; | |
268 | + } | |
269 | +</style> | ... | ... |
src/views/data/board/hook/useUpdateCenter.ts
0 → 100644
1 | +export function useUpdateCenter() { | |
2 | + const eventCenter = new Map<string, Fn>(); | |
3 | + | |
4 | + const update = () => { | |
5 | + eventCenter.forEach((method) => { | |
6 | + method(); | |
7 | + }); | |
8 | + }; | |
9 | + | |
10 | + const add = (key: string, method: Fn) => { | |
11 | + if (eventCenter.has(key)) { | |
12 | + window.console.log(`Update Center Has Exist This Update Method(${key})`); | |
13 | + return; | |
14 | + } | |
15 | + eventCenter.set(key, method); | |
16 | + }; | |
17 | + | |
18 | + const remove = (key: string) => { | |
19 | + if (eventCenter.has(key)) eventCenter.delete(key); | |
20 | + }; | |
21 | + | |
22 | + return { update, add, remove }; | |
23 | +} | ... | ... |
1 | 1 | <script lang="ts" setup> |
2 | - import { List, Card, Statistic, Dropdown, Menu } from 'ant-design-vue'; | |
2 | + import { List, Card, Statistic } from 'ant-design-vue'; | |
3 | 3 | import { ref, unref } from 'vue'; |
4 | 4 | import { PageWrapper } from '/@/components/Page'; |
5 | 5 | import { |
... | ... | @@ -10,9 +10,9 @@ |
10 | 10 | } from '@ant-design/icons-vue'; |
11 | 11 | import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard'; |
12 | 12 | import { useMessage } from '/@/hooks/web/useMessage'; |
13 | - import { useRoute, useRouter } from 'vue-router'; | |
14 | - const ListItem = List.Item; | |
15 | - const MenuItem = Menu.Item; | |
13 | + import { useRouter } from 'vue-router'; | |
14 | + import Dropdown from '/@/components/Dropdown/src/Dropdown.vue'; | |
15 | + import { DropMenu } from '/@/components/Dropdown'; | |
16 | 16 | |
17 | 17 | const router = useRouter(); |
18 | 18 | const { createMessage } = useMessage(); |
... | ... | @@ -46,6 +46,25 @@ |
46 | 46 | clipboardRef.value = '123'; |
47 | 47 | unref(clipboardRef) && createMessage.success('复制成功'); |
48 | 48 | }; |
49 | + enum MoreEvent { | |
50 | + EDIT = 'edit', | |
51 | + DELETE = 'DELETE', | |
52 | + } | |
53 | + | |
54 | + const dropMenuList: DropMenu[] = [ | |
55 | + { | |
56 | + text: '编辑', | |
57 | + event: MoreEvent.EDIT, | |
58 | + icon: 'ant-design:edit-outlined', | |
59 | + }, | |
60 | + { | |
61 | + text: '删除', | |
62 | + event: MoreEvent.DELETE, | |
63 | + icon: 'ant-design:delete-outlined', | |
64 | + }, | |
65 | + ]; | |
66 | + | |
67 | + const handleMenuEvent = (evetn: DropMenu) => {}; | |
49 | 68 | |
50 | 69 | const handleEdit = () => { |
51 | 70 | router.push('/data/board/detail'); |
... | ... | @@ -66,19 +85,11 @@ |
66 | 85 | <ListItem> |
67 | 86 | <Card class="data-card cursor-pointer"> |
68 | 87 | <template #extra> |
69 | - <Dropdown> | |
70 | - <template #overlay> | |
71 | - <Menu> | |
72 | - <MenuItem key="edit" @click="handleEdit"> | |
73 | - <EditOutlined /> | |
74 | - <span>编辑</span> | |
75 | - </MenuItem> | |
76 | - <MenuItem key="remove" @click="handleRemove"> | |
77 | - <DeleteOutlined /> | |
78 | - <span>删除</span> | |
79 | - </MenuItem> | |
80 | - </Menu> | |
81 | - </template> | |
88 | + <Dropdown | |
89 | + :trigger="['click']" | |
90 | + @menu-event="handleMenuEvent" | |
91 | + :drop-menu-list="dropMenuList" | |
92 | + > | |
82 | 93 | <MoreOutlined class="rotate-90 transform cursor-pointer" /> |
83 | 94 | </Dropdown> |
84 | 95 | </template> | ... | ... |
types/grid-layout.d.ts
0 → 100644
1 | +/** | |
2 | + * @descriptin Api docs https://jbaysolutions.github.io/vue-grid-layout/zh/guide/properties.html#griditem | |
3 | + */ | |
4 | +declare module 'vue3-grid-layout' { | |
5 | + import type { CSSProperties } from 'vue'; | |
6 | + | |
7 | + interface Layout { | |
8 | + i: string; | |
9 | + x: number; | |
10 | + y: number; | |
11 | + w: number; | |
12 | + h: number; | |
13 | + static?: boolean; | |
14 | + } | |
15 | + | |
16 | + interface BreakPoints { | |
17 | + lg?: number; | |
18 | + md?: number; | |
19 | + sm?: number; | |
20 | + xs?: number; | |
21 | + xxs?: number; | |
22 | + } | |
23 | + | |
24 | + interface GridLayoutProps { | |
25 | + layout: Layout[]; | |
26 | + | |
27 | + responsiveLayouts?: any; | |
28 | + | |
29 | + /** | |
30 | + * @description Define Col Number, Need A Integer Number | |
31 | + * @type number | |
32 | + * @default 12 | |
33 | + */ | |
34 | + colNum?: number; | |
35 | + | |
36 | + /** | |
37 | + * @description Define Row Height | |
38 | + * @type number | |
39 | + * @default 150 | |
40 | + */ | |
41 | + rowHeight?: number; | |
42 | + | |
43 | + /** | |
44 | + * @description Define Max Rows | |
45 | + * @type number | |
46 | + * @default infinity | |
47 | + */ | |
48 | + maxRows?: number; | |
49 | + | |
50 | + /** | |
51 | + * @description Define Marign Distance In Grid Layout | |
52 | + * @type [number, number] | |
53 | + * @default [10, 10] | |
54 | + */ | |
55 | + margin?: [number, number]; | |
56 | + | |
57 | + /** | |
58 | + * @description Define Element Can Draggable In Grid Layout | |
59 | + * @type boolean | |
60 | + * @default true | |
61 | + */ | |
62 | + isDraggable?: boolean; | |
63 | + | |
64 | + /** | |
65 | + * @description Define Element Can Resizeable In Grid Layout | |
66 | + * @type boolean | |
67 | + * @default true | |
68 | + */ | |
69 | + isResizable?: boolean; | |
70 | + | |
71 | + /** | |
72 | + * @description Define Element Can Mirrored In Grid Layout | |
73 | + * @type boolean | |
74 | + * @default false | |
75 | + */ | |
76 | + isMirrored?: boolean; | |
77 | + | |
78 | + /** | |
79 | + * @descripion Define Element Can Auto Size In Grid Layout | |
80 | + * @type boolean | |
81 | + * @default true | |
82 | + */ | |
83 | + autoSize?: boolean; | |
84 | + | |
85 | + /** | |
86 | + * @description Define Element Can Compact On Vertical Direction | |
87 | + * @type boolean | |
88 | + * @default false | |
89 | + */ | |
90 | + verticalCompact?: boolean; | |
91 | + | |
92 | + /** | |
93 | + * @description Define Element Prevent Collision | |
94 | + * @type {boolean} | |
95 | + * @default false | |
96 | + */ | |
97 | + preventCollision?: boolean; | |
98 | + | |
99 | + /** | |
100 | + * @description Define Element Use Css Transforms | |
101 | + * @type {boolean} | |
102 | + * @default true | |
103 | + */ | |
104 | + useCssTransforms?: boolean; | |
105 | + | |
106 | + /** | |
107 | + * @description Define Whether Is Responsive Layout | |
108 | + * @type {boolean} | |
109 | + * @default false | |
110 | + */ | |
111 | + responsive?: boolean; | |
112 | + | |
113 | + /** | |
114 | + * @description Setting Screen Break Points | |
115 | + * @type {BreakPoints} | |
116 | + * @default lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 | |
117 | + */ | |
118 | + breakPoints?: BreakPoints; | |
119 | + | |
120 | + /** | |
121 | + * @description Setting Column Break Points | |
122 | + * @type {BreakPoints} | |
123 | + * @default lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 | |
124 | + */ | |
125 | + cols?: BreakPoints; | |
126 | + | |
127 | + /** | |
128 | + * @descritpion Use Dynamic Cursor Style | |
129 | + * @type {boolean} | |
130 | + * @default false | |
131 | + * @deprecated | |
132 | + */ | |
133 | + useStyleCursor?: boolean; | |
134 | + | |
135 | + [key: string]: any; | |
136 | + } | |
137 | + | |
138 | + interface GridItemProps extends Layout { | |
139 | + /** | |
140 | + * @description Min Width | |
141 | + * @type {number} | |
142 | + * @default 1 | |
143 | + */ | |
144 | + minW?: number; | |
145 | + | |
146 | + /** | |
147 | + * @description Min Height | |
148 | + * @type {number} | |
149 | + * @default 1 | |
150 | + */ | |
151 | + minH?: number; | |
152 | + | |
153 | + /** | |
154 | + * @description Max Width | |
155 | + * @type {number} | |
156 | + * @default 1 | |
157 | + */ | |
158 | + maxW?: number; | |
159 | + | |
160 | + /** | |
161 | + * @description Min Height | |
162 | + * @type {number} | |
163 | + * @default 1 | |
164 | + */ | |
165 | + maxH?: number; | |
166 | + | |
167 | + /** | |
168 | + * @description Flag Grid Element Can Draggable | |
169 | + * @type {boolean | null} | |
170 | + * @default null | |
171 | + */ | |
172 | + isDraggable?: boolean; | |
173 | + | |
174 | + /** | |
175 | + * @descrition Flag Grid Element Can Resizeable | |
176 | + * @type {boolean | null} | |
177 | + * @default null | |
178 | + */ | |
179 | + isResizeable?: boolean; | |
180 | + | |
181 | + /** | |
182 | + * @description Flag Grid Element Is Static , Can't Draggable Resizeable Move | |
183 | + * @type {boolean} | |
184 | + * @default false | |
185 | + */ | |
186 | + static?: boolean; | |
187 | + | |
188 | + /** | |
189 | + * @description Flag What Are The Element Can't Draggable, Usage Css Picker | |
190 | + * @type {string} | |
191 | + * @default 'a button' | |
192 | + */ | |
193 | + dragIgnoreFrom?: string; | |
194 | + | |
195 | + /** | |
196 | + * @description Flag What Are The Element Allow Draggable, Usage Css Picker | |
197 | + * @type {string} | |
198 | + * @default null | |
199 | + */ | |
200 | + dragAllowFrom?: string; | |
201 | + | |
202 | + /** | |
203 | + * @description Flag What Are The Element Can't Trigger Resize, Usage Css Picker | |
204 | + * @type {string} | |
205 | + * @deafult 'a button' | |
206 | + */ | |
207 | + resizeIgnoreFrom?: string; | |
208 | + | |
209 | + style?: CSSProperties; | |
210 | + | |
211 | + onLayoutCreate?: (newLayout: Layout[]) => void; | |
212 | + | |
213 | + [key: string]: any; | |
214 | + } | |
215 | + type GirdLayoutEvent = | |
216 | + | 'layoutCreate' | |
217 | + | 'layoutBeforeMount' | |
218 | + | 'layoutMounted' | |
219 | + | 'layoutReady' | |
220 | + | 'layoutUpdate' | |
221 | + | 'breakpointChanged'; | |
222 | + | |
223 | + interface LayoutBaseParams { | |
224 | + newLayout: Layout[]; | |
225 | + } | |
226 | + | |
227 | + interface GridLayoutEmit { | |
228 | + layoutCreate: LayoutBaseParams; | |
229 | + layoutBeforeMount: LayoutBeforeMountParams; | |
230 | + layoutMounted: LayoutMountedParams; | |
231 | + layoutReady: layoutUpdateParams; | |
232 | + layoutUpdate: layoutUpdateParams; | |
233 | + breakpointChanged: breakpointChangedParams; | |
234 | + } | |
235 | + | |
236 | + /** | |
237 | + * @description Grid Item Event Name | |
238 | + */ | |
239 | + type GridItemEvent = 'move' | 'moved' | 'resize' | 'resized' | 'containerResized'; | |
240 | + | |
241 | + interface MoveParams { | |
242 | + i: number; | |
243 | + newX?: number; | |
244 | + newY?: number; | |
245 | + } | |
246 | + | |
247 | + interface ResizeParams { | |
248 | + i: number; | |
249 | + newH: number; | |
250 | + newW: number; | |
251 | + newHPx: number; | |
252 | + newWPx: number; | |
253 | + } | |
254 | + | |
255 | + interface GridItemEmit { | |
256 | + move: MoveParams; | |
257 | + moved: MoveParams; | |
258 | + resize: ResizeParams; | |
259 | + resized: ResizeParams; | |
260 | + containerResized: MoveParams; | |
261 | + } | |
262 | + | |
263 | + // const GridLayout: DefineComponent<GridLayoutProps, void, unknown> | |
264 | + // const GridItem: DefineComponent<GridItemProps, void, unknown> | |
265 | + | |
266 | + const GridLayout: { | |
267 | + new (): { | |
268 | + $props: GridLayoutProps; | |
269 | + $emit: <T extends GirdLayoutEvent>(event: T, ...args: GridLayoutEmit[T]) => void; | |
270 | + }; | |
271 | + }; | |
272 | + | |
273 | + const GridItem: { | |
274 | + new (): { | |
275 | + $props: GridItemProps; | |
276 | + $emit: <T extends GridItemEvent>(event: T, ...args: GridItemEmit[T]) => void; | |
277 | + }; | |
278 | + }; | |
279 | + | |
280 | + export { GridLayout, GridItem, BreakPoints, Layout, LayoutBaseParams, ResizeParams }; | |
281 | +} | ... | ... |