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