|
|
@ -1,13 +1,48 @@ |
|
|
|
<template> |
|
|
|
<q-field v-bind="attrs" :stack-label="stackLabelRef" @focus="focus" @blur="blur"> |
|
|
|
<div v-show="showIfComputed"> |
|
|
|
<q-field |
|
|
|
ref="fieldRef" |
|
|
|
v-model="codeMirrorValue" |
|
|
|
:hide-bottom-space="true" |
|
|
|
:hide-hint="true" |
|
|
|
:outlined="true" |
|
|
|
:dense="true" |
|
|
|
v-bind="attrs" |
|
|
|
:stack-label="stackLabelRef" |
|
|
|
:rules="rulesComputed" |
|
|
|
:readonly="readonlyIfComputed" |
|
|
|
:disable="disableIfComputed" |
|
|
|
style="position: relative" |
|
|
|
@focus.stop.prevent="focus" |
|
|
|
@blur.stop.prevent="blur" |
|
|
|
@update:model-value="updateModelValue" |
|
|
|
> |
|
|
|
<template #label> <span v-if="requiredIfComputed" style="color: red">*</span> {{ attrs.label }}</template> |
|
|
|
<template #control> |
|
|
|
<div ref="codemirrorRef" style="width: 100%"></div> |
|
|
|
<div ref="codemirrorRef" style="width: 100%" @focus.stop.prevent="() => {}" @click.stop.prevent="() => {}"></div> |
|
|
|
</template> |
|
|
|
<template #append> |
|
|
|
<q-btn |
|
|
|
round |
|
|
|
dense |
|
|
|
flat |
|
|
|
v-bind="attrs.button" |
|
|
|
:style="{ |
|
|
|
content: '', |
|
|
|
position: 'absolute', |
|
|
|
bottom: '5px', |
|
|
|
right: '5px', |
|
|
|
}" |
|
|
|
@click.stop.prevent="buttonClick(attrs.button)" |
|
|
|
/> |
|
|
|
</template> |
|
|
|
</q-field> |
|
|
|
</div> |
|
|
|
</template> |
|
|
|
<script setup lang="ts"> |
|
|
|
import { ref, useAttrs, onMounted, onUnmounted, watch } from 'vue'; |
|
|
|
import { ref, useAttrs, onMounted, onUnmounted, watch, computed, toRaw } from 'vue'; |
|
|
|
import { Tools } from '@/platform'; |
|
|
|
import { FormValidators } from '@/platform/components'; |
|
|
|
import { EditorView } from '@codemirror/view'; |
|
|
|
import { EditorState, Compartment } from '@codemirror/state'; |
|
|
|
import * as view from '@codemirror/view'; |
|
|
@ -25,7 +60,8 @@ import { sql } from '@codemirror/lang-sql'; |
|
|
|
import { xml } from '@codemirror/lang-xml'; |
|
|
|
|
|
|
|
const attrs = useAttrs(); |
|
|
|
|
|
|
|
const rules = attrs.rules; |
|
|
|
const fieldRef = ref(); |
|
|
|
const props = defineProps({ |
|
|
|
modelValue: { type: String, default: '' }, |
|
|
|
lang: { type: String, default: 'json' }, |
|
|
@ -33,9 +69,76 @@ const props = defineProps({ |
|
|
|
height: { type: [Number, String], default: undefined }, |
|
|
|
rows: { type: Number, default: 4 }, |
|
|
|
tabSize: { type: Number, default: 4 }, |
|
|
|
showIf: { |
|
|
|
type: Function, |
|
|
|
default: () => { |
|
|
|
return true; |
|
|
|
}, |
|
|
|
}, |
|
|
|
required: { |
|
|
|
type: Boolean, |
|
|
|
default: false, |
|
|
|
}, |
|
|
|
requiredIf: { |
|
|
|
type: Function, |
|
|
|
default: undefined, |
|
|
|
}, |
|
|
|
readonlyIf: { |
|
|
|
type: Function, |
|
|
|
default: () => { |
|
|
|
return false; |
|
|
|
}, |
|
|
|
}, |
|
|
|
disableIf: { |
|
|
|
type: Function, |
|
|
|
default: () => { |
|
|
|
return false; |
|
|
|
}, |
|
|
|
}, |
|
|
|
form: { |
|
|
|
type: Object, |
|
|
|
default: undefined, |
|
|
|
}, |
|
|
|
}); |
|
|
|
|
|
|
|
const emits = defineEmits(['update:modelValue']); |
|
|
|
const codeMirrorValue = ref(props.modelValue); |
|
|
|
watch( |
|
|
|
() => props.modelValue, |
|
|
|
(newVal, oldVal) => { |
|
|
|
codeMirrorValue.value = newVal; |
|
|
|
}, |
|
|
|
); |
|
|
|
|
|
|
|
const rulesComputed = computed(() => { |
|
|
|
let result = rules || <any>[]; |
|
|
|
if (showIfComputed.value && requiredIfComputed.value) { |
|
|
|
result.push(FormValidators.required()); |
|
|
|
} else if (!showIfComputed.value) { |
|
|
|
result = []; |
|
|
|
} |
|
|
|
if (fieldRef?.value) { |
|
|
|
fieldRef.value.resetValidation(); |
|
|
|
} |
|
|
|
return result; |
|
|
|
}); |
|
|
|
|
|
|
|
const showIfComputed = computed(() => { |
|
|
|
return props.showIf(props.form); |
|
|
|
}); |
|
|
|
const requiredIfComputed = computed(() => { |
|
|
|
if (props.requiredIf) { |
|
|
|
return props.requiredIf(props.form) || false; |
|
|
|
} else if (props.required) { |
|
|
|
return true; |
|
|
|
} |
|
|
|
return false; |
|
|
|
}); |
|
|
|
const readonlyIfComputed = computed(() => { |
|
|
|
return props.readonlyIf(props.form); |
|
|
|
}); |
|
|
|
const disableIfComputed = computed(() => { |
|
|
|
return props.disableIf(props.form); |
|
|
|
}); |
|
|
|
|
|
|
|
const basicSetup = [ |
|
|
|
EditorState.allowMultipleSelections.of(true), |
|
|
@ -96,7 +199,7 @@ const codemirrorRef = ref(); |
|
|
|
// q-field 的 stack-label 属性是对 field 的 label 进行设置效果 |
|
|
|
// 如果 field 有值时, 无论是否获得焦点 label 都缩小显示 |
|
|
|
// 如果 field 无值时, 如果 field 获得焦点 label 就缩小显示, 否则 label 放大显示 |
|
|
|
const stackLabelRef = ref(!Tools.isUndefinedOrNull(props.modelValue)); |
|
|
|
const stackLabelRef = ref(!Tools.isUndefinedOrNull(codeMirrorValue.value)); |
|
|
|
|
|
|
|
let editorView; |
|
|
|
let isFocus = false; |
|
|
@ -123,14 +226,14 @@ onMounted(() => { |
|
|
|
}), |
|
|
|
], |
|
|
|
parent: codemirrorRef.value, |
|
|
|
doc: props.modelValue, |
|
|
|
doc: codeMirrorValue.value, |
|
|
|
}); |
|
|
|
watch( |
|
|
|
() => props.modelValue, |
|
|
|
() => codeMirrorValue.value, |
|
|
|
() => { |
|
|
|
// 当未获得焦点时,更新变更, 当获得焦点时不能更新 |
|
|
|
if (!isFocus) { |
|
|
|
editorView.dispatch({ changes: { from: 0, to: editorView.state.doc.length, insert: props.modelValue } }); |
|
|
|
editorView.dispatch({ changes: { from: 0, to: editorView.state.doc.length, insert: codeMirrorValue.value } }); |
|
|
|
} |
|
|
|
}, |
|
|
|
); |
|
|
@ -165,6 +268,15 @@ const setValue = (value: string) => { |
|
|
|
editorView.dispatch({ changes: { from: 0, to: editorView.state.doc.length, insert: value } }); |
|
|
|
}; |
|
|
|
|
|
|
|
const updateModelValue = (value) => { |
|
|
|
emits('update:modelValue', value); |
|
|
|
}; |
|
|
|
const buttonClick = (button) => { |
|
|
|
if (button.click) { |
|
|
|
button.click(toRaw(codeMirrorValue), props.form); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
defineExpose({ |
|
|
|
getValue, |
|
|
|
setValue, |
|
|
|