NEZ-3038 feat:web terminal支持个性化设置

This commit is contained in:
zhangyu
2023-08-09 16:48:09 +08:00
parent b8915bb47b
commit 527d206af8
12 changed files with 301 additions and 26 deletions

View File

@@ -309,5 +309,11 @@
text-align: center;
}
}
.terminal-menu {
position: fixed;
width: 150px;
border-radius: 2px;
z-index: 1000;
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -5,6 +5,13 @@
"css_prefix_text": "nz-icon-",
"description": "",
"glyphs": [
{
"icon_id": "7775074",
"name": "个性化",
"font_class": "personalization",
"unicode": "e645",
"unicode_decimal": 58949
},
{
"icon_id": "36651290",
"name": "Batch Synchronize",

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,32 @@
<template>
<div :id="'ternimalContainer'+idIndex" class="console" v-watermark="{text: waterMarkText, text1: terminal.host || '', textColor: 'rgba(215, 215, 215, 0.5)'}">
<div :id="'ternimalContainer'+idIndex" class="console" v-watermark="{show: showWatermark,text: waterMarkText, text1: terminal.host || '', textColor: 'rgba(215, 215, 215, 0.5)'}" v-clickoutside="clickHide">
<div :id="'terminal'+idIndex" style="height: 100%;width: 100%"></div>
<fileDirectory :host="host" v-clickoutside="closeFileDir" :uuid="terminal.uuid" v-show="fileDirectoryShow" @close="showFileDir(false)" :fileDirectoryShow="fileDirectoryShow" ref="fileDirectory"/>
<div
v-if="terminal.isLogin && showMenu"
class="terminal-menu menus"
:style="{
left: menuPosition.left + 'px',
top: menuPosition.top + 'px'
}"
>
<div>
<span @click="copySelection()">{{$t('overall.duplicate')}}</span>
</div>
<div>
<span @click="paste()">{{$t('project.topology.paste')}}</span>
</div>
<div>
<span @click="clearTerminal()">{{$t('terminal.clear')}}</span>
</div>
<div class="line"></div>
<div>
<span @click="showFileDir(true)">{{$t('terminal.sftp')}}</span>
</div>
<div>
<span @click="closeTerminal(true)"> {{$t('overall.close')}}</span>
</div>
</div>
</div>
</template>
<script>
@@ -23,9 +48,12 @@ export default {
type: Boolean,
default: false
},
terminalSetting: {
type: Object,
default: () => {}
},
fontSize: {}
},
inject: ['terminalSetting'],
data () {
return {
term: null,
@@ -51,9 +79,21 @@ export default {
userName: '',
fileDirectoryShow: false,
host: '',
waterMarkText: 'Nezha'
waterMarkText: 'Nezha',
showWatermark: false,
wordSeparator: "\\ :;~`!@#$%^&*()-=+|[]{}'\",.<>/?",
showMenu: false,
menuPosition: {
left: 0,
top: 0
}
}
},
// computed: {
// terminalSetting () {
// return this.provObj.terminalSetting
// }
// },
watch: {
},
methods: {
@@ -99,7 +139,8 @@ export default {
fontSize: 16,
lineHeight: 1.2,
allowTransparency: true,
background: 'transparent'
background: 'transparent',
scrollback: this.terminalSetting.scrollbackLines
})
this.term.open(terminalContainer)
this.term.focus()
@@ -134,6 +175,7 @@ export default {
// 连接成功onclose
this.terminalSocket.onopen = () => {
this.terminal.isLogin = true
this.showMenu = false
this.isInit = true
this.term.focus()
}
@@ -146,11 +188,8 @@ export default {
//that.term.write(data);
} */
})
// 监听选中文字的事件
this.term.on('selection', (p,a) => {
console.log(this.term.getSelection())
this.$copyText(this.term.getSelection())
})
// 个性化配置
this.initTerminalSetting()
// 返回
this.terminalSocket.onmessage = function (evt) {
let backContent = evt.data
@@ -278,7 +317,10 @@ export default {
})
},
clearTerminal () {
this.term.reset()
this.term.clear()
},
closeTerminal () {
this.$emit('close')
},
enterStr (message) {
if (this.terminalSocket && this.terminal.isLogin) {
@@ -293,16 +335,120 @@ export default {
// 新增历史记录
historyChange (message) {
this.$emit('historyChange', message)
},
initTerminalSetting () { // 个性化配置
// 1 render
this.showWatermark = this.terminalSetting.watermark
console.log(this.terminalSetting.watermark)
this.term.on('selection', (p, a) => {
if (this.terminalSetting.copyOnSelect) {
this.copySelection()
}
})
// this.term.on('click', () => {
// console.log('click')
// console.log(this.term.getSelection())
// })
// this.term.on('dbclick', () => {
// console.log('doubleclick123')
// console.log(this.term.getSelection())
// })
this.term.selectionManager._isCharWordSeparator = (charData) => {
console.log(charData)
if (charData[2] === 0) {
return false
}
return this.wordSeparator.indexOf(charData[1]) >= 0
}
},
renderTerminalSetting () {
this.showWatermark = this.terminalSetting.watermark
this.wordSeparator = this.terminalSetting.wordSeparator
this.term.setOption({
scrollback: this.terminalSetting.scrollbackLines
})
},
copySelection () {
let str = this.term.getSelection()
if (!this.terminalSetting.copyWithFormatting) {
str = str.replace(/[\r\n]/g, '')
}
if (this.terminalSetting.copyTrimEnd) {
str = str.replace(/(\s*$)/g, '')
}
this.$copyText(str)
},
dblclick () {
if (this.term) {
if (this.terminalSetting.copyOnSelect) {
this.copySelection()
}
}
},
async contextmenu (event) {
event.preventDefault()
event.stopPropagation()
if (this.terminalSetting.rightClick === 'none') {
return
}
if (this.terminalSetting.rightClick === 'menu') {
this.tiggerMenu(true, event)
}
if (this.terminalSetting.rightClick === 'paste') {
if (!document.execCommand('paste')) {
const text = await navigator.clipboard.readText()
this.term.send(text)
}
}
},
async paste () {
if (!document.execCommand('paste')) {
const text = await navigator.clipboard.readText()
this.term.send(text)
}
},
tiggerMenu (flag, event) {
this.showMenu = false
if (flag) {
let top = 0
let left = 0
if (event.clientY < document.body.clientHeight / 2) {
top = event.clientY + 10
} else {
top = event.clientY - 180 - 10
}
if (event.clientX < document.body.clientWidth / 2) {
left = event.clientX + 10
} else {
left = event.clientX - 150 - 10
}
this.menuPosition = {
left: left,
top: top
}
}
this.showMenu = flag
},
clickHide () {
this.showMenu = false
}
},
mounted () {
this.beforeCreate()
console.log(this.terminalSetting)
this.showWatermark = this.terminalSetting.watermark
const dom = document.getElementById('ternimalContainer' + this.idIndex)
dom.addEventListener('dblclick', this.dblclick)
dom.addEventListener('contextmenu', this.contextmenu)
dom.addEventListener('click', this.clickHide)
},
beforeDestroy () {
this.closeSocket()
this.term.off('selection')
this.term.off('data')
const dom = document.getElementById('ternimalContainer' + this.idIndex)
dom && dom.removeEventListener('dblclick', this.dblclick)
dom && dom.removeEventListener('contextmenu', this.contextmenu)
dom && dom.removeEventListener('click', this.clickHide)
}
}
</script>

View File

@@ -15,6 +15,9 @@
<div class="personal-dropdown__username">{{name}}</div>
<div class="personal-dropdown__name">@{{name}}</div>
</div>
<el-dropdown-item>
<div id="header-to-personalization" @click="showPersonalization"><i class="nz-icon nz-icon-personalization"></i>{{$t('terminal.personal')}}</div>
</el-dropdown-item>
<el-dropdown-item>
<div id="header-to-logout" @click="logout"><i class="nz-icon nz-icon-exit"></i>{{$t('overall.signOut')}}</div>
</el-dropdown-item>
@@ -24,7 +27,7 @@
</div>
</div>
<fileListState v-clickoutside="hideFileState" ref="fileListState"/>
<webSSHNew ref="websshNew" />
<webSSHNew ref="websshNew" :terminalSetting="terminalSetting" />
<div class="shell-input">
<el-input ref="shellInput" :placeholder="placeholder" size="small" v-model="message" @keyup.enter.native="sendMessage" @focus="placeholderChange('focus')" @blur="placeholderChange('blur')">
<i slot="suffix" class="nz-icon nz-icon-history no-style-class" :class="{'active':visible}" :title="$t('terminal.history')" @click="toggleHistory"></i>
@@ -48,6 +51,54 @@
</div>
</transition>
</div>
<!--弹窗-->
<el-dialog :modal-append-to-body='false' :z-index="1000" :show-close="true" :visible.sync="personalization" @close="personalization = false" class="nz-dialog webshell-selectAsset" width="650px">
<div slot="title">{{$t('webshell.selAsset')}}</div>
<div>
<el-form v-model="newTerminalSetting" label-width="180px" size="small" ref="terminalForm" :validate-on-rule-change="false">
<div class="system-title">{{$t('terminal.render')}}</div>
<el-form-item :label="$t('waterMaker')" prop="watermark">
<el-switch v-model="newTerminalSetting.watermark" :active-value='true' :inactive-value='false' id="terminal-setting-watermark_change" size="small">
</el-switch>
</el-form-item>
<el-form-item :label="$t('terminal.scrollbackLines')" prop="scrollbackLines">
<el-input-number :min="1" :precision="0" :controls="false" :placeholder="'默认25000'" v-model="newTerminalSetting.scrollbackLines" size="small"></el-input-number>
</el-form-item>
<div class="system-title">{{$t('terminal.mouse')}}</div>
<el-form-item :label="$t('terminal.rightClick')" prop="rightClick">
<el-radio-group v-model="newTerminalSetting.rightClick" size="small">
<el-radio-button label="none">{{$t('project.topology.none')}}</el-radio-button>
<el-radio-button label="menu">{{$t('terminal.menu')}}</el-radio-button>
<el-radio-button label="paste">{{$t('terminal.paste')}}</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item :label="$t('terminal.wordSeparator')" prop="wordSeparator">
<el-input size="small" v-model="newTerminalSetting.wordSeparator"></el-input>
</el-form-item>
<div class="system-title">{{$t('terminal.clipboard')}}</div>
<el-form-item :label="$t('terminal.copySelect')" prop="copyOnSelect">
<el-switch v-model="newTerminalSetting.copyOnSelect" :active-value='true' :inactive-value='false' id="terminal-setting-watermark_change">
</el-switch>
</el-form-item>
<el-form-item :label="$t('terminal.copyFormatting')" prop="copyWithFormatting">
<el-switch v-model="newTerminalSetting.copyWithFormatting" :active-value='true' :inactive-value='false' id="terminal-setting-watermark_change">
</el-switch>
</el-form-item>
<el-form-item :label="$t('terminal.copyTrimEnd')" prop="copyTrimEnd">
<el-switch v-model="newTerminalSetting.copyTrimEnd" :active-value='true' :inactive-value='false' id="terminal-setting-watermark_change">
</el-switch>
</el-form-item>
</el-form>
</div>
<div slot="footer">
<button class="footer__btn footer__btn--light" @click="personalization = false">
<span>{{$t('overall.cancel')}}</span>
</button>
<button class="footer__btn" :disabled="prevent_opt.save" @click.prevent="savePersonalization">
<span>{{$t('overall.save')}}</span>
</button>
</div>
</el-dialog>
</div>
</template>
@@ -79,13 +130,15 @@ export default {
visible: false,
// 历史命令记录
historyArr: [],
personalization: false,
newTerminalSetting: {},
terminalSetting: {
watermark: false,
watermark: true,
scrollbackLines: 25000,
rightClick: 'menu',
wordSeparator: "\\ :;~`!@#$%^&*()-=+|[]{}'\",.<>/?",
copyOnSelect: false,
copyWithFormatting: false,
copyWithFormatting: true,
copyTrimEnd: false
}
}
@@ -110,6 +163,7 @@ export default {
const self = this
this.name = localStorage.getItem('nz-username')
this.username = localStorage.getItem('nz-username')
this.initTerminalSetting()
window.opener.name = 'parent'
window.onbeforeunload = () => {
const opener = window.opener
@@ -199,6 +253,39 @@ export default {
isLogout: true
})
)
},
showPersonalization () {
this.personalization = true
this.newTerminalSetting = this.$lodash.cloneDeep(this.terminalSetting)
},
savePersonalization () {
this.$put('/sys/user/preference', { terminal: JSON.stringify(this.newTerminalSetting) }).then(res => {
if (res.code === 200) {
this.terminalSetting = this.$lodash.cloneDeep(this.newTerminalSetting)
this.$refs.websshNew.renderTerminalSetting()
this.personalization = false
} else {
this.$message.error(res.msg || res.error)
}
})
},
initTerminalSetting () {
this.$get('/sys/user/preference', { key: 'terminal' }).then(res => {
if (res.code === 200) {
if (res.data.terminal) {
let terminal = {}
try {
terminal = JSON.parse(res.data.terminal)
} catch (e) {}
this.terminalSetting = {
...this.terminalSetting,
...terminal
}
}
} else {
this.$message.error(res.msg || res.error)
}
})
}
}
}

View File

@@ -64,12 +64,14 @@
<terminal
:fontSize="fontSize"
:terminalSetting="terminalSetting"
:terminalType="item.terminal.terminalType"
:idIndex="index"
:ref="'console'+index"
:terminal="item.terminal"
@loginFail="loginFail"
@closeConsole="removeTab"
@close="closeShell(item,index)"
@refreshConsoleTitle="refreshTabTitle"
@historyChange="(message)=>{$parent.historyChange(message)}"
></terminal>
@@ -200,6 +202,12 @@ export default {
terminal,
selectTable
},
props: {
terminalSetting: {
type: Object,
default: () => {}
},
},
computed: {
language () { return this.$store.getters.getLanguage },
fileList () {
@@ -448,6 +456,7 @@ export default {
this.$refs['console' + el.index][0].resize()
this.$refs['console' + el.index][0].term.scrollToBottom()
this.$refs['console' + el.index][0].focusConsole()
this.$refs['console' + el.index][0].clickHide()
})
}
},
@@ -805,6 +814,11 @@ export default {
this.editableTabs.forEach((item, index) => {
this.$refs['console' + index][0].enterStr(message)
})
},
renderTerminalSetting () {
this.editableTabs.forEach((item, index) => {
this.$refs['console' + index][0].renderTerminalSetting()
})
}
},
watch: {

View File

@@ -1127,25 +1127,27 @@ export const watermark = {
bind (el, binding) {
const text = binding.value.text
const text1 = binding.value.text1
const show = binding.value.show
const font = binding.value.font || '24px Roboto-Regular'
const textColor = binding.value.textColor || 'rgba(215, 215, 215, 0.5)'
const width = binding.value.width || 400
const height = binding.value.height || 200
const textRotate = binding.value.textRotate || -30
addWaterMarker(el, text, font, textColor, width, height, textRotate, text1)
addWaterMarker(el, text, font, textColor, width, height, textRotate, show, text1)
},
update (el, binding) {
const text = binding.value.text
const text1 = binding.value.text1
const show = binding.value.show
const font = binding.value.font || '16px Roboto-Regular'
const textColor = binding.value.textColor || 'rgba(215, 215, 215, 0.5)'
const width = binding.value.width || 400
const height = binding.value.height || 200
const textRotate = binding.value.textRotate || -20
addWaterMarker(el, text, font, textColor, width, height, textRotate, text1)
addWaterMarker(el, text, font, textColor, width, height, textRotate, show, text1)
},
}
function addWaterMarker (parentNode, text, font, textColor, width, height, textRotate, text1) {
function addWaterMarker (parentNode, text, font, textColor, width, height, textRotate, show, text1) {
const can = document.createElement('canvas')
parentNode.appendChild(can)
can.width = width
@@ -1160,5 +1162,9 @@ function addWaterMarker (parentNode, text, font, textColor, width, height, textR
cans.textBaseline = 'middle'
cans.fillText(text, 200, 150)
cans.fillText(text1, 200, 170)
parentNode.style.backgroundImage = 'url(' + can.toDataURL('image/png') + ')'
if (show) {
parentNode.style.backgroundImage = 'url(' + can.toDataURL('image/png') + ')'
} else {
parentNode.style.backgroundImage = ''
}
}