feat: 修改终端配置.

This commit is contained in:
lijiahang
2023-12-11 19:21:11 +08:00
parent b9e38a85f8
commit f2ca6abcb0
13 changed files with 345 additions and 115 deletions

View File

@@ -1,5 +1,6 @@
package com.orion.ops.module.infra.handler.preference.model;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
@@ -24,9 +25,50 @@ public class TerminalPreferenceModel implements PreferenceModel {
private String darkTheme;
@Schema(description = "终端主题")
private JSONObject terminalTheme;
private JSONObject themeSchema;
@Schema(description = "显示设置")
private JSONObject viewSetting;
private JSONObject displaySetting;
@Schema(description = "背景设置")
private JSONObject backgroundSetting;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class DisplaySettingModel {
@Schema(description = "字体样式")
private String fontFamily;
@Schema(description = "字体大小")
private Integer fontSize;
@Schema(description = "行高")
private Double lineHeight;
@Schema(description = "文本字重")
private String fontWeight;
@Schema(description = "加粗字重")
private String fontWeightBold;
@Schema(description = "光标样式")
private String cursorStyle;
@Schema(description = "光标闪烁")
private Boolean cursorBlink;
/**
* 转为 json
*
* @return json
*/
public JSONObject toJson() {
return JSON.parseObject(JSON.toJSONString(this));
}
}
}

View File

@@ -18,8 +18,19 @@ public class TerminalPreferenceStrategy implements IPreferenceStrategy<TerminalP
public TerminalPreferenceModel getDefault() {
return TerminalPreferenceModel.builder()
.darkTheme("dark")
.terminalTheme(new JSONObject())
.viewSetting(new JSONObject())
.themeSchema(new JSONObject())
.displaySetting(TerminalPreferenceModel.DisplaySettingModel.builder()
.fontFamily("_")
.fontSize(15)
.lineHeight(1.00)
.fontWeight("normal")
.fontWeightBold("bold")
.cursorStyle("bar")
.cursorBlink(true)
.build()
.toJson()
)
.backgroundSetting(new JSONObject())
.build();
}

View File

@@ -1,4 +1,4 @@
import type { TerminalPreference, TerminalState, TerminalTheme } from './types';
import type { TerminalDisplaySetting, TerminalPreference, TerminalState, TerminalThemeSchema } from './types';
import { defineStore } from 'pinia';
import { getPreference, updatePreferencePartial } from '@/api/user/preference';
import { Message } from '@arco-design/web-vue';
@@ -24,8 +24,9 @@ export default defineStore('terminal', {
}),
preference: {
darkTheme: 'auto',
terminalTheme: {} as TerminalTheme,
}
themeSchema: {} as TerminalThemeSchema,
displaySetting: {} as TerminalDisplaySetting
},
}),
actions: {
@@ -39,14 +40,14 @@ export default defineStore('terminal', {
try {
const { data } = await getPreference<TerminalPreference>('TERMINAL');
// 设置默认终端主题
if (!data.config.terminalTheme?.name) {
data.config.terminalTheme = DEFAULT_SCHEMA;
if (!data.config.themeSchema?.name) {
data.config.themeSchema = DEFAULT_SCHEMA;
}
this.preference = data.config;
// 设置暗色主题
const userDarkTheme = data.config.darkTheme;
if (userDarkTheme === DarkTheme.AUTO) {
this.isDarkTheme = data.config.terminalTheme?.dark === true;
this.isDarkTheme = data.config.themeSchema?.dark === true;
} else {
this.isDarkTheme = userDarkTheme === DarkTheme.DARK;
}

View File

@@ -8,11 +8,12 @@ export interface TerminalState {
// 终端配置
export interface TerminalPreference {
darkTheme: string,
terminalTheme: TerminalTheme,
themeSchema: TerminalThemeSchema,
displaySetting: TerminalDisplaySetting,
}
// 终端主题
export interface TerminalTheme {
export interface TerminalThemeSchema {
name: string;
dark: boolean;
background: string;
@@ -41,3 +42,14 @@ export interface TerminalTheme {
[key: string]: unknown;
}
// 显示设置
export interface TerminalDisplaySetting {
fontFamily?: string;
fontSize?: number;
lineHeight?: number;
fontWeight?: string | number;
fontWeightBold?: string | number;
cursorStyle?: string;
cursorBlink?: boolean;
}

View File

@@ -204,16 +204,16 @@ body[terminal-theme='dark'] .host-layout {
}
// 终端设置容器
@setting-container-width: 1180px;
.terminal-setting-container {
padding: 32px 16px;
width: @setting-container-width;
width: max-content;
margin: auto;
display: flex;
flex-direction: column;
.terminal-setting-title {
margin: 0 0 24px 0;
user-select: none;
color: var(--color-content-text-3);
}
@@ -230,6 +230,7 @@ body[terminal-theme='dark'] .host-layout {
.terminal-setting-subtitle {
margin: 0;
user-select: none;
color: var(--color-content-text-3);
}

View File

@@ -36,7 +36,7 @@
import type { TabItem } from '../../types/terminal.const';
import { computed } from 'vue';
import { TabType, InnerTabs } from '../../types/terminal.const';
import TerminalViewSetting from '../theme-setting/terminal-view-setting.vue';
import TerminalViewSetting from '../view-setting/terminal-view-setting.vue';
const props = defineProps({
modelValue: {

View File

@@ -1,74 +0,0 @@
<template>
<div class="terminal-setting-block">
<!-- 顶部 -->
<div class="terminal-setting-subtitle-wrapper">
<h3 class="terminal-setting-subtitle">
字体设置
</h3>
</div>
<!-- 内容区域 -->
<div class="terminal-setting-body">
<div class="terminal-setting-form">
123
</div>
<!-- 预览区域 -->
<div class="terminal-example">
<div class="terminal-example-wrapper"
:style="{ background: terminalStore.preference.terminalTheme.background }">
<terminal-example :theme="terminalStore.preference.terminalTheme"
ref="previewTerminal" />
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
export default {
name: 'TerminalFontBlock'
};
</script>
<script lang="ts" setup>
import { ref, watch } from 'vue';
import TerminalExample from '../theme-setting/terminal-example.vue';
import { useTerminalStore } from '@/store';
const terminalStore = useTerminalStore();
const previewTerminal = ref();
watch(() => terminalStore.preference.terminalTheme, (v) => {
if (!v) {
return;
}
const options = previewTerminal.value?.term?.options;
options && (options.theme = v);
});
</script>
<style lang="less" scoped>
@terminal-width: 458px;
@terminal-height: 138px;
.terminal-setting-body {
height: 248px;
width: 100%;
border: 1px solid var(--color-border-2);
border-radius: 4px;
display: flex;
justify-content: space-between;
}
.terminal-example {
margin: auto 16px 16px 0;
&-wrapper {
border-radius: 4px;
width: calc(@terminal-width - 16px);
height: @terminal-height;
}
}
</style>

View File

@@ -0,0 +1,224 @@
<template>
<div class="terminal-setting-block">
<!-- 顶部 -->
<div class="terminal-setting-subtitle-wrapper">
<h3 class="terminal-setting-subtitle">
显示设置
</h3>
</div>
<!-- 内容区域 -->
<div class="terminal-setting-body">
<div class="terminal-setting-form">
<a-form :model="formModel" layout="vertical">
<a-space>
<!-- 字体样式 -->
<a-form-item field="fontFamily" label="字体样式">
<a-select v-model="formModel.fontFamily"
class="form-item form-item-font-family"
placeholder="请选择字体样式"
:options="toOptions(fontFamilyKey)"
:allow-create="true"
:filter-option="labelFilter">
<template #option="{ data }">
<span :style="{ fontFamily: data.value }">{{ data.label }}</span>
</template>
</a-select>
</a-form-item>
<!-- 字体大小 -->
<a-form-item field="fontSize" label="字体大小">
<a-select v-model="formModel.fontSize"
class="form-item form-item-font-size"
placeholder="请选择字体大小"
:options="toOptions(fontSizeKey)" />
</a-form-item>
<!-- 行高 -->
<a-form-item field="lineHeight" label="行高">
<a-input-number v-model="formModel.lineHeight"
class="form-item form-item-line-height"
placeholder="请输入行高"
:precision="2"
:min="1"
:max="2"
hide-button />
</a-form-item>
</a-space>
<a-space>
<!-- 普通文本字重 -->
<a-form-item field="fontWeight" label="普通文本字重">
<a-select v-model="formModel.fontWeight"
class="form-item form-item-font-weight"
placeholder="请选择字重"
:options="toOptions(fontWeightKey)" />
</a-form-item>
<!-- 加粗文本字重 -->
<a-form-item field="fontWeightBold" label="加粗文本字重">
<a-select v-model="formModel.fontWeightBold"
class="form-item form-item-font-bold-weight"
placeholder="请选择字重"
:options="toOptions(fontWeightKey)" />
</a-form-item>
</a-space>
<a-space>
<!-- 光标样式 -->
<a-form-item field="cursorStyle" label="光标样式">
<a-radio-group type="button"
v-model="formModel.cursorStyle"
class="form-item form-item-cursor-style usn"
:options="toOptions(cursorStyleKey)" />
</a-form-item>
<!-- 光标闪烁 -->
<a-form-item field="cursorBlink" label="光标是否闪烁">
<a-switch v-model="formModel.cursorBlink"
type="round"
class="form-item form-item-cursor-blink" />
</a-form-item>
</a-space>
</a-form>
</div>
<!-- 预览区域 -->
<div class="terminal-example">
<span class="terminal-example-label">预览效果</span>
<div class="terminal-example-wrapper"
:style="{ background: preference.themeSchema.background }">
<terminal-example :theme="preference.themeSchema"
ref="previewTerminal" />
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
export default {
name: 'TerminalFontBlock'
};
</script>
<script lang="ts" setup>
import type { TerminalDisplaySetting } from '@/store/modules/terminal/types';
import { onMounted, ref, watch } from 'vue';
import { useDictStore, useTerminalStore } from '@/store';
import { fontFamilyKey, fontSizeKey, fontWeightKey, fontFamilySuffix, cursorStyleKey } from '../../types/terminal.const';
import { labelFilter } from '@/types/form';
import { useDebounceFn } from '@vueuse/core';
import TerminalExample from '../view-setting/terminal-example.vue';
const { toOptions } = useDictStore();
const { preference, updatePreference } = useTerminalStore();
// 同步用户偏好 - 防抖函数
const sync = useDebounceFn(updatePreference, 1500);
const previewTerminal = ref();
const formModel = ref<TerminalDisplaySetting>({});
// 监听主题变化
watch(() => preference.themeSchema, (v) => {
if (!v) {
return;
}
const options = previewTerminal.value?.term?.options;
options && (options.theme = v);
});
// 监听内容变化
watch(formModel, (v) => {
if (!v) {
return;
}
const options = previewTerminal.value?.term?.options;
if (!options) {
return;
}
// 修改配置
Object.keys(v).forEach(key => {
if (key === 'fontFamily') {
options[key] = (formModel.value as any)[key] + fontFamilySuffix;
} else {
options[key] = (formModel.value as any)[key];
}
});
preference.displaySetting = formModel.value;
// 同步
sync();
// 聚焦
previewTerminal.value.term.focus();
}, { deep: true });
// 设置默认配置
onMounted(() => {
// 触发 watch 函数
formModel.value = { ...preference.displaySetting };
});
</script>
<style lang="less" scoped>
@terminal-width: 458px;
.terminal-setting-body {
height: 248px;
width: 100%;
padding: 16px;
border: 1px solid var(--color-fill-4);
border-radius: 4px;
display: flex;
justify-content: space-between;
}
:deep(.arco-form) {
.arco-form-item-label {
user-select: none;
}
.arco-form-item {
margin-bottom: 14px;
}
.form-item-font-family {
width: 158px;
}
.form-item-font-size {
width: 148px;
}
.form-item-line-height {
width: 114px;
}
.form-item-font-weight, .form-item-font-bold-weight {
width: 178px;
}
.form-item-font-weight {
margin-right: 70px;
}
.form-item-cursor-style {
margin-right: 90px;
.arco-radio-button-content {
padding: 0 20px;
}
}
}
.terminal-example {
height: 100%;
&-label {
color: var(--color-text-2);
display: block;
height: 16px;
margin-bottom: 12px;
user-select: none;
}
&-wrapper {
border-radius: 4px;
width: calc(@terminal-width - 16px);
height: calc(100% - 16px - 12px);
}
}
</style>

View File

@@ -9,12 +9,12 @@
</script>
<script lang="ts" setup>
import type { TerminalTheme } from '@/store/modules/terminal/types';
import type { TerminalThemeSchema } from '@/store/modules/terminal/types';
import { Terminal } from '@xterm/xterm';
import { onMounted, onUnmounted, ref } from 'vue';
const props = defineProps<{
theme: TerminalTheme | Record<string, any>
theme: TerminalThemeSchema | Record<string, any>
}>();
const terminal = ref();
@@ -22,22 +22,19 @@
onMounted(() => {
term.value = new Terminal({
theme: { ...props.theme, cursor: props.theme.background },
theme: props.theme,
cols: 47,
rows: 6,
fontSize: 15,
convertEol: true,
cursorBlink: false,
cursorInactiveStyle: 'none',
});
term.value.open(terminal.value);
term.value.write(
'[root@OrionServer usr]#\n' +
'dr-xr-xr-x. 2 root root bin\n' +
'dr-xr-xr-x. 2 root root sbin\n' +
'dr-xr-xr-x. 43 root root lib\n' +
'dr-xr-xr-x. 62 root root lib64\n' +
'[root@OrionServer usr]#\r\n' +
'dr-xr-xr-x. 2 root root bin\r\n' +
'dr-xr-xr-x. 2 root root sbin\r\n' +
'dr-xr-xr-x. 43 root root lib\r\n' +
'dr-xr-xr-x. 62 root root lib64\r\n' +
'lrwxrwxrwx. 1 root root tmp'
);
});

View File

@@ -7,6 +7,7 @@
</h3>
<!-- 暗色选择 -->
<a-radio-group :default-value="preference.darkTheme"
class="usn"
size="mini"
type="button"
@change="checkDarkTheme"
@@ -22,7 +23,7 @@
:key="theme.name"
class="terminal-theme-card simple-card"
:class="{
'terminal-theme-card-check': theme.name === preference.terminalTheme.name
'terminal-theme-card-check': theme.name === preference.themeSchema.name
}"
:title="theme.name"
:style="{
@@ -30,14 +31,15 @@
marginRight: index === 0 ? '16px' : 0
}"
:header-style="{
color: theme.dark ? 'rgba(255, 255, 255, .8)' : 'rgba(0, 0, 0, .8)'
color: theme.dark ? 'rgba(255, 255, 255, .8)' : 'rgba(0, 0, 0, .8)',
userSelect: 'none'
}"
@click="checkTheme(theme)">
<!-- 样例 -->
<terminal-example :theme="theme" />
<terminal-example :theme="{ ...theme, cursor: theme.background }" />
<!-- 选中按钮 -->
<icon-check class="theme-check-icon"
v-show="theme.name === preference.terminalTheme.name" />
v-show="theme.name === preference.themeSchema.name" />
</a-card>
</div>
</div>
@@ -51,7 +53,7 @@
</script>
<script lang="ts" setup>
import type { TerminalTheme } from '@/store/modules/terminal/types';
import type { TerminalThemeSchema } from '@/store/modules/terminal/types';
import { darkThemeKey } from '../../types/terminal.const';
import ThemeSchema from '../../types/terminal.theme';
import { useDebounceFn } from '@vueuse/core';
@@ -76,15 +78,15 @@
changeDarkTheme(false);
} else if (value === DarkTheme.AUTO) {
//
changeDarkTheme(preference.terminalTheme.dark);
changeDarkTheme(preference.themeSchema.dark);
}
//
sync();
};
//
const checkTheme = (theme: TerminalTheme) => {
preference.terminalTheme = theme;
const checkTheme = (theme: TerminalThemeSchema) => {
preference.themeSchema = theme;
//
if (preference.darkTheme === DarkTheme.AUTO) {
changeDarkTheme(theme.dark);

View File

@@ -3,8 +3,8 @@
<div class="view-setting-wrapper">
<!-- 主标题 -->
<h2 class="terminal-setting-title">外观设置</h2>
<!-- 字体设置 -->
<terminal-font-block />
<!-- 显示设置 -->
<terminal-display-block />
<!-- 主题设置 -->
<terminal-theme-block />
</div>
@@ -18,7 +18,7 @@
</script>
<script lang="ts" setup>
import TerminalFontBlock from './terminal-font-block.vue';
import TerminalDisplayBlock from './terminal-display-block.vue';
import TerminalThemeBlock from './terminal-theme-block.vue';
</script>
@@ -26,7 +26,6 @@
<style lang="less" scoped>
.view-setting-wrapper {
width: 932px;
user-select: none;
}
</style>

View File

@@ -43,8 +43,23 @@ export interface TabItem {
[key: string]: unknown;
}
// 字体后缀 兜底
export const fontFamilySuffix = ',courier-new, courier, monospace';
// 终端暗色模式 字典项
export const darkThemeKey = 'terminalDarkTheme';
// 终端字体样式
export const fontFamilyKey = 'terminalFontFamily';
// 终端字体大小
export const fontSizeKey = 'terminalFontSize';
// 终端字体字重
export const fontWeightKey = 'terminalFontWeight';
// 终端光标样式
export const cursorStyleKey = 'terminalCursorStyle';
// 加载的字典值
export const dictKeys = [darkThemeKey];
export const dictKeys = [darkThemeKey, fontFamilyKey, fontSizeKey, fontWeightKey, cursorStyleKey];

View File

@@ -1,4 +1,4 @@
import type { TerminalTheme } from '@/store/modules/terminal/types';
import type { TerminalThemeSchema } from '@/store/modules/terminal/types';
// 默认配色
export const DEFAULT_SCHEMA = {
@@ -248,4 +248,4 @@ export default [
brightCyan: '#2488FF',
brightWhite: '#EAE5FF'
}
] as Array<TerminalTheme>;
] as Array<TerminalThemeSchema>;