123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356 |
- <template>
- <div class="magic-script-editor">
- <div class="magic-empty-container" v-if="openedScripts.length === 0">
- <div class="magic-hot-key">
- <p>
- {{ $i('message.save') }}<em>Ctrl + S</em><br/>
- {{ $i('message.run') }}<em>Ctrl + Q</em><br/>
- {{ $i('editor.tooltip.complection')}}<em>Alt + /</em><br/>
- {{ $i('editor.tooltip.resume')}}<em>F8</em><br/>
- {{ $i('editor.tooltip.stepInto')}}<em>F6</em><br/>
- {{ $i('editor.tooltip.format')}}<em>Ctrl + Alt + L</em><br/>
- {{ $i('editor.tooltip.recent')}}<em>Ctrl + E</em>
- </p>
- </div>
- </div>
- <template v-else>
- <magic-tab v-model:value="selectTab" :tabs="openedScripts" className="magic-script-tab" ref="tab"
- :allow-close="true" @close="onClose" @change="tab => bus.$emit('open', tab)"
- @before-change="beforeChange" @item-contextmenu="onContextMenu">
- <template v-slot="{ tab }">
- <magic-text-icon :icon="tab.getIcon(tab.item)"/>{{ tab.item.name }}<span v-if="isUpdated(tab)">*</span>
- <magic-icon v-if="tab.item.lock === $LOCKED" icon="lock"/>
- <magic-avatar-group :users="activateUserFiles[tab.item.id] || []" :max="3" :size="20"/>
- </template>
- </magic-tab>
- <magic-loading :loading="loading">
- <div class="magic-monaco-editor-wrapper">
- <magic-monaco-editor ref="editor" v-model:value="selectTab.item.script" v-model:decorations="selectTab.decorations" :language="selectTab.language" :support-breakpoint="true"/>
- </div>
- </magic-loading>
- </template>
- </div>
- </template>
- <script setup>
- import { getCurrentInstance, inject, nextTick, onMounted, reactive, ref, toRaw, watch } from 'vue'
- import bus from '../../../scripts/bus.js'
- import request from '../../../scripts/request.js'
- import constants from '../../../scripts/constants.js'
- import Message from '../../../scripts/constants/message.js'
- import { Range } from 'monaco-editor'
- import { convertVariables } from '../../../scripts/utils.js'
- import RequestParameter from '../../../scripts/editor/request-parameter.js'
- import Socket from '../../../scripts/constants/socket.js'
- import store from '../../../scripts/store.js'
- import $i from '../../../scripts/i18n.js'
- const { proxy } = getCurrentInstance()
- const openedScripts = reactive([])
- const selectTab = ref({})
- const loading = ref(true)
- const editor = ref(null)
- const tab = ref(null)
- const activateUserFiles = inject('activateUserFiles')
- const javaTypes = {
- 'String': 'java.lang.String',
- 'Integer': 'java.lang.Integer',
- 'Double': 'java.lang.Double',
- 'Long': 'java.lang.Long',
- 'Byte': 'java.lang.Byte',
- 'Short': 'java.lang.Short',
- 'Float': 'java.lang.Float',
- 'MultipartFile': 'org.springframework.web.multipart.MultipartFile',
- 'MultipartFiles': 'java.util.List',
- }
- RequestParameter.setEnvironment(() => {
- const env = {}
- const item = selectTab.value?.item
- const append = array => array && Array.isArray(array) && array.forEach(it => {
- if(it && typeof it.name === 'string' && it.dataType){
- env[it.name] = javaTypes[it.dataType] || 'java.lang.Object'
- }
- })
- if(item){
- append(item?.parameters)
- append(item?.paths)
- }
- return env
- })
- // 关闭tab
- const onClose = tab => {
- let index = openedScripts.findIndex(it => it === tab)
- openedScripts.splice(index, 1)
- if(tab === selectTab.value){
- let len = openedScripts.length
- if(index < len){
- bus.$emit(Message.OPEN, openedScripts[index])
- } else if(len > 0) {
- bus.$emit(Message.OPEN, openedScripts[index - 1])
- }
- }
- bus.$emit(Message.CLOSE, tab.item)
- // 没有打开的文件时
- if(openedScripts.length === 0){
- // 设置标题为空
- bus.$emit(Message.OPEN_EMPTY)
- selectTab.value = {}
- }
- }
- watch(openedScripts, (newVal) => {
- store.set(constants.RECENT_OPENED_TAB, newVal.filter(it => it.item?.id).map(it => it.item.id))
- })
- // 执行保存
- const doSave = (flag) => {
- const opened = selectTab.value
- if(opened && opened.item){
- const item = selectTab.value.processSave(opened.item)
- Object.keys(item).forEach(key => opened.item[key] = item[key])
- return request.sendJson(`/resource/file/${selectTab.value.type}/save?auto=${flag ? 0 : 1}`, item).success((id) => {
- const msg = `${opened.title}「${opened.path()}」`
- if(id) {
- bus.status('message.saveSuccess', true, msg)
- opened.tmpObject = JSON.parse(JSON.stringify(item))
- if(opened.item.id !== id){
- bus.loading(1)
- }
- opened.item.id = id
- }else{
- bus.status('message.saveFailed', false, msg)
- proxy.$alert($i('message.saveFailed', msg))
- }
- })
- }
- }
- // 执行测试
- const doTest = () => selectTab.value.doTest(selectTab.value)
- const doContinue = step => {
- if(selectTab.value.debuging){
- editor.value.removedDecorations(selectTab.value.debugDecorations)
- selectTab.value.debuging = false
- selectTab.value.variables = null
- const breakpoints = (selectTab.value.decorations || []).filter(it => it.options.linesDecorationsClassName === 'breakpoints').map(it => it.range.startLineNumber).join('|')
- bus.send(Socket.RESUME_BREAKPOINT, [selectTab.value.item.id, (step === true ? '1' : '0'), breakpoints].join(','))
- }
- }
- // tab 右键菜单事件
- const onContextMenu = (event, item, index) => {
- const menus = [{
- label: $i('editor.tab.close'),
- divided: true,
- onClick(){
- onClose(item)
- }
- },{
- label: $i('editor.tab.closeOther'),
- divided: true,
- onClick(){
- [...openedScripts].forEach((it, i) => i != index && onClose(it))
- }
- },{
- label: $i('editor.tab.closeLeft'),
- onClick(){
- [...openedScripts].forEach((it, i) => i < index && onClose(it))
- }
- },{
- label: $i('editor.tab.closeRight'),
- divided: true,
- onClick(){
- [...openedScripts].forEach((it, i) => i > index && onClose(it))
- }
- },{
- label: $i('editor.tab.closeAll'),
- onClick(){
- [...openedScripts].forEach(it => onClose(it))
- }
- }]
- proxy.$contextmenu({ menus, event})
- }
- // 判断是否有变动
- const isUpdated = (tab) => Object.keys(tab.tmpObject || {}).some(k => {
- const v1 = tab.tmpObject[k]
- const v2 = tab.item[k]
- if(v1 === v2 || k === 'properties' || k === 'responseBody' || k === 'responseBodyDefinition'){
- return false
- }
- return (typeof v1 === 'object' || typeof v2 === 'object') ? JSON.stringify(v1) !== JSON.stringify(v2) : v1 !== v2
- })
- // 退出登录
- bus.$on(Message.LOGOUT, () => [...openedScripts].forEach(tab => onClose(tab)))
- // 执行删除时
- bus.$on(Message.DELETE_FILE, item => {
- const index = openedScripts.findIndex(it => it.item === item)
- if(index > -1){
- onClose(openedScripts[index])
- }
- })
- // 重新加载资源完毕时
- bus.$on(Message.RELOAD_RESOURCES_FINISH, ()=> {
- openedScripts.filter(opened => opened.item && opened.item.id).forEach(opened => request.sendGet(`/resource/file/${opened.item.id}`).success(data => {
- bus.status('message.getDetailSuccess', true, `${opened.title}「${opened.path()}」`)
- Object.keys(data).forEach(key => opened.item[key] = data[key])
- }))
- })
- // 登录响应
- bus.$event(Socket.LOGIN_RESPONSE, () => {
- if(selectTab.value){
- bus.send(Socket.SET_FILE_ID, selectTab.value.item?.id || '0')
- }
- })
- const beforeChange = tab => {
- if(tab && editor.value){
- tab.scrollTop = editor.value.getScrollTop()
- }
- }
- // 打开文件
- bus.$on(Message.OPEN, opened => {
- let find = openedScripts.find(it => it.item === opened.item || (it.item.id && it.item.id === opened.item.id))
- bus.send(Socket.SET_FILE_ID, opened.item.id || '0')
- if(find){
- selectTab.value = find
- loading.value = false
- nextTick(() => editor.value.setScrollTop(find.scrollTop || 0))
- } else {
- openedScripts.push(opened)
- selectTab.value = opened
- if(opened.item.id && !opened.item.script){
- loading.value = true
- request.sendGet(`/resource/file/${opened.item.id}`).success(data => {
- bus.status('message.getDetail', true, `${opened.title}「${opened.path()}」`)
- Object.keys(data).forEach(key => opened.item[key] = data[key])
- opened.tmpObject = JSON.parse(JSON.stringify(opened.processSave(data)))
- loading.value = false
- nextTick(() => editor.value.setScrollTop(0))
- })
- } else {
- opened.tmpObject = JSON.parse(JSON.stringify(opened.processSave(opened.item)))
- loading.value = false
- nextTick(() => editor.value.setScrollTop(0))
- }
- }
- if(selectTab.value.decorations && selectTab.value.decorations.length > 0){
- nextTick(() => {
- const decorations = toRaw(selectTab.value.decorations)
- selectTab.value.debugDecorations = editor.value.appendDecoration(decorations)
- .map((it, index) => decorations[index].options?.className === 'debug-line' ? it : null)
- .filter(it => it !== null) || []
- })
- }
- nextTick(() => tab.value && tab.value.scrollIntoView(opened))
- })
- // 保存事件
- bus.$on(Message.DO_SAVE, doSave)
- // 测试事件
- bus.$on(Message.DO_TEST, () => {
- const opened = selectTab.value
- if(opened && opened.item && opened.runnable && opened.doTest && opened.running !== true){
- if(constants.AUTO_SAVE && opened.item.lock !== '1'){
- doSave().end(successed => successed && doTest())
- } else {
- doTest()
- }
- }
- })
- // 进入断点
- bus.$event(Socket.BREAKPOINT, ([ scriptId, { range, variables } ]) => {
- // 如果切换或已关闭
- if(selectTab.value?.item?.id !== scriptId){
- const opened = openedScripts.find(it => it.item.id === scriptId)
- if(opened){
- // 切换tab
- bus.$emit(Message.OPEN, opened)
- }else{
- // TODO 重新打开
- }
- }
- nextTick(() => {
- selectTab.value.variables = convertVariables(variables)
- selectTab.value.debuging = true
- selectTab.value.debugDecorations = [editor.value.appendDecoration([{
- range: new Range(range[0], 1, range[0], 1),
- options: {
- isWholeLine: true,
- inlineClassName: 'debug-line',
- className: 'debug-line'
- }
- }])]
- bus.$emit(Message.SWITCH_TOOLBAR, 'debug')
- })
- })
- // 恢复断点
- bus.$on(Message.DEBUG_CONTINUE, doContinue)
- // 断点单步运行
- bus.$on(Message.DEBUG_SETPINTO, () => doContinue(true))
- // 执行出现异常
- bus.$event(Socket.EXCEPTION, ([ [scriptId, message, line] ]) => {
- if(selectTab.value?.item?.id === scriptId){
- const range = new Range(line[0], line[2], line[1], line[3] + 1)
- const instance = editor.value.getInstance()
- const decorations = instance.deltaDecorations([], [{
- range,
- options: {
- hoverMessage: {
- value: message
- },
- inlineClassName: 'squiggly-error'
- }
- }])
- instance.revealRangeInCenter(range)
- instance.focus()
- if(constants.DECORATION_TIMEOUT >= 0){
- setTimeout(() => instance.deltaDecorations(decorations, []), constants.DECORATION_TIMEOUT)
- }
- }
- })
- const emit = defineEmits(['onLoad'])
- onMounted(() => emit('onLoad'))
- </script>
- <style scoped>
- .magic-script-editor{
- flex: 1;
- overflow: hidden;
- position: relative;
- }
- .magic-script-editor .magic-monaco-editor-wrapper{
- position: absolute;
- top: 30px;
- left: 0;
- right: 0;
- bottom: 0;
- }
- .magic-empty-container{
- flex: 1;
- position: relative;
- width: 100%;
- height: 100%;
- background: var(--empty-background-color);
- }
- .magic-hot-key{
- position: absolute;
- top: 50%;
- margin-top: -105px;
- text-align: center;
- color: var(--empty-color);
- font-size: 16px;
- width: 100%;
- }
- .magic-hot-key p{
- display: inline-block;
- text-align: left;
- line-height: 30px;
- }
- .magic-hot-key p em{
- margin-left: 15px;
- font-style: normal;
- color: var(--empty-key-color);
- }
- .magic-monaco-editor{
- position: absolute;
- top: 0;
- bottom: 0;
- left:0;
- right: 0;
- overflow: visible !important;
- }
- .magic-script-editor :deep(.magic-avatar-group){
- margin-left: 10px;
- }
- </style>
|