Compare commits
14 Commits
fc1598be6c
...
44b87816b6
Author | SHA1 | Date |
---|---|---|
|
44b87816b6 | 5 days ago |
|
5546f8ae3e | 5 days ago |
|
54e483963e | 5 days ago |
|
608ce47a13 | 3 weeks ago |
|
e0830d649b | 3 weeks ago |
|
bf1d020d89 | 3 weeks ago |
|
705c728959 | 3 weeks ago |
|
c4b1005ced | 4 weeks ago |
|
68063403e4 | 4 weeks ago |
|
93117269de | 4 weeks ago |
|
55e6908f53 | 4 weeks ago |
|
3080f23955 | 4 weeks ago |
|
1f6437b09b | 3 months ago |
|
83aa271805 | 3 months ago |
@ -0,0 +1,3 @@ |
|||||||
|
VITE_APP_ENV = cloud |
||||||
|
VITE_BASE_URL = 'http://192.168.1.99:8080' |
||||||
|
VITE_ZMQ_BASE_URL = '192.168.1.99' |
@ -1 +1,3 @@ |
|||||||
VITE_APP_ENV = local |
VITE_APP_ENV = local |
||||||
|
VITE_BASE_URL = 'http://192.168.1.99:8080' |
||||||
|
VITE_ZMQ_BASE_URL = '192.168.1.99' |
@ -0,0 +1,69 @@ |
|||||||
|
import { globalServer } from '../index' |
||||||
|
|
||||||
|
interface Param { |
||||||
|
page: number |
||||||
|
size: number |
||||||
|
} |
||||||
|
|
||||||
|
export const getTaskList = (params: Param) => |
||||||
|
globalServer<{ |
||||||
|
tasks: TaskList[] |
||||||
|
total: number |
||||||
|
}>({ |
||||||
|
url: 'api/task/summary', |
||||||
|
method: 'get', |
||||||
|
params, |
||||||
|
}) |
||||||
|
|
||||||
|
|
||||||
|
export const getTaskInfo = (taskId: string) => globalServer<{ details: TaskInfo[] }>({ |
||||||
|
url: 'api/task/detail', |
||||||
|
method: 'GET', |
||||||
|
params: { task: taskId }, |
||||||
|
}) |
||||||
|
|
||||||
|
export interface TaskCreateParams { |
||||||
|
site: string |
||||||
|
devices: { |
||||||
|
sn: string |
||||||
|
disk?: string |
||||||
|
host?: string |
||||||
|
}[], |
||||||
|
mode: 'import' | 'export' | 'update' |
||||||
|
startTime?: string |
||||||
|
endTime?: string |
||||||
|
} |
||||||
|
|
||||||
|
export const createTask = (params: TaskCreateParams) => globalServer({ |
||||||
|
url: 'api/task/apply', |
||||||
|
method: 'POST', |
||||||
|
data: params, |
||||||
|
}) |
||||||
|
|
||||||
|
export const cancelTask = (taskId: string) => globalServer({ |
||||||
|
url: 'api/task/cancel', |
||||||
|
method: 'POST', |
||||||
|
data: { task: taskId }, |
||||||
|
}) |
||||||
|
|
||||||
|
export type TaskInfo = { |
||||||
|
finish: number; |
||||||
|
id: string; |
||||||
|
info: string; |
||||||
|
site: string; |
||||||
|
sn: string; |
||||||
|
status: -1 | 0 | 1 | 2; // -1 失败, 0 未开始, 1 进行中, 2 成功
|
||||||
|
task_id: string; |
||||||
|
total: number; |
||||||
|
}; |
||||||
|
|
||||||
|
export interface TaskList { |
||||||
|
endTime: string; // 结束时间(可能是空字符串)
|
||||||
|
id: string; // "task759956"
|
||||||
|
info: string; // ""
|
||||||
|
mode: string; // "export"
|
||||||
|
site: string; // "test1"
|
||||||
|
startTime: string; // 开始时间(可能是空字符串)
|
||||||
|
status: -1 | 0 | 1 | 2 | 3; // -1 失败, 0 未开始, 1 进行中, 2 取消, 3 成功
|
||||||
|
loading?: boolean |
||||||
|
} |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 878 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 878 B |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1,154 @@ |
|||||||
|
<template> |
||||||
|
<div class="task-list wh-full"> |
||||||
|
<EdfsWrap title="任务列表" class="h-full"> |
||||||
|
<EdfsTable class="wh-full" v-loading="loading" :data="list" ref="tableRef" |
||||||
|
:highlight-current-row="true" |
||||||
|
:page-total="total" :current-page="queryParams.page" :page-size="queryParams.size" |
||||||
|
row-class-name="row" |
||||||
|
@pageCurrentChange="handleJump"> |
||||||
|
<template v-for="(col, idx) in tableCol" :key="idx"> |
||||||
|
<el-table-column v-if="col.prop.endsWith('Time')" :label="col.label" |
||||||
|
:min-width="col.minWidth"> |
||||||
|
<template #default="scope"> |
||||||
|
{{ dayjs(scope.row.createTime).format('YYYY-MM-DD HH:mm:ss') }} |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column v-else-if="col.prop === 'status'" :prop="col.prop" :label="col.label" |
||||||
|
:min-width="col.minWidth"> |
||||||
|
<template #default="scope"> |
||||||
|
{{ taskStatus.find(r => r.value === scope.row.status)?.label || '--' }} |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column v-else-if="col.prop === 'mode'" :prop="col.prop" :label="col.label" |
||||||
|
:min-width="col.minWidth"> |
||||||
|
<template #default="scope"> |
||||||
|
{{ taskMode.find(r => r.value === scope.row.mode)?.label || '--' }} |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column v-else :prop="col.prop" :label="col.label" :min-width="col.minWidth"/> |
||||||
|
</template> |
||||||
|
<el-table-column label="操作" width="210" align="center"> |
||||||
|
<template #default="scope"> |
||||||
|
<EdfsButton size="small" link type="primary" inner-text="详情" |
||||||
|
@click="onDetail(scope.row)"/> |
||||||
|
<EdfsButton size="small" link type="danger" v-if="[0, 1].includes(scope.row.mode)" |
||||||
|
inner-text="取消任务" |
||||||
|
@click="onCancel(scope.row.id)"/> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
</EdfsTable> |
||||||
|
|
||||||
|
</EdfsWrap> |
||||||
|
</div> |
||||||
|
<InfoDrawer ref="InfoDrawerRef" @task-finish="onTaskFinish"/> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup lang="ts"> |
||||||
|
import { |
||||||
|
cancelTask, |
||||||
|
getTaskInfo, |
||||||
|
getTaskList, |
||||||
|
type TaskInfo, |
||||||
|
type TaskList |
||||||
|
} from '@/api/module/taks' |
||||||
|
import { useMessage } from '@/composables/useMessage' |
||||||
|
import EdfsWrap from "@/components/Edfs-wrap.vue"; |
||||||
|
import EdfsTable from "@/components/Edfs-table/index.vue"; |
||||||
|
import EdfsButton from "@/components/Edfs-button.vue"; |
||||||
|
import dayjs from 'dayjs' |
||||||
|
import InfoDrawer from './infoDrawer.vue' |
||||||
|
|
||||||
|
const message = useMessage() |
||||||
|
const taskStatus = [{ |
||||||
|
value: -1, |
||||||
|
label: '执行失败' |
||||||
|
}, { |
||||||
|
label: '等待执行', |
||||||
|
value: 0 |
||||||
|
}, { |
||||||
|
label: '执行中', |
||||||
|
value: 1 |
||||||
|
}, { |
||||||
|
label: '已取消', |
||||||
|
value: 2 |
||||||
|
}, { |
||||||
|
label: '执行成功', |
||||||
|
value: 3 |
||||||
|
}] |
||||||
|
|
||||||
|
const taskMode = [{ |
||||||
|
value: 'export', |
||||||
|
label: '迁移' |
||||||
|
}, { |
||||||
|
value: 'import', |
||||||
|
label: '导入' |
||||||
|
}, { |
||||||
|
value: 'update', |
||||||
|
label: '升级' |
||||||
|
}] |
||||||
|
|
||||||
|
const tableCol = [ |
||||||
|
{ label: '任务ID', prop: 'id', minWidth: '10%' }, |
||||||
|
{ label: '站点', prop: 'site', minWidth: '16%' }, |
||||||
|
{ label: '任务类型', prop: 'mode', minWidth: '10%' }, |
||||||
|
{ label: '任务状态', prop: 'status', minWidth: '10%' }, |
||||||
|
{ label: '创建时间', prop: 'startTime', minWidth: '10%' }, |
||||||
|
] |
||||||
|
|
||||||
|
|
||||||
|
const loading = ref(true) |
||||||
|
const total = ref(0) |
||||||
|
const list = ref<TaskList[]>([]) |
||||||
|
const queryParams = reactive({ |
||||||
|
page: 1, |
||||||
|
size: undefined, |
||||||
|
|
||||||
|
}) |
||||||
|
const tableRef = ref() |
||||||
|
const getList = async () => { |
||||||
|
if (!queryParams.size) { |
||||||
|
queryParams.size = tableRef.value.getSize() |
||||||
|
} |
||||||
|
loading.value = true |
||||||
|
|
||||||
|
const res = await getTaskList(queryParams as any) |
||||||
|
|
||||||
|
if (res !== null && res.code === 0) { |
||||||
|
list.value = res.data.tasks |
||||||
|
total.value = res.data.total |
||||||
|
} |
||||||
|
|
||||||
|
loading.value = false |
||||||
|
} |
||||||
|
|
||||||
|
function handleJump(page: number) { |
||||||
|
queryParams.page = page |
||||||
|
getList() |
||||||
|
} |
||||||
|
|
||||||
|
const InfoDrawerRef = ref<typeof InfoDrawer>() |
||||||
|
|
||||||
|
function onDetail(row: TaskList) { |
||||||
|
InfoDrawerRef.value?.open(row) |
||||||
|
} |
||||||
|
|
||||||
|
const onCancel = async (id: string) => { |
||||||
|
await message.delConfirm(`是否确认取消该任务?`) |
||||||
|
const res = await cancelTask(id) |
||||||
|
if (res.code !== 0) return |
||||||
|
await getList() |
||||||
|
} |
||||||
|
|
||||||
|
function onTaskFinish(sn: string, status: -1 | 0 | 1 | 2 | 3) { |
||||||
|
const curTask = list.value.findIndex(item => item.id === sn) |
||||||
|
if (curTask !== -1) { |
||||||
|
list.value[curTask].status = status |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
getList() |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped></style> |
@ -0,0 +1,137 @@ |
|||||||
|
<template> |
||||||
|
<div class="fault-rule-drawer"> |
||||||
|
<el-drawer v-model="isShowDrawer" title="任务详情" direction="rtl" size="50%" |
||||||
|
modal-class="model-dev-opn" |
||||||
|
:before-close="handleBeforeClose"> |
||||||
|
<main class="wh-full" v-loading="loading"> |
||||||
|
<template v-for="detail in detailList"> |
||||||
|
<el-descriptions border :title="`${detail.sn}任务详情`"> |
||||||
|
<el-descriptions-item :label-width="60" label="状态"> |
||||||
|
<el-tag size="small">{{ |
||||||
|
statusList.find(r => r.value === |
||||||
|
detail.status)?.label || '--' |
||||||
|
}} |
||||||
|
</el-tag> |
||||||
|
</el-descriptions-item> |
||||||
|
<el-descriptions-item :label-width="100" label="总数任务量">{{ |
||||||
|
detail.total |
||||||
|
}} |
||||||
|
</el-descriptions-item> |
||||||
|
<el-descriptions-item :label-width="110" label="已完任务量">{{ |
||||||
|
detail.finish |
||||||
|
}} |
||||||
|
</el-descriptions-item> |
||||||
|
<el-descriptions-item :label-width="60" label="信息"> |
||||||
|
{{ detail?.info ? detail.info : '暂无信息' }} |
||||||
|
</el-descriptions-item> |
||||||
|
<el-descriptions-item label="当前进度"> |
||||||
|
<template v-if="detail.total === 0"> |
||||||
|
<span>暂无进度</span> |
||||||
|
</template> |
||||||
|
<el-progress v-else style="width: 95%;" |
||||||
|
:percentage="Math.floor((detail.finish / detail.total) * 100)" |
||||||
|
:text-inside="true" :stroke-width="20"/> |
||||||
|
</el-descriptions-item> |
||||||
|
</el-descriptions> |
||||||
|
</template> |
||||||
|
</main> |
||||||
|
</el-drawer> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup lang="ts"> |
||||||
|
import { getSubTopic, type SubMsgData } from '@/utils/zmq' |
||||||
|
import ZMQWorker from '@/composables/useZMQJsonWorker' |
||||||
|
import { getTaskInfo, type TaskInfo, type TaskList } from '@/api/module/taks' |
||||||
|
import { useMessage } from '@/composables/useMessage' |
||||||
|
|
||||||
|
const emit = defineEmits<{ 'task-finish': [string, -1 | 0 | 1 | 2 | 3] }>() |
||||||
|
const message = useMessage() |
||||||
|
const worker = ZMQWorker.getInstance() |
||||||
|
const isShowDrawer = defineModel<boolean>() |
||||||
|
|
||||||
|
const detailList = ref<TaskInfo[]>([]) |
||||||
|
const loading = ref(false) |
||||||
|
|
||||||
|
async function onDetail(row: TaskList) { |
||||||
|
loading.value = true |
||||||
|
const res = await getTaskInfo(row.id); |
||||||
|
if (res.code === 0) { |
||||||
|
detailList.value = res?.data?.details ?? [] |
||||||
|
} else { |
||||||
|
message.error('获取任务详情失败') |
||||||
|
} |
||||||
|
loading.value = false |
||||||
|
} |
||||||
|
|
||||||
|
const currentTaskId = ref('') |
||||||
|
|
||||||
|
async function open(row: TaskList) { |
||||||
|
isShowDrawer.value = true |
||||||
|
currentTaskId.value = row.id |
||||||
|
await onDetail(row) |
||||||
|
worker.subscribe(getSubTopic('server', 'event', 'task'), zmqTaskCb) |
||||||
|
} |
||||||
|
|
||||||
|
const statusList = [ |
||||||
|
{ |
||||||
|
value: -1, |
||||||
|
label: '执行失败' |
||||||
|
}, { |
||||||
|
label: '等待执行', |
||||||
|
value: 0 |
||||||
|
}, { |
||||||
|
label: '执行中', |
||||||
|
value: 1 |
||||||
|
}, { |
||||||
|
label: '已取消', |
||||||
|
value: 2 |
||||||
|
}, { |
||||||
|
label: '执行成功', |
||||||
|
value: 3 |
||||||
|
} |
||||||
|
] |
||||||
|
|
||||||
|
|
||||||
|
function zmqTaskCb(msg: SubMsgData) { |
||||||
|
const { feedback } = msg |
||||||
|
const taskId = feedback[0] |
||||||
|
const deviceSN = feedback[1] |
||||||
|
const deviceStatus = feedback[2] || '未知状态' |
||||||
|
const finish = feedback[3] || 0 |
||||||
|
const total = feedback[4] || 0 |
||||||
|
if (currentTaskId.value !== taskId) return |
||||||
|
const detail = detailList.value.find(item => item.sn === deviceSN) |
||||||
|
if (!detail) return |
||||||
|
detail.status = deviceStatus |
||||||
|
detail.finish = finish |
||||||
|
detail.total = total |
||||||
|
if (detail.finish === detail.finish) { |
||||||
|
emit('task-finish', deviceSN, deviceStatus) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function handleBeforeClose(done: () => void) { |
||||||
|
isShowDrawer.value = false |
||||||
|
currentTaskId.value = '' |
||||||
|
done() |
||||||
|
} |
||||||
|
|
||||||
|
defineExpose({ |
||||||
|
open, |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped lang="scss"> |
||||||
|
.fault-rule-drawer { |
||||||
|
font-size: 16px; |
||||||
|
|
||||||
|
:deep(.edfs-wrap) { |
||||||
|
width: auto; |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.el-drawer__header) { |
||||||
|
color: var(--text-color); |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,100 @@ |
|||||||
|
<template> |
||||||
|
<div :style="{ |
||||||
|
width: NODE_SIZE[0] + 'px', |
||||||
|
height: NODE_SIZE[1] + 'px', |
||||||
|
borderRadius: '6px', |
||||||
|
marginLeft: -(NODE_SIZE[0] / 2 - 10) + 'px', |
||||||
|
}" class="box-border rounded-md overflow-hidden flex"> |
||||||
|
<div class="h-100% p-4px" :style="{ background: Device.bgColor}"> |
||||||
|
<img :src="Device.icon" style="height: 100%;" alt=""> |
||||||
|
</div> |
||||||
|
<div class="bg-white flex-1 flex items-center justify-center"> |
||||||
|
<span class="text-12px">{{ Device.name }}</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup lang="ts"> |
||||||
|
import type { MyNodeData } from "../type/index.ts"; |
||||||
|
import { DeviceType, NODE_SIZE } from "../utils/index"; |
||||||
|
import EmsIcon from '@/assets/images/device/ems.png'; |
||||||
|
import EcuIcon from '@/assets/images/device/ecu.png'; |
||||||
|
import EmIcon from '@/assets/images/device/em.png'; |
||||||
|
import EmMeasureIcon from '@/assets/images/device/em.png'; |
||||||
|
import BmsIcon from '@/assets/images/device/bms.png'; |
||||||
|
import CacIcon from '@/assets/images/device/cac.png'; |
||||||
|
import DgsIcon from '@/assets/images/device/dgs.png'; |
||||||
|
import FfsIcon from '@/assets/images/device/ffs.png'; |
||||||
|
import MpptIcon from '@/assets/images/device/mppt.png'; |
||||||
|
import PcsIcon from '@/assets/images/device/pcs.png'; |
||||||
|
import ThsIcon from '@/assets/images/device/ths.png'; |
||||||
|
import TmsIcon from '@/assets/images/device/tms.png'; |
||||||
|
import UnitIcon from '@/assets/images/device/unit.png'; |
||||||
|
import WppIcon from '@/assets/images/device/wpp.png'; |
||||||
|
import { computed } from "vue"; |
||||||
|
|
||||||
|
const typeToIconMap: Record<string, Object> = { |
||||||
|
[DeviceType.Ems]: { |
||||||
|
icon: EmsIcon, |
||||||
|
bgColor: '#0769FF' |
||||||
|
}, |
||||||
|
[DeviceType.Ecu]: { |
||||||
|
icon: EcuIcon, |
||||||
|
bgColor: '#0769FF' |
||||||
|
}, |
||||||
|
[DeviceType.Em]: { |
||||||
|
icon: EmIcon, |
||||||
|
bgColor: '#2CA02C' |
||||||
|
}, |
||||||
|
[DeviceType['Em-Measure']]: { |
||||||
|
icon: EmIcon, |
||||||
|
bgColor: '#2CA02C' |
||||||
|
}, |
||||||
|
[DeviceType.Bms]: { |
||||||
|
icon: BmsIcon, |
||||||
|
bgColor: '#FF7F50' |
||||||
|
}, |
||||||
|
[DeviceType.Cac]: { |
||||||
|
icon: CacIcon, |
||||||
|
bgColor: '#17BECF' |
||||||
|
}, |
||||||
|
[DeviceType.Dgs]: { |
||||||
|
icon: DgsIcon, |
||||||
|
bgColor: '#9467BD' |
||||||
|
}, |
||||||
|
[DeviceType.Ffs]: { |
||||||
|
icon: FfsIcon, |
||||||
|
bgColor: '#17BECF' |
||||||
|
}, |
||||||
|
[DeviceType.Mppt]: { |
||||||
|
icon: MpptIcon, |
||||||
|
bgColor: '#9467BD' |
||||||
|
}, |
||||||
|
[DeviceType.Pcs]: { |
||||||
|
icon: PcsIcon, |
||||||
|
bgColor: '#FF7F50' |
||||||
|
}, |
||||||
|
[DeviceType.Wpp]: { |
||||||
|
icon: WppIcon, |
||||||
|
bgColor: '#9467BD' |
||||||
|
}, |
||||||
|
[DeviceType.Tms]: { |
||||||
|
icon: TmsIcon, |
||||||
|
bgColor: '#17BECF' |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
const props = defineProps<{ |
||||||
|
data: MyNodeData; |
||||||
|
}>() |
||||||
|
|
||||||
|
const Device = computed(() => { |
||||||
|
const style = typeToIconMap[props.data.data.type]; |
||||||
|
return Object.assign(props.data.data, style); |
||||||
|
}) |
||||||
|
|
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped lang="scss"> |
||||||
|
|
||||||
|
</style> |
@ -0,0 +1,93 @@ |
|||||||
|
<template> |
||||||
|
<div id="container" class="w-full h-full"></div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup lang="ts"> |
||||||
|
import DeviceData from './utils/data'; |
||||||
|
import { EmLineEdge, MyLineEdge } from './utils/MyLineEdge'; |
||||||
|
import { DeviceType, flattenTree, NODE_SIZE, VIRTUAL_NODE } from "./utils"; |
||||||
|
import { ExtensionCategory, Graph, register } from '@antv/g6'; |
||||||
|
import GenerateGraphData from "./utils/GenerateGraphData"; |
||||||
|
import { VueNode } from 'g6-extension-vue'; |
||||||
|
import Node from './components/Node.vue' |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
register(ExtensionCategory.NODE, 'vue-node', VueNode); |
||||||
|
register(ExtensionCategory.EDGE, 'em-line-edge', EmLineEdge) |
||||||
|
register(ExtensionCategory.EDGE, 'my-line-edge', MyLineEdge); |
||||||
|
const device = flattenTree(DeviceData) |
||||||
|
const graphData = new GenerateGraphData(device) |
||||||
|
const graph = new Graph({ |
||||||
|
container: document.getElementById('container')!, |
||||||
|
padding: 50, |
||||||
|
autoFit: { |
||||||
|
type: 'center', |
||||||
|
}, |
||||||
|
layout: { |
||||||
|
type: 'dagre', |
||||||
|
nodesep: 50 + NODE_SIZE[0], |
||||||
|
ranksep: 50, |
||||||
|
}, |
||||||
|
animation: false, |
||||||
|
node: { |
||||||
|
type: 'vue-node', |
||||||
|
style: { |
||||||
|
component: (data: any) => { |
||||||
|
return data.data.type !== VIRTUAL_NODE ? h(Node, { data: data }) : '' |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
data: { |
||||||
|
nodes: graphData.getNodes(), |
||||||
|
edges: graphData.getEdges() |
||||||
|
}, |
||||||
|
plugins: [ |
||||||
|
{ |
||||||
|
type: 'contextmenu', |
||||||
|
enable: (e: any) => e.targetType === 'node', |
||||||
|
getItems: () => { |
||||||
|
return [{ name: '查看详情', value: 'detail' }]; |
||||||
|
}, |
||||||
|
onClick: (value: string, item: HTMLElement, current: any) => { |
||||||
|
console.log('展示节点详情', current.config.context.model.getNodeData(current.config.id)); |
||||||
|
}, |
||||||
|
}, |
||||||
|
], |
||||||
|
behaviors: ['drag-canvas', 'zoom-canvas'], |
||||||
|
}); |
||||||
|
graph.render(); |
||||||
|
|
||||||
|
graph.on('afterlayout', async () => { |
||||||
|
const nodes = graph.getNodeData(); |
||||||
|
const root = nodes.find(n => n.data?.type === DeviceType.Ems); |
||||||
|
if (!root) return; |
||||||
|
|
||||||
|
const emNodes = nodes.filter(n => |
||||||
|
n.data?.type === DeviceType.Em || n.data?.type === DeviceType['Em-Measure'] |
||||||
|
); |
||||||
|
|
||||||
|
const [x, y] = graph.getElementPosition(root.id); |
||||||
|
|
||||||
|
graph.updateNodeData( |
||||||
|
emNodes.map((n, idx) => { |
||||||
|
// 获取节点大小 |
||||||
|
return { |
||||||
|
id: n.id, |
||||||
|
style: Object.assign( |
||||||
|
{}, |
||||||
|
n.style, |
||||||
|
{ // 更新Em 节点位置 |
||||||
|
x: x + NODE_SIZE[0] * 2, |
||||||
|
y: -y - (emNodes.length * 10) + (idx * 60), |
||||||
|
}, |
||||||
|
) |
||||||
|
} |
||||||
|
}) |
||||||
|
); |
||||||
|
|
||||||
|
await graph.draw(); |
||||||
|
await graph.fitView(); |
||||||
|
}); |
||||||
|
}) |
||||||
|
; |
||||||
|
</script> |
@ -0,0 +1,5 @@ |
|||||||
|
import type { NodeData } from "@antv/g6"; |
||||||
|
export type Device = any |
||||||
|
export interface MyNodeData extends Omit<NodeData, 'data'> { |
||||||
|
data?: Device; |
||||||
|
} |
@ -0,0 +1,114 @@ |
|||||||
|
import type { EdgeData, NodeData } from "@antv/g6"; |
||||||
|
import { DeviceType, VIRTUAL_NODE } from "./index"; |
||||||
|
import EmsIcon from '@/assets/images/device/ems.png'; |
||||||
|
import type { Device, MyNodeData } from "../type"; |
||||||
|
|
||||||
|
export default class GenerateGraphData { |
||||||
|
private readonly devices: Device[] = []; |
||||||
|
private nodes: MyNodeData[] = []; |
||||||
|
private edges: EdgeData[] = []; |
||||||
|
|
||||||
|
constructor(devices: Device[]) { |
||||||
|
this.devices = devices; |
||||||
|
this.generateNodeData() |
||||||
|
this.generateEdgeData(); |
||||||
|
} |
||||||
|
|
||||||
|
private generateNodeData() { |
||||||
|
for (const device of this.devices) { |
||||||
|
const deviceChild = this.devices.filter((item: Device) => device.id === item.parentId) |
||||||
|
const isNeedPoint = device.type === DeviceType.Ems || device.type === DeviceType['Em-Measure'] || device.type === DeviceType.Em |
||||||
|
this.nodes.push({ |
||||||
|
id: String(device.id), |
||||||
|
label: device.name, |
||||||
|
data: Object.assign(device, { |
||||||
|
typeString: DeviceType[device.type], |
||||||
|
}), |
||||||
|
style: Object.assign( |
||||||
|
{ |
||||||
|
// labelText: device.name,
|
||||||
|
opacity: .3, |
||||||
|
icon: (d: any) => EmsIcon, |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
if (!!deviceChild.length) { |
||||||
|
// 在每个有子节点的设备下插入一个虚拟节点
|
||||||
|
this.nodes.push({ |
||||||
|
id: `${device.id}-virtual`, |
||||||
|
label: '', |
||||||
|
type: '', |
||||||
|
style: { opacity: 0 }, |
||||||
|
size: 0, |
||||||
|
anchorPoints: [], |
||||||
|
data: { |
||||||
|
type: VIRTUAL_NODE, |
||||||
|
parentId: String(device.id), |
||||||
|
depth: device.depth + 1, |
||||||
|
}, |
||||||
|
}) |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private generateEdgeData() { |
||||||
|
for (const node of this.nodes) { |
||||||
|
const customData: Device = node.data |
||||||
|
const virtualId = `${customData.parentId}-virtual`; |
||||||
|
const findParentNode: Device = this.nodes.some((n: MyNodeData) => n.id === String(customData.parentId)); |
||||||
|
|
||||||
|
if (this.isVirtualNode(customData.type)) { |
||||||
|
this.edges.push({ |
||||||
|
source: customData.parentId, |
||||||
|
target: String(node.id), |
||||||
|
type: 'my-line-edge', |
||||||
|
data: customData, |
||||||
|
}) |
||||||
|
continue; |
||||||
|
} |
||||||
|
//
|
||||||
|
|
||||||
|
if (findParentNode) { |
||||||
|
if (this.isEms(customData.type)) continue; |
||||||
|
if (this.isEm(customData.type)) { |
||||||
|
// 如果是 电表 类型的节点 target 指向父节点Ems
|
||||||
|
this.edges.push({ |
||||||
|
target: String(customData.parentId), |
||||||
|
source: String(node.id), |
||||||
|
type: 'em-line-edge', |
||||||
|
data: customData, |
||||||
|
}) |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
this.edges.push({ |
||||||
|
source: virtualId, |
||||||
|
target: String(node.id), |
||||||
|
type: 'my-line-edge', |
||||||
|
data: customData, |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
isEm(type: number): boolean { |
||||||
|
return type == DeviceType["Em-Measure"] || type == DeviceType.Em |
||||||
|
} |
||||||
|
|
||||||
|
isEms(type: number): boolean { |
||||||
|
return type == DeviceType.Ems |
||||||
|
} |
||||||
|
|
||||||
|
isVirtualNode(type: number | string): boolean { |
||||||
|
return type === VIRTUAL_NODE; |
||||||
|
} |
||||||
|
|
||||||
|
getNodes(): NodeData[] { |
||||||
|
return this.nodes as NodeData[]; |
||||||
|
} |
||||||
|
|
||||||
|
getEdges(): EdgeData[] { |
||||||
|
return this.edges; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,146 @@ |
|||||||
|
import { BaseEdge } from '@antv/g6'; |
||||||
|
import type { PathArray } from "@antv/util"; |
||||||
|
import { Circle } from '@antv/g'; |
||||||
|
import { subStyleProps } from '@antv/g6'; |
||||||
|
import { NODE_SIZE } from "./index"; |
||||||
|
import type { Device } from "../type"; |
||||||
|
|
||||||
|
|
||||||
|
export class MyLineEdge extends BaseEdge { |
||||||
|
|
||||||
|
getMarkerStyle(attributes: object) { |
||||||
|
return { |
||||||
|
r: 4, |
||||||
|
fill: '#58C448', |
||||||
|
offsetPath: this.shapeMap.key, ...subStyleProps(attributes, 'marker') |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
onCreate() { |
||||||
|
const marker = this.upsert('marker', Circle, this.getMarkerStyle(this.attributes), this)!; |
||||||
|
|
||||||
|
const prev = this.context.model.getRelatedEdgesData(this.sourceNode.id) // 获取源节点的相关边数据
|
||||||
|
.find((edge) => edge.target === this.targetNode.id) as Device; |
||||||
|
|
||||||
|
|
||||||
|
const depth = prev!.data!.depth - 1; |
||||||
|
const delay = depth * 3000; // 每级延迟 3 秒
|
||||||
|
marker.animate( |
||||||
|
[{ offsetDistance: 0 }, { offsetDistance: 1 }], |
||||||
|
{ |
||||||
|
duration: 3000, |
||||||
|
iterations: Infinity, |
||||||
|
delay, |
||||||
|
} |
||||||
|
); |
||||||
|
|
||||||
|
// 涟漪效果:在 marker 外套一个圆
|
||||||
|
const ripple = this.upsert( |
||||||
|
'ripple', |
||||||
|
Circle, |
||||||
|
{ |
||||||
|
r: 4, // 初始半径和 marker 一样
|
||||||
|
stroke: '#58C448', |
||||||
|
lineWidth: 2, |
||||||
|
fill: 'none', |
||||||
|
offsetPath: this.shapeMap.key, |
||||||
|
}, |
||||||
|
this |
||||||
|
)!; |
||||||
|
|
||||||
|
// 涟漪动画:半径变大 + 透明度变小
|
||||||
|
ripple.animate( |
||||||
|
[ |
||||||
|
{ offsetDistance: 0, r: 4, opacity: 0.7 }, |
||||||
|
{ offsetDistance: 1, r: 6, opacity: 0 }, |
||||||
|
], |
||||||
|
{ |
||||||
|
duration: 3000, |
||||||
|
iterations: Infinity, |
||||||
|
delay, |
||||||
|
} |
||||||
|
); |
||||||
|
// marker.animate([{ offsetDistance: 0 }, { offsetDistance: 1 }], {
|
||||||
|
// duration: 3000,
|
||||||
|
// iterations: Infinity,
|
||||||
|
// });
|
||||||
|
} |
||||||
|
|
||||||
|
protected getKeyPath(attrs: any): PathArray { |
||||||
|
const [sourcePoint, targetPoint] = this.getEndpoints(attrs, false) |
||||||
|
const [sx, sy] = [sourcePoint[0], sourcePoint[1]]; |
||||||
|
const [tx, ty] = [targetPoint[0], targetPoint[1]]; |
||||||
|
|
||||||
|
// 固定的主干 X
|
||||||
|
const busX = attrs.busX ?? tx; |
||||||
|
|
||||||
|
return [ |
||||||
|
['M', sx, sy], |
||||||
|
['L', busX, sy], |
||||||
|
['L', busX, ty], |
||||||
|
['L', tx, ty], |
||||||
|
]; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
export class EmLineEdge extends BaseEdge { |
||||||
|
|
||||||
|
getMarkerStyle(attributes: object) { |
||||||
|
return { |
||||||
|
r: 4, |
||||||
|
fill: '#58C448', |
||||||
|
offsetPath: this.shapeMap.key, ...subStyleProps(attributes, 'marker') |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
onCreate() { |
||||||
|
const marker = this.upsert('marker', Circle, this.getMarkerStyle(this.attributes), this)!; |
||||||
|
|
||||||
|
const delay = 0; |
||||||
|
marker.animate( |
||||||
|
[{ offsetDistance: 0 }, { offsetDistance: 1 }], |
||||||
|
{ |
||||||
|
duration: 3000, |
||||||
|
iterations: Infinity, |
||||||
|
delay, |
||||||
|
} |
||||||
|
); |
||||||
|
|
||||||
|
// 涟漪效果:在 marker 外套一个圆
|
||||||
|
const ripple = this.upsert( |
||||||
|
'ripple', |
||||||
|
Circle, |
||||||
|
{ |
||||||
|
r: 4, // 初始半径和 marker 一样
|
||||||
|
stroke: '#58C448', |
||||||
|
lineWidth: 2, |
||||||
|
fill: 'none', |
||||||
|
offsetPath: this.shapeMap.key, |
||||||
|
}, |
||||||
|
this |
||||||
|
)!; |
||||||
|
|
||||||
|
// 涟漪动画:半径变大 + 透明度变小
|
||||||
|
ripple.animate( |
||||||
|
[ |
||||||
|
{ offsetDistance: 0, r: 4, opacity: 0.7 }, |
||||||
|
{ offsetDistance: 1, r: 6, opacity: 0 }, |
||||||
|
], |
||||||
|
{ |
||||||
|
duration: 3000, |
||||||
|
iterations: Infinity, |
||||||
|
} |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
getKeyPath(attributes: any): PathArray { |
||||||
|
const [sourcePoint, targetPoint] = this.getEndpoints(attributes, false); |
||||||
|
return [ |
||||||
|
['M', sourcePoint[0], sourcePoint[1]], |
||||||
|
['L', targetPoint[0] / 2 + (1 / 2) * sourcePoint[0], sourcePoint[1]], |
||||||
|
['L', targetPoint[0] / 2 + (1 / 2) * sourcePoint[0], targetPoint[1]], |
||||||
|
['L', targetPoint[0], targetPoint[1]], |
||||||
|
]; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,72 @@ |
|||||||
|
import { Graph, register, Rect, ExtensionCategory } from '@antv/g6'; |
||||||
|
import { DeviceType, NODE_SIZE } from "./index"; |
||||||
|
|
||||||
|
|
||||||
|
// const typeToIconMap = Object.entries(DeviceType).filter(([key, value]) => typeof value === 'number')
|
||||||
|
// console.log(typeToIconMap)
|
||||||
|
|
||||||
|
export default class UserCardNode extends Rect { |
||||||
|
get nodeData() { |
||||||
|
return this.context.graph.getNodeData(this.id); |
||||||
|
} |
||||||
|
|
||||||
|
get data() { |
||||||
|
return this.nodeData.data || {}; |
||||||
|
} |
||||||
|
|
||||||
|
// 用户名样式
|
||||||
|
|
||||||
|
|
||||||
|
drawNameShape(attributes: any, container: any) { |
||||||
|
if (!this.data.name) return; |
||||||
|
const getUsernameStyle = (attributes: any) => { |
||||||
|
return { |
||||||
|
x: -14, |
||||||
|
text: this.data.name || '', |
||||||
|
// fontSize: 12,
|
||||||
|
width: 20, |
||||||
|
fill: '#262626', |
||||||
|
fontWeight: 'bold', |
||||||
|
textAlign: 'left', |
||||||
|
textBaseline: 'middle', |
||||||
|
maxWidth: 80, // 限制最大宽度
|
||||||
|
ellipsis: true, // 超出显示 ...
|
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
const usernameStyle = getUsernameStyle(attributes); |
||||||
|
this.upsert('username', 'text', usernameStyle, container); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// 头像样式
|
||||||
|
getAvatarStyle(attributes: any) { |
||||||
|
const [width, height] = this.getSize(attributes); |
||||||
|
const type = this!.data!.type |
||||||
|
return { |
||||||
|
x: -width / 2 + 4, |
||||||
|
y: -height / 2 + 6, |
||||||
|
width: 24, |
||||||
|
height: 24, |
||||||
|
src: typeToIconMap[type as keyof typeof type] || '', |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
drawAvatarShape(attributes: any, container: any) { |
||||||
|
if (!this.data.typeString) return; |
||||||
|
|
||||||
|
const avatarStyle = this.getAvatarStyle(attributes); |
||||||
|
this.upsert('avatar', 'image', avatarStyle, container); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
render(attributes: any, container: any) { |
||||||
|
// 渲染基础矩形
|
||||||
|
super.render(attributes, container); |
||||||
|
|
||||||
|
this.drawAvatarShape(attributes, container); |
||||||
|
this.drawNameShape(attributes, container); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
@ -0,0 +1,239 @@ |
|||||||
|
export default [{ |
||||||
|
"id": 398, |
||||||
|
"sn": "ems1", |
||||||
|
"name": "安庆滨江夜市站", |
||||||
|
"sort": 0, |
||||||
|
"parentId": 0, |
||||||
|
"deviceId": "t00510398", |
||||||
|
"dictName": "device_one_entity_type", |
||||||
|
"level": 0, |
||||||
|
"type": 0, |
||||||
|
"description": null, |
||||||
|
"children": [ |
||||||
|
{ |
||||||
|
"id": 400, |
||||||
|
"sn": "yiming-EMS1", |
||||||
|
"name": "储能单元1", |
||||||
|
"sort": 1, |
||||||
|
"parentId": 398, |
||||||
|
"deviceId": "t00510400", |
||||||
|
"dictName": "device_two_entity_type", |
||||||
|
"level": 1, |
||||||
|
"type": 1, |
||||||
|
"description": "", |
||||||
|
"children": [ |
||||||
|
{ |
||||||
|
"id": 404, |
||||||
|
"sn": "B48100B210TF36003E-bms", |
||||||
|
"name": "BMS-1", |
||||||
|
"sort": 1, |
||||||
|
"parentId": 400, |
||||||
|
"deviceId": "t00510404", |
||||||
|
"dictName": "", |
||||||
|
"level": 2, |
||||||
|
"type": 2, |
||||||
|
"description": "", |
||||||
|
"children": null |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 408, |
||||||
|
"sn": "B48100B210TF36003E-pcs", |
||||||
|
"name": "PCS-1", |
||||||
|
"sort": 1, |
||||||
|
"parentId": 400, |
||||||
|
"deviceId": "t00510408", |
||||||
|
"dictName": "", |
||||||
|
"level": 2, |
||||||
|
"type": 3, |
||||||
|
"description": "", |
||||||
|
"children": null |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 401, |
||||||
|
"sn": "yiming-EMS2", |
||||||
|
"name": "储能单元2", |
||||||
|
"sort": 1, |
||||||
|
"parentId": 398, |
||||||
|
"deviceId": "t00510401", |
||||||
|
"dictName": "device_two_entity_type", |
||||||
|
"level": 1, |
||||||
|
"type": 1, |
||||||
|
"description": "", |
||||||
|
"children": [ |
||||||
|
{ |
||||||
|
"id": 405, |
||||||
|
"sn": "B48100B210TF36003K-bms", |
||||||
|
"name": "BMS-2", |
||||||
|
"sort": 1, |
||||||
|
"parentId": 401, |
||||||
|
"deviceId": "t00510405", |
||||||
|
"dictName": "", |
||||||
|
"level": 2, |
||||||
|
"type": 2, |
||||||
|
"description": "", |
||||||
|
"children": null |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 4022, |
||||||
|
"sn": "B48100B210TF36003K-bms", |
||||||
|
"name": "BMS-3", |
||||||
|
"sort": 1, |
||||||
|
"parentId": 401, |
||||||
|
"deviceId": "t005104205", |
||||||
|
"dictName": "", |
||||||
|
"level": 2, |
||||||
|
"type": 2, |
||||||
|
"description": "", |
||||||
|
"children": null |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 409, |
||||||
|
"sn": "B48100B210TF36003K-pcs", |
||||||
|
"name": "PCS-2", |
||||||
|
"sort": 1, |
||||||
|
"parentId": 401, |
||||||
|
"deviceId": "t00510409", |
||||||
|
"dictName": "", |
||||||
|
"level": 2, |
||||||
|
"type": 3, |
||||||
|
"description": "", |
||||||
|
"children": null |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 402, |
||||||
|
"sn": "yiming-EMS3", |
||||||
|
"name": "储能单元3", |
||||||
|
"sort": 1, |
||||||
|
"parentId": 398, |
||||||
|
"deviceId": "t00510402", |
||||||
|
"dictName": "device_two_entity_type", |
||||||
|
"level": 1, |
||||||
|
"type": 1, |
||||||
|
"description": "", |
||||||
|
"children": [ |
||||||
|
{ |
||||||
|
"id": 406, |
||||||
|
"sn": "B48100B210TF36002V-bms", |
||||||
|
"name": "BMS-3", |
||||||
|
"sort": 1, |
||||||
|
"parentId": 402, |
||||||
|
"deviceId": "t00510406", |
||||||
|
"dictName": "", |
||||||
|
"level": 2, |
||||||
|
"type": 2, |
||||||
|
"description": "", |
||||||
|
"children": null |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 410, |
||||||
|
"sn": "B48100B210TF36002V-pcs", |
||||||
|
"name": "PCS-3", |
||||||
|
"sort": 1, |
||||||
|
"parentId": 402, |
||||||
|
"deviceId": "t00510410", |
||||||
|
"dictName": "", |
||||||
|
"level": 2, |
||||||
|
"type": 3, |
||||||
|
"description": "", |
||||||
|
"children": null |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 403, |
||||||
|
"sn": "yiming-EMS4", |
||||||
|
"name": "储能单元4", |
||||||
|
"sort": 1, |
||||||
|
"parentId": 398, |
||||||
|
"deviceId": "t00510403", |
||||||
|
"dictName": "device_two_entity_type", |
||||||
|
"level": 1, |
||||||
|
"type": 1, |
||||||
|
"description": "", |
||||||
|
"children": [ |
||||||
|
{ |
||||||
|
"id": 407, |
||||||
|
"sn": "B48100B210TF360029-bms", |
||||||
|
"name": "BMS-4", |
||||||
|
"sort": 1, |
||||||
|
"parentId": 403, |
||||||
|
"deviceId": "t00510407", |
||||||
|
"dictName": "", |
||||||
|
"level": 2, |
||||||
|
"type": 2, |
||||||
|
"description": "", |
||||||
|
"children": null |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 411, |
||||||
|
"sn": "B48100B210TF360029-pcs", |
||||||
|
"name": "PCS-4", |
||||||
|
"sort": 1, |
||||||
|
"parentId": 403, |
||||||
|
"deviceId": "t00510411", |
||||||
|
"dictName": "", |
||||||
|
"level": 2, |
||||||
|
"type": 3, |
||||||
|
"description": "", |
||||||
|
"children": null |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 412, |
||||||
|
"sn": "B48100B210TF36003E-em", |
||||||
|
"name": "电量信息-1", |
||||||
|
"sort": 1, |
||||||
|
"parentId": 398, |
||||||
|
"deviceId": "t00510412", |
||||||
|
"dictName": "device_two_entity_type", |
||||||
|
"level": 1, |
||||||
|
"type": 4, |
||||||
|
"description": "", |
||||||
|
"children": null |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 413, |
||||||
|
"sn": "B48100B210TF36003K-em", |
||||||
|
"name": "电量信息-2", |
||||||
|
"sort": 1, |
||||||
|
"parentId": 398, |
||||||
|
"deviceId": "t00510413", |
||||||
|
"dictName": "device_two_entity_type", |
||||||
|
"level": 1, |
||||||
|
"type": 4, |
||||||
|
"description": "", |
||||||
|
"children": null |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 414, |
||||||
|
"sn": "B48100B210TF36002V-em", |
||||||
|
"name": "电量信息-3", |
||||||
|
"sort": 1, |
||||||
|
"parentId": 398, |
||||||
|
"deviceId": "t00510414", |
||||||
|
"dictName": "device_two_entity_type", |
||||||
|
"level": 1, |
||||||
|
"type": 4, |
||||||
|
"description": "", |
||||||
|
"children": null |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 415, |
||||||
|
"sn": "B48100B210TF360029-em", |
||||||
|
"name": "电量信息-4", |
||||||
|
"sort": 1, |
||||||
|
"parentId": 398, |
||||||
|
"deviceId": "t00510415", |
||||||
|
"dictName": "device_two_entity_type", |
||||||
|
"level": 1, |
||||||
|
"type": 4, |
||||||
|
"description": "", |
||||||
|
"children": null |
||||||
|
} |
||||||
|
] |
||||||
|
}] |
@ -0,0 +1,32 @@ |
|||||||
|
import { flatMap } from 'lodash-es' |
||||||
|
import type { Device } from "../type"; |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const NODE_SIZE: [number, number] = [130, 36] |
||||||
|
|
||||||
|
export enum DeviceType { |
||||||
|
Ems, |
||||||
|
Ecu, |
||||||
|
Bms, |
||||||
|
Pcs, |
||||||
|
Em, |
||||||
|
'Em-Measure', |
||||||
|
Tms, |
||||||
|
Ffs, |
||||||
|
Wpp, |
||||||
|
Mppt, |
||||||
|
Dgs, |
||||||
|
Cac, |
||||||
|
} |
||||||
|
|
||||||
|
export const VIRTUAL_NODE = 'virtual-node' |
||||||
|
|
||||||
|
export function flattenTree(tree: Device[], depth = 1): Device[] { |
||||||
|
return flatMap(tree, (node: Device,) => { |
||||||
|
const { children, ...rest } = node |
||||||
|
const current = { ...rest, depth } |
||||||
|
// 有用虚拟节点的存在 depth + 2
|
||||||
|
return [current, ...(children ? flattenTree(children, depth + 2) : [])] |
||||||
|
}) |
||||||
|
} |
@ -0,0 +1,43 @@ |
|||||||
|
"use strict"; |
||||||
|
Object.defineProperty(exports, "__esModule", { value: true }); |
||||||
|
var preset_rem_to_px_1 = require("@unocss/preset-rem-to-px"); |
||||||
|
var unocss_preset_scalpel_1 = require("unocss-preset-scalpel"); |
||||||
|
var index_1 = require("./src/uno-preset/src/index"); |
||||||
|
var unocss_1 = require("unocss"); |
||||||
|
exports.default = (0, unocss_1.defineConfig)({ |
||||||
|
shortcuts: [], |
||||||
|
theme: { |
||||||
|
colors: {}, |
||||||
|
}, |
||||||
|
content: { |
||||||
|
pipeline: { |
||||||
|
include: [ |
||||||
|
//参考:https://unocss.dev/guide/extracting#extracting-from-build-tools-pipeline
|
||||||
|
/\.(vue|svelte|[jt]sx|mdx?|astro|elm|php|phtml|html)($|\?)/, |
||||||
|
'src/**/*.{js,ts}', |
||||||
|
'src/router/index.ts', |
||||||
|
'src/views/home/utils/menuConfig.ts', |
||||||
|
], |
||||||
|
}, |
||||||
|
}, |
||||||
|
rules: [['wh-full', { width: '100%', height: '100%' }]], |
||||||
|
presets: [ |
||||||
|
(0, preset_rem_to_px_1.default)(), |
||||||
|
(0, unocss_preset_scalpel_1.presetScalpel)(), |
||||||
|
(0, unocss_1.presetWind3)(), |
||||||
|
(0, index_1.presetSoybeanAdmin)(), |
||||||
|
(0, unocss_1.presetAttributify)({ |
||||||
|
prefix: 'uno-', |
||||||
|
prefixedOnly: true, |
||||||
|
}), |
||||||
|
(0, unocss_1.presetIcons)({ |
||||||
|
scale: 1.2, |
||||||
|
warn: true, |
||||||
|
}), |
||||||
|
(0, unocss_1.presetTypography)(), |
||||||
|
(0, unocss_1.presetWebFonts)({ |
||||||
|
fonts: {}, |
||||||
|
}), |
||||||
|
], |
||||||
|
transformers: [(0, unocss_1.transformerDirectives)(), (0, unocss_1.transformerVariantGroup)()], |
||||||
|
}); |