Sfoglia il codice sorgente

富文本编辑器

lifanagju_citu 8 mesi fa
parent
commit
2ab514deba

+ 2 - 0
components.d.ts

@@ -60,5 +60,7 @@ declare module 'vue' {
     TextArea: typeof import('./src/components/FormUI/textArea/index.vue')['default']
     TextInput: typeof import('./src/components/FormUI/TextInput/index.vue')['default']
     VerificationCode: typeof import('./src/components/VerificationCode/index.vue')['default']
+    Wangeditor: typeof import('./src/components/FormUI/wangeditor/index.vue')['default']
+    WangEditor: typeof import('./src/components/FormUI/wangEditor/index.vue')['default']
   }
 }

+ 2 - 0
package.json

@@ -9,6 +9,8 @@
     "format": "prettier --write src/"
   },
   "dependencies": {
+    "@wangeditor/editor": "^5.1.23",
+    "@wangeditor/editor-for-vue": "^5.1.10",
     "@mdi/font": "7.0.96",
     "@vuepic/vue-datepicker": "^8.7.0",
     "axios": "^1.6.8",

+ 7 - 0
src/components/CtForm/index.vue

@@ -75,6 +75,12 @@
                 :item="item"
                 @change="handleChange(item)"
               ></datePickerUI>
+              <wangEditorUI
+                v-if="item.type === 'wangEditor'"
+                v-model="item.value"
+                :item="item"
+                @change="handleChange(item)"
+              ></wangEditorUI>
               <DatePicker v-if="item.type === 'vueDatePicker'" v-model="item.value" :options="item.options" :width="item.width" :class="item.class"></DatePicker>
               <v-file-input
                 v-if="item.type === 'upload'"
@@ -128,6 +134,7 @@ import cascadeUI from './../FormUI/cascade'
 import nestedListGroupUI from './../FormUI/nestedListGroup'
 import datePickerUI from './../FormUI/datePicker'
 import DatePicker from '@/components/DatePicker'
+import wangEditorUI from './../FormUI/wangEditor'
 import { ref } from 'vue'
 const emit = defineEmits(['change', 'inputUpdateAutocomplete'])// 定义一个或多个自定义事件
 const props = defineProps({items: Object})

+ 227 - 0
src/components/FormUI/wangEditor/index.vue

@@ -0,0 +1,227 @@
+<!-- 富文本编辑器 -->
+<template>
+  <div>
+    <div v-if="props.item?.label" class="ml-2 mb-2 color959595">{{ props.item.label  }}</div>
+    <div class="box z-10">
+      <!-- 工具栏 -->
+      <Toolbar
+        :editor="editorRef"
+        :editorId="editorId"
+        class="border-0 b-b-1 border-solid border-[var(--tags-view-border-color)]"
+      />
+      <!-- 编辑器 -->
+      <Editor
+        v-model="valueHtml"
+        :defaultConfig="editorConfig"
+        :editorId="editorId"
+        :style="editorStyle"
+        @on-change="handleChange"
+        @on-created="handleCreated"
+      />
+    </div>
+  </div>
+</template>
+<!-- <style src="@wangeditor/editor/dist/css/style.css"></style> -->
+<script setup>
+defineOptions({ name: 'wangeditor-index' })
+import '@wangeditor/editor/dist/css/style.css' // 引入 css
+import { onBeforeUnmount, ref, shallowRef, unref, computed, nextTick, watch } from 'vue'
+import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
+import { i18nChangeLanguage } from '@wangeditor/editor'
+import { isNumber } from '@/utils/is'
+// import { getAccessToken, getTenantId } from '@/utils/auth'
+
+i18nChangeLanguage('zh-CN')
+
+const props = defineProps({
+  modelValue: {
+    type: String,
+    default: '',
+  },
+  item: { // 表单配置数据
+    type: Object,
+    default: () => {}
+  },
+  editorId: {
+    type: String,
+    default: 'wangEditor-1',
+  },
+  height: {
+    type: [Number, String],
+    default: '200px'
+  },
+  editorConfig: {
+    type: Object,
+    default: () => {}
+  },
+  readonly: {
+    type: Boolean,
+    default: false
+  },
+})
+
+const emit = defineEmits(['change', 'update:modelValue'])
+
+// 编辑器实例,必须用 shallowRef
+const editorRef = shallowRef()
+
+const valueHtml = ref('')
+
+watch(
+  () => props.modelValue,
+  (val) => {
+    if (val === null) val = ''
+    if (val === unref(valueHtml)) return
+    valueHtml.value = val
+  },
+  {
+    immediate: true
+  }
+)
+
+// 监听
+watch(
+  () => valueHtml.value,
+  (val) => {
+    emit('update:modelValue', val)
+  }
+)
+
+const handleCreated = (editor) => {
+  editorRef.value = editor
+}
+
+// 编辑器配置
+import Snackbar from '@/plugins/snackbar'
+const editorConfig = computed(() => {
+  return Object.assign(
+    {
+      placeholder: '请输入内容...',
+      readOnly: props.item.readonly || props.readonly,
+      customAlert: (s, t) => {
+        switch (t) {
+          case 'success':
+            Snackbar.success(s)
+            break
+          case 'info':
+            Snackbar.info(s)
+            break
+          case 'warning':
+            Snackbar.warning(s)
+            break
+          case 'error':
+            Snackbar.error(s)
+            break
+          default:
+          Snackbar.info(s)
+            break
+        }
+      },
+      autoFocus: false,
+      scroll: true,
+      MENU_CONF: {
+        ['uploadImage']: {
+          server:`${import.meta.env.VITE_BASE_URL}/commons/upload`,
+          // 单个文件的最大体积限制,默认为 2M
+          // maxFileSize: 5 * 1024 * 1024,
+          // 最多可上传几个文件,默认为 100
+          // maxNumberOfFiles: 10,
+          // 选择文件时的类型限制,默认为 ['image/*'] 。如不想限制,则设置为 []
+          // allowedFileTypes: ['image/*'],
+
+          // 自定义上传参数,例如传递验证的 token 等。参数会被添加到 formData 中,一起上传到服务端。
+          // meta: { updateSupport: 0 },
+          // 将 meta 拼接到 url 参数中,默认 false
+          // metaWithUrl: true,
+
+          // 自定义增加 http  header
+          // headers: {
+          //   Accept: '*',
+          //   Authorization: 'Bearer ' + getAccessToken(),
+          //   'tenant-id': getTenantId()
+          // },
+
+          // 跨域是否传递 cookie ,默认为 false
+          // withCredentials: true,
+
+          // 超时时间,默认为 10 秒
+          timeout: 5 * 1000, // 5 秒
+
+          // form-data fieldName,后端接口参数名称,默认值wangeditor-uploaded-image
+          fieldName: 'file',
+
+          // 上传之前触发
+          onBeforeUpload(file) {
+            console.log(file)
+            return file
+          },
+          // 上传进度的回调函数
+          onProgress(progress) {
+            // progress 是 0-100 的数字
+            console.log('progress', progress)
+          },
+          onSuccess(file, res) {
+            console.log('onSuccess', file, res)
+          },
+          onFailed(file, res) {
+            alert(res.message)
+            console.log('onFailed', file, res)
+          },
+          onError(file, err, res) {
+            alert(err.message)
+            console.error('onError', file, err, res)
+          },
+          // 自定义插入图片
+          // customInsert(res, insertFn: InsertFnType) {
+          //   insertFn(res.data, 'image', res.data)
+          // }
+        }
+      },
+      uploadImgShowBase64: true,
+    },
+    props.editorConfig || props.item.editorConfig || {}
+  )
+})
+
+const editorStyle = computed(() => {
+  const h = props.item.height || props.height
+  return {
+    height: isNumber(h) ? `${h}px` : h
+  }
+})
+
+// 回调函数
+const handleChange = (editor) => {
+  emit('change', editor)
+}
+
+// 组件销毁时,及时销毁编辑器
+onBeforeUnmount(() => {
+  const editor = unref(editorRef.value)
+
+  // 销毁,并移除 editor
+  editor?.destroy()
+})
+
+const getEditorRef = async () => {
+  await nextTick()
+  return unref(editorRef.value)
+}
+
+defineExpose({
+  getEditorRef
+})
+</script>
+<style lang="scss" scoped>
+.box {
+  border: 1px solid #a2a2a2;
+  border-radius: 5px;
+  padding: 1px;
+  &:hover {
+    border: 1px solid #2b2b2b;
+  }
+}
+.color959595 {
+  color: #959595;
+}
+</style>

+ 52 - 29
src/views/recruit/enterprise/positionManagement/components/baseInfo.vue

@@ -49,6 +49,7 @@
           </template>
           <jobTypeCard class="jobTypeCardBox" :select="[query.positionId].filter(Boolean)" :isSingle="true" @handleJobClick="handleJobClickItem"></jobTypeCard>
         </v-menu>
+        <v-btn class="ml-3 half-button" color="primary" style="margin-top: 2px;" @click="useJobTemplate(item)">岗位模板</v-btn>
       </template>
     </CtForm>
 
@@ -175,6 +176,7 @@ const items = ref({
       labelKey: 'positionName',
       label: '职位类型 *',
       noParam: true,
+      readonly: true,
       rules: [v => !!v || '请选择职位类型']
     },
     // {
@@ -187,43 +189,55 @@ const items = ref({
     //   rules: [v => !!v || '请选择到期时间']
     // },
     {
-      type: 'textarea',
+      type: 'wangEditor',
       key: 'content',
-      rows: 10,
       value: '',
       label: '岗位职责 *',
-      counter: 5000,
-      clearable: true,
-      rules: [
-        value => {
-          if (value) return true
-          return '请输入岗位职责'
-        },
-        value => {
-          if (value?.length <= 5000) return true
-          return '请输入2-5000个字符'
-        }
-      ]
     },
     {
-      type: 'textarea',
+      type: 'wangEditor',
       key: 'requirement',
-      rows: 10,
       value: '',
       label: '岗位要求 *',
-      counter: 5000,
-      clearable: true,
-      rules: [
-        value => {
-          if (value) return true
-          return '请输入岗位要求'
-        },
-        value => {
-          if (value?.length <= 5000) return true
-          return '请输入2-5000个字符'
-        }
-      ]
-    }
+    },
+    // {
+    //   type: 'textarea',
+    //   key: 'content',
+    //   rows: 10,
+    //   value: '',
+    //   label: '岗位职责 *',
+    //   counter: 5000,
+    //   clearable: true,
+    //   rules: [
+    //     value => {
+    //       if (value) return true
+    //       return '请输入岗位职责'
+    //     },
+    //     value => {
+    //       if (value?.length <= 5000) return true
+    //       return '请输入2-5000个字符'
+    //     }
+    //   ]
+    // },
+    // {
+    //   type: 'textarea',
+    //   key: 'requirement',
+    //   rows: 10,
+    //   value: '',
+    //   label: '岗位要求 *',
+    //   counter: 5000,
+    //   clearable: true,
+    //   rules: [
+    //     value => {
+    //       if (value) return true
+    //       return '请输入岗位要求'
+    //     },
+    //     value => {
+    //       if (value?.length <= 5000) return true
+    //       return '请输入2-5000个字符'
+    //     }
+    //   ]
+    // }
   ]
 })
 
@@ -269,6 +283,15 @@ const handleJobClickItem = (list, name) => {
   positionId.value = name
 }
 
+// 岗位模板
+import Snackbar from '@/plugins/snackbar'
+const useJobTemplate = (item) => {
+  if (!query.positionId) return Snackbar.warning('请先选择职位类型')
+  console.log('1', item)
+  console.log('query.positionId', query.positionId)
+  // 获取职位模板内容-赋值
+}
+
 // 众聘规则查看
 const handleViewRule = () => {
   show.value = true