35 changed files with 895 additions and 26 deletions
@ -0,0 +1,17 @@ |
|||||
|
import type { CSSProperties } from 'vue' |
||||
|
|
||||
|
export interface showDialogOption { |
||||
|
confirmButtonText?: string |
||||
|
dangerouslyUseHTMLString?: boolean |
||||
|
showClose?: boolean |
||||
|
callback?: Function |
||||
|
showInput?: boolean |
||||
|
inputPlaceholder?: string |
||||
|
draggable?: boolean |
||||
|
closeOnClickModal?: boolean |
||||
|
showCancelButton?: boolean |
||||
|
closeOnPressEscape?: boolean |
||||
|
autofocus?: boolean |
||||
|
customClass?: string |
||||
|
customStyle?: CSSProperties |
||||
|
} |
||||
@ -0,0 +1,48 @@ |
|||||
|
import { renderIcon } from '@/utils/webutils.ts' |
||||
|
import { defineMenu } from '@/runtime/DefineMenu.ts' |
||||
|
|
||||
|
export default defineMenu((menus) => { |
||||
|
menus.insertChildren('modelFile', { |
||||
|
name: 'modelFile', |
||||
|
label: '编辑', |
||||
|
icon: renderIcon('ModelFile'), |
||||
|
order: 1 |
||||
|
}, [ |
||||
|
{ |
||||
|
name: 'find', label: '查找', icon: renderIcon('ModelFile'), order: 1, tip: 'Ctrl+F', divided: true, |
||||
|
click: () => { |
||||
|
system.msg('查找') |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
name: 'undo', label: '撤销', icon: renderIcon('ModelFile'), order: 2, tip: 'Ctrl+Z', |
||||
|
click: () => { |
||||
|
system.msg('撤销') |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
name: 'redo', label: '重做', icon: renderIcon('ModelFile'), order: 3, tip: 'Ctrl+Y', divided: true, |
||||
|
click: () => { |
||||
|
system.msg('重做') |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
name: 'copy', label: '复制', icon: renderIcon('ModelFile'), order: 4, tip: 'Ctrl+C', |
||||
|
click: () => { |
||||
|
system.msg('复制') |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
name: 'cut', label: '剪切', icon: renderIcon('ModelFile'), order: 5, tip: 'Ctrl+X', |
||||
|
click: () => { |
||||
|
system.msg('剪切') |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
name: 'paste', label: '粘贴', icon: renderIcon('ModelFile'), order: 6, tip: 'Ctrl+V', |
||||
|
click: () => { |
||||
|
system.msg('粘贴') |
||||
|
} |
||||
|
} |
||||
|
]) |
||||
|
}) |
||||
@ -0,0 +1,30 @@ |
|||||
|
import { renderIcon } from '@/utils/webutils.ts' |
||||
|
import { defineMenu } from '@/runtime/DefineMenu.ts' |
||||
|
|
||||
|
export default defineMenu((menus) => { |
||||
|
menus.insertChildren('file', |
||||
|
{ |
||||
|
name: 'file', label: '模型', icon: renderIcon('ModelFile'), order: 1, disabled: false |
||||
|
}, |
||||
|
[ |
||||
|
{ |
||||
|
name: 'open', label: '打开', icon: renderIcon('ModelFile'), order: 1, tip: 'Ctrl+O', |
||||
|
click: () => { |
||||
|
system.msg('打开模型文件') |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
name: 'save', label: '保存', icon: renderIcon('ModelFile'), order: 2, tip: 'Ctrl+S', |
||||
|
click: () => { |
||||
|
system.msg('保存模型文件') |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
name: 'saveAs', label: '另存为', icon: renderIcon('ModelFile'), order: 3, |
||||
|
click: () => { |
||||
|
system.msg('另存为模型文件') |
||||
|
} |
||||
|
} |
||||
|
] |
||||
|
) |
||||
|
}) |
||||
@ -0,0 +1,12 @@ |
|||||
|
import { defineWidget } from '@/runtime/DefineWidget.ts' |
||||
|
import { renderIcon } from '@/utils/webutils.ts' |
||||
|
import AlarmView from './AlarmView.vue' |
||||
|
|
||||
|
export default defineWidget({ |
||||
|
name: 'alarm', |
||||
|
title: '告警', |
||||
|
icon: renderIcon('AlarmClock'), |
||||
|
side: 'right', |
||||
|
order: 2, |
||||
|
component: AlarmView |
||||
|
}) |
||||
@ -0,0 +1,12 @@ |
|||||
|
import { defineWidget } from '@/runtime/DefineWidget.ts' |
||||
|
import { renderIcon } from '@/utils/webutils.ts' |
||||
|
import LoggerView from './LoggerView.vue' |
||||
|
|
||||
|
export default defineWidget({ |
||||
|
name: 'logger', |
||||
|
title: '日志', |
||||
|
icon: renderIcon('AlarmClock'), |
||||
|
side: 'bottom', |
||||
|
order: 2, |
||||
|
component: LoggerView |
||||
|
}) |
||||
@ -0,0 +1,3 @@ |
|||||
|
<template> |
||||
|
LoggerView |
||||
|
</template> |
||||
@ -0,0 +1,12 @@ |
|||||
|
import { defineWidget } from '@/runtime/DefineWidget.ts' |
||||
|
import { renderIcon } from '@/utils/webutils.ts' |
||||
|
import ModeltreeView from './ModeltreeView.vue' |
||||
|
|
||||
|
export default defineWidget({ |
||||
|
name: 'modeltree', |
||||
|
title: '模型', |
||||
|
icon: renderIcon('AlarmClock'), |
||||
|
side: 'left', |
||||
|
order: 1, |
||||
|
component: ModeltreeView |
||||
|
}) |
||||
@ -0,0 +1,3 @@ |
|||||
|
<template> |
||||
|
ModeltreeView |
||||
|
</template> |
||||
@ -0,0 +1,12 @@ |
|||||
|
import { defineWidget } from '@/runtime/DefineWidget.ts' |
||||
|
import { renderIcon } from '@/utils/webutils.ts' |
||||
|
import MonitorView from './MonitorView.vue' |
||||
|
|
||||
|
export default defineWidget({ |
||||
|
name: 'monitor', |
||||
|
title: '监控', |
||||
|
icon: renderIcon('AlarmClock'), |
||||
|
side: 'left', |
||||
|
order: 2, |
||||
|
component: MonitorView |
||||
|
}) |
||||
@ -0,0 +1,3 @@ |
|||||
|
<template> |
||||
|
MonitorView |
||||
|
</template> |
||||
@ -0,0 +1,12 @@ |
|||||
|
import { defineWidget } from '@/runtime/DefineWidget.ts' |
||||
|
import { renderIcon } from '@/utils/webutils.ts' |
||||
|
import PropertyView from './PropertyView.vue' |
||||
|
|
||||
|
export default defineWidget({ |
||||
|
name: 'property', |
||||
|
title: '属性', |
||||
|
icon: renderIcon('AlarmClock'), |
||||
|
side: 'right', |
||||
|
order: 1, |
||||
|
component: PropertyView |
||||
|
}) |
||||
@ -0,0 +1,3 @@ |
|||||
|
<template> |
||||
|
PropertyView |
||||
|
</template> |
||||
@ -0,0 +1,12 @@ |
|||||
|
import { defineWidget } from '../../../runtime/DefineWidget.ts' |
||||
|
import { renderIcon } from '@/utils/webutils.ts' |
||||
|
import ScriptView from './ScriptView.vue' |
||||
|
|
||||
|
export default defineWidget({ |
||||
|
name: 'script', |
||||
|
title: '脚本', |
||||
|
icon: renderIcon('AlarmClock'), |
||||
|
side: 'bottom', |
||||
|
order: 3, |
||||
|
component: ScriptView |
||||
|
}) |
||||
@ -0,0 +1,3 @@ |
|||||
|
<template> |
||||
|
ScriptView |
||||
|
</template> |
||||
@ -0,0 +1,12 @@ |
|||||
|
import { defineWidget } from '@/runtime/DefineWidget.ts' |
||||
|
import { renderIcon } from '@/utils/webutils.ts' |
||||
|
import TaskView from './TaskView.vue' |
||||
|
|
||||
|
export default defineWidget({ |
||||
|
name: 'task', |
||||
|
title: '任务', |
||||
|
icon: renderIcon('AlarmClock'), |
||||
|
side: 'bottom', |
||||
|
order: 1, |
||||
|
component: TaskView |
||||
|
}) |
||||
@ -0,0 +1,3 @@ |
|||||
|
<template> |
||||
|
TaskView |
||||
|
</template> |
||||
@ -0,0 +1,83 @@ |
|||||
|
import type { Component } from 'vue' |
||||
|
|
||||
|
export interface MenuOption { |
||||
|
name: string |
||||
|
label?: string |
||||
|
icon?: Component |
||||
|
order?: number |
||||
|
tip?: string |
||||
|
disabled?: boolean | (() => boolean) |
||||
|
divided?: boolean, |
||||
|
click?: () => void |
||||
|
children?: MenuOption[] |
||||
|
} |
||||
|
|
||||
|
export class Menus { |
||||
|
menus: MenuOption[] = [] |
||||
|
|
||||
|
constructor() { |
||||
|
this.menus = [] |
||||
|
} |
||||
|
|
||||
|
insertChildren(parentName: string, parentIfNotExists: MenuOption, menus: MenuOption[]) { |
||||
|
const parent = this.getMenu(parentName) |
||||
|
if (parent) { |
||||
|
if (!parent.children) { |
||||
|
parent.children = [] |
||||
|
} |
||||
|
// 去重
|
||||
|
const existingChildren = parent.children.filter((item) => { |
||||
|
return !menus.some((menu) => menu.name === item.name) |
||||
|
}) |
||||
|
menus = [...existingChildren, ...menus] |
||||
|
// 排序
|
||||
|
menus.sort((a, b) => { |
||||
|
return (a.order || 0) - (b.order || 0) |
||||
|
}) |
||||
|
parent.children = menus |
||||
|
|
||||
|
} else { |
||||
|
const newParent = { ...parentIfNotExists, children: menus } |
||||
|
this.insertMenu(newParent) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
insertMenu(menu: MenuOption) { |
||||
|
const existingMenu = this.getMenu(menu.name) |
||||
|
if (existingMenu) { |
||||
|
console.log(`Menu ${menu.name} already exists`) |
||||
|
return |
||||
|
} |
||||
|
this.menus.push(menu) |
||||
|
} |
||||
|
|
||||
|
getMenu(name: string): MenuOption | undefined { |
||||
|
return this.menus.find((menu) => menu.name === name) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const _menus = new Menus() |
||||
|
|
||||
|
export interface MenuDefineResult { |
||||
|
install(): void |
||||
|
} |
||||
|
|
||||
|
export function getRootMenu() { |
||||
|
return _menus.menus |
||||
|
} |
||||
|
|
||||
|
export function getChildrenMenu(name: string) { |
||||
|
const menu = _menus.getMenu(name) |
||||
|
return menu ? menu.children : [] |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 定义一个 Widget |
||||
|
*/ |
||||
|
export function defineMenu(func: (menus: Menus) => void): MenuDefineResult { |
||||
|
return { |
||||
|
install() { |
||||
|
func(_menus) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,52 @@ |
|||||
|
import type { Component } from 'vue' |
||||
|
|
||||
|
export type WidgetSide = 'left' | 'right' | 'bottom' |
||||
|
|
||||
|
export interface WidgetOption { |
||||
|
name: string |
||||
|
title?: string |
||||
|
icon: Component |
||||
|
side: WidgetSide |
||||
|
component: Component |
||||
|
order?: number |
||||
|
} |
||||
|
|
||||
|
const _widgetMap = new Map<string, WidgetOption>() |
||||
|
|
||||
|
export class WidgetInfo { |
||||
|
option: WidgetOption |
||||
|
|
||||
|
constructor(option: WidgetOption) { |
||||
|
this.option = option |
||||
|
} |
||||
|
|
||||
|
public install() { |
||||
|
if (!this.option.name) { |
||||
|
throw new Error('Widget name is required') |
||||
|
} |
||||
|
if (!this.option.component) { |
||||
|
throw new Error('Widget component is required') |
||||
|
} |
||||
|
if (_widgetMap.has(this.option.name)) { |
||||
|
console.log(`Widget ${this.option.name} already exists`) |
||||
|
return |
||||
|
} |
||||
|
_widgetMap.set(this.option.name, this.option) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 根据 Widget 定义的位置获取 Widget, 并按 order 排序 |
||||
|
*/ |
||||
|
export function getWidgetBySide(side: WidgetSide): WidgetOption[] { |
||||
|
return Array.from(_widgetMap.values()) |
||||
|
.filter((widget) => widget.side === side) |
||||
|
.sort((a, b) => (a.order || 0) - (b.order || 0)) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 定义一个 Widget |
||||
|
*/ |
||||
|
export function defineWidget(option: WidgetOption): WidgetInfo { |
||||
|
return new WidgetInfo(option) |
||||
|
} |
||||
@ -0,0 +1,188 @@ |
|||||
|
import $ from 'jquery' |
||||
|
import _ from 'lodash' |
||||
|
import localforage from 'localforage' |
||||
|
import JSON5 from 'json5' |
||||
|
import { defineComponent, h, markRaw, nextTick, reactive, toRaw, unref, type App, createApp } from 'vue' |
||||
|
import { ElMessage, ElMessageBox, ElNotification } from 'element-plus' |
||||
|
import { QuestionFilled } from '@element-plus/icons-vue' |
||||
|
import { renderIcon } from '@/utils/webutils.ts' |
||||
|
import type { showDialogOption } from '@/SystemOption' |
||||
|
|
||||
|
export default class System { |
||||
|
_ = _ |
||||
|
$ = $ |
||||
|
createApp = createApp |
||||
|
toRaw = toRaw |
||||
|
unref = unref |
||||
|
nextTick = nextTick |
||||
|
defer = _.defer |
||||
|
defineComponent = defineComponent |
||||
|
markRaw = markRaw |
||||
|
reactive = reactive |
||||
|
renderIcon = renderIcon |
||||
|
|
||||
|
JSON5 = JSON5 |
||||
|
json5 = JSON5 |
||||
|
app!: App |
||||
|
|
||||
|
errorDialogContent: string[] = reactive([]) |
||||
|
errorDialogIsShowing: boolean = false |
||||
|
|
||||
|
constructor(app: App) { |
||||
|
this.app = app |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 轻量级提示信息 |
||||
|
* @param message 消息内容 |
||||
|
*/ |
||||
|
msg(message: string) { |
||||
|
// ElMessage(cc)
|
||||
|
console.trace(message) |
||||
|
|
||||
|
const $body = $('body') |
||||
|
|
||||
|
$body.find('[xtype=tooltip]').remove() |
||||
|
const $w = $( |
||||
|
'<div xtype="tooltip" class="yvan-msg yvan-anim yvan-anim-00">' + |
||||
|
' <div class="yvan-msg-content">' + |
||||
|
_.escape(message) + |
||||
|
'</div></div>' |
||||
|
) |
||||
|
$body.append($w) |
||||
|
|
||||
|
const iframeWidth = $w.parent().width() as number |
||||
|
const iframeHeight = $w.parent().height() as number |
||||
|
|
||||
|
const windowWidth = $w.width() as number |
||||
|
const windowHeight = $w.height() as number |
||||
|
|
||||
|
let setWidth = (iframeWidth - windowWidth) / 2 |
||||
|
let setHeight = (iframeHeight - windowHeight) / 2 |
||||
|
if (iframeHeight < windowHeight || setHeight < 0) { |
||||
|
setHeight = 0 |
||||
|
} |
||||
|
if (iframeWidth < windowWidth || setWidth < 0) { |
||||
|
setWidth = 0 |
||||
|
} |
||||
|
$w.css({ left: setWidth, top: setHeight }) |
||||
|
setTimeout(() => { |
||||
|
$w.remove() |
||||
|
}, 3000) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 弹出用户必须点击确认的错误信息 |
||||
|
*/ |
||||
|
showErrorDialog(msgOrTitle: string, msg?: string, dangerouslyUseHTMLString?: boolean, closeCallBack?: () => void) { |
||||
|
let title, message |
||||
|
if (!msg) { |
||||
|
console.trace(msgOrTitle) |
||||
|
title = '错误' |
||||
|
message = msgOrTitle |
||||
|
|
||||
|
} else { |
||||
|
console.trace(msg) |
||||
|
title = msgOrTitle |
||||
|
message = msg |
||||
|
} |
||||
|
|
||||
|
// 如果有一样的内容,就不添加
|
||||
|
if (_.findIndex(this.errorDialogContent, r => r === message) >= 0) { |
||||
|
return |
||||
|
} |
||||
|
this.errorDialogContent.push(message) |
||||
|
if (!this.errorDialogIsShowing) { |
||||
|
// 只有在没有弹出对话框的清空下才弹出
|
||||
|
this.errorDialogIsShowing = true |
||||
|
ElMessageBox.alert( |
||||
|
//@ts-ignore
|
||||
|
() => { |
||||
|
if (this.errorDialogContent.length <= 0) { |
||||
|
return '' |
||||
|
} |
||||
|
return _.map(this.errorDialogContent, (item) => |
||||
|
h('div', null, item) |
||||
|
) |
||||
|
}, |
||||
|
title, |
||||
|
{ |
||||
|
dangerouslyUseHTMLString: false, |
||||
|
closeOnPressEscape: true, |
||||
|
closeOnClickModal: true, |
||||
|
confirmButtonText: '关闭', |
||||
|
type: 'error', |
||||
|
draggable: true, |
||||
|
callback: () => { |
||||
|
this.errorDialogIsShowing = false |
||||
|
nextTick(() => { |
||||
|
this.errorDialogContent.splice(0, this.errorDialogContent.length) |
||||
|
}) |
||||
|
if (closeCallBack) closeCallBack() |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 弹出用户必须确认的提示信息 |
||||
|
*/ |
||||
|
showInfoDialog(content: string, option: showDialogOption = { |
||||
|
confirmButtonText: '关闭', |
||||
|
draggable: true, |
||||
|
showCancelButton: false, |
||||
|
showClose: false, |
||||
|
dangerouslyUseHTMLString: true, |
||||
|
autofocus: true, |
||||
|
closeOnClickModal: false, |
||||
|
closeOnPressEscape: true |
||||
|
}) { |
||||
|
return this.alert(content, option) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 信息提示内容,强提示,必须用户点击确认 |
||||
|
*/ |
||||
|
alert(msg: string, option?: showDialogOption): Promise<any> { |
||||
|
console.trace(msg) |
||||
|
|
||||
|
// 将 msg 转译为 html enable 格式
|
||||
|
msg = _.join(_.split(_.escape(msg), '\n'), '<br />') |
||||
|
|
||||
|
const newOption = _.extend({ |
||||
|
confirmButtonText: '关闭', |
||||
|
draggable: true, |
||||
|
showCancelButton: false, |
||||
|
showClose: false, |
||||
|
dangerouslyUseHTMLString: true, |
||||
|
autofocus: true, |
||||
|
closeOnClickModal: false, |
||||
|
closeOnPressEscape: true |
||||
|
}, option) |
||||
|
|
||||
|
//@ts-ignore
|
||||
|
return ElMessageBox.alert(msg, '提示', newOption) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 弹出确认对话框 |
||||
|
* @param msg 消息内容 |
||||
|
*/ |
||||
|
confirm(msg: string): Promise<void> { |
||||
|
return new Promise((resolve, reject) => { |
||||
|
ElMessageBox.confirm(msg, '再次确认', |
||||
|
{ |
||||
|
confirmButtonText: '确定', |
||||
|
cancelButtonText: '取消', |
||||
|
icon: markRaw(QuestionFilled) |
||||
|
} |
||||
|
).then(() => { |
||||
|
resolve() |
||||
|
|
||||
|
}).catch(() => { |
||||
|
reject() |
||||
|
}) |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
import _ from 'lodash' |
||||
|
import $ from 'jquery' |
||||
|
import type System from '@/runtime/System' |
||||
|
|
||||
|
declare global { |
||||
|
const $: $ |
||||
|
const _: _ |
||||
|
const system: System |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
import AlarmMeta from '@/components/viewWidgets/alarm/AlarmMeta' |
||||
|
import LoggerMeta from '@/components/viewWidgets/logger/LoggerMeta' |
||||
|
import ModeltreeMeta from '@/components/viewWidgets/modeltree/ModeltreeMeta' |
||||
|
import MonitorMeta from '@/components/viewWidgets/monitor/MonitorMeta' |
||||
|
import PropertyMeta from '@/components/viewWidgets/property/PropertyMeta' |
||||
|
import ScriptMeta from '@/components/viewWidgets/script/ScriptMeta' |
||||
|
import TaskMeta from '@/components/viewWidgets/task/TaskMeta' |
||||
|
|
||||
|
import FileMenu from '@/components/viewMenus/fileMenu/FileMenu' |
||||
|
import EditMenu from '@/components/viewMenus/editMenu/EditMenu' |
||||
|
|
||||
|
/** |
||||
|
* 初始化模型编辑器的基础控件 |
||||
|
*/ |
||||
|
export default function ModelMainInit() { |
||||
|
AlarmMeta.install() |
||||
|
LoggerMeta.install() |
||||
|
ModeltreeMeta.install() |
||||
|
MonitorMeta.install() |
||||
|
PropertyMeta.install() |
||||
|
ScriptMeta.install() |
||||
|
TaskMeta.install() |
||||
|
|
||||
|
FileMenu.install() |
||||
|
EditMenu.install() |
||||
|
} |
||||
@ -1,3 +0,0 @@ |
|||||
<template> |
|
||||
AlarmView |
|
||||
</template> |
|
||||
@ -1,3 +0,0 @@ |
|||||
<template> |
|
||||
AlarmView |
|
||||
</template> |
|
||||
@ -1,3 +0,0 @@ |
|||||
<template> |
|
||||
AlarmView |
|
||||
</template> |
|
||||
@ -1,3 +0,0 @@ |
|||||
<template> |
|
||||
AlarmView |
|
||||
</template> |
|
||||
@ -1,3 +0,0 @@ |
|||||
<template> |
|
||||
AlarmView |
|
||||
</template> |
|
||||
@ -1,3 +0,0 @@ |
|||||
<template> |
|
||||
AlarmView |
|
||||
</template> |
|
||||
Loading…
Reference in new issue