<template>
|
<div v-bind="boxBindProps">
|
<!-- 全屏按钮 -->
|
<a-icon v-if="fullScreen" class="full-screen-icon" :type="fullScreenIcon" @click="onToggleFullScreen" />
|
<textarea ref="textarea" v-bind="getBindValue"></textarea>
|
</div>
|
</template>
|
|
<script lang="ts">
|
import { defineComponent, onMounted, reactive, ref, watch, unref, computed } from 'vue';
|
import { propTypes } from '/@/utils/propTypes';
|
import { useRuleFormItem } from '/@/hooks/component/useFormItem';
|
// 引入全局实例
|
import _CodeMirror, { EditorFromTextArea } from 'codemirror';
|
// 核心样式
|
import 'codemirror/lib/codemirror.css';
|
// 引入主题后还需要在 options 中指定主题才会生效
|
import 'codemirror/theme/idea.css';
|
// 需要引入具体的语法高亮库才会有对应的语法高亮效果
|
import 'codemirror/mode/javascript/javascript.js';
|
import 'codemirror/mode/css/css.js';
|
import 'codemirror/mode/xml/xml.js';
|
import 'codemirror/mode/clike/clike.js';
|
import 'codemirror/mode/markdown/markdown.js';
|
import 'codemirror/mode/python/python.js';
|
import 'codemirror/mode/r/r.js';
|
import 'codemirror/mode/shell/shell.js';
|
import 'codemirror/mode/sql/sql.js';
|
import 'codemirror/mode/swift/swift.js';
|
import 'codemirror/mode/vue/vue.js';
|
// 折叠资源引入:开始
|
import 'codemirror/addon/fold/foldgutter.css';
|
import 'codemirror/addon/fold/foldcode.js';
|
import 'codemirror/addon/fold/brace-fold.js';
|
import 'codemirror/addon/fold/comment-fold.js';
|
import 'codemirror/addon/fold/indent-fold.js';
|
import 'codemirror/addon/fold/foldgutter.js';
|
// 折叠资源引入:结束
|
//光标行背景高亮,配置里面也需要styleActiveLine设置为true
|
import 'codemirror/addon/selection/active-line.js';
|
// 支持代码自动补全
|
import 'codemirror/addon/hint/show-hint.css';
|
import 'codemirror/addon/hint/show-hint.js';
|
import 'codemirror/addon/hint/anyword-hint.js';
|
import { useAttrs } from '/@/hooks/core/useAttrs';
|
import { useDesign } from '/@/hooks/web/useDesign';
|
import { isJsonObjectString } from '/@/utils/is.ts';
|
|
export default defineComponent({
|
name: 'JCodeEditor',
|
// 不将 attrs 的属性绑定到 html 标签上
|
inheritAttrs: false,
|
components: {},
|
props: {
|
value: propTypes.string.def(''),
|
height: propTypes.string.def('auto'),
|
disabled: propTypes.bool.def(false),
|
// 是否显示全屏按钮
|
fullScreen: propTypes.bool.def(false),
|
// 全屏以后的z-index
|
zIndex: propTypes.any.def(999),
|
theme: propTypes.string.def('idea'),
|
language: propTypes.string.def(''),
|
},
|
emits: ['change', 'update:value'],
|
setup(props, { emit }) {
|
const { prefixCls } = useDesign('code-editer');
|
const CodeMirror = window.CodeMirror || _CodeMirror;
|
const emitData = ref<object>();
|
//表单值
|
const [state] = useRuleFormItem(props, 'value', 'change', emitData);
|
const textarea = ref<HTMLTextAreaElement>();
|
let coder: Nullable<EditorFromTextArea> = null;
|
const attrs = useAttrs();
|
const height = ref(props.height);
|
const options = reactive({
|
// 缩进格式
|
tabSize: 2,
|
// 主题,对应主题库 JS 需要提前引入
|
theme: props.theme,
|
smartIndent: true, // 是否智能缩进
|
// 显示行号
|
lineNumbers: true,
|
line: true,
|
// 启用代码折叠相关功能:开始
|
foldGutter: true,
|
lineWrapping: true,
|
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'],
|
// 启用代码折叠相关功能:结束
|
// 光标行高亮
|
styleActiveLine: true,
|
//代码格式化
|
extraKeys: {
|
Tab: function autoFormat(editor) {
|
//var totalLines = editor.lineCount();
|
//editor.autoFormatRange({line:0, ch:0}, {line:totalLines});
|
setValue(innerValue, false);
|
},
|
},
|
});
|
// 内部存储值,初始为 props.value
|
let innerValue = props.value ?? '';
|
// 全屏状态
|
const isFullScreen = ref(false);
|
const fullScreenIcon = computed(() => (isFullScreen.value ? 'fullscreen-exit' : 'fullscreen'));
|
// 外部盒子参数
|
const boxBindProps = computed(() => {
|
let _props = {
|
class: [
|
prefixCls,
|
'full-screen-parent',
|
'auto-height',
|
{
|
'full-screen': isFullScreen.value,
|
},
|
],
|
style: {},
|
};
|
if (isFullScreen.value) {
|
_props.style['z-index'] = props.zIndex;
|
}
|
return _props;
|
});
|
/**
|
* 监听组件值
|
*/
|
watch(
|
() => props.value,
|
() => {
|
if (innerValue != props.value) {
|
setValue(props.value, false);
|
}
|
}
|
);
|
onMounted(() => {
|
initialize();
|
});
|
|
/**
|
* 组件赋值
|
* @param value
|
* @param trigger 是否触发 change 事件
|
*/
|
function setValue(value: string, trigger = true) {
|
if (value && isJsonObjectString(value)) {
|
value = JSON.stringify(JSON.parse(value), null, 2);
|
}
|
coder?.setValue(value ?? '');
|
innerValue = value;
|
trigger && emitChange(innerValue);
|
}
|
|
//编辑器值修改事件
|
function onChange(obj) {
|
let value = obj.getValue();
|
innerValue = value || '';
|
if (props.value != innerValue) {
|
emitChange(innerValue);
|
}
|
}
|
|
function emitChange(value) {
|
emit('change', value);
|
emit('update:value', value);
|
}
|
|
//组件初始化
|
function initialize() {
|
coder = CodeMirror.fromTextArea(textarea.value!, options);
|
//绑定值修改事件
|
coder.on('change', onChange);
|
// 初始化成功时赋值一次
|
setValue(innerValue, false);
|
}
|
|
// 切换全屏状态
|
function onToggleFullScreen() {
|
isFullScreen.value = !isFullScreen.value;
|
}
|
|
//update-begin-author:taoyan date:2022-5-9 for: codeEditor禁用功能
|
watch(
|
() => props.disabled,
|
(val) => {
|
if (coder) {
|
coder.setOption('readOnly', val);
|
}
|
}
|
);
|
//update-end-author:taoyan date:2022-5-9 for: codeEditor禁用功能
|
|
// 支持动态设置语言
|
watch(()=>props.language, (val)=>{
|
if(val && coder){
|
coder.setOption('mode', val);
|
}
|
});
|
|
const getBindValue = Object.assign({}, unref(props), unref(attrs));
|
|
//update-begin-author:taoyan date:2022-10-18 for: VUEN-2480【严重bug】online vue3测试的问题 8、online js增强样式问题
|
function refresh(){
|
if(coder){
|
coder.refresh();
|
}
|
}
|
//update-end-author:taoyan date:2022-10-18 for: VUEN-2480【严重bug】online vue3测试的问题 8、online js增强样式问题
|
|
return {
|
state,
|
textarea,
|
boxBindProps,
|
getBindValue,
|
setValue,
|
isFullScreen,
|
fullScreenIcon,
|
onToggleFullScreen,
|
refresh
|
};
|
},
|
});
|
</script>
|
|
<style lang="less">
|
//noinspection LessUnresolvedVariable
|
@prefix-cls: ~'@{namespace}-code-editer';
|
.@{prefix-cls} {
|
&.auto-height {
|
.CodeMirror {
|
height: v-bind(height) !important;
|
min-height: 100px;
|
}
|
}
|
|
/* 全屏样式 */
|
|
&.full-screen-parent {
|
position: relative;
|
|
.full-screen-icon {
|
opacity: 0;
|
color: black;
|
width: 20px;
|
height: 20px;
|
line-height: 24px;
|
background-color: white;
|
position: absolute;
|
top: 2px;
|
right: 2px;
|
z-index: 9;
|
cursor: pointer;
|
transition: opacity 0.3s;
|
padding: 2px 0 0 1.5px;
|
}
|
|
&:hover {
|
.full-screen-icon {
|
opacity: 1;
|
|
&:hover {
|
background-color: rgba(255, 255, 255, 0.88);
|
}
|
}
|
}
|
|
&.full-screen {
|
position: fixed;
|
top: 0;
|
right: 0;
|
bottom: 0;
|
left: 0;
|
padding: 8px;
|
background-color: #f5f5f5;
|
|
.full-screen-icon {
|
top: 12px;
|
right: 12px;
|
}
|
|
.full-screen-child,
|
.CodeMirror {
|
height: 100%;
|
max-height: 100%;
|
min-height: 100%;
|
}
|
}
|
|
.full-screen-child {
|
height: 100%;
|
}
|
}
|
|
/** VUEN-2344【vue3】这个样式有问题,是不是加个边框 */
|
.CodeMirror{
|
border: 1px solid #ddd;
|
}
|
}
|
</style>
|