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 | +} | ... | ... |