Commit 0637688d97f60cea17db4f04d382bf1bbdd59227

Authored by ww
1 parent d15f8544

wip: visual options component

... ... @@ -15,6 +15,7 @@ VITE_PUBLIC_PATH = /
15 15 # 线上测试环境
16 16 # VITE_PROXY = [["/api","http://localhost:8080/api"],["/thingskit-drawio","http://localhost:3000/"]]
17 17 VITE_PROXY = [["/api","https://dev.thingskit.com/api"],["/thingskit-drawio","http://localhost:3000/"]]
  18 +# VITE_PROXY = [["/api","http://192.168.10.106:8080/api"],["/thingskit-drawio","http://192.168.10.106:8080/api"]]
18 19
19 20 # 实时数据的ws地址
20 21 # VITE_WEB_SOCKET = ws://localhost:8080/api/ws/plugins/telemetry?token=
... ...
  1 +<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 16 16"><path fill="currentColor" d="M3.5 2A1.5 1.5 0 0 1 5 3.5V5H3.5a1.5 1.5 0 1 1 0-3zM6 5V3.5A2.5 2.5 0 1 0 3.5 6H5v4H3.5A2.5 2.5 0 1 0 6 12.5V11h4v1.5a2.5 2.5 0 1 0 2.5-2.5H11V6h1.5A2.5 2.5 0 1 0 10 3.5V5H6zm4 1v4H6V6h4zm1-1V3.5A1.5 1.5 0 1 1 12.5 5H11zm0 6h1.5a1.5 1.5 0 1 1-1.5 1.5V11zm-6 0v1.5A1.5 1.5 0 1 1 3.5 11H5z"/></svg>
... ...
  1 +<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 64 64"><path fill="currentColor" d="M32 2C20.973 2 12 9.889 12 19.586c0 5.13 3.368 10.412 6.621 15.516c2.803 4.393 5.48 8.586 6.316 12.898h14.125c.838-4.313 3.512-8.506 6.314-12.898C48.631 29.998 52 24.716 52 19.586C52 9.889 43.027 2 32 2m0 27.674c1.932 2.085 3.752 2.705 4.584 2.889c-.125.555-.238 1.097-.369 1.631c-.246 1.092-.486 2.156-.719 3.178a723.122 723.122 0 0 0-1.895 8.629h-3.203a787.762 787.762 0 0 0-1.895-8.629l-.721-3.178c-.131-.534-.244-1.076-.367-1.631c.833-.184 2.653-.804 4.585-2.889m-.539-2.183c-.191-.413-.26-.835-.064-1.146c.164-.247.381-.388.604-.388s.438.141.604.388c.195.311.127.732-.064 1.146c-.141.292-.334.57-.541.839a4.66 4.66 0 0 1-.539-.839m12.23 6.534c-2.583 4.049-5.039 7.898-6.203 11.975h-3.246a801.908 801.908 0 0 0 2.113-8.465c.246-1.021.506-2.078.771-3.168c.148-.58.279-1.178.424-1.773c.756-.051 1.479-.287 2.043-.804c.578-.569.879-1.313.744-2.107c-.061-.38-.346-.872-.896-1.04a1.397 1.397 0 0 0-.832-.019a1.572 1.572 0 0 0-.598.396c-.533.592-.727 1.183-.945 1.769c-.102.295-.186.59-.266.881c0 0-.85.065-2.668-1.158c-1.01-.679-1.301-1.17-1.594-1.495c.316-.351.619-.736.854-1.203c.125-.261.23-.544.266-.862s-.023-.678-.205-.988a1.749 1.749 0 0 0-.865-.76c-.182-.065-.385-.096-.588-.096s-.408.03-.588.096a1.758 1.758 0 0 0-.867.76c-.182.311-.24.67-.205.988a2.7 2.7 0 0 0 .268.862c.234.467.537.853.854 1.203c-.293.325-.586.816-1.596 1.495c-1.818 1.224-2.668 1.158-2.668 1.158a14.153 14.153 0 0 0-.264-.881c-.221-.586-.412-1.177-.945-1.769a1.565 1.565 0 0 0-.598-.396a1.397 1.397 0 0 0-.832.019c-.553.168-.836.66-.896 1.04c-.137.794.166 1.538.744 2.107c.566.517 1.287.753 2.043.804c.143.596.273 1.193.424 1.773l.771 3.168A834.101 834.101 0 0 0 29.758 46h-3.246c-1.162-4.073-3.619-7.922-6.209-11.979C17.204 29.158 14 24.13 14 19.586C14 10.992 22.074 4 32 4c9.925 0 18 6.992 18 15.586c0 4.546-3.207 9.575-6.309 14.439m-5.892-2.361c.059-.205.119-.413.189-.613c.189-.527.43-1.1.758-1.443c.078-.088.158-.123.199-.146c0 .004.098-.012.16.019c.111.028.217.143.258.364c.08.428-.123.979-.49 1.324c-.258.245-.656.417-1.074.495m-11.6 0c-.418-.078-.816-.25-1.072-.496c-.369-.345-.57-.896-.49-1.324c.041-.222.146-.336.258-.364c.063-.03.16-.015.16-.019c.039.023.121.059.195.146c.33.344.57.916.76 1.443c.07.201.131.409.189.614m.321 17.893h-1v3.6h.768v3.201h.768v3.201h1.286C28.947 60.988 30.347 62 32 62c1.652 0 3.051-1.012 3.658-2.443h1.327v-3.221l.112-.041l.655-.24v-2.923l.065-.021l.701-.22v-3.335H26.52z"/></svg>
... ...
  1 +<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 64 64"><path fill="#616466" d="M28 58c0 2.2 1.8 4 4 4s4-1.8 4-4h-8"/><path fill="#ffce31" d="M24.9 48H39c.8-4.3 3.5-8.5 6.3-12.9C48.6 30 52 24.7 52 19.6C52 9.9 43 2 32 2S12 9.9 12 19.6c0 5.1 3.4 10.4 6.6 15.5c2.8 4.4 5.5 8.6 6.3 12.9"/><path fill="#c79127" d="M26.4 33.6c.1.6.3 1.2.4 1.8c.3 1.1.5 2.1.8 3.2c.9 3.8 1.7 7 2.4 9.5h.6c-.5-2.5-1.2-5.8-2.1-9.6c-.2-1-.5-2.1-.7-3.2c-.1-.5-.2-1.1-.4-1.6c.8-.2 2.7-.8 4.6-2.9c1.9 2.1 3.8 2.7 4.6 2.9c-.1.6-.2 1.1-.4 1.6c-.2 1.1-.5 2.2-.7 3.2c-.9 3.8-1.6 7.1-2.1 9.6h.6c.6-2.5 1.5-5.7 2.4-9.5c.2-1 .5-2.1.8-3.2c.1-.6.3-1.2.4-1.8c.8-.1 1.5-.3 2-.8c.6-.6.9-1.3.7-2.1c-.1-.4-.3-.9-.9-1c-.3-.1-.5-.1-.8 0c-.3.1-.5.3-.6.4c-.5.6-.7 1.2-.9 1.8l-.3.9s-.8.1-2.7-1.2c-1-.7-1.3-1.2-1.6-1.5c.3-.4.6-.7.9-1.2c.1-.3.2-.5.3-.9c0-.3 0-.7-.2-1c-.2-.3-.4-.6-.9-.8c-.2-.1-.4-.1-.6-.1s-.4 0-.6.1c-.4.2-.7.5-.9.8c-.2.3-.2.7-.2 1c0 .3.1.6.3.9c.2.5.5.9.9 1.2c-.3.3-.6.8-1.6 1.5c-1.8 1.2-2.7 1.2-2.7 1.2l-.3-.9c-.2-.6-.4-1.2-.9-1.8c-.1-.1-.3-.3-.6-.4c-.3-.1-.6-.1-.8 0c-.6.2-.8.7-.9 1c-.1.8.2 1.5.7 2.1c.6.5 1.3.7 2 .8M38 32.1c.2-.5.4-1.1.8-1.4c.1-.1.2-.1.2-.1h.2c.1 0 .2.1.3.4c.1.4-.1 1-.5 1.3c-.3.2-.7.4-1.1.5c0-.3 0-.5.1-.7m-6.6-4.8c.2-.2.4-.4.6-.4c.2 0 .4.1.6.4c.2.3.1.7-.1 1.1c-.1.3-.3.6-.5.8c-.2-.3-.4-.5-.5-.8c-.2-.3-.3-.7-.1-1.1m-6.8 3.5c0-.2.1-.3.3-.4h.2s.1.1.2.1c.3.3.6.9.8 1.4c.1.2.1.4.2.6c-.4-.1-.8-.2-1.1-.5c-.4-.2-.6-.7-.6-1.2"/><path fill="#94989b" d="M24.9 50h14.3v1.8H24.9zm1 3.6h12.3v1.8H25.9z"/><path fill="#616466" d="M25.9 51.8h12.3v1.8H25.9z"/><path fill="#94989b" d="m39.2 50l-13.3 3.6v1.9l13.3-3.7zm-12.3 7.3h10.3v1.8H26.9z"/><path fill="#616466" d="M26.9 55.5h10.3v1.8H26.9z"/><path fill="#94989b" d="m38.2 53.6l-11.3 3.7v1.9l11.3-3.7z"/></svg>
... ...
1 1 <script lang="ts" setup>
2   - import on from '../assets/light-bulb-on.svg';
3   - import off from '../assets/light-bulb-off.svg';
  2 + import on from '/@/assets/icons/light-bulb-on.svg';
  3 + import off from '/@/assets/icons/light-bulb-off.svg';
4 4
5 5 const props = defineProps<{
6 6 value?: boolean;
... ...
  1 +<script lang="ts" setup>
  2 + import { computed } from '@vue/reactivity';
  3 + import { Statistic } from 'ant-design-vue';
  4 + import type { TextComponentLayout, TextComponentValue } from './config';
  5 + import { SvgIcon } from '/@/components/Icon';
  6 + const props = defineProps({
  7 + layout: {
  8 + type: Object as PropType<TextComponentLayout>,
  9 + default: () => ({ base: true } as TextComponentLayout),
  10 + },
  11 + value: {
  12 + type: Object as PropType<TextComponentValue>,
  13 + default: () => ({ name: '温度', value: 123 } as TextComponentValue),
  14 + },
  15 + });
  16 +
  17 + const getIsColumnLayout = computed(() => {
  18 + const { base } = props.layout;
  19 + return base;
  20 + });
  21 +
  22 + const getShowIcon = computed(() => {
  23 + const { showIcon, base } = props.layout;
  24 + return base ? false : showIcon;
  25 + });
  26 +
  27 + const getShowUpdate = computed(() => {
  28 + const { showUpdate, base } = props.layout;
  29 + return base ? false : showUpdate;
  30 + });
  31 +
  32 + const getShowUnit = computed(() => {
  33 + const { showUnit, base } = props.layout;
  34 + return base ? false : showUnit;
  35 + });
  36 +</script>
  37 +
  38 +<template>
  39 + <div class="w-full h-full flex flex-col">
  40 + <div
  41 + class="flex justify-center items-center w-full text-center flex-auto"
  42 + :style="{ flexDirection: getIsColumnLayout ? 'column' : 'row' }"
  43 + >
  44 + <div class="w-1/2">
  45 + <div>
  46 + <div v-if="getShowIcon">
  47 + <SvgIcon name="CO2" prefix="iconfont" class="!w-1/2 !h-[2em]" />
  48 + </div>
  49 + <div>{{ props.value.name }}</div>
  50 + </div>
  51 + </div>
  52 + <div class="w-1/2 flex justify-center">
  53 + <Statistic
  54 + value="123"
  55 + :suffix="getShowUnit ? props.value.unit : ''"
  56 + :value-style="{ fontSize: '1.3em' }"
  57 + />
  58 + </div>
  59 + </div>
  60 + <div v-if="getShowUpdate" class="h-6 text-center text-xs text-gray-400">
  61 + <span> 更新时间: {{ props.value.updateTime }}</span>
  62 + </div>
  63 + </div>
  64 +</template>
... ...
  1 +import { formatToDateTime } from '/@/utils/dateUtil';
  2 +
  3 +export interface TextComponentLayout {
  4 + id: string;
  5 + base?: boolean;
  6 + showUpdate?: boolean;
  7 + showIcon?: boolean;
  8 + showUnit?: boolean;
  9 +}
  10 +
  11 +export interface TextComponentValue {
  12 + name: string;
  13 + value: number;
  14 + icon?: string;
  15 + unit?: string;
  16 + updateTime?: string;
  17 + fontColor?: string;
  18 + iconColor?: string;
  19 +}
  20 +
  21 +type TextComponentDefault = TextComponentLayout & { value: TextComponentValue };
  22 +
  23 +export const textComponentConfig: TextComponentDefault[] = [
  24 + { id: 'text-component-1', base: true, value: { value: 123, name: '温度' } },
  25 + { id: 'text-component-2', base: false, value: { value: 123, name: '温度' } },
  26 + {
  27 + id: 'text-component-3',
  28 + base: false,
  29 + showUpdate: true,
  30 + value: {
  31 + value: 123,
  32 + name: '温度',
  33 + updateTime: formatToDateTime(new Date(), 'YYYY-MM-DD HH:mm:ss'),
  34 + },
  35 + },
  36 + {
  37 + id: 'text-component-4',
  38 + base: false,
  39 + showIcon: true,
  40 + showUpdate: true,
  41 + showUnit: true,
  42 + value: {
  43 + value: 123,
  44 + name: '温度',
  45 + updateTime: formatToDateTime(new Date(), 'YYYY-MM-DD HH:mm:ss'),
  46 + unit: '℃',
  47 + },
  48 + },
  49 + {
  50 + id: 'text-component-5',
  51 + base: false,
  52 + showIcon: true,
  53 + showUnit: true,
  54 + value: { value: 123, name: '温度', unit: '℃' },
  55 + },
  56 +];
... ...
1 1 <script lang="ts" setup>
2   - import { CopyOutlined, DeleteOutlined } from '@ant-design/icons-vue';
  2 + import { CopyOutlined, DeleteOutlined, SettingOutlined } from '@ant-design/icons-vue';
3 3 import { Tooltip, Button } from 'ant-design-vue';
4 4 import { useForm } from '/@/components/Form';
5 5 import { basicSchema, dataSourceSchema } from '../config/basicConfiguration';
6 6 import BasicForm from '/@/components/Form/src/BasicForm.vue';
7 7 import { ref, unref } from 'vue';
  8 + import VisualOptionsModal from './VisualOptionsModal.vue';
  9 + import { useModal } from '/@/components/Modal';
8 10
9 11 const dataSource = ref([{ id: 1 }]);
10 12
... ... @@ -32,6 +34,12 @@
32 34 });
33 35 };
34 36
  37 + const [registerVisualOptionModal, { openModal }] = useModal();
  38 +
  39 + const handleSetting = () => {
  40 + openModal(true);
  41 + };
  42 +
35 43 const handleDelete = (data: Recordable) => {
36 44 const index = unref(dataSource).findIndex((item) => item.id === data.id);
37 45
... ... @@ -51,16 +59,19 @@
51 59 <div class="w-3/4">
52 60 <BasicForm @register="basicRegister" />
53 61 </div>
54   - <h3 class="w-24 text-right pr-2 my-4">选择数据源</h3>
  62 + <h3 class="w-24 flex-shrink-0 text-right pr-2 my-4">选择数据源</h3>
55 63 <div v-for="item in dataSource" :key="item.id" class="flex">
56   - <div class="w-24 text-right">选择设备</div>
  64 + <div class="w-24 text-right" style="flex: 0 0 96px; padding-right: 8px">选择设备</div>
57 65 <div class="pl-2 flex-auto">
58 66 <BasicForm @register="dataSourceRegister" />
59 67 </div>
60   - <div class="flex justify-center gap-3 w-12">
  68 + <div class="flex justify-center gap-3 w-18">
61 69 <Tooltip title="复制">
62 70 <CopyOutlined @click="handleCopy(item)" class="cursor-pointer text-lg" />
63 71 </Tooltip>
  72 + <Tooltip title="设置">
  73 + <SettingOutlined @click="handleSetting(item)" class="cursor-pointer text-lg" />
  74 + </Tooltip>
64 75 <Tooltip title="删除">
65 76 <DeleteOutlined @click="handleDelete(item)" class="cursor-pointer text-lg" />
66 77 </Tooltip>
... ... @@ -69,5 +80,6 @@
69 80 <div class="text-center">
70 81 <Button type="primary" @click="handleAdd">添加数据源</Button>
71 82 </div>
  83 + <VisualOptionsModal @register="registerVisualOptionModal" />
72 84 </section>
73 85 </template>
... ...
... ... @@ -4,7 +4,7 @@
4 4 import BasicModal from '/@/components/Modal/src/BasicModal.vue';
5 5 import BasicConfiguration from './BasicConfiguration.vue';
6 6 import VisualConfiguration from './VisualConfiguration.vue';
7   - import VisualOptions from './VisualOptions.vue';
  7 + import VisualOptionsModal from './VisualOptionsModal.vue';
8 8 const activeKey = ref('1');
9 9 </script>
10 10
... ... @@ -19,7 +19,7 @@
19 19 <VisualConfiguration />
20 20 </Tabs.TabPane>
21 21 <Tabs.TabPane key="3" tab="可视化选项">
22   - <VisualOptions />
  22 + <VisualOptionsModal />
23 23 </Tabs.TabPane>
24 24 </Tabs>
25 25 </section>
... ...
1 1 <script lang="ts" setup>
2   - import {} from 'ant-design-vue';
  2 + import { Tabs, List } from 'ant-design-vue';
  3 + import { ref } from 'vue';
  4 + import VisualWidgetSelect from './VisualWidgetSelect.vue';
  5 + import TextComponent from '../../components/TextComponent/TextComponent.vue';
  6 + import { textComponentConfig } from '../../components/TextComponent/config';
  7 + const checkedId = ref('1');
  8 +
  9 + const handleCheck = (checked: string) => {
  10 + checkedId.value = checked;
  11 + };
3 12 </script>
4 13
5 14 <template>
6   - <section> 可视化配置 </section>
  15 + <section>
  16 + <Tabs>
  17 + <Tabs.TabPane key="1" tab="文本组件">
  18 + <List :grid="{ gutter: 10, column: 3 }" :data-source="textComponentConfig">
  19 + <template #renderItem="{ item }">
  20 + <List.Item>
  21 + <VisualWidgetSelect
  22 + :checked-id="checkedId"
  23 + :control-id="item.id"
  24 + @change="handleCheck"
  25 + >
  26 + <TextComponent :layout="item" :value="item.value" />
  27 + </VisualWidgetSelect>
  28 + </List.Item>
  29 + </template>
  30 + </List>
  31 + </Tabs.TabPane>
  32 + <Tabs.TabPane key="2" tab="仪表组件">
  33 + <div>仪表组件</div>
  34 + </Tabs.TabPane>
  35 + </Tabs>
  36 + </section>
7 37 </template>
... ...
1   -<script lang="ts" setup></script>
2   -
3   -<template>
4   - <section> 可视化选项 </section>
5   -</template>
  1 +<script lang="ts" setup>
  2 + import { BasicModal } from '/@/components/Modal';
  3 +</script>
  4 +
  5 +<template>
  6 + <BasicModal v-bind="$attrs" title="" width="60%">
  7 + <section> content </section>
  8 + </BasicModal>
  9 +</template>
... ...
  1 +<script lang="ts" setup>
  2 + import { Card } from 'ant-design-vue';
  3 +
  4 + const props = defineProps({
  5 + controlId: {
  6 + type: String,
  7 + required: true,
  8 + },
  9 + checkedId: {
  10 + type: String,
  11 + required: true,
  12 + },
  13 + });
  14 + const emit = defineEmits(['change']);
  15 +
  16 + const handleClick = () => {
  17 + emit('change', props.controlId);
  18 + };
  19 +</script>
  20 +
  21 +<template>
  22 + <Card
  23 + :style="{ borderColor: props.controlId === props.checkedId ? '#1a74e8' : '#f0f0f0' }"
  24 + hoverable
  25 + bordered
  26 + class="w-60 h-60 widget-select"
  27 + @click="handleClick"
  28 + >
  29 + <div class="widget-container">
  30 + <slot></slot>
  31 + </div>
  32 + <Card.Meta>
  33 + <template #description> 选择 </template>
  34 + </Card.Meta>
  35 + </Card>
  36 +</template>
  37 +
  38 +<style scoped>
  39 + .widget-select:deep(.ant-card-body) {
  40 + /* height: 240px; */
  41 + width: 240px;
  42 + padding: 0;
  43 + box-sizing: border-box;
  44 + display: flex;
  45 + flex-direction: column;
  46 + align-items: center;
  47 + justify-content: center;
  48 + }
  49 +
  50 + .widget-select:deep(.ant-card-meta) {
  51 + border-top: 1px solid #efefef;
  52 + width: 100%;
  53 + height: 40px;
  54 + text-align: center;
  55 + line-height: 40px;
  56 + margin: 0;
  57 + }
  58 +
  59 + .widget-select .widget-container {
  60 + width: 240px;
  61 + height: 200px;
  62 + display: flex;
  63 + justify-content: center;
  64 + align-items: center;
  65 + }
  66 +</style>
... ...
  1 +import { FormSchema } from '/@/components/Form';
  2 +export enum defaultOptions {
  3 + fontColor = '#rer',
  4 +}
  5 +
  6 +export const mode: FormSchema[] = [
  7 + {
  8 + field: 'fontColor',
  9 + label: '数值字体颜色',
  10 + component: 'ColorPicker',
  11 + },
  12 +];
... ...