vite+ts uses vue-codemirror to implement sql and js code editor

To achieve the effect, the ui library chooses the latest naive-ui library. You can choose the appropriate library according to your needs
1. Install dependencies
npm install vue-codemirror --save

At the same time, install a code editor using sql/javascript language and choose according to your needs.

npm install @codemirror/lang-sql
npm install @codemirror/lang-javascript

If you want a black editor theme, you can install it from the command line

npm i @codemirror/theme-one-dark

Format/clear function for SQL code

1. npm install sql-formatter plug-in

npm install sql-formatter --save

2. Introduce the sql-formatter.js file

import sqlFormatter from 'sql-formatter';
2. Encapsulate the public component MyCodeEdit.vue. The complete code is as follows:
<template>
  <codemirror
    ref="codeEdit"
    v-model="sqlCode"
    :placeholder="editorPlaceholder"
    :style="{ height: editorHeight + 'px' }"
    :autofocus="true"
    :indent-with-tab="true"
    :tabSize="tabSize"
    :extensions="extensions"
    :scrollbarStyle="null"
    @change="emit('change', $event)"
  />
  <div class="sql-format">
    <div class="theme">
      <span style="margin-right: 6px">Theme</span>
      <n-switch :size="'small'" :value="theme" @click="handleTheme"></n-switch>
    </div>
    <span @click="formatSql" style="margin: 0 10px">Format SQL</span>
    <span @click="clearVal">Clear with one click</span>
  </div>
</template>

<script setup>
import { Codemirror } from "vue-codemirror";
import { sql } from "@codemirror/lang-sql";
import { defineEmits, ref, defineProps, computed, watch } from "vue";
import { NSwitch, NSpace } from "naive-ui";
import * as sqlFormatter from "sql-formatter";
import { oneDark } from "@codemirror/theme-one-dark";

const emit = defineEmits();
const props = defineProps({
  value: {
    type: String,
    default: "",
  },
  editorPlaceholder: {
    type: String,
    default: "Please enter the code",
  },
  editorHeight: {
    type: String,
    default: "300",
  },
  tabSize: {
    type: Number,
    default: 2,
  },
});
const codeEdit = ref();
const theme = ref(false);
const sqlCode = ref();
watch(
  () => props.value,
  () => {
    sqlCode.value = props.value;
  },
  {
    immediate: true,
    deep: true,
  }
);
const extensions = ref([sql()]);
function handleTheme() {
  console.log(theme.value);
  theme.value = !theme.value;
  theme.value
    ? (extensions.value = [sql(), oneDark])
    : (extensions.value = [sql()]);
}

//Code formatting
const formatSql = () => {
  sqlCode.value = sqlFormatter.format(sqlCode.value);
};
// clear value
const clearVal = () => {
  sqlCode.value = "";
};
</script>
<style scoped lang="scss">
.sql-format {
  background-color: #f7f7f7;
  display: flex;
  justify-content: flex-end;
  color: #2a99ff;
  padding: 10px;
  .theme {
    display: flex;
    justify-content: center;
    align-items: center;
  }

   & amp; > span:hover {
    cursor: pointer;
    text-decoration: underline;
  }

  > span:first-child {
    margin-right: 10px;
  }
}
</style>
3. Used in the page, I use it in the pop-up box and modify it as needed
<template>
  <n-modal
    v-model:show="props.show"
    :show-icon="false"
    @update:show="$emit('update:show')"
    preset="dialog"
    style="width: 80%"
    :title="title"
  >
    <div class="code-editor">
      <n-form
        :model="formParams"
        :rules="rules"
        ref="formRef"
        label-placement="top"
        :label-width="120"
        class="py-4"
        :label-align="'left'"
      >
        <n-form-item label="Custom query name" path="sqlName">
          <n-input v-model:value="formParams.sqlName" clearable />
        </n-form-item>
      </n-form>
      <div class="code-label">SQL editing area</div>
      <sql-code-edit
        :value="formParams.sqlCode"
        :editor-placeholder="'Please enter the sql statement'"
        :editor-height="'300'"
        :tab-size="4"
        @change="changeSqlCode"
      />
    </div>

    <template #action>
      <n-space>
        <n-button
          v-show="view === false"
          type="primary"
          :loading="formBtnLoading"
          @click="confirmForm"
          >Save</n-button
        >
        <n-button @click="emit('update:show')">Cancel</n-button>
      </n-space>
    </template>
  </n-modal>
</template>
<script lang="ts" setup>
import {
  computed,
  onBeforeUpdate,
  onMounted,
  reactive,
  ref,
  watch,
  withDefaults,
  unref,
  onUpdated,
  watchEffect,
} from "vue";
import {
  useMessage,
  FormItemRule,
  TreeSelectOption,
  NLog,
  NScrollbar,
  NText,
  NH6,
  NInputNumber,
  Button,
} from "naive-ui";
import { each, cloneDeep, filter, find, groupBy } from "lodash-es";
import SqlCodeEdit from "./coms/MyCodeEdit.vue";
const isEdit = computed(() => !!props.current?.id);
const title = computed(() => (isEdit.value ? "Edit" : "New"));
const message = useMessage();
interface IProps {
  show?: boolean;
  current?: Record<string, any> | null;
  view?: boolean;
  params?: any;
}
const props = withDefaults(defineProps<IProps>(), {
  show: false,
  view: false,
});
const formRef = ref();
// Child component passes value to parent component
const emit = defineEmits<{
  (e: "update:show"): void;
  (e: "reloadTable"): void;
}>();
const formParams = reactive<any>({
  sqlName: "",
  sqlCode: "",
});
// define form
const INIT_MODEL = {
  id: "",
  sqlName: "",
  sqlCode: "",
};
// Get sql statements in real time
const changeSqlCode = (val: any) => {
  formParams.sqlCode = val;
};
function handleSave() {
  console.log(formParams);
}

// Validation rules
const rules = {
  sqlName: {
    required: true,
    trigger: ["blur", "input"],
    message: "Please enter the query name",
  },
};
const formBtnLoading = ref(false);
// form submission
function confirmForm(e: any) {
  e.preventDefault();
  formBtnLoading.value = true;
  formRef.value.validate(async (errors: any) => {
    if (!errors) {
      setTimeout(() => {
        const params = { ...formParams };
        console.log(params);
        emit("update:show");
        emit("reloadTable");
      });
    } else {
      message.error("Please fill in the complete information");
    }
    formBtnLoading.value = false;
  });
}

watchEffect(() => {
  if (props.show) {
    each(INIT_MODEL, (v, field) => {
      formParams[field] = props.current?.[field];
    });
  } else {
    Object.keys(formParams).forEach((item) => {
      formParams[item] = null;
    });
  }
});
</script>
<style scoped lang="scss">
.sql-format {
  background-color: #f7f7f7;
  text-align: right;
  color: #2a99ff;
  padding: 10px;

  span:hover {
    cursor: pointer;
    text-decoration: underline;
  }

  > span:first-child {
    margin-right: 10px;
  }
}
</style>