瀏覽代碼

添加通知模块及添加富文本编辑器

yangg 11 月之前
父節點
當前提交
ac73c35a44

+ 3 - 0
package.json

@@ -22,8 +22,11 @@
     "@suadelabs/vue3-multiselect": "^1.0.2",
     "@suadelabs/vue3-multiselect": "^1.0.2",
     "@vuelidate/core": "^2.0.0",
     "@vuelidate/core": "^2.0.0",
     "@vuelidate/validators": "^2.0.0",
     "@vuelidate/validators": "^2.0.0",
+    "@vueup/vue-quill": "^1.2.0",
     "@vueuse/core": "^9.3.0",
     "@vueuse/core": "^9.3.0",
     "@vueuse/head": "^0.9.7",
     "@vueuse/head": "^0.9.7",
+    "@wangeditor/editor": "^5.1.23",
+    "@wangeditor/editor-for-vue": "^5.1.12",
     "apexcharts": "^3.35.5",
     "apexcharts": "^3.35.5",
     "axios": "^1.6.8",
     "axios": "^1.6.8",
     "dotenv": "^16.4.5",
     "dotenv": "^16.4.5",

+ 67 - 0
src/api/templateManage.js

@@ -0,0 +1,67 @@
+import request from '@/utils/request'
+/* 获取模版列表 */
+export function getSMSList(params) {
+    return request({
+      url: '/admin/erp/SmsMailTemplate',
+      method: 'get',
+      loaidng: false,
+      params
+    })
+  }
+  /* 创建模版 */
+  export function addTemplate(parmas) {
+    return request({
+      url: `/admin/erp/SmsMailTemplate/createTemplate`,
+      method: 'post',
+      headers: { 'Content-Type': 'application/json' },
+      data: parmas
+    })
+  }
+
+  /*编辑模版*/
+
+export function editTemplate(id,parmas) {
+  return request({
+    url: `/admin/erp/SmsMailTemplate/updateTemplate/${id}`,
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    data: parmas
+  })
+}
+
+/* 修改状态  */
+
+export function editStatus(id) {
+  return request({
+    url: `/admin/erp/SmsMailTemplate/alterStatus/${id}`,
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+  })
+}
+
+/* 删除模版 */
+export function delTemplate(id,parmas) {
+    return request({
+      url: `/admin/erp/SmsMailTemplate/delete/${id}`,
+      method: 'post',
+      headers: { 'Content-Type': 'application/json' },
+      data: parmas
+    })
+  }
+
+/* 获取上级模板列表 */
+export function getPidList(type) {
+  return request({
+    url: `/admin/erp/SmsMailTemplate/getSysNotBand/${type}`,
+    method: 'get',
+    loaidng: false,
+  })
+}
+/* 获取套餐详情 */
+export function companyPackage(id) {
+  return request({
+    url: `/admin/erp/companyPackage/${id}`,
+    method: 'get',
+    loaidng: false,
+  })
+}

+ 1 - 1
src/app-setting.ts

@@ -13,7 +13,7 @@ export default {
         store.dispatch('toggleMenu', val);
         store.dispatch('toggleMenu', val);
 
 
         val = localStorage.getItem('superMenu'); // vertical, collapsible-vertical, horizontal
         val = localStorage.getItem('superMenu'); // vertical, collapsible-vertical, horizontal
-        val = val || $themeConfig.menu;
+        val = val || $themeConfig.superMenu;
         store.dispatch('toggleSuperMenu', val);
         store.dispatch('toggleSuperMenu', val);
 
 
         val = localStorage.getItem('layout'); // full, boxed-layout
         val = localStorage.getItem('layout'); // full, boxed-layout

+ 36 - 0
src/components/layout/Sidebar.vue

@@ -96,6 +96,42 @@
                 <ul class="sub-menu text-gray-500"></ul>
                 <ul class="sub-menu text-gray-500"></ul>
               </vue-collapsible>
               </vue-collapsible>
             </li>
             </li>
+            <li class="menu nav-item">
+              <button
+                type="button"
+                class="nav-link group w-full"
+                :class="{ active: activeDropdown === 'templateManage' }"
+                @click="activeDropdown === 'templateManage' ? (activeDropdown = null) : (activeDropdown = 'templateManage')"
+              >
+                <div class="flex items-center">
+                  <icon-menu-users class="group-hover:!text-primary shrink-0" />
+
+                  <span class="ltr:pl-3 rtl:pr-3 text-black dark:text-[#506690] dark:group-hover:text-white-dark">{{ $t('templateManage') }}</span>
+                </div>
+                <div :class="{ 'rtl:rotate-90 -rotate-90': activeDropdown == 'templateManage' }">
+                  <icon-caret-down />
+                </div>
+              </button>
+              <vue-collapsible :isOpen="activeDropdown === 'templateManage'">
+                <ul class="sub-menu text-gray-500">
+                  <li>
+                    <router-link to="/templateManage/smsTemplate" @click="toggleMobileMenu">{{ $t('smsTemplate') }}</router-link>
+                  </li>
+                  <li>
+                    <router-link to="/templateManage/emailtemplate" @click="toggleMobileMenu">{{ $t('emailtemplate') }}</router-link>
+                  </li>
+                  <li>
+                    <router-link to="/templateManage/accountTemplate" @click="toggleMobileMenu">{{ $t('accountTemplate') }}</router-link>
+                  </li>
+                   <li>
+                    <router-link to="/templateManage/systemMessage" @click="toggleMobileMenu">{{ $t('systemMessage') }}</router-link>
+                  </li>
+                </ul>
+              </vue-collapsible>
+              <vue-collapsible :isOpen="activeDropdown === 'package'">
+                <ul class="sub-menu text-gray-500"></ul>
+              </vue-collapsible>
+            </li>
             <li class="menu nav-item">
             <li class="menu nav-item">
               <button
               <button
                 type="button"
                 type="button"

+ 5 - 2
src/locales/zh.json

@@ -13,8 +13,11 @@
     "package":"套餐管理",
     "package":"套餐管理",
     "packageList":"套餐列表",
     "packageList":"套餐列表",
     "packagePermissions":"套餐权限",
     "packagePermissions":"套餐权限",
-
-    
+    "templateManage":"模版管理",
+    "smsTemplate":"短信模版",
+    "systemMessage":"系统消息模版",
+    "emailtemplate":"邮件模板",
+    "accountTemplate":"公众号模板",
 
 
     "sales": "销售量",
     "sales": "销售量",
     "analytics": "分析",
     "analytics": "分析",

+ 12 - 0
src/router/index.ts

@@ -44,6 +44,18 @@ const asyncRouterMap: RouteRecordRaw[] = [
             { path: 'packageAdd', component: () => import('@/views/package/packageAdd.vue'), name: 'packageAdd', meta: { title: 'packageAdd', layout: 'app' } },
             { path: 'packageAdd', component: () => import('@/views/package/packageAdd.vue'), name: 'packageAdd', meta: { title: 'packageAdd', layout: 'app' } },
         ]
         ]
     },
     },
+    {
+        path: '/templateManage',
+        redirect: '/templateManage',
+        name: 'templateManage',
+        children: [
+            { path: 'smsTemplate', component: () => import('@/views/templateManage/smsTemplate.vue'), name: 'smsTemplate', meta: { title: 'smsTemplate', layout: 'app' } },
+            { path: 'systemMessage', component: () => import('@/views/templateManage/systemMessage.vue'), name: 'systemMessage', meta: { title: 'systemMessage', layout: 'app' } },
+            { path: 'emailtemplate', component: () => import('@/views/templateManage/emailtemplate.vue'), name: 'emailtemplate', meta: { title: 'emailtemplate', layout: 'app' } },
+           { path: 'accountTemplate', component: () => import('@/views/templateManage/accountTemplate.vue'), name: 'accountTemplate', meta: { title: 'accountTemplate', layout: 'app' } },
+            /*{ path: 'goodsOutBound', component: () => import('@/views/templateManage/outbound.vue'), name: 'goodsOutBound', meta: { title: 'goodsOutBound' } }, */
+        ]
+    },
     {
     {
         path: '/setting',
         path: '/setting',
         redirect: '/setting',
         redirect: '/setting',

+ 303 - 0
src/views/templateManage/accountTemplate.vue

@@ -0,0 +1,303 @@
+<template>
+  <div class="search bg-white p-3">
+    <el-form :model="searchForm" label-width="100px">
+      <el-row>
+        <el-col :span="5">
+          <el-form-item label="状态">
+            <el-select v-model="searchForm.enable_status" clearable placeholder="请选择模板状态">
+              <el-option label="启用" :value="true" />
+              <el-option label="禁用" :value="false" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="5">
+          <el-form-item label="模板名称">
+            <el-input v-model="searchForm.name" clearable placeholder="请输入模板名称"></el-input>
+          </el-form-item>
+        </el-col>
+        <el-col :span="5">
+          <el-form-item label="模板code">
+            <el-input v-model="searchForm.code" clearable placeholder="请输入模板code"></el-input>
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <div class="flex justify-end">
+      <el-button class="mr-3" @click="searchEvent"> 搜索 </el-button>
+    </div>
+  </div>
+  <en-table-layout :tableData="tableData.records" :loading="loading">
+    <template #toolbar>
+      <div class="inner-toolbar w-11/12 flex justify-between">
+        <div class="toolbar-btns">
+          <el-button size="mini" type="primary" :icon="CirclePlus" v-if="checkPermission([''])" @click="handleAddRole">添加</el-button>
+        </div>
+        <!-- <div class="toolbar-search">
+                <en-table-search @search="searchEvent" placeholder="请输入关键字" />
+              </div> -->
+      </div>
+    </template>
+
+    <template #table-columns>
+      <el-table-column prop="name" label="模版名称" />
+      <!-- <el-table-column prop="template_type" label="短信类型" /> -->
+      <el-table-column prop="remark" label="模版说明" />
+      <!--  <el-table-column prop="audit_status" label="审核状态" /> -->
+      <el-table-column prop="enable_status" label="状态">
+        <template #default="scope">
+          <el-switch v-model="scope.row.enable_status" inline-prompt active-text="启用" inactive-text="禁用" @change="switchEnableStatus($event, scope.row)" />
+        </template>
+      </el-table-column>
+      <el-table-column label="操作">
+        <template #default="scope">
+          <el-button size="mini" @click="handleView(scope.row)" v-if="checkPermission([''])">查看</el-button>
+          <el-button size="mini" type="primary" @click="handleEditRole(scope.row)" v-if="checkPermission([''])">编辑</el-button>
+          <el-button size="mini" type="danger" @click="handleDeleteRole(scope.row)" v-if="checkPermission([''])">删除</el-button>
+        </template>
+      </el-table-column>
+    </template>
+
+    <template #pagination>
+      <el-pagination
+        v-if="tableData"
+        @size-change="handlePageSizeChange"
+        @current-change="handlePageCurrentChange"
+        :current-page="tableData.pages"
+        :page-sizes="[10, 20, 50, 100]"
+        :page-size="tableData.size"
+        layout="total, sizes, prev, pager, next, jumper"
+        :total="tableData.total"
+      >
+      </el-pagination>
+    </template>
+  </en-table-layout>
+  <el-dialog :title="templateTitle" v-model="systemVisible" :modal-append-to-body="false" :close-on-click-modal="false" :close-on-press-escape="false">
+    <el-form :model="accountForm" :rules="accountFormRules" ref="accountFormRef" label-width="120px">
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="上级模板:" prop="pid">
+            <el-select v-model="accountForm.pid" clearable placeholder="请选择上级模板" :disabled="disabled">
+              <el-option v-for="item in pidList" :key="item.id" :label="item.name" :value="item.id" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="模版名称:" prop="name">
+            <el-input v-model="accountForm.name" :minlength="2" :maxlength="20" :disabled="disabled" clearable placeholder="请输入模版名称"></el-input>
+          </el-form-item>
+        </el-col>
+        <!-- <el-form-item label="短信类型:" prop="template_type">
+            <el-select v-model="accountForm.template_type" clearable :disabled="disabled" placeholder="请选择短信类型">
+              <el-option label="验证码" value="0" />
+              <el-option label="短信通知" value="1" />
+              <el-option label="推广短信" value="2" />
+            </el-select>
+          </el-form-item> -->
+        <el-col :span="12">
+          <el-form-item label="模版code:" prop="code">
+            <el-input v-model="accountForm.code" :minlength="2" :maxlength="20" :disabled="disabled" clearable placeholder="请输入模版code"></el-input>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="是否启用:" prop="enable_status">
+            <el-switch v-model="accountForm.enable_status" inline-prompt :disabled="disabled" active-text="启用" inactive-text="禁用" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="24">
+        <el-form-item label="模版内容:" prop="content">
+          <!--   <div style="border: 1px solid #ccc">
+              <Toolbar style="border-bottom: 1px solid #ccc" :editor="editorRef" :defaultConfig="toolbarConfig" :mode="mode" />
+              <Editor
+                style="height: 300px; overflow-y: hidden"
+                v-model="accountForm.content"
+                :defaultConfig="editorConfig"
+                :readOnly="editorReadonly || disabled"
+                :mode="editorReadonly || disabled ? 'simple' : 'default'"
+                @onCreated="handleCreated"
+              />
+            </div> -->
+          <el-input v-model="accountForm.content"  type="textarea" :autosize="{ minRows: 3, maxRows: 4 }" :disabled="disabled" clearable placeholder="请输入模版内容"></el-input>
+        </el-form-item>
+    </el-col>
+        <el-col :span="24">
+          <el-form-item label="模版说明:" prop="remark">
+            <el-input
+              v-model="accountForm.remark"
+              type="textarea"
+              :disabled="disabled"
+              :autosize="{ minRows: 3, maxRows: 4 }"
+              placeholder="请输入模版说明"
+            ></el-input>
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="systemVisible = false">取 消</el-button>
+        <el-button type="primary" @click="submitaccountForm" :disabled="disabled">确 定</el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+            
+            <script lang="ts" setup>
+import { ref, onMounted, reactive, watch, shallowRef, onBeforeUnmount, nextTick } from 'vue';
+import { useRouter } from 'vue-router';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import { Delete, Edit, Search, Share, Upload, CirclePlus } from '@element-plus/icons-vue';
+import * as API_Template from '@/api/templateManage';
+
+const disabled = ref(false);
+
+const router = useRouter();
+/* 模版表单 */
+const pidList = ref<any>([]);
+let templateTitle = ref('');
+const accountFormRef = ref();
+const systemVisible = ref(false);
+const accountForm = ref<any>({
+  name: '',
+  content: '',
+  code: '',
+  enable_status: false,
+  remark: '',
+  type: 'OFFICIAL',
+});
+const accountFormRules = ref<any>({
+  pid: [{ required: true, message: '请选择上级模板', trigger: 'change' }],
+  name: [{ required: true, message: '请输入模版名称', trigger: 'blur' }],
+  content: [{ required: true, message: '请输入模版内容', trigger: 'blur' }],
+  code: [
+    { required: true, message: '请输入模版code', trigger: 'blur' },
+    { pattern: /^[a-zA-Z0-9]+$/, message: '只能输入英文和数字,不能包含空格', trigger: 'blur' },
+  ],
+  template_type: [{ required: true, message: '请选择短信类型', trigger: 'change' }],
+  /*  user_num: [{ required: true, message: '请输入可创建用户数', trigger: 'blur' }], */
+});
+/* 新增 */
+const handleAddRole = () => {
+  templateTitle.value = '添加模版';
+  disabled.value = false;
+  accountForm.value = {};
+  accountForm.value.type = 'OFFICIAL';
+  accountForm.value.enable_status = false;
+  systemVisible.value = true;
+};
+/* 表单提交 */
+const submitaccountForm = () => {
+  accountFormRef.value.validate((valid: boolean) => {
+    if (valid) {
+      const { id } = accountForm.value;
+      const params = { ...accountForm.value };
+      if (id) {
+        API_Template.editTemplate(id, params).then(() => {
+          systemVisible.value = false;
+          ElMessage.success('修改成功!');
+          GET_AccountList();
+        });
+      } else {
+        API_Template.addTemplate(params).then(() => {
+          systemVisible.value = false;
+          ElMessage.success('创建成功!');
+          GET_AccountList();
+        });
+      }
+    } else {
+      ElMessage.error('表单填写有误,请核对!');
+      /*    store.dispatch('showMessage', { message: '表单填写有误,请核对!', type: 'error' }); */
+      return false;
+    }
+  });
+};
+/* 修改状态 */
+const switchEnableStatus = (event, row) => {
+  API_Template.editStatus(row.id).then((res) => {
+    ElMessage.success('状态修改成功!');
+    GET_AccountList();
+  });
+};
+/* 查看模版 */
+const handleView = (row) => {
+  templateTitle.value = '查看模版';
+  accountForm.value = { ...row };
+  disabled.value = true;
+  systemVisible.value = true;
+};
+/* 编辑模版 */
+const handleEditRole = (row: any) => {
+  templateTitle.value = '编辑模版';
+  disabled.value = false;
+  accountForm.value = { ...row };
+  accountForm.value.type = 'OFFICIAL';
+  systemVisible.value = true;
+};
+/* 删除模版 */
+const handleDeleteRole = (row: any) => {
+  ElMessageBox.confirm('确定要删除这个模版吗?', '提示', { type: 'warning' })
+    .then(() => {
+      API_Template.delTemplate(row.id).then(() => {
+        ElMessage.success('删除成功!');
+        GET_AccountList();
+      });
+    })
+    .catch(() => {});
+};
+
+const loading = ref(false);
+const params = ref({
+  page_no: 1,
+  page_size: 10,
+  type: 'OFFICIAL',
+});
+const tableData = ref<any>({});
+/* 搜索 */
+const searchForm = reactive({
+  enable_status: '',
+  name: '',
+  code: '',
+  type: 'OFFICIAL',
+});
+
+const checkPermission = (permissions: string[]): boolean => {
+  return true;
+};
+
+const handlePageSizeChange = (size: number) => {
+  params.value.page_size = size;
+  GET_AccountList();
+};
+
+const handlePageCurrentChange = (page: number) => {
+  params.value.page_no = page;
+  GET_AccountList();
+};
+
+const searchEvent = (data: string) => {
+  Object.assign(params.value, searchForm);
+  params.value.page_no = 1;
+  GET_AccountList();
+};
+
+const GET_AccountList = () => {
+  loading.value = true;
+  API_Template.getSMSList(params.value)
+    .then((response) => {
+      loading.value = false;
+      tableData.value = response;
+    })
+    .catch(() => {
+      loading.value = false;
+    });
+};
+const GET_PidList = () => {
+  API_Template.getPidList('OFFICIAL').then((res) => {
+    pidList.value = res;
+  });
+};
+
+onMounted(() => {
+  GET_AccountList();
+  GET_PidList();
+});
+</script>

+ 369 - 0
src/views/templateManage/emailtemplate.vue

@@ -0,0 +1,369 @@
+<template>
+  <div class="search bg-white p-3">
+    <el-form :model="searchForm" label-width="100px">
+      <el-row>
+        <el-col :span="5">
+          <el-form-item label="状态">
+            <el-select v-model="searchForm.enable_status" clearable placeholder="请选择模板状态">
+              <el-option label="启用" :value="true" />
+              <el-option label="禁用" :value="false" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="5">
+          <el-form-item label="模板名称">
+            <el-input v-model="searchForm.name" clearable placeholder="请输入模板名称"></el-input>
+          </el-form-item>
+        </el-col>
+        <el-col :span="5">
+          <el-form-item label="模板code">
+            <el-input v-model="searchForm.code" clearable placeholder="请输入模板code"></el-input>
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <div class="flex justify-end">
+      <el-button class="mr-3" @click="searchEvent"> 搜索 </el-button>
+    </div>
+  </div>
+  <en-table-layout :tableData="tableData.records" :loading="loading">
+    <template #toolbar>
+      <div class="inner-toolbar w-11/12 flex justify-between">
+        <div class="toolbar-btns">
+          <el-button size="mini" type="primary" :icon="CirclePlus" v-if="checkPermission([''])" @click="handleAddRole">添加</el-button>
+        </div>
+        <!-- <div class="toolbar-search">
+              <en-table-search @search="searchEvent" placeholder="请输入关键字" />
+            </div> -->
+      </div>
+    </template>
+
+    <template #table-columns>
+      <el-table-column prop="name" label="模版名称" />
+      <!-- <el-table-column prop="template_type" label="短信类型" /> -->
+      <el-table-column prop="remark" label="模版说明" />
+      <!--  <el-table-column prop="audit_status" label="审核状态" /> -->
+      <el-table-column prop="enable_status" label="状态">
+        <template #default="scope">
+          <el-switch v-model="scope.row.enable_status" inline-prompt active-text="启用" inactive-text="禁用" @change="switchEnableStatus($event, scope.row)" />
+        </template>
+      </el-table-column>
+      <el-table-column label="操作">
+        <template #default="scope">
+          <el-button size="mini" @click="handleView(scope.row)" v-if="checkPermission([''])">查看</el-button>
+          <el-button size="mini" type="primary" @click="handleEditRole(scope.row)" v-if="checkPermission([''])">编辑</el-button>
+          <el-button size="mini" type="danger" @click="handleDeleteRole(scope.row)" v-if="checkPermission([''])">删除</el-button>
+        </template>
+      </el-table-column>
+    </template>
+
+    <template #pagination>
+      <el-pagination
+        v-if="tableData"
+        @size-change="handlePageSizeChange"
+        @current-change="handlePageCurrentChange"
+        :current-page="tableData.pages"
+        :page-sizes="[10, 20, 50, 100]"
+        :page-size="tableData.size"
+        layout="total, sizes, prev, pager, next, jumper"
+        :total="tableData.total"
+      >
+      </el-pagination>
+    </template>
+  </en-table-layout>
+  <el-dialog :title="templateTitle" v-model="systemVisible" :modal-append-to-body="false" :close-on-click-modal="false" :close-on-press-escape="false">
+    <el-form :model="EmailForm" :rules="EmailFormRules" ref="EmailFormRef" label-width="120px">
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="上级模板:" prop="pid">
+            <el-select v-model="EmailForm.pid" clearable placeholder="请选择上级模板" :disabled="disabled">
+              <el-option v-for="item in pidList" :key="item.id" :label="item.name" :value="item.id" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="模版名称:" prop="name">
+            <el-input v-model="EmailForm.name" :minlength="2" :maxlength="20" :disabled="disabled" clearable placeholder="请输入模版名称"></el-input>
+          </el-form-item>
+        </el-col>
+        <!-- <el-form-item label="短信类型:" prop="template_type">
+          <el-select v-model="EmailForm.template_type" clearable :disabled="disabled" placeholder="请选择短信类型">
+            <el-option label="验证码" value="0" />
+            <el-option label="短信通知" value="1" />
+            <el-option label="推广短信" value="2" />
+          </el-select>
+        </el-form-item> -->
+        <el-col :span="12">
+          <el-form-item label="模版code:" prop="code">
+            <el-input v-model="EmailForm.code" :minlength="2" :maxlength="20" :disabled="disabled" clearable placeholder="请输入模版code"></el-input>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="是否启用:" prop="enable_status">
+            <el-switch v-model="EmailForm.enable_status" inline-prompt :disabled="disabled" active-text="启用" inactive-text="禁用" />
+          </el-form-item>
+        </el-col>
+        <el-form-item label="模版内容:" prop="content">
+          <div style="border: 1px solid #ccc">
+            <Toolbar style="border-bottom: 1px solid #ccc" :editor="editorRef" :defaultConfig="toolbarConfig" :mode="mode" />
+            <Editor
+              style="height: 300px; overflow-y: hidden"
+              v-model="EmailForm.content"
+              :defaultConfig="editorConfig"
+              :readOnly="editorReadonly || disabled"
+              :mode="editorReadonly || disabled ? 'simple' : 'default'"
+              @onCreated="handleCreated"
+            />
+          </div>
+          <!-- <el-input v-model="EmailForm.content" :minlength="2" :maxlength="20" :disabled="disabled" clearable placeholder="请输入模版内容"></el-input> -->
+        </el-form-item>
+        <el-col :span="24">
+          <el-form-item label="模版说明:" prop="remark">
+            <el-input
+              v-model="EmailForm.remark"
+              type="textarea"
+              :disabled="disabled"
+              :autosize="{ minRows: 3, maxRows: 4 }"
+              placeholder="请输入模版说明"
+            ></el-input>
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="systemVisible = false">取 消</el-button>
+        <el-button type="primary" @click="submitEmailForm" :disabled="disabled">确 定</el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+          
+          <script lang="ts" setup>
+import { ref, onMounted, reactive, watch, shallowRef, onBeforeUnmount, nextTick } from 'vue';
+import { useRouter } from 'vue-router';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import { Delete, Edit, Search, Share, Upload, CirclePlus } from '@element-plus/icons-vue';
+import * as API_Template from '@/api/templateManage';
+import { createEditor, createToolbar, IEditorConfig, IDomEditor } from '@wangeditor/editor';
+import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
+import '@wangeditor/editor/dist/css/style.css';
+
+const editorReadonly = ref(false);
+const disabled = ref(false);
+const editorRef = shallowRef();
+const mode = ref('default');
+const toolbarConfig = {};
+const editorConfig = {
+  placeholder: '请输入内容...',
+  readOnly: editorReadonly.value || disabled.value,
+};
+
+// 组件销毁时,也及时销毁编辑器
+onBeforeUnmount(() => {
+  const editor = editorRef.value;
+  if (editor == null) return;
+  editor.destroy();
+});
+
+const handleCreated = (editor: IDomEditor) => {
+  editorRef.value = editor; // 记录 editor 实例,重要!
+};
+
+// 修改 onEditorContentChange 函数
+const onEditorContentChange = (editor: IDomEditor) => {
+  console.log('Editor content changed:', editor.getHtml());
+};
+
+const router = useRouter();
+/* 模版表单 */
+const pidList = ref<any>([]);
+let templateTitle = ref('');
+const EmailFormRef = ref();
+const systemVisible = ref(false);
+const EmailForm = ref<any>({
+  name: '',
+  content: '',
+  code: '',
+  enable_status: false,
+  remark: '',
+  type: 'MAIL',
+});
+const EmailFormRules = ref<any>({
+  pid: [{ required: true, message: '请选择上级模板', trigger: 'change' }],
+  name: [{ required: true, message: '请输入模版名称', trigger: 'blur' }],
+  content: [{ required: true, message: '请输入模版内容', trigger: 'blur' }],
+  code: [
+    { required: true, message: '请输入模版code', trigger: 'blur' },
+    { pattern: /^[a-zA-Z0-9]+$/, message: '只能输入英文和数字,不能包含空格', trigger: 'blur' },
+  ],
+  template_type: [{ required: true, message: '请选择短信类型', trigger: 'change' }],
+  /*  user_num: [{ required: true, message: '请输入可创建用户数', trigger: 'blur' }], */
+});
+/* 新增 */
+const handleAddRole = () => {
+  templateTitle.value = '添加模版';
+  disabled.value = false;
+  editorReadonly.value = false; // 允许编辑
+  EmailForm.value = {};
+  EmailForm.value.type = 'MAIL';
+  EmailForm.value.enable_status = false;
+  systemVisible.value = true;
+  nextTick(() => {
+    if (editorRef.value) {
+      editorRef.value.setHtml(''); // 清空编辑器内容
+      editorRef.value.enable(); // 启用编辑器
+    }
+  });
+};
+/* 表单提交 */
+const submitEmailForm = () => {
+  EmailFormRef.value.validate((valid: boolean) => {
+    if (valid) {
+      const { id } = EmailForm.value;
+      const params = { ...EmailForm.value };
+      // 确保获取最新的编辑器内容
+      if (editorRef.value) {
+        params.content = editorRef.value.getHtml();
+      }
+      if (id) {
+        API_Template.editTemplate(id, params).then(() => {
+          systemVisible.value = false;
+          ElMessage.success('修改成功!');
+          GET_EmailList();
+        });
+      } else {
+        API_Template.addTemplate(params).then(() => {
+          systemVisible.value = false;
+          ElMessage.success('创建成功!');
+          GET_EmailList();
+        });
+      }
+    } else {
+      ElMessage.error('表单填写有误,请核对!');
+      /*    store.dispatch('showMessage', { message: '表单填写有误,请核对!', type: 'error' }); */
+      return false;
+    }
+  });
+};
+/* 修改状态 */
+const switchEnableStatus = (event, row) => {
+  API_Template.editStatus(row.id).then((res) => {
+    ElMessage.success('状态修改成功!');
+    GET_EmailList();
+  });
+};
+/* 查看模版 */
+const handleView = (row) => {
+  templateTitle.value = '查看模版';
+  EmailForm.value = { ...row };
+  disabled.value = true;
+  editorReadonly.value = true; // 设置编辑器为只读
+  systemVisible.value = true;
+  nextTick(() => {
+    if (editorRef.value) {
+      editorRef.value.setHtml(EmailForm.value.content);
+      editorRef.value.disable(); // 禁用编辑器
+    }
+  });
+};
+/* 编辑模版 */
+const handleEditRole = (row: any) => {
+  templateTitle.value = '编辑模版';
+  disabled.value = false;
+  editorReadonly.value = false; // 允许编辑
+  EmailForm.value = { ...row };
+  EmailForm.value.type = 'MAIL';
+  systemVisible.value = true;
+  nextTick(() => {
+    if (editorRef.value) {
+      editorRef.value.setHtml(EmailForm.value.content);
+      editorRef.value.enable(); // 启用编辑器
+    }
+  });
+};
+/* 删除模版 */
+const handleDeleteRole = (row: any) => {
+  ElMessageBox.confirm('确定要删除这个模版吗?', '提示', { type: 'warning' })
+    .then(() => {
+      API_Template.delTemplate(row.id).then(() => {
+        ElMessage.success('删除成功!');
+        GET_EmailList();
+      });
+    })
+    .catch(() => {});
+};
+
+const loading = ref(false);
+const params = ref({
+  page_no: 1,
+  page_size: 10,
+  type: 'MAIL',
+});
+const tableData = ref<any>({});
+/* 搜索 */
+const searchForm = reactive({
+  enable_status: '',
+  name: '',
+  code: '',
+  type: 'MAIL',
+});
+
+const checkPermission = (permissions: string[]): boolean => {
+  return true;
+};
+
+const handlePageSizeChange = (size: number) => {
+  params.value.page_size = size;
+  GET_EmailList();
+};
+
+const handlePageCurrentChange = (page: number) => {
+  params.value.page_no = page;
+  GET_EmailList();
+};
+
+const searchEvent = (data: string) => {
+  Object.assign(params.value, searchForm);
+  params.value.page_no = 1;
+  GET_EmailList();
+};
+
+const GET_EmailList = () => {
+  loading.value = true;
+  API_Template.getSMSList(params.value)
+    .then((response) => {
+      loading.value = false;
+      tableData.value = response;
+    })
+    .catch(() => {
+      loading.value = false;
+    });
+};
+const GET_PidList = () => {
+  API_Template.getPidList('MAIL').then((res) => {
+    pidList.value = res;
+  });
+};
+watch(systemVisible, (newValue) => {
+  if (!newValue) {
+    editorReadonly.value = false;
+    if (editorRef.value) {
+      editorRef.value.enable();
+    }
+  }
+});
+onMounted(() => {
+  GET_EmailList();
+  GET_PidList();
+});
+</script>
+<style scoped>
+:deep(.w-e-text-container) {
+  min-height: 200px !important;
+}
+
+:deep(.w-e-full-screen-container) {
+  z-index: 3000;
+}
+</style>

+ 276 - 0
src/views/templateManage/smsTemplate.vue

@@ -0,0 +1,276 @@
+<template>
+  <div class="search bg-white p-3">
+    <el-form :model="searchForm" label-width="100px">
+      <el-row>
+        <el-col :span="5">
+          <el-form-item label="状态">
+            <el-select v-model="searchForm.enable_status" clearable placeholder="请选择模板状态">
+              <el-option label="启用" :value="true" />
+              <el-option label="禁用" :value="false" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="5">
+          <el-form-item label="模板名称">
+            <el-input v-model="searchForm.name" clearable placeholder="请输入模板名称"></el-input>
+          </el-form-item>
+        </el-col>
+        <el-col :span="5">
+          <el-form-item label="模板code">
+            <el-input v-model="searchForm.code" clearable placeholder="请输入模板code"></el-input>
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <div class="flex justify-end">
+      <el-button class="mr-3" @click="searchEvent"> 搜索 </el-button>
+    </div>
+  </div>
+  <en-table-layout :tableData="tableData.records" :loading="loading">
+    <template #toolbar>
+      <div class="inner-toolbar w-11/12 flex justify-between">
+        <div class="toolbar-btns">
+          <el-button size="mini" type="primary" :icon="CirclePlus" v-if="checkPermission([''])" @click="handleAddRole">添加</el-button>
+        </div>
+        <!-- <div class="toolbar-search">
+            <en-table-search @search="searchEvent" placeholder="请输入关键字" />
+          </div> -->
+      </div>
+    </template>
+
+    <template #table-columns>
+      <el-table-column prop="name" label="模版名称" />
+      <el-table-column prop="template_type" label="短信类型" />
+      <el-table-column prop="remark" label="模版说明" />
+      <el-table-column prop="audit_status" label="审核状态" />
+      <el-table-column prop="enable_status" label="状态">
+        <template #default="scope">
+          <el-switch v-model="scope.row.enable_status" inline-prompt active-text="启用" inactive-text="禁用" @change="switchEnableStatus($event, scope.row)" />
+        </template>
+      </el-table-column>
+      <el-table-column label="操作">
+        <template #default="scope">
+          <el-button size="mini" @click="handleView(scope.row)" v-if="checkPermission([''])">查看</el-button>
+          <el-button size="mini" type="primary" @click="handleEditRole(scope.row)" v-if="checkPermission([''])">编辑</el-button>
+          <el-button size="mini" type="danger" @click="handleDeleteRole(scope.row)" v-if="checkPermission([''])">删除</el-button>
+        </template>
+      </el-table-column>
+    </template>
+
+    <template #pagination>
+      <el-pagination
+        v-if="tableData"
+        @size-change="handlePageSizeChange"
+        @current-change="handlePageCurrentChange"
+        :current-page="tableData.pages"
+        :page-sizes="[10, 20, 50, 100]"
+        :page-size="tableData.size"
+        layout="total, sizes, prev, pager, next, jumper"
+        :total="tableData.total"
+      >
+      </el-pagination>
+    </template>
+  </en-table-layout>
+  <el-dialog
+    :title="templateTitle"
+    v-model="systemVisible"
+    width="500px"
+    :modal-append-to-body="false"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+  >
+    <el-form :model="SMSForm" :rules="SMSFormRules" ref="SMSFormRef" label-width="120px">
+      <el-form-item label="上级模板:" prop="pid">
+        <el-select v-model="SMSForm.pid" clearable placeholder="请选择上级模板" :disabled="disabled">
+          <el-option v-for="item in pidList" :key="item.id" :label="item.name" :value="item.id" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="模版名称:" prop="name">
+        <el-input v-model="SMSForm.name" :minlength="2" :maxlength="20" :disabled="disabled" clearable placeholder="请输入模版名称"></el-input>
+      </el-form-item>
+      <el-form-item label="短信类型:" prop="template_type">
+        <el-select v-model="SMSForm.template_type" clearable :disabled="disabled" placeholder="请选择短信类型">
+          <el-option label="验证码" value="0" />
+          <el-option label="短信通知" value="1" />
+          <el-option label="推广短信" value="2" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="模版code:" prop="code">
+        <el-input v-model="SMSForm.code" :disabled="disabled" clearable placeholder="请输入模版code"></el-input>
+      </el-form-item>
+      <el-form-item label="是否启用:" prop="enable_status">
+        <el-switch v-model="SMSForm.enable_status" inline-prompt :disabled="disabled" active-text="启用" inactive-text="禁用" />
+      </el-form-item>
+       <el-form-item label="模版内容:" prop="content">
+        <el-input v-model="SMSForm.content"  :disabled="disabled" :autosize="{ minRows: 3}" clearable placeholder="请输入模版内容"></el-input>
+      </el-form-item>
+      <el-form-item label="模版说明:" prop="remark">
+        <el-input v-model="SMSForm.remark" type="textarea" :disabled="disabled" :autosize="{ minRows: 3, maxRows: 4 }" placeholder="请输入模版说明"></el-input>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="systemVisible = false">取 消</el-button>
+        <el-button type="primary" @click="submitSMSForm" :disabled="disabled">确 定</el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+        
+        <script lang="ts" setup>
+import { ref, onMounted, reactive } from 'vue';
+import { useRouter } from 'vue-router';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import { Delete, Edit, Search, Share, Upload, CirclePlus } from '@element-plus/icons-vue';
+import * as API_Template from '@/api/templateManage';
+
+const router = useRouter();
+/* 模版表单 */
+const pidList = ref<any>([]);
+const disabled = ref(false);
+let templateTitle = ref('');
+const SMSFormRef = ref();
+const systemVisible = ref(false);
+const SMSForm = ref<any>({
+  name: '',
+  content: '',
+  code: '',
+  enable_status: false,
+  remark: '',
+  type: 'SMS',
+});
+const SMSFormRules = ref<any>({
+  pid: [{ required: true, message: '请选择上级模板', trigger: 'change' }],
+  name: [{ required: true, message: '请输入模版名称', trigger: 'blur' }],
+  content: [{ required: true, message: '请输入模版内容', trigger: 'blur' }],
+  code: [
+    { required: true, message: '请输入模版code', trigger: 'blur' },
+    { pattern: /^[a-zA-Z0-9]+$/, message: '只能输入英文和数字,不能包含空格', trigger: 'blur' },
+  ],
+  template_type: [{ required: true, message: '请选择短信类型', trigger: 'change' }],
+  /*  user_num: [{ required: true, message: '请输入可创建用户数', trigger: 'blur' }], */
+});
+/* 新增 */
+const handleAddRole = () => {
+  templateTitle.value = '添加模版';
+  disabled.value = false;
+  SMSForm.value = {};
+  SMSForm.value.type = 'SMS';
+  (SMSForm.value.enable_status = false), (systemVisible.value = true);
+};
+/* 表单提交 */
+const submitSMSForm = () => {
+  SMSFormRef.value.validate((valid: boolean) => {
+    if (valid) {
+      const { id } = SMSForm.value;
+      const params = { ...SMSForm.value };
+      if (id) {
+        API_Template.editTemplate(id, params).then(() => {
+          systemVisible.value = false;
+          ElMessage.success('修改成功!');
+          GET_SMSList();
+        });
+      } else {
+        API_Template.addTemplate(params).then(() => {
+          systemVisible.value = false;
+          ElMessage.success('创建成功!');
+          GET_SMSList();
+        });
+      }
+    } else {
+      ElMessage.error('表单填写有误,请核对!');
+      /*    store.dispatch('showMessage', { message: '表单填写有误,请核对!', type: 'error' }); */
+      return false;
+    }
+  });
+};
+/* 修改状态 */
+const switchEnableStatus = (event, row) => {
+  API_Template.editStatus(row.id).then((res) => {
+    ElMessage.success('状态修改成功!');
+    GET_SMSList();
+  });
+};
+/* 查看模版 */
+const handleView = (row) => {
+  templateTitle.value = '查看模版';
+  SMSForm.value = { ...row };
+  disabled.value = true;
+  systemVisible.value = true;
+};
+/* 编辑模版 */
+const handleEditRole = (row: any) => {
+  templateTitle.value = '编辑模版';
+  disabled.value = false;
+  SMSForm.value = { ...row };
+  SMSForm.value.type = 'SMS';
+  systemVisible.value = true;
+};
+/* 删除模版 */
+const handleDeleteRole = (row: any) => {
+  ElMessageBox.confirm('确定要删除这个模版吗?', '提示', { type: 'warning' })
+    .then(() => {
+      API_Template.delTemplate(row.id).then(() => {
+        ElMessage.success('删除成功!');
+        GET_SMSList();
+      });
+    })
+    .catch(() => {});
+};
+
+const loading = ref(false);
+const params = ref({
+  page_no: 1,
+  page_size: 10,
+  type: 'SMS',
+});
+const tableData = ref<any>({});
+/* 搜索 */
+const searchForm = reactive({
+  enable_status: '',
+  name: '',
+  code: '',
+  type: 'SMS',
+});
+
+const checkPermission = (permissions: string[]): boolean => {
+  return true;
+};
+
+const handlePageSizeChange = (size: number) => {
+  params.value.page_size = size;
+  GET_SMSList();
+};
+
+const handlePageCurrentChange = (page: number) => {
+  params.value.page_no = page;
+  GET_SMSList();
+};
+
+const searchEvent = (data: string) => {
+  Object.assign(params.value, searchForm);
+  params.value.page_no = 1;
+  GET_SMSList();
+};
+
+const GET_SMSList = () => {
+  loading.value = true;
+  API_Template.getSMSList(params.value)
+    .then((response) => {
+      loading.value = false;
+      tableData.value = response;
+    })
+    .catch(() => {
+      loading.value = false;
+    });
+};
+const GET_PidList = () => {
+  API_Template.getPidList('SMS').then((res) => {
+    pidList.value = res;
+  });
+};
+onMounted(() => {
+  GET_SMSList();
+  GET_PidList();
+});
+</script>

+ 263 - 0
src/views/templateManage/systemMessage.vue

@@ -0,0 +1,263 @@
+<template>
+  <div class="search bg-white p-3">
+    <el-form :model="searchForm" label-width="100px">
+      <el-row>
+        <el-col :span="5">
+          <el-form-item label="状态">
+            <el-select v-model="searchForm.enable_status" clearable placeholder="请选择模板状态">
+              <el-option label="启用" :value="true" />
+              <el-option label="禁用" :value="false" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="5">
+          <el-form-item label="模板名称">
+            <el-input v-model="searchForm.name" clearable placeholder="请输入模板名称"></el-input>
+          </el-form-item>
+        </el-col>
+        <el-col :span="5">
+          <el-form-item label="模板code">
+            <el-input v-model="searchForm.code" clearable placeholder="请输入模板code"></el-input>
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <div class="flex justify-end">
+      <el-button class="mr-3" @click="searchEvent"> 搜索 </el-button>
+    </div>
+  </div>
+  <en-table-layout :tableData="tableData.records" :loading="loading">
+    <template #toolbar>
+      <div class="inner-toolbar w-11/12 flex justify-between">
+        <div class="toolbar-btns">
+          <el-button size="mini" type="primary" :icon="CirclePlus" v-if="checkPermission([''])" @click="handleAddRole">添加</el-button>
+        </div>
+        <!-- <div class="toolbar-search">
+          <en-table-search @search="searchEvent" placeholder="请输入关键字" />
+        </div> -->
+      </div>
+    </template>
+
+    <template #table-columns>
+      <el-table-column prop="name" label="模版名称" />
+      <!-- <el-table-column prop="template_type" label="短信类型" /> -->
+      <el-table-column prop="remark" label="模版说明" />
+      <!--  <el-table-column prop="audit_status" label="审核状态" /> -->
+      <el-table-column prop="enable_status" label="状态">
+        <template #default="scope">
+          <el-switch v-model="scope.row.enable_status" inline-prompt active-text="启用" inactive-text="禁用" @change="switchEnableStatus($event, scope.row)" />
+        </template>
+      </el-table-column>
+      <el-table-column label="操作">
+        <template #default="scope">
+          <el-button size="mini" @click="handleView(scope.row)" v-if="checkPermission([''])">查看</el-button>
+          <el-button size="mini" type="primary" @click="handleEditRole(scope.row)" v-if="checkPermission([''])">编辑</el-button>
+          <el-button size="mini" type="danger" @click="handleDeleteRole(scope.row)" v-if="checkPermission([''])">删除</el-button>
+        </template>
+      </el-table-column>
+    </template>
+
+    <template #pagination>
+      <el-pagination
+        v-if="tableData"
+        @size-change="handlePageSizeChange"
+        @current-change="handlePageCurrentChange"
+        :current-page="tableData.pages"
+        :page-sizes="[10, 20, 50, 100]"
+        :page-size="tableData.size"
+        layout="total, sizes, prev, pager, next, jumper"
+        :total="tableData.total"
+      >
+      </el-pagination>
+    </template>
+  </en-table-layout>
+  <el-dialog
+    :title="templateTitle"
+    v-model="systemVisible"
+    width="500px"
+    :modal-append-to-body="false"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+  >
+    <el-form :model="systemForm" :rules="systemFormRules" ref="systemFormRef" label-width="120px">
+      <el-form-item label="模版名称:" prop="name">
+        <el-input v-model="systemForm.name" :minlength="2" :maxlength="20" :disabled="disabled" clearable placeholder="请输入模版名称"></el-input>
+      </el-form-item>
+      <el-form-item label="模版内容:" prop="content">
+        <el-input v-model="systemForm.content" :minlength="2" :maxlength="20" :disabled="disabled" clearable placeholder="请输入模版内容"></el-input>
+      </el-form-item>
+      <el-form-item label="模版code:" prop="code">
+        <el-input v-model="systemForm.code" :minlength="2" :maxlength="20" :disabled="disabled" clearable placeholder="请输入模版code"></el-input>
+      </el-form-item>
+      <el-form-item label="是否启用:" prop="enable_status">
+        <el-switch v-model="systemForm.enable_status" inline-prompt :disabled="disabled" active-text="启用" inactive-text="禁用" />
+      </el-form-item>
+      <el-form-item label="模版说明:" prop="remark">
+        <el-input
+          v-model="systemForm.remark"
+          type="textarea"
+          :disabled="disabled"
+          :autosize="{ minRows: 3, maxRows: 4 }"
+          placeholder="请输入模版说明"
+        ></el-input>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="systemVisible = false">取 消</el-button>
+        <el-button type="primary" @click="submitsystemForm" :disabled="disabled">确 定</el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+      
+      <script lang="ts" setup>
+import { ref, onMounted, reactive } from 'vue';
+import { useRouter } from 'vue-router';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import { Delete, Edit, Search, Share, Upload, CirclePlus } from '@element-plus/icons-vue';
+import * as API_Template from '@/api/templateManage';
+
+const router = useRouter();
+/* 模版表单 */
+const disabled = ref(false);
+let templateTitle = ref('');
+const systemFormRef = ref();
+const systemVisible = ref(false);
+const systemForm = ref<any>({
+  name: '',
+  content: '',
+  code: '',
+  enable_status: false,
+  remark: '',
+  type: 'SYSTEM',
+});
+const systemFormRules = ref<any>({
+  name: [{ required: true, message: '请输入模版名称', trigger: 'blur' }],
+  content: [{ required: true, message: '请输入模版内容', trigger: 'blur' }],
+  code: [
+    { required: true, message: '请输入模版code', trigger: 'blur' },
+    { pattern: /^[a-zA-Z0-9]+$/, message: '只能输入英文和数字,不能包含空格', trigger: 'blur' },
+  ],
+  /* enable_status: [{ required: true, message: '请选择状态', trigger: 'change' }],
+    user_num: [{ required: true, message: '请输入可创建用户数', trigger: 'blur' }], */
+});
+/* 新增 */
+const handleAddRole = () => {
+  templateTitle.value = '添加模版';
+  disabled.value = false;
+  systemForm.value = {};
+  systemForm.value.type = 'SYSTEM';
+  (systemForm.value.enable_status = false), (systemVisible.value = true);
+};
+/* 表单提交 */
+const submitsystemForm = () => {
+  systemFormRef.value.validate((valid: boolean) => {
+    if (valid) {
+      const { id } = systemForm.value;
+      const params = { ...systemForm.value };
+      if (id) {
+        API_Template.editTemplate(id, params).then(() => {
+          systemVisible.value = false;
+          ElMessage.success('修改成功!');
+          GET_systemList();
+        });
+      } else {
+        API_Template.addTemplate(params).then(() => {
+          systemVisible.value = false;
+          ElMessage.success('创建成功!');
+          GET_systemList();
+        });
+      }
+    } else {
+      ElMessage.error('表单填写有误,请核对!');
+      /*    store.dispatch('showMessage', { message: '表单填写有误,请核对!', type: 'error' }); */
+      return false;
+    }
+  });
+};
+/* 修改状态 */
+const switchEnableStatus = (event, row) => {
+  API_Template.editStatus(row.id).then((res) => {
+    ElMessage.success('状态修改成功!');
+    GET_systemList();
+  });
+};
+/* 查看模版 */
+const handleView = (row) => {
+  templateTitle.value = '查看模版';
+  systemForm.value = { ...row };
+  disabled.value = true;
+  systemVisible.value = true;
+};
+/* 编辑模版 */
+const handleEditRole = (row: any) => {
+  templateTitle.value = '编辑模版';
+  disabled.value = false;
+  systemForm.value = { ...row };
+  systemForm.value.type = 'SYSTEM';
+  systemVisible.value = true;
+};
+/* 删除模版 */
+const handleDeleteRole = (row: any) => {
+  ElMessageBox.confirm('确定要删除这个模版吗?', '提示', { type: 'warning' })
+    .then(() => {
+      API_Template.delTemplate(row.id).then(() => {
+        ElMessage.success('删除成功!');
+        GET_systemList();
+      });
+    })
+    .catch(() => {});
+};
+
+const loading = ref(false);
+const params = ref({
+  page_no: 1,
+  page_size: 10,
+  type: 'SYSTEM',
+});
+const tableData = ref<any>({});
+/* 搜索 */
+const searchForm = reactive({
+  enable_status: '',
+  name: '',
+  code: '',
+  type: 'SYSTEM',
+});
+
+const checkPermission = (permissions: string[]): boolean => {
+  return true;
+};
+
+const handlePageSizeChange = (size: number) => {
+  params.value.page_size = size;
+  GET_systemList();
+};
+
+const handlePageCurrentChange = (page: number) => {
+  params.value.page_no = page;
+  GET_systemList();
+};
+
+const searchEvent = (data: string) => {
+  Object.assign(params.value, searchForm);
+  params.value.page_no = 1;
+  GET_systemList();
+};
+
+const GET_systemList = () => {
+  loading.value = true;
+  API_Template.getSMSList(params.value)
+    .then((response) => {
+      loading.value = false;
+      tableData.value = response;
+    })
+    .catch(() => {
+      loading.value = false;
+    });
+};
+
+onMounted(() => {
+  GET_systemList();
+});
+</script>

+ 1 - 0
theme.config.ts

@@ -8,4 +8,5 @@ export const $themeConfig = {
     animation: '', // animate__fadeIn, animate__fadeInDown, animate__fadeInUp, animate__fadeInLeft, animate__fadeInRight, animate__slideInDown, animate__slideInLeft, animate__slideInRight, animate__zoomIn
     animation: '', // animate__fadeIn, animate__fadeInDown, animate__fadeInUp, animate__fadeInLeft, animate__fadeInRight, animate__slideInDown, animate__slideInLeft, animate__slideInRight, animate__zoomIn
     navbar: 'navbar-sticky', // navbar-sticky, navbar-floating, navbar-static
     navbar: 'navbar-sticky', // navbar-sticky, navbar-floating, navbar-static
     semidark: false,
     semidark: false,
+    superMenu:'super'
 };
 };

+ 380 - 1
yarn.lock

@@ -12,6 +12,13 @@
   resolved "https://registry.npmmirror.com/@babel/parser/-/parser-7.24.7.tgz#9a5226f92f0c5c8ead550b750f5608e766c8ce85"
   resolved "https://registry.npmmirror.com/@babel/parser/-/parser-7.24.7.tgz#9a5226f92f0c5c8ead550b750f5608e766c8ce85"
   integrity sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==
   integrity sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==
 
 
+"@babel/runtime@^7.12.0":
+  version "7.24.8"
+  resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.24.8.tgz#5d958c3827b13cc6d05e038c07fb2e5e3420d82e"
+  integrity sha512-5F7SDGs1T72ZczbRwbGO9lQi0NLjQxzl6i4lJxLxfW9U5UluCSyEJeniWvnhl3/euNiqQVbo8zruhsDfid0esA==
+  dependencies:
+    regenerator-runtime "^0.14.0"
+
 "@bhplugin/vue3-datatable@^1.0.3":
 "@bhplugin/vue3-datatable@^1.0.3":
   version "1.1.4"
   version "1.1.4"
   resolved "https://registry.npmmirror.com/@bhplugin/vue3-datatable/-/vue3-datatable-1.1.4.tgz#a0738b42784606b98051c0893a73ee639c23044c"
   resolved "https://registry.npmmirror.com/@bhplugin/vue3-datatable/-/vue3-datatable-1.1.4.tgz#a0738b42784606b98051c0893a73ee639c23044c"
@@ -644,6 +651,11 @@
   dependencies:
   dependencies:
     "@tanstack/virtual-core" "3.5.1"
     "@tanstack/virtual-core" "3.5.1"
 
 
+"@transloadit/prettier-bytes@0.0.7":
+  version "0.0.7"
+  resolved "https://registry.npmmirror.com/@transloadit/prettier-bytes/-/prettier-bytes-0.0.7.tgz#cdb5399f445fdd606ed833872fa0cabdbc51686b"
+  integrity sha512-VeJbUb0wEKbcwaSlj5n+LscBl9IPgLPkHVGBkh00cztv6X4L/TJXK58LzFuBKX7/GAfiGhIwH67YTLTlzvIzBA==
+
 "@trysound/sax@0.2.0":
 "@trysound/sax@0.2.0":
   version "0.2.0"
   version "0.2.0"
   resolved "https://registry.npmmirror.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"
   resolved "https://registry.npmmirror.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"
@@ -661,6 +673,11 @@
   resolved "https://registry.npmmirror.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
   resolved "https://registry.npmmirror.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
   integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
   integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
 
 
+"@types/event-emitter@^0.3.3":
+  version "0.3.5"
+  resolved "https://registry.npmmirror.com/@types/event-emitter/-/event-emitter-0.3.5.tgz#ce9b513f72c50dcf0443a12165a93a79ba7a7092"
+  integrity sha512-zx2/Gg0Eg7gwEiOIIh5w9TrhKKTeQh7CPCOPNc0el4pLSwzebA8SmnHwZs2dWlLONvyulykSwGSQxQHLhjGLvQ==
+
 "@types/fs-extra@^8.0.0":
 "@types/fs-extra@^8.0.0":
   version "8.1.5"
   version "8.1.5"
   resolved "https://registry.npmmirror.com/@types/fs-extra/-/fs-extra-8.1.5.tgz#33aae2962d3b3ec9219b5aca2555ee00274f5927"
   resolved "https://registry.npmmirror.com/@types/fs-extra/-/fs-extra-8.1.5.tgz#33aae2962d3b3ec9219b5aca2555ee00274f5927"
@@ -709,6 +726,49 @@
   resolved "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz#1d12873a8e49567371f2a75fe3e7f7edca6662d8"
   resolved "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz#1d12873a8e49567371f2a75fe3e7f7edca6662d8"
   integrity sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==
   integrity sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==
 
 
+"@uppy/companion-client@^2.2.2":
+  version "2.2.2"
+  resolved "https://registry.npmmirror.com/@uppy/companion-client/-/companion-client-2.2.2.tgz#c70b42fdcca728ef88b3eebf7ee3e2fa04b4923b"
+  integrity sha512-5mTp2iq97/mYSisMaBtFRry6PTgZA6SIL7LePteOV5x0/DxKfrZW3DEiQERJmYpHzy7k8johpm2gHnEKto56Og==
+  dependencies:
+    "@uppy/utils" "^4.1.2"
+    namespace-emitter "^2.0.1"
+
+"@uppy/core@^2.1.1":
+  version "2.3.4"
+  resolved "https://registry.npmmirror.com/@uppy/core/-/core-2.3.4.tgz#260b85b6bf3aa03cdc67da231f8c69cfbfdcc84a"
+  integrity sha512-iWAqppC8FD8mMVqewavCz+TNaet6HPXitmGXpGGREGrakZ4FeuWytVdrelydzTdXx6vVKkOmI2FLztGg73sENQ==
+  dependencies:
+    "@transloadit/prettier-bytes" "0.0.7"
+    "@uppy/store-default" "^2.1.1"
+    "@uppy/utils" "^4.1.3"
+    lodash.throttle "^4.1.1"
+    mime-match "^1.0.2"
+    namespace-emitter "^2.0.1"
+    nanoid "^3.1.25"
+    preact "^10.5.13"
+
+"@uppy/store-default@^2.1.1":
+  version "2.1.1"
+  resolved "https://registry.npmmirror.com/@uppy/store-default/-/store-default-2.1.1.tgz#62a656a099bdaa012306e054d093754cb2d36e3e"
+  integrity sha512-xnpTxvot2SeAwGwbvmJ899ASk5tYXhmZzD/aCFsXePh/v8rNvR2pKlcQUH7cF/y4baUGq3FHO/daKCok/mpKqQ==
+
+"@uppy/utils@^4.1.2", "@uppy/utils@^4.1.3":
+  version "4.1.3"
+  resolved "https://registry.npmmirror.com/@uppy/utils/-/utils-4.1.3.tgz#9d0be6ece4df25f228d30ef40be0f14208258ce3"
+  integrity sha512-nTuMvwWYobnJcytDO3t+D6IkVq/Qs4Xv3vyoEZ+Iaf8gegZP+rEyoaFT2CK5XLRMienPyqRqNbIfRuFaOWSIFw==
+  dependencies:
+    lodash.throttle "^4.1.1"
+
+"@uppy/xhr-upload@^2.0.3":
+  version "2.1.3"
+  resolved "https://registry.npmmirror.com/@uppy/xhr-upload/-/xhr-upload-2.1.3.tgz#0d4e355332fe0c6eb372d7731315e04d02aeeb18"
+  integrity sha512-YWOQ6myBVPs+mhNjfdWsQyMRWUlrDLMoaG7nvf/G6Y3GKZf8AyjFDjvvJ49XWQ+DaZOftGkHmF1uh/DBeGivJQ==
+  dependencies:
+    "@uppy/companion-client" "^2.2.2"
+    "@uppy/utils" "^4.1.2"
+    nanoid "^3.1.25"
+
 "@vant/popperjs@^1.3.0":
 "@vant/popperjs@^1.3.0":
   version "1.3.0"
   version "1.3.0"
   resolved "https://registry.npmmirror.com/@vant/popperjs/-/popperjs-1.3.0.tgz#e0eff017124b5b2352ef3b36a6df06277f4400f2"
   resolved "https://registry.npmmirror.com/@vant/popperjs/-/popperjs-1.3.0.tgz#e0eff017124b5b2352ef3b36a6df06277f4400f2"
@@ -878,6 +938,14 @@
   dependencies:
   dependencies:
     vue-demi "^0.13.11"
     vue-demi "^0.13.11"
 
 
+"@vueup/vue-quill@^1.2.0":
+  version "1.2.0"
+  resolved "https://registry.npmmirror.com/@vueup/vue-quill/-/vue-quill-1.2.0.tgz#cd0d93559256d069f639723dd91c044e8162c72a"
+  integrity sha512-kd5QPSHMDpycklojPXno2Kw2JSiKMYduKYQckTm1RJoVDA557MnyUXgcuuDpry4HY/Rny9nGNcK+m3AHk94wag==
+  dependencies:
+    quill "^1.3.7"
+    quill-delta "^4.2.2"
+
 "@vueuse/core@^9.1.0", "@vueuse/core@^9.3.0":
 "@vueuse/core@^9.1.0", "@vueuse/core@^9.3.0":
   version "9.13.0"
   version "9.13.0"
   resolved "https://registry.npmmirror.com/@vueuse/core/-/core-9.13.0.tgz#2f69e66d1905c1e4eebc249a01759cf88ea00cf4"
   resolved "https://registry.npmmirror.com/@vueuse/core/-/core-9.13.0.tgz#2f69e66d1905c1e4eebc249a01759cf88ea00cf4"
@@ -909,6 +977,84 @@
   dependencies:
   dependencies:
     vue-demi "*"
     vue-demi "*"
 
 
+"@wangeditor/basic-modules@^1.1.7":
+  version "1.1.7"
+  resolved "https://registry.npmmirror.com/@wangeditor/basic-modules/-/basic-modules-1.1.7.tgz#a9c3ccf4ef53332f29550d59d3676e15f395946f"
+  integrity sha512-cY9CPkLJaqF05STqfpZKWG4LpxTMeGSIIF1fHvfm/mz+JXatCagjdkbxdikOuKYlxDdeqvOeBmsUBItufDLXZg==
+  dependencies:
+    is-url "^1.2.4"
+
+"@wangeditor/code-highlight@^1.0.3":
+  version "1.0.3"
+  resolved "https://registry.npmmirror.com/@wangeditor/code-highlight/-/code-highlight-1.0.3.tgz#90256857714d5c0cf83ac475aea64db7bf29a7cd"
+  integrity sha512-iazHwO14XpCuIWJNTQTikqUhGKyqj+dUNWJ9288Oym9M2xMVHvnsOmDU2sgUDWVy+pOLojReMPgXCsvvNlOOhw==
+  dependencies:
+    prismjs "^1.23.0"
+
+"@wangeditor/core@^1.1.19":
+  version "1.1.19"
+  resolved "https://registry.npmmirror.com/@wangeditor/core/-/core-1.1.19.tgz#f9155f7fd92d03cb1982405b3b82e54c31f1c2b0"
+  integrity sha512-KevkB47+7GhVszyYF2pKGKtCSj/YzmClsD03C3zTt+9SR2XWT5T0e3yQqg8baZpcMvkjs1D8Dv4fk8ok/UaS2Q==
+  dependencies:
+    "@types/event-emitter" "^0.3.3"
+    event-emitter "^0.3.5"
+    html-void-elements "^2.0.0"
+    i18next "^20.4.0"
+    scroll-into-view-if-needed "^2.2.28"
+    slate-history "^0.66.0"
+
+"@wangeditor/editor-for-vue@^5.1.12":
+  version "5.1.12"
+  resolved "https://registry.npmmirror.com/@wangeditor/editor-for-vue/-/editor-for-vue-5.1.12.tgz#f7d5f239b39cdfc01d31151488de8443fe6edc64"
+  integrity sha512-0Ds3D8I+xnpNWezAeO7HmPRgTfUxHLMd9JKcIw+QzvSmhC5xUHbpCcLU+KLmeBKTR/zffnS5GQo6qi3GhTMJWQ==
+
+"@wangeditor/editor@^5.1.23":
+  version "5.1.23"
+  resolved "https://registry.npmmirror.com/@wangeditor/editor/-/editor-5.1.23.tgz#c9d2007b7cb0ceef6b72692b4ee87b01ee2367b3"
+  integrity sha512-0RxfeVTuK1tktUaPROnCoFfaHVJpRAIE2zdS0mpP+vq1axVQpLjM8+fCvKzqYIkH0Pg+C+44hJpe3VVroSkEuQ==
+  dependencies:
+    "@uppy/core" "^2.1.1"
+    "@uppy/xhr-upload" "^2.0.3"
+    "@wangeditor/basic-modules" "^1.1.7"
+    "@wangeditor/code-highlight" "^1.0.3"
+    "@wangeditor/core" "^1.1.19"
+    "@wangeditor/list-module" "^1.0.5"
+    "@wangeditor/table-module" "^1.1.4"
+    "@wangeditor/upload-image-module" "^1.0.2"
+    "@wangeditor/video-module" "^1.1.4"
+    dom7 "^3.0.0"
+    is-hotkey "^0.2.0"
+    lodash.camelcase "^4.3.0"
+    lodash.clonedeep "^4.5.0"
+    lodash.debounce "^4.0.8"
+    lodash.foreach "^4.5.0"
+    lodash.isequal "^4.5.0"
+    lodash.throttle "^4.1.1"
+    lodash.toarray "^4.4.0"
+    nanoid "^3.2.0"
+    slate "^0.72.0"
+    snabbdom "^3.1.0"
+
+"@wangeditor/list-module@^1.0.5":
+  version "1.0.5"
+  resolved "https://registry.npmmirror.com/@wangeditor/list-module/-/list-module-1.0.5.tgz#3fc0b167acddf885536b45fa0c127f9c6adaea33"
+  integrity sha512-uDuYTP6DVhcYf7mF1pTlmNn5jOb4QtcVhYwSSAkyg09zqxI1qBqsfUnveeDeDqIuptSJhkh81cyxi+MF8sEPOQ==
+
+"@wangeditor/table-module@^1.1.4":
+  version "1.1.4"
+  resolved "https://registry.npmmirror.com/@wangeditor/table-module/-/table-module-1.1.4.tgz#757d4a5868b2b658041cd323854a4d707c8347e9"
+  integrity sha512-5saanU9xuEocxaemGdNi9t8MCDSucnykEC6jtuiT72kt+/Hhh4nERYx1J20OPsTCCdVr7hIyQenFD1iSRkIQ6w==
+
+"@wangeditor/upload-image-module@^1.0.2":
+  version "1.0.2"
+  resolved "https://registry.npmmirror.com/@wangeditor/upload-image-module/-/upload-image-module-1.0.2.tgz#89e9b9467e10cbc6b11dc5748e08dd23aaebee30"
+  integrity sha512-z81lk/v71OwPDYeQDxj6cVr81aDP90aFuywb8nPD6eQeECtOymrqRODjpO6VGvCVxVck8nUxBHtbxKtjgcwyiA==
+
+"@wangeditor/video-module@^1.1.4":
+  version "1.1.4"
+  resolved "https://registry.npmmirror.com/@wangeditor/video-module/-/video-module-1.1.4.tgz#b9df1b3ab2cd53f678b19b4d927e200774a6f532"
+  integrity sha512-ZdodDPqKQrgx3IwWu4ZiQmXI8EXZ3hm2/fM6E3t5dB8tCaIGWQZhmqd6P5knfkRAd3z2+YRSRbxOGfoRSp/rLg==
+
 "@xmldom/xmldom@^0.8.8":
 "@xmldom/xmldom@^0.8.8":
   version "0.8.10"
   version "0.8.10"
   resolved "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.8.10.tgz#a1337ca426aa61cef9fe15b5b28e340a72f6fa99"
   resolved "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.8.10.tgz#a1337ca426aa61cef9fe15b5b28e340a72f6fa99"
@@ -1240,6 +1386,11 @@ commander@^9.3.0:
   resolved "https://registry.npmmirror.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30"
   resolved "https://registry.npmmirror.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30"
   integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==
   integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==
 
 
+compute-scroll-into-view@^1.0.20:
+  version "1.0.20"
+  resolved "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz#1768b5522d1172754f5d0c9b02de3af6be506a43"
+  integrity sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==
+
 core-js@^3.6.5:
 core-js@^3.6.5:
   version "3.37.1"
   version "3.37.1"
   resolved "https://registry.npmmirror.com/core-js/-/core-js-3.37.1.tgz#d21751ddb756518ac5a00e4d66499df981a62db9"
   resolved "https://registry.npmmirror.com/core-js/-/core-js-3.37.1.tgz#d21751ddb756518ac5a00e4d66499df981a62db9"
@@ -1354,6 +1505,14 @@ csstype@^3.1.3:
   resolved "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
   resolved "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
   integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
   integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
 
 
+d@1, d@^1.0.1, d@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.npmmirror.com/d/-/d-1.0.2.tgz#2aefd554b81981e7dccf72d6842ae725cb17e5de"
+  integrity sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==
+  dependencies:
+    es5-ext "^0.10.64"
+    type "^2.7.2"
+
 dayjs@^1.11.3:
 dayjs@^1.11.3:
   version "1.11.11"
   version "1.11.11"
   resolved "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.11.tgz#dfe0e9d54c5f8b68ccf8ca5f72ac603e7e5ed59e"
   resolved "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.11.tgz#dfe0e9d54c5f8b68ccf8ca5f72ac603e7e5ed59e"
@@ -1445,6 +1604,13 @@ dom-serializer@^1.0.1:
     domhandler "^4.2.0"
     domhandler "^4.2.0"
     entities "^2.0.0"
     entities "^2.0.0"
 
 
+dom7@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.npmmirror.com/dom7/-/dom7-3.0.0.tgz#b861ce5d67a6becd7aaa3ad02942ff14b1240331"
+  integrity sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g==
+  dependencies:
+    ssr-window "^3.0.0-alpha.1"
+
 dom7@^4.0.4:
 dom7@^4.0.4:
   version "4.0.6"
   version "4.0.6"
   resolved "https://registry.npmmirror.com/dom7/-/dom7-4.0.6.tgz#091a51621d7a19ce0fb86045cafb3c10035e97ed"
   resolved "https://registry.npmmirror.com/dom7/-/dom7-4.0.6.tgz#091a51621d7a19ce0fb86045cafb3c10035e97ed"
@@ -1581,6 +1747,33 @@ es-errors@^1.3.0:
   resolved "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
   resolved "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
   integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
   integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
 
 
+es5-ext@^0.10.35, es5-ext@^0.10.62, es5-ext@^0.10.64, es5-ext@~0.10.14:
+  version "0.10.64"
+  resolved "https://registry.npmmirror.com/es5-ext/-/es5-ext-0.10.64.tgz#12e4ffb48f1ba2ea777f1fcdd1918ef73ea21714"
+  integrity sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==
+  dependencies:
+    es6-iterator "^2.0.3"
+    es6-symbol "^3.1.3"
+    esniff "^2.0.1"
+    next-tick "^1.1.0"
+
+es6-iterator@^2.0.3:
+  version "2.0.3"
+  resolved "https://registry.npmmirror.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7"
+  integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==
+  dependencies:
+    d "1"
+    es5-ext "^0.10.35"
+    es6-symbol "^3.1.1"
+
+es6-symbol@^3.1.1, es6-symbol@^3.1.3:
+  version "3.1.4"
+  resolved "https://registry.npmmirror.com/es6-symbol/-/es6-symbol-3.1.4.tgz#f4e7d28013770b4208ecbf3e0bf14d3bcb557b8c"
+  integrity sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==
+  dependencies:
+    d "^1.0.2"
+    ext "^1.7.0"
+
 esbuild@^0.21.3:
 esbuild@^0.21.3:
   version "0.21.5"
   version "0.21.5"
   resolved "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d"
   resolved "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d"
@@ -1632,6 +1825,16 @@ eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0:
   resolved "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e"
   resolved "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e"
   integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==
   integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==
 
 
+esniff@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.npmmirror.com/esniff/-/esniff-2.0.1.tgz#a4d4b43a5c71c7ec51c51098c1d8a29081f9b308"
+  integrity sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==
+  dependencies:
+    d "^1.0.1"
+    es5-ext "^0.10.62"
+    event-emitter "^0.3.5"
+    type "^2.7.2"
+
 espree@^6.0.0:
 espree@^6.0.0:
   version "6.2.1"
   version "6.2.1"
   resolved "https://registry.npmmirror.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a"
   resolved "https://registry.npmmirror.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a"
@@ -1646,11 +1849,26 @@ estree-walker@^2.0.1, estree-walker@^2.0.2:
   resolved "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
   resolved "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
   integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
   integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
 
 
+event-emitter@^0.3.5:
+  version "0.3.5"
+  resolved "https://registry.npmmirror.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39"
+  integrity sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==
+  dependencies:
+    d "1"
+    es5-ext "~0.10.14"
+
 eventemitter3@^2.0.3:
 eventemitter3@^2.0.3:
   version "2.0.3"
   version "2.0.3"
   resolved "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba"
   resolved "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba"
   integrity sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==
   integrity sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==
 
 
+ext@^1.7.0:
+  version "1.7.0"
+  resolved "https://registry.npmmirror.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f"
+  integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==
+  dependencies:
+    type "^2.7.2"
+
 extend@^3.0.2:
 extend@^3.0.2:
   version "3.0.2"
   version "3.0.2"
   resolved "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
   resolved "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
@@ -1661,6 +1879,11 @@ fast-diff@1.1.2:
   resolved "https://registry.npmmirror.com/fast-diff/-/fast-diff-1.1.2.tgz#4b62c42b8e03de3f848460b639079920695d0154"
   resolved "https://registry.npmmirror.com/fast-diff/-/fast-diff-1.1.2.tgz#4b62c42b8e03de3f848460b639079920695d0154"
   integrity sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==
   integrity sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==
 
 
+fast-diff@1.2.0:
+  version "1.2.0"
+  resolved "https://registry.npmmirror.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
+  integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==
+
 fast-glob@^3.2.5, fast-glob@^3.3.0:
 fast-glob@^3.2.5, fast-glob@^3.3.0:
   version "3.3.2"
   version "3.3.2"
   resolved "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
   resolved "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
@@ -1881,11 +2104,28 @@ highlight.js@^11.3.1:
   resolved "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.9.0.tgz#04ab9ee43b52a41a047432c8103e2158a1b8b5b0"
   resolved "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.9.0.tgz#04ab9ee43b52a41a047432c8103e2158a1b8b5b0"
   integrity sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==
   integrity sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==
 
 
+html-void-elements@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.npmmirror.com/html-void-elements/-/html-void-elements-2.0.1.tgz#29459b8b05c200b6c5ee98743c41b979d577549f"
+  integrity sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==
+
 https@^1.0.0:
 https@^1.0.0:
   version "1.0.0"
   version "1.0.0"
   resolved "https://registry.npmmirror.com/https/-/https-1.0.0.tgz#3c37c7ae1a8eeb966904a2ad1e975a194b7ed3a4"
   resolved "https://registry.npmmirror.com/https/-/https-1.0.0.tgz#3c37c7ae1a8eeb966904a2ad1e975a194b7ed3a4"
   integrity sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg==
   integrity sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg==
 
 
+i18next@^20.4.0:
+  version "20.6.1"
+  resolved "https://registry.npmmirror.com/i18next/-/i18next-20.6.1.tgz#535e5f6e5baeb685c7d25df70db63bf3cc0aa345"
+  integrity sha512-yCMYTMEJ9ihCwEQQ3phLo7I/Pwycf8uAx+sRHwwk5U9Aui/IZYgQRyMqXafQOw5QQ7DM1Z+WyEXWIqSuJHhG2A==
+  dependencies:
+    "@babel/runtime" "^7.12.0"
+
+immer@^9.0.6:
+  version "9.0.21"
+  resolved "https://registry.npmmirror.com/immer/-/immer-9.0.21.tgz#1e025ea31a40f24fb064f1fef23e931496330176"
+  integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==
+
 inherits@2.0.3:
 inherits@2.0.3:
   version "2.0.3"
   version "2.0.3"
   resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
   resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
@@ -1952,11 +2192,21 @@ is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
   dependencies:
   dependencies:
     is-extglob "^2.1.1"
     is-extglob "^2.1.1"
 
 
+is-hotkey@^0.2.0:
+  version "0.2.0"
+  resolved "https://registry.npmmirror.com/is-hotkey/-/is-hotkey-0.2.0.tgz#1835a68171a91e5c9460869d96336947c8340cef"
+  integrity sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==
+
 is-number@^7.0.0:
 is-number@^7.0.0:
   version "7.0.0"
   version "7.0.0"
   resolved "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
   resolved "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
   integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
   integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
 
 
+is-plain-object@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344"
+  integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==
+
 is-regex@^1.1.4:
 is-regex@^1.1.4:
   version "1.1.4"
   version "1.1.4"
   resolved "https://registry.npmmirror.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
   resolved "https://registry.npmmirror.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
@@ -1965,6 +2215,11 @@ is-regex@^1.1.4:
     call-bind "^1.0.2"
     call-bind "^1.0.2"
     has-tostringtag "^1.0.0"
     has-tostringtag "^1.0.0"
 
 
+is-url@^1.2.4:
+  version "1.2.4"
+  resolved "https://registry.npmmirror.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52"
+  integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==
+
 is-wsl@^2.2.0:
 is-wsl@^2.2.0:
   version "2.2.0"
   version "2.2.0"
   resolved "https://registry.npmmirror.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
   resolved "https://registry.npmmirror.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
@@ -2068,11 +2323,36 @@ lodash-unified@^1.0.2:
   resolved "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz#80b1eac10ed2eb02ed189f08614a29c27d07c894"
   resolved "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz#80b1eac10ed2eb02ed189f08614a29c27d07c894"
   integrity sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==
   integrity sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==
 
 
+lodash.camelcase@^4.3.0:
+  version "4.3.0"
+  resolved "https://registry.npmmirror.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
+  integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==
+
 lodash.castarray@^4.4.0:
 lodash.castarray@^4.4.0:
   version "4.4.0"
   version "4.4.0"
   resolved "https://registry.npmmirror.com/lodash.castarray/-/lodash.castarray-4.4.0.tgz#c02513515e309daddd4c24c60cfddcf5976d9115"
   resolved "https://registry.npmmirror.com/lodash.castarray/-/lodash.castarray-4.4.0.tgz#c02513515e309daddd4c24c60cfddcf5976d9115"
   integrity sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==
   integrity sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==
 
 
+lodash.clonedeep@^4.5.0:
+  version "4.5.0"
+  resolved "https://registry.npmmirror.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
+  integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==
+
+lodash.debounce@^4.0.8:
+  version "4.0.8"
+  resolved "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
+  integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
+
+lodash.foreach@^4.5.0:
+  version "4.5.0"
+  resolved "https://registry.npmmirror.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53"
+  integrity sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==
+
+lodash.isequal@^4.5.0:
+  version "4.5.0"
+  resolved "https://registry.npmmirror.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
+  integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==
+
 lodash.isplainobject@^4.0.6:
 lodash.isplainobject@^4.0.6:
   version "4.0.6"
   version "4.0.6"
   resolved "https://registry.npmmirror.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
   resolved "https://registry.npmmirror.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
@@ -2088,6 +2368,16 @@ lodash.merge@^4.6.2:
   resolved "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
   resolved "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
   integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
   integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
 
 
+lodash.throttle@^4.1.1:
+  version "4.1.1"
+  resolved "https://registry.npmmirror.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
+  integrity sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==
+
+lodash.toarray@^4.4.0:
+  version "4.4.0"
+  resolved "https://registry.npmmirror.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561"
+  integrity sha512-QyffEA3i5dma5q2490+SgCvDN0pXLmRGSyAANuVi0HQ01Pkfr9fuoKQW8wm1wGBnJITs/mS7wQvS6VshUEBFCw==
+
 lodash.uniq@^4.5.0:
 lodash.uniq@^4.5.0:
   version "4.5.0"
   version "4.5.0"
   resolved "https://registry.npmmirror.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
   resolved "https://registry.npmmirror.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
@@ -2148,6 +2438,13 @@ mime-db@1.52.0:
   resolved "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
   resolved "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
   integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
   integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
 
 
+mime-match@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.npmmirror.com/mime-match/-/mime-match-1.0.2.tgz#3f87c31e9af1a5fd485fb9db134428b23bbb7ba8"
+  integrity sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg==
+  dependencies:
+    wildcard "^1.1.0"
+
 mime-types@^2.1.12:
 mime-types@^2.1.12:
   version "2.1.35"
   version "2.1.35"
   resolved "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
   resolved "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
@@ -2223,7 +2520,12 @@ mz@^2.7.0:
     object-assign "^4.0.1"
     object-assign "^4.0.1"
     thenify-all "^1.0.0"
     thenify-all "^1.0.0"
 
 
-nanoid@^3.3.7:
+namespace-emitter@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.npmmirror.com/namespace-emitter/-/namespace-emitter-2.0.1.tgz#978d51361c61313b4e6b8cf6f3853d08dfa2b17c"
+  integrity sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g==
+
+nanoid@^3.1.25, nanoid@^3.2.0, nanoid@^3.3.7:
   version "3.3.7"
   version "3.3.7"
   resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
   resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
   integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
   integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
@@ -2245,6 +2547,11 @@ native-run@^2.0.0:
     tslib "^2.6.2"
     tslib "^2.6.2"
     yauzl "^2.10.0"
     yauzl "^2.10.0"
 
 
+next-tick@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.npmmirror.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb"
+  integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==
+
 node-releases@^2.0.14:
 node-releases@^2.0.14:
   version "2.0.14"
   version "2.0.14"
   resolved "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b"
   resolved "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b"
@@ -2712,6 +3019,11 @@ postcss@^8.4.17, postcss@^8.4.23, postcss@^8.4.38:
     picocolors "^1.0.0"
     picocolors "^1.0.0"
     source-map-js "^1.2.0"
     source-map-js "^1.2.0"
 
 
+preact@^10.5.13:
+  version "10.22.1"
+  resolved "https://registry.npmmirror.com/preact/-/preact-10.22.1.tgz#6a3589973fe0c6e53211091607d31f4b7b27334d"
+  integrity sha512-jRYbDDgMpIb5LHq3hkI0bbl+l/TQ9UnkdQ0ww+lp+4MMOdqaUYdFc5qeyP+IV8FAd/2Em7drVPeKdQxsiWCf/A==
+
 preact@~10.12.1:
 preact@~10.12.1:
   version "10.12.1"
   version "10.12.1"
   resolved "https://registry.npmmirror.com/preact/-/preact-10.12.1.tgz#8f9cb5442f560e532729b7d23d42fd1161354a21"
   resolved "https://registry.npmmirror.com/preact/-/preact-10.12.1.tgz#8f9cb5442f560e532729b7d23d42fd1161354a21"
@@ -2722,6 +3034,11 @@ print-js@^1.6.0:
   resolved "https://registry.npmmirror.com/print-js/-/print-js-1.6.0.tgz#692b046cf31992b46afa6c6d8a9db1c69d431d1f"
   resolved "https://registry.npmmirror.com/print-js/-/print-js-1.6.0.tgz#692b046cf31992b46afa6c6d8a9db1c69d431d1f"
   integrity sha512-BfnOIzSKbqGRtO4o0rnj/K3681BSd2QUrsIZy/+WdCIugjIswjmx3lDEZpXB2ruGf9d4b3YNINri81+J0FsBWg==
   integrity sha512-BfnOIzSKbqGRtO4o0rnj/K3681BSd2QUrsIZy/+WdCIugjIswjmx3lDEZpXB2ruGf9d4b3YNINri81+J0FsBWg==
 
 
+prismjs@^1.23.0:
+  version "1.29.0"
+  resolved "https://registry.npmmirror.com/prismjs/-/prismjs-1.29.0.tgz#f113555a8fa9b57c35e637bba27509dcf802dd12"
+  integrity sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==
+
 process@^0.11.1:
 process@^0.11.1:
   version "0.11.10"
   version "0.11.10"
   resolved "https://registry.npmmirror.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
   resolved "https://registry.npmmirror.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
@@ -2781,6 +3098,15 @@ quill-delta@^3.6.2:
     extend "^3.0.2"
     extend "^3.0.2"
     fast-diff "1.1.2"
     fast-diff "1.1.2"
 
 
+quill-delta@^4.2.2:
+  version "4.2.2"
+  resolved "https://registry.npmmirror.com/quill-delta/-/quill-delta-4.2.2.tgz#015397d046e0a3bed087cd8a51f98c11a1b8f351"
+  integrity sha512-qjbn82b/yJzOjstBgkhtBjN2TNK+ZHP/BgUQO+j6bRhWQQdmj2lH6hXG7+nwwLF41Xgn//7/83lxs9n2BkTtTg==
+  dependencies:
+    fast-diff "1.2.0"
+    lodash.clonedeep "^4.5.0"
+    lodash.isequal "^4.5.0"
+
 quill@^1.3.7:
 quill@^1.3.7:
   version "1.3.7"
   version "1.3.7"
   resolved "https://registry.npmmirror.com/quill/-/quill-1.3.7.tgz#da5b2f3a2c470e932340cdbf3668c9f21f9286e8"
   resolved "https://registry.npmmirror.com/quill/-/quill-1.3.7.tgz#da5b2f3a2c470e932340cdbf3668c9f21f9286e8"
@@ -2816,6 +3142,11 @@ readdirp@~3.6.0:
   dependencies:
   dependencies:
     picomatch "^2.2.1"
     picomatch "^2.2.1"
 
 
+regenerator-runtime@^0.14.0:
+  version "0.14.1"
+  resolved "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f"
+  integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==
+
 regexp.prototype.flags@^1.5.1:
 regexp.prototype.flags@^1.5.1:
   version "1.5.2"
   version "1.5.2"
   resolved "https://registry.npmmirror.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334"
   resolved "https://registry.npmmirror.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334"
@@ -2904,6 +3235,13 @@ sax@>=0.6.0:
   resolved "https://registry.npmmirror.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f"
   resolved "https://registry.npmmirror.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f"
   integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==
   integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==
 
 
+scroll-into-view-if-needed@^2.2.28:
+  version "2.2.31"
+  resolved "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz#d3c482959dc483e37962d1521254e3295d0d1587"
+  integrity sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==
+  dependencies:
+    compute-scroll-into-view "^1.0.20"
+
 select@^1.1.2:
 select@^1.1.2:
   version "1.1.2"
   version "1.1.2"
   resolved "https://registry.npmmirror.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d"
   resolved "https://registry.npmmirror.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d"
@@ -2988,6 +3326,22 @@ slash@^3.0.0:
   resolved "https://registry.npmmirror.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
   resolved "https://registry.npmmirror.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
   integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
   integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
 
 
+slate-history@^0.66.0:
+  version "0.66.0"
+  resolved "https://registry.npmmirror.com/slate-history/-/slate-history-0.66.0.tgz#ac63fddb903098ceb4c944433e3f75fe63acf940"
+  integrity sha512-6MWpxGQZiMvSINlCbMW43E2YBSVMCMCIwQfBzGssjWw4kb0qfvj0pIdblWNRQZD0hR6WHP+dHHgGSeVdMWzfng==
+  dependencies:
+    is-plain-object "^5.0.0"
+
+slate@^0.72.0:
+  version "0.72.8"
+  resolved "https://registry.npmmirror.com/slate/-/slate-0.72.8.tgz#5a018edf24e45448655293a68bfbcf563aa5ba81"
+  integrity sha512-/nJwTswQgnRurpK+bGJFH1oM7naD5qDmHd89JyiKNT2oOKD8marW0QSBtuFnwEbL5aGCS8AmrhXQgNOsn4osAw==
+  dependencies:
+    immer "^9.0.6"
+    is-plain-object "^5.0.0"
+    tiny-warning "^1.0.3"
+
 slice-ansi@^4.0.0:
 slice-ansi@^4.0.0:
   version "4.0.0"
   version "4.0.0"
   resolved "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b"
   resolved "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b"
@@ -2997,6 +3351,11 @@ slice-ansi@^4.0.0:
     astral-regex "^2.0.0"
     astral-regex "^2.0.0"
     is-fullwidth-code-point "^3.0.0"
     is-fullwidth-code-point "^3.0.0"
 
 
+snabbdom@^3.1.0:
+  version "3.6.2"
+  resolved "https://registry.npmmirror.com/snabbdom/-/snabbdom-3.6.2.tgz#57dd66878f6320497fa7f67941df356a045c75a1"
+  integrity sha512-ig5qOnCDbugFntKi6c7Xlib8bA6xiJVk8O+WdFrV3wxbMqeHO0hXFQC4nAhPVWfZfi8255lcZkNhtIBINCc4+Q==
+
 source-map-js@^1.0.2, source-map-js@^1.2.0:
 source-map-js@^1.0.2, source-map-js@^1.2.0:
   version "1.2.0"
   version "1.2.0"
   resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af"
   resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af"
@@ -3012,6 +3371,11 @@ split2@^4.2.0:
   resolved "https://registry.npmmirror.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4"
   resolved "https://registry.npmmirror.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4"
   integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==
   integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==
 
 
+ssr-window@^3.0.0-alpha.1:
+  version "3.0.0"
+  resolved "https://registry.npmmirror.com/ssr-window/-/ssr-window-3.0.0.tgz#fd5b82801638943e0cc704c4691801435af7ac37"
+  integrity sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA==
+
 ssr-window@^4.0.0, ssr-window@^4.0.2:
 ssr-window@^4.0.0, ssr-window@^4.0.2:
   version "4.0.2"
   version "4.0.2"
   resolved "https://registry.npmmirror.com/ssr-window/-/ssr-window-4.0.2.tgz#dc6b3ee37be86ac0e3ddc60030f7b3bc9b8553be"
   resolved "https://registry.npmmirror.com/ssr-window/-/ssr-window-4.0.2.tgz#dc6b3ee37be86ac0e3ddc60030f7b3bc9b8553be"
@@ -3250,6 +3614,11 @@ tiny-emitter@^2.0.0:
   resolved "https://registry.npmmirror.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423"
   resolved "https://registry.npmmirror.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423"
   integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==
   integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==
 
 
+tiny-warning@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.npmmirror.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
+  integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
+
 tippy.js@^6.3.7:
 tippy.js@^6.3.7:
   version "6.3.7"
   version "6.3.7"
   resolved "https://registry.npmmirror.com/tippy.js/-/tippy.js-6.3.7.tgz#8ccfb651d642010ed9a32ff29b0e9e19c5b8c61c"
   resolved "https://registry.npmmirror.com/tippy.js/-/tippy.js-6.3.7.tgz#8ccfb651d642010ed9a32ff29b0e9e19c5b8c61c"
@@ -3286,6 +3655,11 @@ tslib@^2.0.1, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.6.2:
   resolved "https://registry.npmmirror.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0"
   resolved "https://registry.npmmirror.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0"
   integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==
   integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==
 
 
+type@^2.7.2:
+  version "2.7.3"
+  resolved "https://registry.npmmirror.com/type/-/type-2.7.3.tgz#436981652129285cc3ba94f392886c2637ea0486"
+  integrity sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==
+
 typescript@^4.6.4:
 typescript@^4.6.4:
   version "4.9.5"
   version "4.9.5"
   resolved "https://registry.npmmirror.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
   resolved "https://registry.npmmirror.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
@@ -3519,6 +3893,11 @@ which@^2.0.1:
   dependencies:
   dependencies:
     isexe "^2.0.0"
     isexe "^2.0.0"
 
 
+wildcard@^1.1.0:
+  version "1.1.2"
+  resolved "https://registry.npmmirror.com/wildcard/-/wildcard-1.1.2.tgz#a7020453084d8cd2efe70ba9d3696263de1710a5"
+  integrity sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng==
+
 "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
 "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
   version "7.0.0"
   version "7.0.0"
   resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
   resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"