123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 |
- <script setup>
- import {assistanceDocInterence} from "@/api/api.js"
- import {createBottomHref} from "./utils/common"
- import { ref, reactive, watch, onUnmounted } from 'vue'
- import { useRoute, useRouter } from 'vue-router'
- import { ElMessage } from 'element-plus'
- const route = useRoute()
- const router = useRouter()
- const froalaConfig = {
- toolbarButtons: [
- "insertImage",
- "insertVideo",
- "embedly",
- "insertFile",
- "textColor",
- "bold",
- "italic",
- "underline",
- "strikeThrough",
- "subscript",
- "superscript",
- "fontFamily",
- "fontSize",
- "color",
- "inlineClass",
- "inlineStyle",
- "paragraphStyle",
- "lineHeight",
- "paragraphFormat",
- "align",
- "formatOL",
- "formatUL",
- "outdent",
- "indent",
- "quote",
- "insertTable",
- "emoticons",
- "fontAwesome",
- "specialCharacters",
- "insertHR",
- "selectAll",
- "clearFormatting",
- "html",
- "undo",
- "redo"
- ],
- height:"calc(100vh - 230px)",
- fontSize: ["12", "14", "16", "18", "20", "24", "28", "32", "36", "40"],
- fontSizeDefaultSelection: "16",
- theme: "dark", //主题
- placeholderText: "请输入内容",
- language: "zh_cn", //国际化
- imageUploadURL: import.meta.env.VITE_APP_API_URL + "/report/uploadImg", //上传url
- videoUploadURL: import.meta.env.VITE_APP_API_URL + "/report/uploadImg", //上传url
- fileUploadURL: import.meta.env.VITE_APP_API_URL + "/report/uploadImg", //上传url 更多上传介绍 请访问https://www.froala.com/wysiwyg-editor/docs/options
- imageEditButtons:['imageAlign', 'imageCaption', 'imageRemove', '-', 'imageDisplay', 'imageSize'],
- quickInsertButtons: ["image","video","hr"], //快速插入项
- quickInsertEnabled:false, // 是否启用快速插入功能
- toolbarVisibleWithoutSelection: false, //是否开启 不选中模式
- toolbarSticky: false, //操作栏是否自动吸顶
- saveInterval: 0,
- }
- let classifyList = ref([])
- let addDocForm = reactive({
- Title:"",
- ClassifyId:"",
- Author:"",
- Status:1,
- Content:'',
- AnchorData:[],
- RecommendData:[{Name:"",Url:""},{Name:"",Url:""}],
- })
- const addDocRules = reactive({
- Title:{required:true,message:'文章标题不能为空',trigger:'blur'},
- ClassifyId:{required:true,message:'文章所属分类不能为空',trigger:'change'},
- Author:{required:true,message:'文章作者不能为空',trigger:'blur'}
- })
- let anchorData = ref([])
- let isSubmiting = ref(false)
- let autoSaveTimer = ref(null)
- let modifyTime = ref('')
- let contentChange = ref(false)
- watch(addDocForm,()=>{
- contentChange.value=true
- },{deep:true})
- function getClassifyData(){
- assistanceDocInterence.getAssistanceClassifyList().then(res=>{
- if(res.Ret == 200){
- classifyList.value = res.Data?.AllNodes||[]
- }
- })
- }
- function init(){
- getClassifyData()
- if(route.query.DocId){
- assistanceDocInterence.getAssistanceDoc({DocId:route.query.DocId}).then(res=>{
- if(res.Ret == 200){
- Object.assign(addDocForm,{
- Id:res.Data.Id,
- Title:res.Data.Title,
- ClassifyId:res.Data.ClassifyId,
- Author:res.Data.Author,
- Status:res.Data.Status,
- Content:res.Data.Content,
- RecommendData:res.Data.Recommend || [{Name:"",Url:""},{Name:"",Url:""}]
- })
- modifyTime.value=res.Data.ModifyTime
- if((!autoSaveTimer.value) && addDocForm.Id){
- autoSaveTimer.value=setInterval(()=>{
- saveDocument('保存',true)
- },6000)
- }
- }
- })
- }else{
- addDocForm.Content=""
- }
- }
- init()
- onUnmounted(()=>{
- clearInterval(autoSaveTimer.value)
- autoSaveTimer.value=null
- })
- // 生成锚点
- function generateAnchor(){
- anchorData.value=[]
- searchTitleTag(0,1)
- }
- // 搜索标题标签h1,h2
- function searchTitleTag(searchPosition,firstLevel){
- let frontH1Posiiton,nextH1Posiiton,H2Posiiton=0
- let frontH1RightPosiiton,H2RightPosiiton=0
- let backH1Posiiton,backH2Posiiton=0
- // 本次搜索第一个h1的位置
- frontH1Posiiton = addDocForm.Content.indexOf('<h1',searchPosition)
- // 右闭合标签
- frontH1RightPosiiton = addDocForm.Content.indexOf('>',frontH1Posiiton)
- if(frontH1Posiiton == -1) return
- let anchorText=`id="doc_anchor_${firstLevel}"`
- // console.log(frontH1Posiiton,firstLevel,'firstLevel');
- addDocForm.Content = addDocForm.Content.substring(0, frontH1Posiiton+3)
- +" "+anchorText + addDocForm.Content.substring(frontH1RightPosiiton);
- // 再次获取右闭合标签
- frontH1RightPosiiton = addDocForm.Content.indexOf('>',frontH1Posiiton)
- // 对应的</h1>的位置 原本用的</h1>,后来发现> 和 </h1>之间会掺其他标签
- backH1Posiiton = addDocForm.Content.indexOf('<',frontH1RightPosiiton)
- // 获取标题
- let AnchorTitle = addDocForm.Content.substring(frontH1RightPosiiton+1,backH1Posiiton)
- anchorData.push({
- AnchorId:`${firstLevel}`,
- Anchor:`doc_anchor_${firstLevel}`,
- AnchorName:AnchorTitle,
- Child:[]})
- // 本次搜索下一个h1的位置
- nextH1Posiiton = addDocForm.Content.indexOf('<h1',backH1Posiiton)==-1?
- addDocForm.Content.length:addDocForm.Content.indexOf('<h1',backH1Posiiton)
- // 从第一个h1的位置开始查找h2标签
- H2Posiiton = addDocForm.Content.indexOf('<h2',backH1Posiiton)
- let secondLevel=1
- while (!(H2Posiiton==-1 || H2Posiiton>nextH1Posiiton)) {
- // 右闭合标签
- H2RightPosiiton = addDocForm.Content.indexOf('>',H2Posiiton)
- // 找到了,并且位置小于下一个h1的位置
- let anchorTextH2=`id="doc_anchor_${firstLevel}_${secondLevel}"`
- // console.log(H2Posiiton,secondLevel,'secondLevel');
- addDocForm.Content = addDocForm.Content.substring(0, H2Posiiton+3)
- +" "+anchorTextH2 + addDocForm.Content.substring(H2RightPosiiton);
- // 再次获取右闭合标签
- H2RightPosiiton = addDocForm.Content.indexOf('>',H2Posiiton)
- // 对应的</h2>的位置 原本用的</h2>,后来发现> 和 </h2>之间会掺其他标签
- backH2Posiiton = addDocForm.Content.indexOf('<',H2RightPosiiton)
- // 获取标题
- let AnchorTitleLevelTwo = addDocForm.Content.substring(H2RightPosiiton+1,backH2Posiiton)
- anchorData[firstLevel-1].Child.push(
- {AnchorId:`${firstLevel}_${secondLevel}`,
- Anchor:`doc_anchor_${firstLevel}_${secondLevel}`,
- AnchorName:AnchorTitleLevelTwo,
- Child:[]
- })
- // nextH1Posiiton 和 secondLevel 随之增加
- // nextH1Posiiton +=anchorTextH2.length+1
- // 更新nextH1Posiiton位置
- nextH1Posiiton = addDocForm.Content.indexOf('<h1',backH1Posiiton)==-1?
- addDocForm.Content.length:addDocForm.Content.indexOf('<h1',backH1Posiiton)
- secondLevel++
- H2Posiiton = addDocForm.Content.indexOf('<h2',backH2Posiiton)
- }
- // 结束一轮 <h1></h1>标签的寻找
- if(addDocForm.Content.indexOf('<h1',backH1Posiiton+4)!=-1){
- // 如果有下一个h1的标签,说明寻找还没结束,继续寻找
- firstLevel++
- searchTitleTag(nextH1Posiiton,firstLevel)
- }
- }
- function previewDocument(){
- if(isSubmiting.value) return
- if(!addDocForm.Content){
- ElMessage.error("文章内容不能为空")
- return
- }
- let bottomLink = createBottomHref(addDocForm.RecommendData)
- sessionStorage.setItem("documentDoc",addDocForm.Content+bottomLink)
- let { href } = router.resolve({ path: "/assistanceDocDetail" });
- window.open(href, "_blank");
- }
- let addDocFormRef = ref(null)
- function saveDocument(type,isAuto){
- if(isSubmiting.value) return
- addDocFormRef.value?.validate(valid=>{
- if(valid){
- if(!addDocForm.Content){
- ElMessage.error("文章内容不能为空")
- return
- }
- if(!isAuto) isSubmiting.value=true
- if(type=="发布") addDocForm.Status=2
- generateAnchor()
- addDocForm.AnchorData = anchorData.value
- //保存
- assistanceDocInterence.addAssistanceDoc({...addDocForm,IsChange:contentChange.value}).then(res=>{
- if(res.Ret == 200){
- contentChange.value=false
- !isAuto && ElMessage({
- type:'success',
- message:'操作成功',
- duration:1000
- })
- if(type=="发布"){
- setTimeout(()=>{
- router.back()
- },1000)
- }else{
- isSubmiting.value=false
- modifyTime.value=res.Data.ModifyTime
- if(!addDocForm.Id){
- //新增
- setTimeout(()=>{
- router.replace("/assistanceDocAdd?DocId="+res.Data.HelpDocId)
- addDocForm.Id = res.Data.HelpDocId
- if((!autoSaveTimer) && addDocForm.Id){
- autoSaveTimer=setInterval(()=>{
- saveDocument('保存',true)
- },6000)
- }
- },1000)
- }
- }
- }
- }).catch(()=>{
- isSubmiting.value=false
- })
- }
- })
- }
- </script>
- <template>
- <div class="assistance-edit-container">
- <div class="edit-container-rich-text">
- <froala
- id="froala-editor"
- ref="froalaEditor"
- :tag="'textarea'"
- :config="froalaConfig"
- v-model:value="addDocForm.Content"
- ></froala>
- </div>
- <div class="right-area">
- <div class="save-time" v-if="modifyTime">
- 最近保存时间:{{ modifyTime }}
- </div>
- <div class="edit-container-document-options">
- <div class="document-options-button-box">
- <el-button type="primary" class="document-options-button" @click="previewDocument" v-loading="isSubmiting">预览</el-button>
- <el-button type="primary" class="document-options-button" @click="saveDocument('保存')" v-loading="isSubmiting">保存</el-button>
- <el-button type="primary" class="document-options-button" @click="saveDocument('发布')" v-loading="isSubmiting">发布</el-button>
- </div>
- <div class="document-options-form">
- <el-form :model="addDocForm" ref="addDocFormRef" :rules="addDocRules" label-position="top">
- <el-form-item label="文章标题" prop="Title">
- <el-input v-model="addDocForm.Title" placeholder="请输入文章标题"></el-input>
- </el-form-item>
- <el-form-item label="所属分类" prop="ClassifyId">
- <el-cascader style="width: 100%;"
- v-model="addDocForm.ClassifyId" :options="classifyList"
- :props="{value:'ClassifyId',label:'ClassifyName',children:'Children',emitPath:false,disabled:'Disabled'}" placeholder="所属分类"/>
- </el-form-item>
- <el-form-item label="文章作者" prop="Author">
- <el-input v-model="addDocForm.Author" placeholder="请输入文章作者"></el-input>
- </el-form-item>
- <el-form-item label="相关推荐">
- <div v-for="(item,index) in addDocForm.RecommendData" :key="index" class="form-item-recommendedLink">
- <el-input v-model="item.Name" placeholder="请输入链接名称" style="width: 190px;"></el-input>
- <div class="recommendedLink-line"></div>
- <el-input v-model="item.Url" placeholder="请输入链接" style="width: 190px;"></el-input>
- </div>
- </el-form-item>
- </el-form>
- </div>
- </div>
- </div>
- </div>
- </template>
- <style lang="scss" scoped>
- .assistance-edit-container{
- display: flex;
- justify-content: flex-start;
- .edit-container-rich-text{
- flex-grow: 1;
- min-height: calc(100vh - 110px);
- background-color: white;
- border:solid 1px #ECECEC;
- box-sizing: border-box;
- }
- .right-area{
- margin-left: 20px;
- min-height: calc(100vh - 112px);
- .save-time{
- font-size: 16px;
- line-height: 22px;
- color: #000000;
- font-weight: 400;
- background-color: unset;
- margin-bottom: 10px;
- }
- .edit-container-document-options{
- min-height: calc(100% - 32px);
- background-color: white;
- border:solid 1px #ECECEC;
- box-sizing: border-box;
- width: 440px;
- min-width: 440px;
- .document-options-button-box{
- width: 100%;
- height: 80px;
- box-sizing: border-box;
- padding: 20px;
- box-shadow: 0px 5px 10px #ECECEC;
- border-bottom: solid 1px #ECECEC;
- .document-options-button{
- height: 40px;
- width: 120px;
- }
- }
- .document-options-form{
- padding: 30px 20px;
- .form-item-recommendedLink{
- display: flex;
- align-items: center;
- justify-content: flex-start;
- width: 100%;
- margin-bottom: 20px;
- &:last-child{
- margin-bottom: 0;
- }
- .recommendedLink-line{
- flex: 1;
- height: 1px;
- background-color:#DCDFE6 ;
- }
- }
- }
- }
- }
- }
- </style>
- <style lang="scss">
- .assistance-edit-container{
- .fr-toolbar,.fr-box.fr-basic .fr-wrapper{
- border: none;
- }
- }
- .fr-popup.fr-active{
- z-index: 100000!important;
- opacity: 1!important;
- }
- </style>
|