353 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			353 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
| <template>
 | ||
|   <KlNavTab></KlNavTab>
 | ||
|   <div class="mx-auto w-[1440px]">
 | ||
|     <!-- 使用 el-form 重构表单区域 -->
 | ||
|     <el-form ref="formRef" inline :model="formData" label-width="110px" class="custom-form mb-[20px] mt-[20px] border rounded p-[20px]!">
 | ||
|       <el-form-item label="标题:" prop="postsTitle" :rules="{ required: true, message: '请输入标题', trigger: 'blur' }">
 | ||
|         <el-input v-model="formData.postsTitle" placeholder="请输入标题" class="w-[300px]!" minlength="4" maxlength="40"></el-input>
 | ||
|       </el-form-item>
 | ||
|       <el-form-item label="分类:" class="mb-[10px]" prop="projectDicId" :rules="{ required: true, message: '请选择分类', trigger: ['blur', 'change'] }">
 | ||
|         <el-select v-model="formData.projectDicId" placeholder="请选择分类" class="w-[300px]!">
 | ||
|           <el-option v-for="item in projectTypeList" :key="item.id" :label="item.name" :value="item.id" />
 | ||
|         </el-select>
 | ||
|       </el-form-item>
 | ||
| 
 | ||
|       <el-form-item label="标签:" class="mb-[10px]" prop="postsTags" :rules="{ required: true, message: '请输入标签', trigger: ['blur', 'change'] }">
 | ||
|         <el-select
 | ||
|           v-model="formData.postsTags"
 | ||
|           :remote-method="remoteMethod"
 | ||
|           :loading="loading"
 | ||
|           filterable
 | ||
|           remote
 | ||
|           multiple
 | ||
|           placeholder="请输入搜索标签"
 | ||
|           class="w-[300px]!"
 | ||
|         >
 | ||
|           <el-option v-for="(item, index) in labelsList" :key="index" :label="item" :value="item" />
 | ||
|         </el-select>
 | ||
|       </el-form-item>
 | ||
|       <el-form-item label="频道列表:" class="mb-[10px]" prop="channelId" :rules="{ required: true, message: '请选择频道', trigger: ['blur', 'change'] }">
 | ||
|         <el-select v-model="formData.channelId" placeholder="请选择频道" class="w-[300px]!">
 | ||
|           <el-option v-for="item in channelIdList" :key="item.channelId" :label="item.channelTitle" :value="item.channelId" />
 | ||
|         </el-select>
 | ||
|       </el-form-item>
 | ||
|       <!-- <el-form-item label="上传封面:" prop="postsCover" :rules="{ required: true, message: '请上传封面', trigger: ['blur', 'change'] }">
 | ||
|         <KlUploader v-model:file-list="formData.postsCover" :limit="1" :size="1" tips="上传图片支持jpg/gif/png格式"> </KlUploader>
 | ||
|       </el-form-item> -->
 | ||
|     </el-form>
 | ||
|     <div style="border: 1px solid #eeeeee; margin-top: 10px" class="rounded">
 | ||
|       <WeToolbar style="border-bottom: 1px solid #eeeeee" :editor="editorRef" :defaultConfig="toolbarConfig" :mode="mode" />
 | ||
| 
 | ||
|       <WeEditor
 | ||
|         style="min-height: 300px; overflow-y: hidden"
 | ||
|         v-model="valueHtml"
 | ||
|         :defaultConfig="editorConfig"
 | ||
|         :mode="mode"
 | ||
|         @onCreated="handleCreated"
 | ||
|         @onChange="handleChange"
 | ||
|         @onDestroyed="handleDestroyed"
 | ||
|         @onFocus="handleFocus"
 | ||
|         @onBlur="handleBlur"
 | ||
|         @customAlert="customAlert"
 | ||
|         @customPaste="customPaste"
 | ||
|       />
 | ||
|     </div>
 | ||
|     <!-- 按钮区域 -->
 | ||
|     <div class="mt-[20px] flex justify-end">
 | ||
|       <!-- <el-button :loading="post_loading" class="mr-10px" @click="previewContent">预览</el-button> -->
 | ||
|       <el-button :loading="post_loading" type="primary" @click="saveContent">发表</el-button>
 | ||
|     </div>
 | ||
|   </div>
 | ||
| </template>
 | ||
| 
 | ||
| <script setup lang="ts">
 | ||
|   import { keywords } from '@/api/upnew/index'
 | ||
|   import { reactive, ref, onMounted } from 'vue'
 | ||
|   import { useRouter, useRoute } from 'vue-router'
 | ||
|   const router = useRouter()
 | ||
|   const route = useRoute()
 | ||
|   import { create, list } from '@/api/channel/index'
 | ||
|   import { parent } from '@/api/upnew/index'
 | ||
|   import { upload } from '@/api/common/index' // 自定义上传方法
 | ||
|   import '@wangeditor/editor/dist/css/style.css' // 引入 css
 | ||
|   // import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
 | ||
|   import { onBeforeUnmount, shallowRef } from 'vue'
 | ||
| 
 | ||
|   import useUserStore from '~/stores/user'
 | ||
|   const userStore = useUserStore()
 | ||
|   // 获取从其他地方传过来的参数
 | ||
|   const channelId = route.query.channelId as string
 | ||
| 
 | ||
|   const props = defineProps({
 | ||
|     value: {
 | ||
|       type: String,
 | ||
|       default: '',
 | ||
|     },
 | ||
|     placeholder: {
 | ||
|       type: String,
 | ||
|       default: '请输入帖子内容',
 | ||
|     },
 | ||
|     height: {
 | ||
|       type: Number,
 | ||
|       default: 500,
 | ||
|     },
 | ||
|     disabled: {
 | ||
|       type: Boolean,
 | ||
|       default: false,
 | ||
|     },
 | ||
|     plugins: {
 | ||
|       type: [String, Array],
 | ||
|       default: 'code image link preview table quickbars pagebreak lists advlist',
 | ||
|     },
 | ||
|     toolbar: {
 | ||
|       type: [String, Array],
 | ||
|       default:
 | ||
|         'undo redo codesample bold italic underline strikethrough link alignleft aligncenter alignright alignjustify \
 | ||
|       bullist numlist outdent indent removeformat forecolor backcolor |formatselect fontselect fontsizeselect | \
 | ||
|       blocks fontfamily fontsize pagebreak lists image customvideoupload table preview | code selectall',
 | ||
|     },
 | ||
|     templates: {
 | ||
|       type: Array,
 | ||
|       default: () => [],
 | ||
|     },
 | ||
|     options: {
 | ||
|       type: Object,
 | ||
|       default: () => ({}),
 | ||
|     },
 | ||
|   })
 | ||
| 
 | ||
|   const mode = 'default'
 | ||
| 
 | ||
|   const isClient = ref(false)
 | ||
|   if (import.meta.client) {
 | ||
|     isClient.value = true
 | ||
|   }
 | ||
| 
 | ||
|   // 编辑器实例,必须用 shallowRef
 | ||
|   const editorRef = shallowRef()
 | ||
| 
 | ||
|   // 内容 HTML
 | ||
|   const valueHtml = ref('<p></p>')
 | ||
| 
 | ||
|   // 模拟 ajax 异步获取内容
 | ||
|   onMounted(() => {
 | ||
|     setTimeout(() => {
 | ||
|       valueHtml.value = '<p></p>'
 | ||
|     }, 1500)
 | ||
|   })
 | ||
| 
 | ||
|   const toolbarConfig = {}
 | ||
|   const editorConfig = {
 | ||
|     placeholder: '请输入内容...',
 | ||
|     // 上传图片的配置
 | ||
|     MENU_CONF: {
 | ||
|       uploadImage: {
 | ||
|         server: 'https://tuxixi.net/prod-api/app-api/infra/file/upload',
 | ||
|         fieldName: 'file', //这个是参数名字
 | ||
|         headers: {
 | ||
|           //配置token 接口需要就配 不需要就不用
 | ||
|           Authorization: `Bearer ${userStore.token}`,
 | ||
|         },
 | ||
|         customInsert(res: any, insertFn: any) {
 | ||
|           // 这个是获取接口返回的数据
 | ||
|           insertFn(res.data) // 从 res 中找到 url(也就是接口返回的图片地址),然后插入图片
 | ||
|         },
 | ||
|       },
 | ||
|       uploadVideo: {
 | ||
|         server: 'https://tuxixi.net/prod-api/app-api/infra/file/upload',
 | ||
|         fieldName: 'file', //这个是参数名字
 | ||
|         maxFileSize: 1200 * 1024 * 1024, // 1200M
 | ||
|         headers: {
 | ||
|           //配置token 接口需要就配 不需要就不用
 | ||
|           Authorization: `Bearer ${userStore.token}`,
 | ||
|         },
 | ||
|         customInsert(res: any, insertFn: any) {
 | ||
|           // 这个是获取接口返回的数据
 | ||
|           insertFn(res.data) // 从 res 中找到 url(也就是接口返回的图片地址),然后插入图片
 | ||
|         },
 | ||
|       },
 | ||
|     },
 | ||
|   }
 | ||
| 
 | ||
|   // 表单数据
 | ||
|   const formData = ref({
 | ||
|     projectDicId: undefined,
 | ||
|     postsTags: [],
 | ||
|     postsCover: [] as any,
 | ||
|     postsTitle: '',
 | ||
|     channelId: channelId || undefined,
 | ||
|   })
 | ||
| 
 | ||
|   const formRef = ref()
 | ||
|   const post_loading = ref(false)
 | ||
|   const saveContent = () => {
 | ||
|     formRef.value.validate().then(() => {
 | ||
|       if (!valueHtml.value) {
 | ||
|         ElMessage.error('请输入帖子内容')
 | ||
|         return
 | ||
|       }
 | ||
|       post_loading.value = true
 | ||
|       create({
 | ||
|         postsTitle: formData.value.postsTitle,
 | ||
|         // postsCover: formData.value.postsCover[0].url,
 | ||
|         postsTags: formData.value.postsTags.join(','),
 | ||
|         postsContent: valueHtml.value,
 | ||
|         projectDicId: formData.value.projectDicId,
 | ||
|         channelId: formData.value.channelId,
 | ||
|       })
 | ||
|         .then((res) => {
 | ||
|           console.log(res)
 | ||
|           if (res.code === 0) {
 | ||
|             ElMessage.success('发表成功')
 | ||
|             router.back()
 | ||
|             // router.push('/communication/channel')
 | ||
|           }
 | ||
|         })
 | ||
|         .catch((err) => {
 | ||
|           console.log(err)
 | ||
|         })
 | ||
|         .finally(() => {
 | ||
|           post_loading.value = false
 | ||
|         })
 | ||
|     })
 | ||
|   }
 | ||
| 
 | ||
|   // 组件销毁时,也及时销毁编辑器
 | ||
|   onBeforeUnmount(() => {
 | ||
|     const editor = editorRef.value
 | ||
|     if (editor == null) return
 | ||
|     editor.destroy()
 | ||
|   })
 | ||
| 
 | ||
|   const handleCreated = (editor: any) => {
 | ||
|     editorRef.value = editor // 记录 editor 实例,重要!
 | ||
|   }
 | ||
| 
 | ||
|   const handleChange = (editor: any) => {
 | ||
|     console.log('change:', editor.getHtml())
 | ||
|   }
 | ||
| 
 | ||
|   const handleDestroyed = (editor: any) => {
 | ||
|     console.log('destroyed', editor)
 | ||
|   }
 | ||
| 
 | ||
|   const handleFocus = (editor: any) => {
 | ||
|     console.log('focus', editor)
 | ||
|   }
 | ||
| 
 | ||
|   const handleBlur = (editor: any) => {
 | ||
|     console.log('blur', editor)
 | ||
|   }
 | ||
|   const customAlert = (info: any, type: any) => {
 | ||
|     alert(`【自定义提示】${type} - ${info}`)
 | ||
|   }
 | ||
|   const customPaste = (editor: any, event: any, callback: any) => {
 | ||
|     console.log('ClipboardEvent 粘贴事件对象', event)
 | ||
|     // const html = event.clipboardData.getData('text/html') // 获取粘贴的 html
 | ||
|     const text = event.clipboardData.getData('text/plain') // 获取粘贴的纯文本
 | ||
|     // const rtf = event.clipboardData.getData('text/rtf') // 获取 rtf 数据(如从 word wsp 复制粘贴)
 | ||
| 
 | ||
|     // 自定义插入内容
 | ||
|     editor.insertText(text)
 | ||
| 
 | ||
|     // 返回 false ,阻止默认粘贴行为
 | ||
|     event.preventDefault()
 | ||
|     callback(false) // 返回值(注意,vue 事件的返回值,不能用 return)
 | ||
| 
 | ||
|     // 返回 true ,继续默认的粘贴行为
 | ||
|     // callback(true)
 | ||
|   }
 | ||
| 
 | ||
|   /** 获取频道列表 */
 | ||
|   const channelIdList = ref<any>([])
 | ||
|   const getChannelIdList = () => {
 | ||
|     list().then((res) => {
 | ||
|       channelIdList.value = res.data
 | ||
|     })
 | ||
|   }
 | ||
|   getChannelIdList()
 | ||
| 
 | ||
|   const projectTypeList = ref<any>([])
 | ||
|   /** 获取分类下拉框 */
 | ||
|   const getParent = () => {
 | ||
|     parent({
 | ||
|       type: 1,
 | ||
|       parentId: 0,
 | ||
|     }).then((res) => {
 | ||
|       projectTypeList.value = res.data
 | ||
|     })
 | ||
|   }
 | ||
|   getParent()
 | ||
| 
 | ||
|   const loading = ref(false)
 | ||
|   /** 获取标签 */
 | ||
|   const labelsList = ref<any>([])
 | ||
|   const remoteMethod = (query: string) => {
 | ||
|     if (query) {
 | ||
|       loading.value = true
 | ||
|       keywords({
 | ||
|         type: 1,
 | ||
|         keywords: query,
 | ||
|       })
 | ||
|         .then((res) => {
 | ||
|           labelsList.value = res.data
 | ||
|         })
 | ||
|         .finally(() => {
 | ||
|           loading.value = false
 | ||
|         })
 | ||
|     } else {
 | ||
|       labelsList.value = []
 | ||
|     }
 | ||
|   }
 | ||
| </script>
 | ||
| 
 | ||
| <style scoped>
 | ||
|   .custom-form {
 | ||
|     border: 2px solid #eeeeee;
 | ||
|     border-radius: 4px;
 | ||
|   }
 | ||
| 
 | ||
|   .upload-container {
 | ||
|     display: flex;
 | ||
|     flex-direction: column;
 | ||
|   }
 | ||
| 
 | ||
|   .cover-uploader {
 | ||
|     :deep(.el-upload) {
 | ||
|       width: fit-content;
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   .image-error {
 | ||
|     display: flex;
 | ||
|     flex-direction: column;
 | ||
|     align-items: center;
 | ||
|     justify-content: center;
 | ||
|     color: #909399;
 | ||
|     height: 100%;
 | ||
|   }
 | ||
| 
 | ||
|   .image-actions {
 | ||
|     display: flex;
 | ||
|     justify-content: center;
 | ||
|   }
 | ||
| 
 | ||
|   :deep(.el-form-item__label) {
 | ||
|     font-weight: normal;
 | ||
|   }
 | ||
| 
 | ||
|   :deep(.el-button--primary.is-link) {
 | ||
|     padding: 0;
 | ||
|     height: auto;
 | ||
|     font-size: 14px;
 | ||
|   }
 | ||
| 
 | ||
|   .text-gray-500 {
 | ||
|     color: #999;
 | ||
|   }
 | ||
| 
 | ||
|   .text-12px {
 | ||
|     font-size: 12px;
 | ||
|   }
 | ||
| </style>
 | 
