Merge remote-tracking branch 'origin/dev' into dev
						commit
						b133983eb6
					
				|  | @ -0,0 +1,201 @@ | |||
| <script setup> | ||||
| import { ref, reactive } from 'vue' | ||||
| import { QuillEditor } from '@vueup/vue-quill' | ||||
| import '@vueup/vue-quill/dist/vue-quill.snow.css' | ||||
| import { getAccessToken } from '@/utils/auth' | ||||
| import editorOptions from './quill-options' | ||||
| 
 | ||||
| const BASE_URL = import.meta.env.VITE_BASE_URL | ||||
| 
 | ||||
| const message = useMessage() | ||||
| 
 | ||||
| const props = defineProps({ | ||||
|   /* 公众号账号编号 */ | ||||
|   accountId: { | ||||
|     type: Number, | ||||
|     required: true | ||||
|   }, | ||||
|   /* 编辑器的内容 */ | ||||
|   value: { | ||||
|     type: String, | ||||
|     default: '' | ||||
|   }, | ||||
|   /* 图片大小 */ | ||||
|   maxSize: { | ||||
|     type: Number, | ||||
|     default: 4000 // kb | ||||
|   } | ||||
| }) | ||||
| 
 | ||||
| const emit = defineEmits(['input']) | ||||
| 
 | ||||
| const myQuillEditorRef = ref() | ||||
| 
 | ||||
| const content = ref(props.value.replace(/data-src/g, 'src')) | ||||
| 
 | ||||
| const loading = ref(false) // 根据图片上传状态来确定是否显示loading动画,刚开始是false,不显示 | ||||
| 
 | ||||
| const actionUrl = ref(BASE_URL + '/admin-api/mp/material/upload-news-image') // 这里写你要上传的图片服务器地址 | ||||
| const headers = ref({ Authorization: 'Bearer ' + getAccessToken() }) // 设置上传的请求头部 | ||||
| const uploadData = reactive({ | ||||
|   type: 'image', // TODO 芋艿:试试要不要换成 thumb | ||||
|   accountId: props.accountId | ||||
| }) | ||||
| 
 | ||||
| const onEditorChange = () => { | ||||
|   //内容改变事件 | ||||
|   emit('input', content.value) | ||||
| } | ||||
| 
 | ||||
| // 富文本图片上传前 | ||||
| const beforeUpload = () => { | ||||
|   // 显示 loading 动画 | ||||
|   loading.value = true | ||||
| } | ||||
| 
 | ||||
| // 图片上传成功 | ||||
| // 注意!由于微信公众号的图片有访问限制,所以会显示“此图片来自微信公众号,未经允许不可引用” | ||||
| const uploadSuccess = (res) => { | ||||
|   // res为图片服务器返回的数据 | ||||
|   // 获取富文本组件实例 | ||||
|   const quill = myQuillEditorRef.value.quill | ||||
|   // 如果上传成功 | ||||
|   const link = res.data | ||||
|   if (link) { | ||||
|     // 获取光标所在位置 | ||||
|     let length = quill.getSelection().index | ||||
|     // 插入图片  res.info为服务器返回的图片地址 | ||||
|     quill.insertEmbed(length, 'image', link) | ||||
|     // 调整光标到最后 | ||||
|     quill.setSelection(length + 1) | ||||
|   } else { | ||||
|     message.error('图片插入失败') | ||||
|   } | ||||
|   // loading 动画消失 | ||||
|   loading.value = false | ||||
| } | ||||
| 
 | ||||
| // 富文本图片上传失败 | ||||
| const uploadError = () => { | ||||
|   // loading 动画消失 | ||||
|   loading.value = false | ||||
|   message.error('图片插入失败') | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <div id="wxEditor"> | ||||
|     <div v-loading="loading" element-loading-text="请稍等,图片上传中"> | ||||
|       <!-- 图片上传组件辅助--> | ||||
|       <el-upload | ||||
|         class="avatar-uploader" | ||||
|         name="file" | ||||
|         :action="actionUrl" | ||||
|         :headers="headers" | ||||
|         :show-file-list="false" | ||||
|         :data="uploadData" | ||||
|         :on-success="uploadSuccess" | ||||
|         :on-error="uploadError" | ||||
|         :before-upload="beforeUpload" | ||||
|       /> | ||||
|       <QuillEditor | ||||
|         class="editor" | ||||
|         v-model="content" | ||||
|         ref="quillEditorRef" | ||||
|         :options="editorOptions" | ||||
|         @change="onEditorChange($event)" | ||||
|       /> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <style> | ||||
| .editor { | ||||
|   line-height: normal !important; | ||||
|   height: 500px; | ||||
| } | ||||
| 
 | ||||
| .ql-snow .ql-tooltip[data-mode='link']::before { | ||||
|   content: '请输入链接地址:'; | ||||
| } | ||||
| 
 | ||||
| .ql-snow .ql-tooltip.ql-editing a.ql-action::after { | ||||
|   border-right: 0; | ||||
|   content: '保存'; | ||||
|   padding-right: 0; | ||||
| } | ||||
| 
 | ||||
| .ql-snow .ql-tooltip[data-mode='video']::before { | ||||
|   content: '请输入视频地址:'; | ||||
| } | ||||
| 
 | ||||
| .ql-snow .ql-picker.ql-size .ql-picker-label::before, | ||||
| .ql-snow .ql-picker.ql-size .ql-picker-item::before { | ||||
|   content: '14px'; | ||||
| } | ||||
| 
 | ||||
| .ql-snow .ql-picker.ql-size .ql-picker-label[data-value='small']::before, | ||||
| .ql-snow .ql-picker.ql-size .ql-picker-item[data-value='small']::before { | ||||
|   content: '10px'; | ||||
| } | ||||
| 
 | ||||
| .ql-snow .ql-picker.ql-size .ql-picker-label[data-value='large']::before, | ||||
| .ql-snow .ql-picker.ql-size .ql-picker-item[data-value='large']::before { | ||||
|   content: '18px'; | ||||
| } | ||||
| 
 | ||||
| .ql-snow .ql-picker.ql-size .ql-picker-label[data-value='huge']::before, | ||||
| .ql-snow .ql-picker.ql-size .ql-picker-item[data-value='huge']::before { | ||||
|   content: '32px'; | ||||
| } | ||||
| 
 | ||||
| .ql-snow .ql-picker.ql-header .ql-picker-label::before, | ||||
| .ql-snow .ql-picker.ql-header .ql-picker-item::before { | ||||
|   content: '文本'; | ||||
| } | ||||
| 
 | ||||
| .ql-snow .ql-picker.ql-header .ql-picker-label[data-value='1']::before, | ||||
| .ql-snow .ql-picker.ql-header .ql-picker-item[data-value='1']::before { | ||||
|   content: '标题1'; | ||||
| } | ||||
| 
 | ||||
| .ql-snow .ql-picker.ql-header .ql-picker-label[data-value='2']::before, | ||||
| .ql-snow .ql-picker.ql-header .ql-picker-item[data-value='2']::before { | ||||
|   content: '标题2'; | ||||
| } | ||||
| 
 | ||||
| .ql-snow .ql-picker.ql-header .ql-picker-label[data-value='3']::before, | ||||
| .ql-snow .ql-picker.ql-header .ql-picker-item[data-value='3']::before { | ||||
|   content: '标题3'; | ||||
| } | ||||
| 
 | ||||
| .ql-snow .ql-picker.ql-header .ql-picker-label[data-value='4']::before, | ||||
| .ql-snow .ql-picker.ql-header .ql-picker-item[data-value='4']::before { | ||||
|   content: '标题4'; | ||||
| } | ||||
| 
 | ||||
| .ql-snow .ql-picker.ql-header .ql-picker-label[data-value='5']::before, | ||||
| .ql-snow .ql-picker.ql-header .ql-picker-item[data-value='5']::before { | ||||
|   content: '标题5'; | ||||
| } | ||||
| 
 | ||||
| .ql-snow .ql-picker.ql-header .ql-picker-label[data-value='6']::before, | ||||
| .ql-snow .ql-picker.ql-header .ql-picker-item[data-value='6']::before { | ||||
|   content: '标题6'; | ||||
| } | ||||
| 
 | ||||
| .ql-snow .ql-picker.ql-font .ql-picker-label::before, | ||||
| .ql-snow .ql-picker.ql-font .ql-picker-item::before { | ||||
|   content: '标准字体'; | ||||
| } | ||||
| 
 | ||||
| .ql-snow .ql-picker.ql-font .ql-picker-label[data-value='serif']::before, | ||||
| .ql-snow .ql-picker.ql-font .ql-picker-item[data-value='serif']::before { | ||||
|   content: '衬线字体'; | ||||
| } | ||||
| 
 | ||||
| .ql-snow .ql-picker.ql-font .ql-picker-label[data-value='monospace']::before, | ||||
| .ql-snow .ql-picker.ql-font .ql-picker-item[data-value='monospace']::before { | ||||
|   content: '等宽字体'; | ||||
| } | ||||
| </style> | ||||
|  | @ -0,0 +1,45 @@ | |||
| const toolbarOptions = [ | ||||
|   ['bold', 'italic', 'underline', 'strike'], // 加粗 斜体 下划线 删除线
 | ||||
|   ['blockquote', 'code-block'], // 引用  代码块
 | ||||
|   [{ header: 1 }, { header: 2 }], // 1、2 级标题
 | ||||
|   [{ list: 'ordered' }, { list: 'bullet' }], // 有序、无序列表
 | ||||
|   [{ script: 'sub' }, { script: 'super' }], // 上标/下标
 | ||||
|   [{ indent: '-1' }, { indent: '+1' }], // 缩进
 | ||||
|   // [{'direction': 'rtl'}],                         // 文本方向
 | ||||
|   [{ size: ['small', false, 'large', 'huge'] }], // 字体大小
 | ||||
|   [{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
 | ||||
|   [{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
 | ||||
|   [{ font: [] }], // 字体种类
 | ||||
|   [{ align: [] }], // 对齐方式
 | ||||
|   ['clean'], // 清除文本格式
 | ||||
|   ['link', 'image', 'video'] // 链接、图片、视频
 | ||||
| ] | ||||
| 
 | ||||
| export default { | ||||
|   theme: 'snow', | ||||
|   placeholder: '请输入文章内容', | ||||
|   modules: { | ||||
|     toolbar: { | ||||
|       container: toolbarOptions, | ||||
|       // container: "#toolbar",
 | ||||
|       handlers: { | ||||
|         image: function (value) { | ||||
|           if (value) { | ||||
|             // 触发input框选择图片文件
 | ||||
|             document.querySelector('.avatar-uploader input').click() | ||||
|           } else { | ||||
|             this.quill.format('image', false) | ||||
|           } | ||||
|         }, | ||||
|         link: function (value) { | ||||
|           if (value) { | ||||
|             const href = prompt('注意!只支持公众号图文链接') | ||||
|             this.quill.format('link', href) | ||||
|           } else { | ||||
|             this.quill.format('link', false) | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -29,6 +29,7 @@ | |||
|     </div> | ||||
|     <!-- 类型:voice --> | ||||
|     <div v-else-if="objData.type === 'voice'"> | ||||
|       <!-- 列表 --> | ||||
|       <el-table v-loading="loading" :data="list"> | ||||
|         <el-table-column label="编号" align="center" prop="mediaId" /> | ||||
|         <el-table-column label="文件名" align="center" prop="name" /> | ||||
|  | @ -47,7 +48,7 @@ | |||
|         <el-table-column label="操作" align="center" fixed="right"> | ||||
|           <template #default="scope"> | ||||
|             <el-button type="text" @click="selectMaterialFun(scope.row)"> | ||||
|               选择 <Icon icon="ep:plus" /> | ||||
|               选择<Icon icon="ep:plus" /> | ||||
|             </el-button> | ||||
|           </template> | ||||
|         </el-table-column> | ||||
|  | @ -80,10 +81,15 @@ | |||
|           width="180" | ||||
|           :formatter="dateFormatter" | ||||
|         /> | ||||
|         <el-table-column label="操作" align="center"> | ||||
|         <el-table-column | ||||
|           label="操作" | ||||
|           align="center" | ||||
|           fixed="right" | ||||
|           class-name="small-padding fixed-width" | ||||
|         > | ||||
|           <template #default="scope"> | ||||
|             <el-button type="text" @click="selectMaterialFun(scope.row)" | ||||
|               >选择<Icon icon="ep:circle-plus" /> | ||||
|             <el-button type="text" @click="selectMaterialFun(scope.row)"> | ||||
|               选择<Icon icon="akar-icons:circle-plus" /> | ||||
|             </el-button> | ||||
|           </template> | ||||
|         </el-table-column> | ||||
|  | @ -104,7 +110,7 @@ | |||
|             <WxNews :articles="item.content.newsItem" /> | ||||
|             <el-row class="ope-row"> | ||||
|               <el-button type="success" @click="selectMaterialFun(item)"> | ||||
|                 选择 <Icon icon="ep:circle-check" /> | ||||
|                 选择<Icon icon="ep:circle-check" /> | ||||
|               </el-button> | ||||
|             </el-row> | ||||
|           </div> | ||||
|  | @ -120,6 +126,7 @@ | |||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts" name="WxMaterialSelect"> | ||||
| import WxNews from '@/views/mp/components/wx-news/main.vue' | ||||
| import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue' | ||||
|  | @ -231,7 +238,7 @@ export default defineComponent({ | |||
|       selectMaterialFun, | ||||
|       getMaterialPageFun, | ||||
|       getPage, | ||||
|       newsTypeRef, | ||||
|       formatDate, | ||||
|       queryParams, | ||||
|       objDataRef, | ||||
|       list, | ||||
|  | @ -241,7 +248,6 @@ export default defineComponent({ | |||
|   } | ||||
| }) | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| /*瀑布流样式*/ | ||||
| .waterfall { | ||||
|  |  | |||
|  | @ -1,3 +1,815 @@ | |||
| <template> | ||||
|   <span>开发中</span> | ||||
|   <doc-alert title="公众号图文" url="https://doc.iocoder.cn/mp/article/" /> | ||||
| 
 | ||||
|   <!-- 搜索工作栏 --> | ||||
|   <ContentWrap> | ||||
|     <el-form | ||||
|       class="-mb-15px" | ||||
|       :model="queryParams" | ||||
|       ref="queryFormRef" | ||||
|       :inline="true" | ||||
|       label-width="68px" | ||||
|     > | ||||
|       <el-form-item label="公众号" prop="accountId"> | ||||
|         <el-select v-model="queryParams.accountId" placeholder="请选择公众号"> | ||||
|           <el-option | ||||
|             v-for="item in accountList" | ||||
|             :key="item.id" | ||||
|             :label="item.name" | ||||
|             :value="item.id" | ||||
|           /> | ||||
|         </el-select> | ||||
|       </el-form-item> | ||||
|       <el-form-item> | ||||
|         <el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button> | ||||
|         <el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button> | ||||
|         <el-button type="primary" plain @click="handleAdd" v-hasPermi="['mp:draft:create']"> | ||||
|           <Icon icon="ep:plus" />新增 | ||||
|         </el-button> | ||||
|       </el-form-item> | ||||
|     </el-form> | ||||
|   </ContentWrap> | ||||
| 
 | ||||
|   <!-- 列表 --> | ||||
|   <ContentWrap> | ||||
|     <div class="waterfall" v-loading="loading"> | ||||
|       <template v-for="item in list" :key="item.articleId"> | ||||
|         <div class="waterfall-item" v-if="item.content && item.content.newsItem"> | ||||
|           <wx-news :articles="item.content.newsItem" /> | ||||
|           <!-- 操作按钮 --> | ||||
|           <el-row class="ope-row"> | ||||
|             <el-button | ||||
|               type="success" | ||||
|               circle | ||||
|               @click="handlePublish(item)" | ||||
|               v-hasPermi="['mp:free-publish:submit']" | ||||
|             > | ||||
|               <Icon icon="fa:upload" /> | ||||
|             </el-button> | ||||
|             <el-button | ||||
|               type="primary" | ||||
|               circle | ||||
|               @click="handleUpdate(item)" | ||||
|               v-hasPermi="['mp:draft:update']" | ||||
|             > | ||||
|               <Icon icon="ep:edit" /> | ||||
|             </el-button> | ||||
|             <el-button | ||||
|               type="danger" | ||||
|               circle | ||||
|               @click="handleDelete(item)" | ||||
|               v-hasPermi="['mp:draft:delete']" | ||||
|             > | ||||
|               <Icon icon="ep:delete" /> | ||||
|             </el-button> | ||||
|           </el-row> | ||||
|         </div> | ||||
|       </template> | ||||
|     </div> | ||||
|     <!-- 分页记录 --> | ||||
|     <Pagination | ||||
|       :total="total" | ||||
|       v-model:page="queryParams.pageNo" | ||||
|       v-model:limit="queryParams.pageSize" | ||||
|       @pagination="getList" | ||||
|     /> | ||||
|   </ContentWrap> | ||||
| 
 | ||||
|   <!-- TODO @Dhb52:迁移成独立路由 --> | ||||
|   <div class="app-container"> | ||||
|     <!-- 添加或修改草稿对话框 --> | ||||
|     <Teleport to="body"> | ||||
|       <el-dialog | ||||
|         :title="operateMaterial === 'add' ? '新建图文' : '修改图文'" | ||||
|         width="80%" | ||||
|         top="20px" | ||||
|         v-model="dialogNewsVisible" | ||||
|         :before-close="dialogNewsClose" | ||||
|         :close-on-click-modal="false" | ||||
|       > | ||||
|         <div class="left"> | ||||
|           <div class="select-item"> | ||||
|             <div v-for="(news, index) in articlesAdd" :key="news.id"> | ||||
|               <div | ||||
|                 class="news-main father" | ||||
|                 v-if="index === 0" | ||||
|                 :class="{ activeAddNews: isActiveAddNews === index }" | ||||
|                 @click="activeNews(index)" | ||||
|               > | ||||
|                 <div class="news-content"> | ||||
|                   <img class="material-img" v-if="news.thumbUrl" :src="news.thumbUrl" /> | ||||
|                   <div class="news-content-title">{{ news.title }}</div> | ||||
|                 </div> | ||||
|                 <div class="child" v-if="articlesAdd.length > 1"> | ||||
|                   <el-button size="small" @click="downNews(index)" | ||||
|                     ><Icon icon="ep:sort-down" />下移</el-button | ||||
|                   > | ||||
|                   <el-button v-if="operateMaterial === 'add'" size="small" @click="minusNews(index)" | ||||
|                     ><Icon icon="ep:delete" />删除 | ||||
|                   </el-button> | ||||
|                 </div> | ||||
|               </div> | ||||
|               <div | ||||
|                 class="news-main-item father" | ||||
|                 v-if="index > 0" | ||||
|                 :class="{ activeAddNews: isActiveAddNews === index }" | ||||
|                 @click="activeNews(index)" | ||||
|               > | ||||
|                 <div class="news-content-item"> | ||||
|                   <div class="news-content-item-title">{{ news.title }}</div> | ||||
|                   <div class="news-content-item-img"> | ||||
|                     <img | ||||
|                       class="material-img" | ||||
|                       v-if="news.thumbUrl" | ||||
|                       :src="news.thumbUrl" | ||||
|                       height="100%" | ||||
|                     /> | ||||
|                   </div> | ||||
|                 </div> | ||||
|                 <div class="child"> | ||||
|                   <el-button | ||||
|                     v-if="articlesAdd.length > index + 1" | ||||
|                     size="small" | ||||
|                     @click="downNews(index)" | ||||
|                     ><Icon icon="ep:sort-down" />下移 | ||||
|                   </el-button> | ||||
|                   <el-button size="small" @click="upNews(index)" | ||||
|                     ><Icon icon="ep:sort-up" />上移</el-button | ||||
|                   > | ||||
|                   <el-button | ||||
|                     v-if="operateMaterial === 'add'" | ||||
|                     type="danger" | ||||
|                     size="small" | ||||
|                     @click="minusNews(index)" | ||||
|                     ><Icon icon="ep:delete" />删除 | ||||
|                   </el-button> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|             <el-row justify="center" class="ope-row"> | ||||
|               <el-button | ||||
|                 type="primary" | ||||
|                 circle | ||||
|                 @click="plusNews(item)" | ||||
|                 v-if="articlesAdd.length < 8 && operateMaterial === 'add'" | ||||
|               > | ||||
|                 <Icon icon="ep:plus" /> | ||||
|               </el-button> | ||||
|             </el-row> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div class="right" v-loading="addMaterialLoading" v-if="articlesAdd.length > 0"> | ||||
|           <br /> | ||||
|           <br /> | ||||
|           <br /> | ||||
|           <br /> | ||||
|           <!-- 标题、作者、原文地址 --> | ||||
|           <el-input v-model="articlesAdd[isActiveAddNews].title" placeholder="请输入标题(必填)" /> | ||||
|           <el-input | ||||
|             v-model="articlesAdd[isActiveAddNews].author" | ||||
|             placeholder="请输入作者" | ||||
|             style="margin-top: 5px" | ||||
|           /> | ||||
|           <el-input | ||||
|             v-model="articlesAdd[isActiveAddNews].contentSourceUrl" | ||||
|             placeholder="请输入原文地址" | ||||
|             style="margin-top: 5px" | ||||
|           /> | ||||
|           <!-- 封面和摘要 --> | ||||
|           <div class="input-tt">封面和摘要:</div> | ||||
|           <div> | ||||
|             <div class="thumb-div"> | ||||
|               <img | ||||
|                 class="material-img" | ||||
|                 v-if="articlesAdd[isActiveAddNews].thumbUrl" | ||||
|                 :src="articlesAdd[isActiveAddNews].thumbUrl" | ||||
|                 :class="isActiveAddNews === 0 ? 'avatar' : 'avatar1'" | ||||
|               /> | ||||
|               <Icon | ||||
|                 v-else | ||||
|                 icon="ep:plus" | ||||
|                 class="avatar-uploader-icon" | ||||
|                 :class="isActiveAddNews === 0 ? 'avatar' : 'avatar1'" | ||||
|               /> | ||||
|               <div class="thumb-but"> | ||||
|                 <el-upload | ||||
|                   :action="actionUrl" | ||||
|                   :headers="headers" | ||||
|                   multiple | ||||
|                   :limit="1" | ||||
|                   :file-list="fileList" | ||||
|                   :data="uploadData" | ||||
|                   :before-upload="beforeThumbImageUpload" | ||||
|                   :on-success="handleUploadSuccess" | ||||
|                 > | ||||
|                   <template #trigger> | ||||
|                     <el-button size="small" type="primary">本地上传</el-button> | ||||
|                   </template> | ||||
|                   <el-button | ||||
|                     size="small" | ||||
|                     type="primary" | ||||
|                     @click="openMaterial" | ||||
|                     style="margin-left: 5px" | ||||
|                     >素材库选择</el-button | ||||
|                   > | ||||
|                   <template #tip> | ||||
|                     <div class="el-upload__tip">支持 bmp/png/jpeg/jpg/gif 格式,大小不超过 2M</div> | ||||
|                   </template> | ||||
|                 </el-upload> | ||||
|               </div> | ||||
|               <Teleport to="body"> | ||||
|                 <el-dialog title="选择图片" v-model="dialogImageVisible" width="80%"> | ||||
|                   <WxMaterialSelect | ||||
|                     ref="materialSelectRef" | ||||
|                     :objData="{ type: 'image', accountId: queryParams.accountId }" | ||||
|                     @select-material="selectMaterial" | ||||
|                   /> | ||||
|                 </el-dialog> | ||||
|               </Teleport> | ||||
|             </div> | ||||
|             <el-input | ||||
|               :rows="8" | ||||
|               type="textarea" | ||||
|               v-model="articlesAdd[isActiveAddNews].digest" | ||||
|               placeholder="请输入摘要" | ||||
|               class="digest" | ||||
|               maxlength="120" | ||||
|               style="float: right" | ||||
|             /> | ||||
|           </div> | ||||
|           <!--富文本编辑器组件--> | ||||
|           <el-row> | ||||
|             <wx-editor | ||||
|               v-model="articlesAdd[isActiveAddNews].content" | ||||
|               :account-id="uploadData.accountId" | ||||
|               v-if="hackResetEditor" | ||||
|             /> | ||||
|           </el-row> | ||||
|         </div> | ||||
|         <template #footer> | ||||
|           <div class="dialog-footer"> | ||||
|             <el-button @click="dialogNewsVisible = false">取 消</el-button> | ||||
|             <el-button type="primary" @click="submitForm">提 交</el-button> | ||||
|           </div> | ||||
|         </template> | ||||
|       </el-dialog> | ||||
|     </Teleport> | ||||
|   </div> | ||||
| </template> | ||||
| <script setup name="MpDraft"> | ||||
| import WxEditor from '@/views/mp/components/wx-editor/WxEditor.vue' | ||||
| import WxNews from '@/views/mp/components/wx-news/main.vue' | ||||
| import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue' | ||||
| import { getAccessToken } from '@/utils/auth' | ||||
| import { createDraft, deleteDraft, getDraftPage, updateDraft } from '@/api/mp/draft' | ||||
| import { getSimpleAccountList } from '@/api/mp/account' | ||||
| import { submitFreePublish } from '@/api/mp/freePublish' | ||||
| const message = useMessage() // 消息 | ||||
| // 可以用改本地数据模拟,避免API调用超限 | ||||
| // import drafts from './mock' | ||||
| 
 | ||||
| const loading = ref(true) // 列表的加载中 | ||||
| const total = ref(0) // 列表的总页数 | ||||
| const list = ref([]) // 列表的数据 | ||||
| const queryParams = reactive({ | ||||
|   pageNo: 1, | ||||
|   pageSize: 10, | ||||
|   accountId: undefined | ||||
| }) | ||||
| const queryFormRef = ref() // 搜索的表单 | ||||
| const accountList = ref([]) // 公众号账号列表 | ||||
| 
 | ||||
| // ========== 文件上传 ========== | ||||
| const materialSelectRef = ref() | ||||
| const BASE_URL = import.meta.env.VITE_BASE_URL | ||||
| const actionUrl = ref(BASE_URL + '/admin-api/mp/material/upload-permanent') // 上传永久素材的地址 | ||||
| const headers = ref({ Authorization: 'Bearer ' + getAccessToken() }) // 设置上传的请求头部 | ||||
| const fileList = ref([]) | ||||
| const uploadData = reactive({ | ||||
|   type: 'image', | ||||
|   accountId: 1 | ||||
| }) | ||||
| 
 | ||||
| // ========== 草稿新建 or 修改 ========== | ||||
| const dialogNewsVisible = ref(false) | ||||
| const addMaterialLoading = ref(false) // 添加草稿的 loading 标识 | ||||
| const articlesAdd = ref([]) | ||||
| const isActiveAddNews = ref(0) | ||||
| const dialogImageVisible = ref(false) | ||||
| const operateMaterial = ref('add') | ||||
| const articlesMediaId = ref('') | ||||
| const hackResetEditor = ref(false) | ||||
| 
 | ||||
| /** 初始化 **/ | ||||
| onMounted(async () => { | ||||
|   accountList.value = await getSimpleAccountList() | ||||
|   // 选中第一个 | ||||
|   if (accountList.value.length > 0) { | ||||
|     // @ts-ignore | ||||
|     queryParams.accountId = accountList.value[0].id | ||||
|   } | ||||
|   await getList() | ||||
| }) | ||||
| 
 | ||||
| // ======================== 列表查询 ======================== | ||||
| /** 设置账号编号 */ | ||||
| const setAccountId = (accountId) => { | ||||
|   queryParams.accountId = accountId | ||||
|   uploadData.accountId = accountId | ||||
| } | ||||
| 
 | ||||
| /** 查询列表 */ | ||||
| const getList = async () => { | ||||
|   // 如果没有选中公众号账号,则进行提示。 | ||||
|   if (!queryParams.accountId) { | ||||
|     message.error('未选中公众号,无法查询草稿箱') | ||||
|     return false | ||||
|   } | ||||
| 
 | ||||
|   loading.value = true | ||||
|   try { | ||||
|     const drafts = await getDraftPage(queryParams) | ||||
|     drafts.list.forEach((item) => { | ||||
|       const newsItem = item.content.newsItem | ||||
|       // 将 thumbUrl 转成 picUrl,保证 wx-news 组件可以预览封面 | ||||
|       newsItem.forEach((article) => { | ||||
|         article.picUrl = article.thumbUrl | ||||
|       }) | ||||
|     }) | ||||
|     list.value = drafts.list | ||||
|     total.value = drafts.total | ||||
|   } finally { | ||||
|     loading.value = false | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 搜索按钮操作 */ | ||||
| const handleQuery = () => { | ||||
|   queryParams.pageNo = 1 | ||||
|   // 默认选中第一个 | ||||
|   if (queryParams.accountId) { | ||||
|     setAccountId(queryParams.accountId) | ||||
|   } | ||||
|   getList() | ||||
| } | ||||
| 
 | ||||
| /** 重置按钮操作 */ | ||||
| const resetQuery = () => { | ||||
|   queryFormRef.value.resetFields() | ||||
|   // 默认选中第一个 | ||||
|   if (accountList.value.length > 0) { | ||||
|     setAccountId(accountList.value[0].id) | ||||
|   } | ||||
|   handleQuery() | ||||
| } | ||||
| 
 | ||||
| // ======================== 新增/修改草稿 ======================== | ||||
| /** 新增按钮操作 */ | ||||
| const handleAdd = () => { | ||||
|   resetEditor() | ||||
|   reset() | ||||
|   // 打开表单,并设置初始化 | ||||
|   operateMaterial.value = 'add' | ||||
|   dialogNewsVisible.value = true | ||||
| } | ||||
| 
 | ||||
| /** 更新按钮操作 */ | ||||
| const handleUpdate = (item) => { | ||||
|   resetEditor() | ||||
|   reset() | ||||
|   articlesMediaId.value = item.mediaId | ||||
|   articlesAdd.value = JSON.parse(JSON.stringify(item.content.newsItem)) | ||||
|   // 打开表单,并设置初始化 | ||||
|   operateMaterial.value = 'edit' | ||||
|   dialogNewsVisible.value = true | ||||
| } | ||||
| 
 | ||||
| /** 提交按钮 */ | ||||
| const submitForm = () => { | ||||
|   // TODO @Dhb52: 参考别的模块写法,改成 await 方式 | ||||
|   addMaterialLoading.value = true | ||||
|   if (operateMaterial.value === 'add') { | ||||
|     createDraft(queryParams.accountId, articlesAdd.value) | ||||
|       .then(() => { | ||||
|         message.notifySuccess('新增成功') | ||||
|         dialogNewsVisible.value = false | ||||
|         getList() | ||||
|       }) | ||||
|       .finally(() => { | ||||
|         addMaterialLoading.value = false | ||||
|       }) | ||||
|   } else { | ||||
|     updateDraft(queryParams.accountId, articlesMediaId.value, articlesAdd.value) | ||||
|       .then(() => { | ||||
|         message.notifySuccess('更新成功') | ||||
|         dialogNewsVisible.value = false | ||||
|         getList() | ||||
|       }) | ||||
|       .finally(() => { | ||||
|         addMaterialLoading.value = false | ||||
|       }) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // 关闭弹窗 | ||||
| const dialogNewsClose = async (done) => { | ||||
|   try { | ||||
|     await message.confirm('修改内容可能还未保存,确定关闭吗?') | ||||
|     reset() | ||||
|     resetEditor() | ||||
|     done() | ||||
|   } catch {} | ||||
| } | ||||
| 
 | ||||
| // 表单重置 | ||||
| const reset = () => { | ||||
|   isActiveAddNews.value = 0 | ||||
|   articlesAdd.value = [buildEmptyArticle()] | ||||
| } | ||||
| 
 | ||||
| // 表单 Editor 重置 | ||||
| const resetEditor = () => { | ||||
|   hackResetEditor.value = false // 销毁组件 | ||||
|   nextTick(() => { | ||||
|     hackResetEditor.value = true // 重建组件 | ||||
|   }) | ||||
| } | ||||
| 
 | ||||
| // 将图文向下移动 | ||||
| const downNews = (index) => { | ||||
|   let temp = articlesAdd.value[index] | ||||
|   articlesAdd.value[index] = articlesAdd.value[index + 1] | ||||
|   articlesAdd.value[index + 1] = temp | ||||
|   isActiveAddNews.value = index + 1 | ||||
| } | ||||
| 
 | ||||
| // 将图文向上移动 | ||||
| const upNews = (index) => { | ||||
|   let temp = articlesAdd.value[index] | ||||
|   articlesAdd.value[index] = articlesAdd.value[index - 1] | ||||
|   articlesAdd.value[index - 1] = temp | ||||
|   isActiveAddNews.value = index - 1 | ||||
| } | ||||
| 
 | ||||
| // 选中指定 index 的图文 | ||||
| const activeNews = (index) => { | ||||
|   resetEditor() | ||||
|   isActiveAddNews.value = index | ||||
| } | ||||
| 
 | ||||
| // 删除指定 index 的图文 | ||||
| const minusNews = async (index) => { | ||||
|   try { | ||||
|     await message.confirm('确定删除该图文吗?') | ||||
|     articlesAdd.value.splice(index, 1) | ||||
|     if (isActiveAddNews.value === index) { | ||||
|       isActiveAddNews.value = 0 | ||||
|     } | ||||
|   } catch {} | ||||
| } | ||||
| 
 | ||||
| // 添加一个图文 | ||||
| const plusNews = () => { | ||||
|   articlesAdd.value.push(buildEmptyArticle()) | ||||
|   isActiveAddNews.value = articlesAdd.value.length - 1 | ||||
| } | ||||
| 
 | ||||
| // 创建空的 article | ||||
| const buildEmptyArticle = () => { | ||||
|   return { | ||||
|     title: '', | ||||
|     thumbMediaId: '', | ||||
|     author: '', | ||||
|     digest: '', | ||||
|     showCoverPic: '', | ||||
|     content: '', | ||||
|     contentSourceUrl: '', | ||||
|     needOpenComment: '', | ||||
|     onlyFansCanComment: '', | ||||
|     thumbUrl: '' | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // ======================== 文件上传 ======================== | ||||
| const beforeThumbImageUpload = (file) => { | ||||
|   addMaterialLoading.value = true | ||||
|   const isType = | ||||
|     file.type === 'image/jpeg' || | ||||
|     file.type === 'image/png' || | ||||
|     file.type === 'image/gif' || | ||||
|     file.type === 'image/bmp' || | ||||
|     file.type === 'image/jpg' | ||||
|   if (!isType) { | ||||
|     message.error('上传图片格式不对!') | ||||
|     addMaterialLoading.value = false | ||||
|     return false | ||||
|   } | ||||
|   const isLt = file.size / 1024 / 1024 < 2 | ||||
|   if (!isLt) { | ||||
|     message.error('上传图片大小不能超过 2M!') | ||||
|     addMaterialLoading.value = false | ||||
|     return false | ||||
|   } | ||||
|   // 校验通过 | ||||
|   return true | ||||
| } | ||||
| 
 | ||||
| const handleUploadSuccess = (response, file, fileList) => { | ||||
|   addMaterialLoading.value = false | ||||
|   if (response.code !== 0) { | ||||
|     message.error('上传出错:' + response.msg) | ||||
|     return false | ||||
|   } | ||||
| 
 | ||||
|   // 重置上传文件的表单 | ||||
|   fileList.value = [] | ||||
| 
 | ||||
|   // 设置草稿的封面字段 | ||||
|   articlesAdd.value[isActiveAddNews.value].thumbMediaId = response.data.mediaId | ||||
|   articlesAdd.value[isActiveAddNews.value].thumbUrl = response.data.url | ||||
| } | ||||
| 
 | ||||
| // 选择 or 上传完素材,设置回草稿 | ||||
| const selectMaterial = (item) => { | ||||
|   dialogImageVisible.value = false | ||||
|   articlesAdd.value[isActiveAddNews.value].thumbMediaId = item.mediaId | ||||
|   articlesAdd.value[isActiveAddNews.value].thumbUrl = item.url | ||||
| } | ||||
| 
 | ||||
| // 打开素材选择 | ||||
| const openMaterial = () => { | ||||
|   dialogImageVisible.value = true | ||||
|   try { | ||||
|     materialSelectRef.value.queryParams.accountId = queryParams.accountId // 强制设置下 accountId,避免二次查询不对 | ||||
|     materialSelectRef.value.handleQuery() // 刷新列表,失败也无所谓 | ||||
|   } catch (e) {} | ||||
| } | ||||
| 
 | ||||
| // ======================== 草稿箱发布 ======================== | ||||
| const handlePublish = async (item) => { | ||||
|   const accountId = queryParams.accountId | ||||
|   const mediaId = item.mediaId | ||||
|   const content = | ||||
|     '你正在通过发布的方式发表内容。 发布不占用群发次数,一天可多次发布。已发布内容不会推送给用户,也不会展示在公众号主页中。 发布后,你可以前往发表记录获取链接,也可以将发布内容添加到自定义菜单、自动回复、话题和页面模板中。' | ||||
|   try { | ||||
|     await message.confirm(content) | ||||
|     await submitFreePublish(accountId, mediaId) | ||||
|     message.notifySuccess('发布成功') | ||||
|     await getList() | ||||
|   } catch {} | ||||
| } | ||||
| 
 | ||||
| /** 删除按钮操作 */ | ||||
| const handleDelete = async (item) => { | ||||
|   const accountId = queryParams.accountId | ||||
|   const mediaId = item.mediaId | ||||
|   try { | ||||
|     await message.confirm('此操作将永久删除该草稿, 是否继续?') | ||||
|     await deleteDraft(accountId, mediaId) | ||||
|     message.notifySuccess('删除成功') | ||||
|     await getList() | ||||
|   } catch {} | ||||
| } | ||||
| </script> | ||||
| <style lang="scss" scoped> | ||||
| .pagination { | ||||
|   float: right; | ||||
|   margin-right: 25px; | ||||
| } | ||||
| 
 | ||||
| .add_but { | ||||
|   padding: 10px; | ||||
| } | ||||
| 
 | ||||
| .ope-row { | ||||
|   margin-top: 5px; | ||||
|   text-align: center; | ||||
|   border-top: 1px solid #eaeaea; | ||||
|   padding-top: 5px; | ||||
| } | ||||
| 
 | ||||
| .item-name { | ||||
|   font-size: 12px; | ||||
|   overflow: hidden; | ||||
|   text-overflow: ellipsis; | ||||
|   white-space: nowrap; | ||||
|   text-align: center; | ||||
| } | ||||
| 
 | ||||
| .el-upload__tip { | ||||
|   margin-left: 5px; | ||||
| } | ||||
| 
 | ||||
| /*新增图文*/ | ||||
| .left { | ||||
|   display: inline-block; | ||||
|   width: 35%; | ||||
|   vertical-align: top; | ||||
|   margin-top: 200px; | ||||
| } | ||||
| 
 | ||||
| .right { | ||||
|   display: inline-block; | ||||
|   width: 60%; | ||||
|   margin-top: -40px; | ||||
| } | ||||
| 
 | ||||
| .avatar-uploader { | ||||
|   width: 20%; | ||||
|   display: inline-block; | ||||
| } | ||||
| 
 | ||||
| .avatar-uploader .el-upload { | ||||
|   border-radius: 6px; | ||||
|   cursor: pointer; | ||||
|   position: relative; | ||||
|   overflow: hidden; | ||||
|   text-align: unset !important; | ||||
| } | ||||
| 
 | ||||
| .avatar-uploader .el-upload:hover { | ||||
|   border-color: #165dff; | ||||
| } | ||||
| 
 | ||||
| .avatar-uploader-icon { | ||||
|   border: 1px solid #d9d9d9; | ||||
|   font-size: 28px; | ||||
|   color: #8c939d; | ||||
|   width: 120px; | ||||
|   height: 120px; | ||||
|   line-height: 120px; | ||||
|   text-align: center; | ||||
| } | ||||
| 
 | ||||
| .avatar { | ||||
|   width: 230px; | ||||
|   height: 120px; | ||||
| } | ||||
| 
 | ||||
| .avatar1 { | ||||
|   width: 120px; | ||||
|   height: 120px; | ||||
| } | ||||
| 
 | ||||
| .digest { | ||||
|   width: 60%; | ||||
|   display: inline-block; | ||||
|   vertical-align: top; | ||||
| } | ||||
| 
 | ||||
| /*新增图文*/ | ||||
| /*瀑布流样式*/ | ||||
| .waterfall { | ||||
|   width: 100%; | ||||
|   column-gap: 10px; | ||||
|   column-count: 5; | ||||
|   margin: 0 auto; | ||||
| } | ||||
| 
 | ||||
| .waterfall-item { | ||||
|   padding: 10px; | ||||
|   margin-bottom: 10px; | ||||
|   break-inside: avoid; | ||||
|   border: 1px solid #eaeaea; | ||||
| } | ||||
| 
 | ||||
| p { | ||||
|   line-height: 30px; | ||||
| } | ||||
| 
 | ||||
| @media (min-width: 992px) and (max-width: 1300px) { | ||||
|   .waterfall { | ||||
|     column-count: 3; | ||||
|   } | ||||
| 
 | ||||
|   p { | ||||
|     color: red; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @media (min-width: 768px) and (max-width: 991px) { | ||||
|   .waterfall { | ||||
|     column-count: 2; | ||||
|   } | ||||
| 
 | ||||
|   p { | ||||
|     color: orange; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @media (max-width: 767px) { | ||||
|   .waterfall { | ||||
|     column-count: 1; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /*瀑布流样式*/ | ||||
| .news-main { | ||||
|   background-color: #ffffff; | ||||
|   width: 100%; | ||||
|   margin: auto; | ||||
|   height: 120px; | ||||
| } | ||||
| 
 | ||||
| .news-content { | ||||
|   background-color: #acadae; | ||||
|   width: 100%; | ||||
|   height: 120px; | ||||
|   position: relative; | ||||
| } | ||||
| 
 | ||||
| .news-content-title { | ||||
|   display: inline-block; | ||||
|   font-size: 15px; | ||||
|   color: #ffffff; | ||||
|   position: absolute; | ||||
|   left: 0px; | ||||
|   bottom: 0px; | ||||
|   background-color: black; | ||||
|   width: 98%; | ||||
|   padding: 1%; | ||||
|   opacity: 0.65; | ||||
|   overflow: hidden; | ||||
|   text-overflow: ellipsis; | ||||
|   white-space: nowrap; | ||||
|   height: 25px; | ||||
| } | ||||
| 
 | ||||
| .news-main-item { | ||||
|   background-color: #ffffff; | ||||
|   padding: 5px 0px; | ||||
|   border-top: 1px solid #eaeaea; | ||||
|   width: 100%; | ||||
|   margin: auto; | ||||
| } | ||||
| 
 | ||||
| .news-content-item { | ||||
|   position: relative; | ||||
|   margin-left: -3px; | ||||
| } | ||||
| 
 | ||||
| .news-content-item-title { | ||||
|   display: inline-block; | ||||
|   font-size: 12px; | ||||
|   width: 70%; | ||||
| } | ||||
| 
 | ||||
| .news-content-item-img { | ||||
|   display: inline-block; | ||||
|   width: 25%; | ||||
|   background-color: #acadae; | ||||
| } | ||||
| 
 | ||||
| .input-tt { | ||||
|   padding: 5px; | ||||
| } | ||||
| 
 | ||||
| .activeAddNews { | ||||
|   border: 5px solid #2bb673; | ||||
| } | ||||
| 
 | ||||
| .news-main-plus { | ||||
|   width: 280px; | ||||
|   text-align: center; | ||||
|   margin: auto; | ||||
|   height: 50px; | ||||
| } | ||||
| 
 | ||||
| .icon-plus { | ||||
|   margin: 10px; | ||||
|   font-size: 25px; | ||||
| } | ||||
| 
 | ||||
| .select-item { | ||||
|   width: 60%; | ||||
|   padding: 10px; | ||||
|   margin: 0 auto 10px auto; | ||||
|   border: 1px solid #eaeaea; | ||||
| } | ||||
| 
 | ||||
| .father .child { | ||||
|   display: none; | ||||
|   text-align: center; | ||||
|   position: relative; | ||||
|   bottom: 25px; | ||||
| } | ||||
| 
 | ||||
| .father:hover .child { | ||||
|   display: block; | ||||
| } | ||||
| 
 | ||||
| .thumb-div { | ||||
|   display: inline-block; | ||||
|   width: 30%; | ||||
|   text-align: center; | ||||
| } | ||||
| 
 | ||||
| .thumb-but { | ||||
|   margin: 5px; | ||||
| } | ||||
| 
 | ||||
| .material-img { | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
| } | ||||
| </style> | ||||
|  |  | |||
|  | @ -0,0 +1,151 @@ | |||
| export default { | ||||
|   list: [ | ||||
|     { | ||||
|       mediaId: 'r6ryvl6LrxBU0miaST4Y-q-G9pdsmZw0OYG4FzHQkKfpLfEwIH51wy2bxisx8PvW', | ||||
|       content: { | ||||
|         newsItem: [ | ||||
|           { | ||||
|             title: '我是标题(OOO)', | ||||
|             author: '我是作者', | ||||
|             digest: '我是摘要', | ||||
|             content: '我是内容', | ||||
|             contentSourceUrl: 'https://www.iocoder.cn', | ||||
|             thumbMediaId: 'r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn', | ||||
|             showCoverPic: 0, | ||||
|             needOpenComment: 0, | ||||
|             onlyFansCanComment: 0, | ||||
|             url: 'http://mp.weixin.qq.com/s?__biz=MzA3NjM4MzQzOQ==&tempkey=MTIxMl9XaFphcmtJVFh3VEc4Q1MxQWwxQ3R5R0JGTXBDM1Q0N2ZFQm8zeUphOFlwNEpXSWxTYm9RQnJ6cHVuN2QxTE56SFBCYXc2RE9NcUxIeS1CQjJuUHhTWjBlN2VOeGRpRi1fZUhwN1FNQjdrQV9yRU9EU0hibHREZmZoVW5acnZrN3ZjaWsxejR3RGpKczBzTHFIM0dFNFZWVkpBc0dWWlAzUEhlVmpnfn4%3D&chksm=1f6354802814dd969ef83c0f3babe555c614270b30bc383beaf7ffd13b0257f0fe5ced9af694#rd', | ||||
|             thumbUrl: | ||||
|               'http://test.yudao.iocoder.cn/r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn.png' | ||||
|           }, | ||||
|           { | ||||
|             title: '我是标题(XXX)', | ||||
|             author: '我是作者', | ||||
|             digest: '我是摘要', | ||||
|             content: '我是内容', | ||||
|             contentSourceUrl: 'https://www.iocoder.cn', | ||||
|             thumbMediaId: 'r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn', | ||||
|             showCoverPic: 0, | ||||
|             needOpenComment: 0, | ||||
|             onlyFansCanComment: 0, | ||||
|             url: 'http://mp.weixin.qq.com/s?__biz=MzA3NjM4MzQzOQ==&tempkey=MTIxMl9yTlYwOEs1clpwcE5OUEhCQWwxQ3R5R0JGTXBDM1Q0N2ZFQm8zeUphOFlwNEpXSWxTYm9RQnJ6cHVuN0NSMjFqN3N1aUZMbFNVLTZHN2ZDME9qOGp2THk2RFNlSTlKZ3Y1czFVZDdQQm5IeUg3dEppSUtpQUh5SExOOTRkT3dHNUdBdHdWSWlOendlREV3dS1jUEVQbFpiVTZmVW5iRWhZcGdkNTFRfn4%3D&chksm=1f6354802814dd96a403151cd44c7da4eecf0e475d25423e46ecd795b513bafd829a75daef9b#rd', | ||||
|             thumbUrl: | ||||
|               'http://test.yudao.iocoder.cn/r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn.png' | ||||
|           } | ||||
|         ] | ||||
|       }, | ||||
|       updateTime: 1673655730 | ||||
|     }, | ||||
|     { | ||||
|       mediaId: 'r6ryvl6LrxBU0miaST4Y-jGpXnO73ihN0lsNXknCRQHapp2xgHMRxHKG50LituFe', | ||||
|       content: { | ||||
|         newsItem: [ | ||||
|           { | ||||
|             title: '我是标题(修改)', | ||||
|             author: '我是作者', | ||||
|             digest: '我是摘要', | ||||
|             content: '我是内容', | ||||
|             contentSourceUrl: 'https://www.iocoder.cn', | ||||
|             thumbMediaId: 'r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn', | ||||
|             showCoverPic: 0, | ||||
|             needOpenComment: 0, | ||||
|             onlyFansCanComment: 0, | ||||
|             url: 'http://mp.weixin.qq.com/s?__biz=MzA3NjM4MzQzOQ==&tempkey=MTIxMl95WVFXYndIZnZJd0t5cjgvQWwxQ3R5R0JGTXBDM1Q0N2ZFQm8zeUphOFlwNEpXSWxTYm9RQnJ6cHVuN1dlNURPbWswbEF4RDd5dVJTdjQ4cm9Cc0Q1TWhpMUh6SE1hVEE3ZHljaHhlZjZYSGF5N2JNSHpDTlh6ajNZbkpGTGpTcUQ4M3NMdW41ZUpXNFZZQ1VKbVlaMVp5ekxEV1czREdsY1dOYTZnfn4%3D&chksm=1f6354be2814dda8e6238037c2ebd52b1c8e80e93249a861ad80e4d40e5ca7207233475ca689#rd', | ||||
|             thumbUrl: | ||||
|               'http://test.yudao.iocoder.cn/r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn.png' | ||||
|           } | ||||
|         ] | ||||
|       }, | ||||
|       updateTime: 1673655584 | ||||
|     }, | ||||
|     { | ||||
|       mediaId: 'r6ryvl6LrxBU0miaST4Y-v5SrbNCPpD6M_p3TmSrYwTjKogs-0DMJgmjMyNZPeMO', | ||||
|       content: { | ||||
|         newsItem: [ | ||||
|           { | ||||
|             title: '1321', | ||||
|             author: '3232', | ||||
|             digest: '1333', | ||||
|             content: '<p>444</p>', | ||||
|             contentSourceUrl: 'http://www.iocoder.cn', | ||||
|             thumbMediaId: 'r6ryvl6LrxBU0miaST4Y-tlQmcl3RdC-Jcgns6IQtf7zenGy3b86WLT7GzUcrb1T', | ||||
|             showCoverPic: 0, | ||||
|             needOpenComment: 0, | ||||
|             onlyFansCanComment: 0, | ||||
|             url: 'http://mp.weixin.qq.com/s?__biz=MzA3NjM4MzQzOQ==&tempkey=MTIxMl9jelJiaDAzbmdpSkJOZ2M2QWwxQ3R5R0JGTXBDM1Q0N2ZFQm8zeUphOFlwNEpXSWxTYm9RQnJ6cHVuNDNXVVc2ZDRYeTY0Zm1weXR6dE9vQWh1TzEwbEpUVnRfVzJyaGFDNXBkZ0ZXM2JFOTNaRHNhOHRUeFdEanhMeS01X01kMUNWQ1BpRER3cjYwTl9pMnpFLUJhZXFucVVfM1pDUXlTUEl1S25nfn4%3D&chksm=1f6354bc2814ddaa56a90ad5bc3d078601c8d1589ba01827a8170587bc830ff9747b5f59c3a0#rd', | ||||
|             thumbUrl: | ||||
|               'http://mmbiz.qpic.cn/mmbiz_png/btUmCVHwbJUoicwBiacjVeQbu6QxgBVrukfSJXz509boa21SpH8OVHAqXCJiaiaAaHQJNxwwsa0gHRXVr0G5EZYamw/0?wx_fmt=png' | ||||
|           } | ||||
|         ] | ||||
|       }, | ||||
|       updateTime: 1673628969 | ||||
|     }, | ||||
|     { | ||||
|       mediaId: 'r6ryvl6LrxBU0miaST4Y-vdWrisK5EZbk4Y3tzh8P0PG0eEUbnQrh0BcsEb3WNP0', | ||||
|       content: { | ||||
|         newsItem: [ | ||||
|           { | ||||
|             title: 'tudou', | ||||
|             author: 'haha', | ||||
|             digest: '312', | ||||
|             content: '<p>132312</p>', | ||||
|             contentSourceUrl: 'http://www.iocoder.cn', | ||||
|             thumbMediaId: 'r6ryvl6LrxBU0miaST4Y-pgFtUNLu1foMSAMkoOsrQrTZ8EtTMssBLfTtzP0dfjG', | ||||
|             showCoverPic: 0, | ||||
|             needOpenComment: 0, | ||||
|             onlyFansCanComment: 0, | ||||
|             url: 'http://mp.weixin.qq.com/s?__biz=MzA3NjM4MzQzOQ==&tempkey=MTIxMl9qdkJ1ZjBoUmg2Uk9TS3RlQWwxQ3R5R0JGTXBDM1Q0N2ZFQm8zeUphOFlwNEpXSWxTYm9RQnJ6cHVuNVg2aTJsaC1fMkU2eXNacUplN3VDTTZFZkhtMjhuTUZvWkxsNDBRSXExY2tiVXRHb09TaHgtREhzY3doZ0JYeC1TSTZ5eWZldXJsOWtfbV8yMi1aYkcyZ2pOY0haM0Ntb3VSWEtxUGVFRlNBfn4%3D&chksm=1f6354ba2814ddacf0184b24d310483641ef190b1faac098c285eb416c70017e2f54decfa1af#rd', | ||||
|             thumbUrl: | ||||
|               'http://test.yudao.iocoder.cn/r6ryvl6LrxBU0miaST4Y-pgFtUNLu1foMSAMkoOsrQrTZ8EtTMssBLfTtzP0dfjG.png' | ||||
|           } | ||||
|         ] | ||||
|       }, | ||||
|       updateTime: 1673628760 | ||||
|     }, | ||||
|     { | ||||
|       mediaId: 'r6ryvl6LrxBU0miaST4Y-u9kTIm1DhWZDdXyxsxUVv2Z5DAB99IPxkIRTUUD206k', | ||||
|       content: { | ||||
|         newsItem: [ | ||||
|           { | ||||
|             title: '12', | ||||
|             author: '333', | ||||
|             digest: '123', | ||||
|             content: '123', | ||||
|             contentSourceUrl: 'https://www.iocoder.cn', | ||||
|             thumbMediaId: 'r6ryvl6LrxBU0miaST4Y-jVixJGgnBnkBPRbuVptOW0CHYuQFyiOVNtamctS8xU8', | ||||
|             showCoverPic: 0, | ||||
|             needOpenComment: 0, | ||||
|             onlyFansCanComment: 0, | ||||
|             url: 'http://mp.weixin.qq.com/s?__biz=MzA3NjM4MzQzOQ==&tempkey=MTIxMl9qVVhpSDZUaFJWTzBBWWRVQWwxQ3R5R0JGTXBDM1Q0N2ZFQm8zeUphOFlwNEpXSWxTYm9RQnJ6cHVuNWRnTDJWYmF2NER0clV1bThmQ0xUR3hqQnJkZ3BJSUNmNDJmc0lCZ1dadkVnZ3Z5bkN4YWtVUjhoaWZWYzZURUR4NnpMd0Y4Z3U5aUdib0lkMzI4Rjg3SG9JX2FycTMxbUctOHplaTlQVVhnfn4%3D&chksm=1f6354b62814dda076c778af33f06580165d8aa81f7798d55cfabb1886b5c74d9b2124a3535c#rd', | ||||
|             thumbUrl: | ||||
|               'http://test.yudao.iocoder.cn/r6ryvl6LrxBU0miaST4Y-jVixJGgnBnkBPRbuVptOW0CHYuQFyiOVNtamctS8xU8.jpg' | ||||
|           } | ||||
|         ] | ||||
|       }, | ||||
|       updateTime: 1673626494 | ||||
|     }, | ||||
|     { | ||||
|       mediaId: 'r6ryvl6LrxBU0miaST4Y-sO24upobaENDmeByfBTfaozB3aOqSMAV0lGy-UkHXE7', | ||||
|       content: { | ||||
|         newsItem: [ | ||||
|           { | ||||
|             title: '我是标题', | ||||
|             author: '我是作者', | ||||
|             digest: '我是摘要', | ||||
|             content: '我是内容', | ||||
|             contentSourceUrl: 'https://www.iocoder.cn', | ||||
|             thumbMediaId: 'r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn', | ||||
|             showCoverPic: 0, | ||||
|             needOpenComment: 0, | ||||
|             onlyFansCanComment: 0, | ||||
|             url: 'http://mp.weixin.qq.com/s?__biz=MzA3NjM4MzQzOQ==&tempkey=MTIxMl9LT2dqRnpMNUpsR0hjYWtBQWwxQ3R5R0JGTXBDM1Q0N2ZFQm8zeUphOFlwNEpXSWxTYm9RQnJ6cHVuNGNmazZTdlE5WkxvU0tfX2V5cjV2WjJiR0xjQUhyREFSZWo2eWNrUW9EYVh6ZkpWRXBLR3FmTEV6YldBMno3Q2ZvVXBSdzlaVDc3aFhndEpQWUwzWmFMUWt0YVVURE1VZ1FsQTdPMlRtc3JBfn4%3D&chksm=1f6354aa2814ddbcc2637382f963a8742993ac38ebcebe6e3411df5ac82ac7bbdb391be6494a#rd', | ||||
|             thumbUrl: | ||||
|               'http://test.yudao.iocoder.cn/r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn.png' | ||||
|           } | ||||
|         ] | ||||
|       }, | ||||
|       updateTime: 1673534279 | ||||
|     } | ||||
|   ], | ||||
|   total: 6 | ||||
| } | ||||
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 34 KiB | 
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 1.3 KiB | 
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 12 KiB | 
|  | @ -1,3 +1,793 @@ | |||
| <template> | ||||
|   <span>开发中</span> | ||||
|   <div class="app-container"> | ||||
|     <doc-alert title="公众号菜单" url="https://doc.iocoder.cn/mp/menu/" /> | ||||
| 
 | ||||
|     <!-- 搜索工作栏 --> | ||||
|     <el-form ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px"> | ||||
|       <el-form-item label="公众号" prop="accountId"> | ||||
|         <el-select v-model="accountId" placeholder="请选择公众号"> | ||||
|           <el-option | ||||
|             v-for="item in accountList" | ||||
|             :key="parseInt(item.id)" | ||||
|             :label="item.name" | ||||
|             :value="parseInt(item.id)" | ||||
|           /> | ||||
|         </el-select> | ||||
|       </el-form-item> | ||||
|       <el-form-item> | ||||
|         <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button> | ||||
|         <el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button> | ||||
|       </el-form-item> | ||||
|     </el-form> | ||||
| 
 | ||||
|     <div class="public-account-management clearfix" v-loading="loading"> | ||||
|       <!--左边配置菜单--> | ||||
|       <div class="left"> | ||||
|         <div class="weixin-hd"> | ||||
|           <div class="weixin-title">{{ name }}</div> | ||||
|         </div> | ||||
|         <div class="weixin-menu menu_main clearfix"> | ||||
|           <div class="menu_bottom" v-for="(item, i) of menuList" :key="i"> | ||||
|             <!-- 一级菜单 --> | ||||
|             <div @click="menuClick(i, item)" class="menu_item" :class="{ active: isActive === i }" | ||||
|               ><Icon icon="ep:fold" color="black" />{{ item.name }} | ||||
|             </div> | ||||
|             <!-- 以下为二级菜单--> | ||||
|             <div class="submenu" v-if="isSubMenuFlag === i"> | ||||
|               <div class="subtitle menu_bottom" v-for="(subItem, k) in item.children" :key="k"> | ||||
|                 <div | ||||
|                   class="menu_subItem" | ||||
|                   v-if="item.children" | ||||
|                   :class="{ active: isSubMenuActive === i + '' + k }" | ||||
|                   @click="subMenuClick(subItem, i, k)" | ||||
|                 > | ||||
|                   {{ subItem.name }} | ||||
|                 </div> | ||||
|               </div> | ||||
|               <!-- 二级菜单加号, 当长度 小于 5 才显示二级菜单的加号  --> | ||||
|               <div | ||||
|                 class="menu_bottom menu_addicon" | ||||
|                 v-if="!item.children || item.children.length < 5" | ||||
|                 @click="addSubMenu(i, item)" | ||||
|               > | ||||
|                 <Icon icon="ep:plus" /> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|           <!-- 一级菜单加号 --> | ||||
|           <div class="menu_bottom menu_addicon" v-if="menuList.length < 3" @click="addMenu"> | ||||
|             <Icon icon="ep:plus" /> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div class="save_div"> | ||||
|           <el-button | ||||
|             class="save_btn" | ||||
|             type="success" | ||||
|             size="small" | ||||
|             @click="handleSave" | ||||
|             v-hasPermi="['mp:menu:save']" | ||||
|             >保存并发布菜单</el-button | ||||
|           > | ||||
|           <el-button | ||||
|             class="save_btn" | ||||
|             type="danger" | ||||
|             size="small" | ||||
|             @click="handleDelete" | ||||
|             v-hasPermi="['mp:menu:delete']" | ||||
|             >清空菜单</el-button | ||||
|           > | ||||
|         </div> | ||||
|       </div> | ||||
|       <!--右边配置--> | ||||
|       <div v-if="showRightFlag" class="right"> | ||||
|         <div class="configure_page"> | ||||
|           <div class="delete_btn"> | ||||
|             <el-button size="small" type="danger" @click="handleDeleteMenu(tempObj)" | ||||
|               >删除当前菜单<Icon icon="ep:delete" | ||||
|             /></el-button> | ||||
|           </div> | ||||
|           <div> | ||||
|             <span>菜单名称:</span> | ||||
|             <el-input | ||||
|               class="input_width" | ||||
|               v-model="tempObj.name" | ||||
|               placeholder="请输入菜单名称" | ||||
|               :maxlength="nameMaxLength" | ||||
|               clearable | ||||
|             /> | ||||
|           </div> | ||||
|           <div v-if="showConfigureContent"> | ||||
|             <div class="menu_content"> | ||||
|               <span>菜单标识:</span> | ||||
|               <el-input | ||||
|                 class="input_width" | ||||
|                 v-model="tempObj.menuKey" | ||||
|                 placeholder="请输入菜单 KEY" | ||||
|                 clearable | ||||
|               /> | ||||
|             </div> | ||||
|             <div class="menu_content"> | ||||
|               <span>菜单内容:</span> | ||||
|               <el-select v-model="tempObj.type" clearable placeholder="请选择" class="menu_option"> | ||||
|                 <el-option | ||||
|                   v-for="item in menuOptions" | ||||
|                   :label="item.label" | ||||
|                   :value="item.value" | ||||
|                   :key="item.value" | ||||
|                 /> | ||||
|               </el-select> | ||||
|             </div> | ||||
|             <div class="configur_content" v-if="tempObj.type === 'view'"> | ||||
|               <span>跳转链接:</span> | ||||
|               <el-input | ||||
|                 class="input_width" | ||||
|                 v-model="tempObj.url" | ||||
|                 placeholder="请输入链接" | ||||
|                 clearable | ||||
|               /> | ||||
|             </div> | ||||
|             <div class="configur_content" v-if="tempObj.type === 'miniprogram'"> | ||||
|               <div class="applet"> | ||||
|                 <span>小程序的 appid :</span> | ||||
|                 <el-input | ||||
|                   class="input_width" | ||||
|                   v-model="tempObj.miniProgramAppId" | ||||
|                   placeholder="请输入小程序的appid" | ||||
|                   clearable | ||||
|                 /> | ||||
|               </div> | ||||
|               <div class="applet"> | ||||
|                 <span>小程序的页面路径:</span> | ||||
|                 <el-input | ||||
|                   class="input_width" | ||||
|                   v-model="tempObj.miniProgramPagePath" | ||||
|                   placeholder="请输入小程序的页面路径,如:pages/index" | ||||
|                   clearable | ||||
|                 /> | ||||
|               </div> | ||||
|               <div class="applet"> | ||||
|                 <span>小程序的备用网页:</span> | ||||
|                 <el-input | ||||
|                   class="input_width" | ||||
|                   v-model="tempObj.url" | ||||
|                   placeholder="不支持小程序的老版本客户端将打开本网页" | ||||
|                   clearable | ||||
|                 /> | ||||
|               </div> | ||||
|               <p class="blue">tips:需要和公众号进行关联才可以把小程序绑定带微信菜单上哟!</p> | ||||
|             </div> | ||||
|             <div class="configur_content" v-if="tempObj.type === 'article_view_limited'"> | ||||
|               <el-row> | ||||
|                 <div class="select-item" v-if="tempObj && tempObj.replyArticles"> | ||||
|                   <WxNews :articles="tempObj.replyArticles" /> | ||||
|                   <el-row class="ope-row" justify="center" align="middle"> | ||||
|                     <el-button type="danger" circle @click="deleteMaterial" | ||||
|                       ><icon icon="ep:delete" | ||||
|                     /></el-button> | ||||
|                   </el-row> | ||||
|                 </div> | ||||
|                 <div v-else> | ||||
|                   <el-row justify="center"> | ||||
|                     <el-col :span="24" style="text-align: center"> | ||||
|                       <el-button type="success" @click="openMaterial"> | ||||
|                         素材库选择<Icon icon="ep:circle-check" /> | ||||
|                       </el-button> | ||||
|                     </el-col> | ||||
|                   </el-row> | ||||
|                 </div> | ||||
|                 <el-dialog title="选择图文" v-model="dialogNewsVisible" width="90%"> | ||||
|                   <WxMaterialSelect | ||||
|                     :objData="{ type: 'news', accountId: accountId }" | ||||
|                     @select-material="selectMaterial" | ||||
|                   /> | ||||
|                 </el-dialog> | ||||
|               </el-row> | ||||
|             </div> | ||||
|             <div | ||||
|               class="configur_content" | ||||
|               v-if="tempObj.type === 'click' || tempObj.type === 'scancode_waitmsg'" | ||||
|             > | ||||
|               <WxReplySelect :objData="tempObj.reply" v-if="hackResetWxReplySelect" /> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <!-- 一进页面就显示的默认页面,当点击左边按钮的时候,就不显示了--> | ||||
|       <div v-else class="right"> | ||||
|         <p>请选择菜单配置</p> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
| import { ref, nextTick } from 'vue' | ||||
| import WxReplySelect from '@/views/mp/components/wx-reply/main.vue' | ||||
| import WxNews from '@/views/mp/components/wx-news/main.vue' | ||||
| import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue' | ||||
| import { deleteMenu, getMenuList, saveMenu } from '@/api/mp/menu' | ||||
| import { getSimpleAccountList } from '@/api/mp/account' | ||||
| import { handleTree } from '@/utils/tree' | ||||
| import menuOptions from './menuOptions' | ||||
| 
 | ||||
| const message = useMessage() | ||||
| 
 | ||||
| // ======================== 列表查询 ======================== | ||||
| // 遮罩层 | ||||
| const loading = ref(true) | ||||
| // 显示搜索条件 | ||||
| const showSearch = ref(true) | ||||
| // 公众号Id | ||||
| const accountId = ref(undefined) | ||||
| // 公众号名 | ||||
| const name = ref('') | ||||
| const menuList = ref({ children: [] }) | ||||
| 
 | ||||
| // const menuList = ref(menuListData) | ||||
| // ======================== 菜单操作 ======================== | ||||
| const isActive = ref(-1) // 一级菜单点中样式 | ||||
| const isSubMenuActive = ref(-1) // 一级菜单点中样式 | ||||
| const isSubMenuFlag = ref(-1) // 二级菜单显示标志 | ||||
| 
 | ||||
| // ======================== 菜单编辑 ======================== | ||||
| const showRightFlag = ref(false) // 右边配置显示默认详情还是配置详情 | ||||
| const nameMaxLength = ref(0) // 菜单名称最大长度;1 级是 4 字符;2 级是 7 字符; | ||||
| const showConfigureContent = ref(true) // 是否展示配置内容;如果有子菜单,就不显示配置内容 | ||||
| const hackResetWxReplySelect = ref(false) // 重置 WxReplySelect 组件 | ||||
| const tempObj = ref({}) // 右边临时变量,作为中间值牵引关系 | ||||
| 
 | ||||
| const tempSelfObj = ref({ | ||||
|   // 一些临时值放在这里进行判断,如果放在 tempObj,由于引用关系,menu 也会多了多余的参数 | ||||
| }) | ||||
| const dialogNewsVisible = ref(false) // 跳转图文时的素材选择弹窗 | ||||
| 
 | ||||
| // 公众号账号列表 | ||||
| const accountList = ref([]) | ||||
| 
 | ||||
| onMounted(async () => { | ||||
|   accountList.value = await getSimpleAccountList() | ||||
|   // 选中第一个 | ||||
|   if (accountList.value.length > 0) { | ||||
|     // @ts-ignore | ||||
|     setAccountId(accountList.value[0].id) | ||||
|   } | ||||
|   await getList() | ||||
| }) | ||||
| 
 | ||||
| // ======================== 列表查询 ======================== | ||||
| /** 设置账号编号 */ | ||||
| const setAccountId = (id) => { | ||||
|   accountId.value = id | ||||
|   name.value = accountList.value.find((item) => item.id === accountId.value)?.name | ||||
| } | ||||
| 
 | ||||
| const getList = async () => { | ||||
|   loading.value = false | ||||
|   getMenuList(accountId.value) | ||||
|     .then((response) => { | ||||
|       const menuData = convertMenuList(response) | ||||
|       menuList.value = handleTree(menuData, 'id') | ||||
|     }) | ||||
|     .finally(() => { | ||||
|       loading.value = false | ||||
|     }) | ||||
| } | ||||
| 
 | ||||
| /** 搜索按钮操作 */ | ||||
| const handleQuery = () => { | ||||
|   resetForm() | ||||
|   // 默认选中第一个 | ||||
|   if (accountId.value) { | ||||
|     setAccountId(accountId.value) | ||||
|   } | ||||
|   getList() | ||||
| } | ||||
| 
 | ||||
| /** 重置按钮操作 */ | ||||
| const resetQuery = () => { | ||||
|   resetForm() | ||||
|   // 默认选中第一个 | ||||
|   if (accountList.value.length > 0) { | ||||
|     setAccountId(accountList.value[0].id) | ||||
|   } | ||||
|   handleQuery() | ||||
| } | ||||
| 
 | ||||
| // 将后端返回的 menuList,转换成前端的 menuList | ||||
| const convertMenuList = (list) => { | ||||
|   if (!list) return [] | ||||
| 
 | ||||
|   const menuList = [] | ||||
|   list.forEach((item) => { | ||||
|     const menu = { | ||||
|       ...item | ||||
|     } | ||||
|     if (item.type === 'click' || item.type === 'scancode_waitmsg') { | ||||
|       delete menu.replyMessageType | ||||
|       delete menu.replyContent | ||||
|       delete menu.replyMediaId | ||||
|       delete menu.replyMediaUrl | ||||
|       delete menu.replyDescription | ||||
|       delete menu.replyArticles | ||||
|       menu.reply = { | ||||
|         type: item.replyMessageType, | ||||
|         accountId: item.accountId, | ||||
|         content: item.replyContent, | ||||
|         mediaId: item.replyMediaId, | ||||
|         url: item.replyMediaUrl, | ||||
|         title: item.replyTitle, | ||||
|         description: item.replyDescription, | ||||
|         thumbMediaId: item.replyThumbMediaId, | ||||
|         thumbMediaUrl: item.replyThumbMediaUrl, | ||||
|         articles: item.replyArticles, | ||||
|         musicUrl: item.replyMusicUrl, | ||||
|         hqMusicUrl: item.replyHqMusicUrl | ||||
|       } | ||||
|     } | ||||
|     menuList.push(menu) | ||||
|   }) | ||||
|   return menuList | ||||
| } | ||||
| 
 | ||||
| // 重置表单,清空表单数据 | ||||
| const resetForm = () => { | ||||
|   // 菜单操作 | ||||
|   isActive.value = -1 | ||||
|   isSubMenuActive.value = -1 | ||||
|   isSubMenuFlag.value = -1 | ||||
| 
 | ||||
|   // 菜单编辑 | ||||
|   showRightFlag.value = false | ||||
|   nameMaxLength.value = 0 | ||||
|   showConfigureContent.value = 0 | ||||
|   hackResetWxReplySelect.value = false | ||||
|   tempObj.value = {} | ||||
|   tempSelfObj.value = {} | ||||
|   dialogNewsVisible.value = false | ||||
| } | ||||
| 
 | ||||
| // ======================== 菜单操作 ======================== | ||||
| // 一级菜单点击事件 | ||||
| const menuClick = (i, item) => { | ||||
|   // 右侧的表单相关 | ||||
|   resetEditor() | ||||
|   showRightFlag.value = true // 右边菜单 | ||||
|   tempObj.value = item // 这个如果放在顶部,flag 会没有。因为重新赋值了。 | ||||
|   tempSelfObj.value.grand = '1' // 表示一级菜单 | ||||
|   tempSelfObj.value.index = i // 表示一级菜单索引 | ||||
|   nameMaxLength.value = 4 | ||||
|   showConfigureContent.value = !(item.children && item.children.length > 0) // 有子菜单,就不显示配置内容 | ||||
| 
 | ||||
|   // 左侧的选中 | ||||
|   isActive.value = i // 一级菜单选中样式 | ||||
|   isSubMenuFlag.value = i // 二级菜单显示标志 | ||||
|   isSubMenuActive.value = -1 // 二级菜单去除选中样式 | ||||
| } | ||||
| 
 | ||||
| // 二级菜单点击事件 | ||||
| const subMenuClick = (subItem, index, k) => { | ||||
|   // 右侧的表单相关 | ||||
|   resetEditor() | ||||
|   showRightFlag.value = true // 右边菜单 | ||||
|   console.log(subItem) | ||||
|   tempObj.value = subItem // 将点击的数据放到临时变量,对象有引用作用 | ||||
|   tempSelfObj.value.grand = '2' // 表示二级菜单 | ||||
|   tempSelfObj.value.index = index // 表示一级菜单索引 | ||||
|   tempSelfObj.value.secondIndex = k // 表示二级菜单索引 | ||||
|   nameMaxLength.value = 7 | ||||
|   showConfigureContent.value = true | ||||
| 
 | ||||
|   // 左侧的选中 | ||||
|   isActive.value = -1 // 一级菜单去除样式 | ||||
|   isSubMenuActive.value = index + '' + k // 二级菜单选中样式 | ||||
| } | ||||
| 
 | ||||
| // 添加横向一级菜单 | ||||
| const addMenu = () => { | ||||
|   const menuKeyLength = menuList.value.length | ||||
|   const addButton = { | ||||
|     name: '菜单名称', | ||||
|     children: [], | ||||
|     reply: { | ||||
|       // 用于存储回复内容 | ||||
|       type: 'text', | ||||
|       accountId: accountId.value // 保证组件里,可以使用到对应的公众号 | ||||
|     } | ||||
|   } | ||||
|   menuList.value[menuKeyLength] = addButton | ||||
|   menuClick(menuKeyLength.value - 1, addButton) | ||||
| } | ||||
| // 添加横向二级菜单;item 表示要操作的父菜单 | ||||
| const addSubMenu = (i, item) => { | ||||
|   // 清空父菜单的属性,因为它只需要 name 属性即可 | ||||
|   if (!item.children || item.children.length <= 0) { | ||||
|     item.children = [] | ||||
|     delete item['type'] | ||||
|     delete item['menuKey'] | ||||
|     delete item['miniProgramAppId'] | ||||
|     delete item['miniProgramPagePath'] | ||||
|     delete item['url'] | ||||
|     delete item['reply'] | ||||
|     delete item['articleId'] | ||||
|     delete item['replyArticles'] | ||||
|     // 关闭配置面板 | ||||
|     showConfigureContent.value = false | ||||
|   } | ||||
| 
 | ||||
|   let subMenuKeyLength = item.children.length // 获取二级菜单key长度 | ||||
|   let addButton = { | ||||
|     name: '子菜单名称', | ||||
|     reply: { | ||||
|       // 用于存储回复内容 | ||||
|       type: 'text', | ||||
|       accountId: accountId.value // 保证组件里,可以使用到对应的公众号 | ||||
|     } | ||||
|   } | ||||
|   item.children[subMenuKeyLength] = addButton | ||||
|   subMenuClick(item.children[subMenuKeyLength], i, subMenuKeyLength) | ||||
| } | ||||
| 
 | ||||
| // 删除当前菜单 | ||||
| const handleDeleteMenu = async () => { | ||||
|   try { | ||||
|     await message.confirm('确定要删除吗?') | ||||
|     if (tempSelfObj.value.grand === '1') { | ||||
|       // 一级菜单的删除方法 | ||||
|       menuList.value.splice(tempSelfObj.value.index, 1) | ||||
|     } else if (tempSelfObj.value.grand === '2') { | ||||
|       // 二级菜单的删除方法 | ||||
|       menuList.value[tempSelfObj.value.index].children.splice(tempSelfObj.value.secondIndex, 1) | ||||
|     } | ||||
|     // 提示 | ||||
|     message.notifySuccess('删除成功') | ||||
| 
 | ||||
|     // 处理菜单的选中 | ||||
|     tempObj.value = {} | ||||
|     showRightFlag.value = false | ||||
|     isActive.value = -1 | ||||
|     isSubMenuActive.value = -1 | ||||
|   } catch {} | ||||
| } | ||||
| 
 | ||||
| // ======================== 菜单编辑 ======================== | ||||
| const handleSave = async () => { | ||||
|   try { | ||||
|     await message.confirm('确定要删除吗?') | ||||
|     loading.value = true | ||||
|     await saveMenu(accountId.value, convertMenuFormList()) | ||||
|     getList() | ||||
|     message.notifySuccess('发布成功') | ||||
|   } finally { | ||||
|     loading.value = false | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // 表单 Editor 重置 | ||||
| const resetEditor = () => { | ||||
|   hackResetWxReplySelect.value = false // 销毁组件 | ||||
|   nextTick(() => { | ||||
|     console.log('nextTick') | ||||
|     hackResetWxReplySelect.value = true // 重建组件 | ||||
|   }) | ||||
| } | ||||
| 
 | ||||
| const handleDelete = async () => { | ||||
|   try { | ||||
|     await message.confirm('确定要删除吗?') | ||||
|     loading.value = true | ||||
|     await deleteMenu(accountId.value) | ||||
|     handleQuery() | ||||
|     message.notifySuccess('清空成功') | ||||
|   } finally { | ||||
|     loading.value = false | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // 将前端的 menuList,转换成后端接收的 menuList | ||||
| const convertMenuFormList = () => { | ||||
|   const result = [] | ||||
|   menuList.value.forEach((item) => { | ||||
|     let menu = convertMenuForm(item) | ||||
|     result.push(menu) | ||||
| 
 | ||||
|     // 处理子菜单 | ||||
|     if (!item.children || item.children.length <= 0) { | ||||
|       return | ||||
|     } | ||||
|     menu.children = [] | ||||
|     item.children.forEach((subItem) => { | ||||
|       menu.children.push(convertMenuForm(subItem)) | ||||
|     }) | ||||
|   }) | ||||
|   return result | ||||
| } | ||||
| 
 | ||||
| // 将前端的 menu,转换成后端接收的 menu | ||||
| const convertMenuForm = (menu) => { | ||||
|   let result = { | ||||
|     ...menu, | ||||
|     children: undefined, // 不处理子节点 | ||||
|     reply: undefined // 稍后复制 | ||||
|   } | ||||
|   if (menu.type === 'click' || menu.type === 'scancode_waitmsg') { | ||||
|     result.replyMessageType = menu.reply.type | ||||
|     result.replyContent = menu.reply.content | ||||
|     result.replyMediaId = menu.reply.mediaId | ||||
|     result.replyMediaUrl = menu.reply.url | ||||
|     result.replyTitle = menu.reply.title | ||||
|     result.replyDescription = menu.reply.description | ||||
|     result.replyThumbMediaId = menu.reply.thumbMediaId | ||||
|     result.replyThumbMediaUrl = menu.reply.thumbMediaUrl | ||||
|     result.replyArticles = menu.reply.articles | ||||
|     result.replyMusicUrl = menu.reply.musicUrl | ||||
|     result.replyHqMusicUrl = menu.reply.hqMusicUrl | ||||
|   } | ||||
|   return result | ||||
| } | ||||
| 
 | ||||
| // ======================== 菜单编辑(素材选择) ======================== | ||||
| const openMaterial = () => { | ||||
|   dialogNewsVisible.value = true | ||||
| } | ||||
| 
 | ||||
| const selectMaterial = (item) => { | ||||
|   const articleId = item.articleId | ||||
|   const articles = item.content.newsItem | ||||
|   // 提示,针对多图文 | ||||
|   if (articles.length > 1) { | ||||
|     message.alertWarning('您选择的是多图文,将默认跳转第一篇') | ||||
|   } | ||||
|   dialogNewsVisible.value = false | ||||
| 
 | ||||
|   // 设置菜单的回复 | ||||
|   tempObj.value.articleId = articleId | ||||
|   tempObj.value.replyArticles = [] | ||||
|   articles.forEach((article) => { | ||||
|     tempObj.value.replyArticles.push({ | ||||
|       title: article.title, | ||||
|       description: article.digest, | ||||
|       picUrl: article.picUrl, | ||||
|       url: article.url | ||||
|     }) | ||||
|   }) | ||||
| } | ||||
| 
 | ||||
| const deleteMaterial = () => { | ||||
|   delete tempObj.value['articleId'] | ||||
|   delete tempObj.value['replyArticles'] | ||||
| } | ||||
| </script> | ||||
| <!--本组件样式--> | ||||
| <style lang="scss" scoped="scoped"> | ||||
| /* 公共颜色变量 */ | ||||
| .clearfix { | ||||
|   *zoom: 1; | ||||
| } | ||||
| 
 | ||||
| .clearfix::after { | ||||
|   content: ''; | ||||
|   display: table; | ||||
|   clear: both; | ||||
| } | ||||
| 
 | ||||
| div { | ||||
|   text-align: left; | ||||
| } | ||||
| 
 | ||||
| .weixin-hd { | ||||
|   color: #fff; | ||||
|   text-align: center; | ||||
|   position: relative; | ||||
|   bottom: 426px; | ||||
|   left: 0px; | ||||
|   width: 300px; | ||||
|   height: 64px; | ||||
|   background: transparent url('./assets/menu_head.png') no-repeat 0 0; | ||||
|   background-position: 0 0; | ||||
|   background-size: 100%; | ||||
| } | ||||
| 
 | ||||
| .weixin-title { | ||||
|   color: #fff; | ||||
|   font-size: 14px; | ||||
|   width: 100%; | ||||
|   text-align: center; | ||||
|   position: absolute; | ||||
|   top: 33px; | ||||
|   left: 0px; | ||||
| } | ||||
| 
 | ||||
| .weixin-menu { | ||||
|   background: transparent url('./assets/menu_foot.png') no-repeat 0 0; | ||||
|   padding-left: 43px; | ||||
|   font-size: 12px; | ||||
| } | ||||
| 
 | ||||
| .menu_option { | ||||
|   width: 40% !important; | ||||
| } | ||||
| 
 | ||||
| .public-account-management { | ||||
|   min-width: 1200px; | ||||
|   width: 1200px; | ||||
|   margin: 0 auto; | ||||
| 
 | ||||
|   .left { | ||||
|     float: left; | ||||
|     display: inline-block; | ||||
|     width: 350px; | ||||
|     height: 715px; | ||||
|     background: url('./assets/iphone_backImg.png') no-repeat; | ||||
|     background-size: 100% auto; | ||||
|     padding: 518px 25px 88px; | ||||
|     position: relative; | ||||
|     box-sizing: border-box; | ||||
| 
 | ||||
|     /*第一级菜单*/ | ||||
|     .menu_main { | ||||
|       .menu_bottom { | ||||
|         position: relative; | ||||
|         float: left; | ||||
|         display: inline-block; | ||||
|         box-sizing: border-box; | ||||
|         width: 85.5px; | ||||
|         text-align: center; | ||||
|         border: 1px solid #ebedee; | ||||
|         background-color: #fff; | ||||
|         cursor: pointer; | ||||
| 
 | ||||
|         &.menu_addicon { | ||||
|           height: 46px; | ||||
|           line-height: 46px; | ||||
|         } | ||||
| 
 | ||||
|         .menu_item { | ||||
|           height: 44px; | ||||
|           line-height: 44px; | ||||
|           // text-align: center; | ||||
|           box-sizing: border-box; | ||||
|           width: 100%; | ||||
|           display: flex; | ||||
|           align-items: center; | ||||
|           justify-content: center; | ||||
| 
 | ||||
|           &.active { | ||||
|             border: 1px solid #2bb673; | ||||
|           } | ||||
|         } | ||||
| 
 | ||||
|         .menu_subItem { | ||||
|           height: 44px; | ||||
|           line-height: 44px; | ||||
|           text-align: center; | ||||
|           box-sizing: border-box; | ||||
| 
 | ||||
|           &.active { | ||||
|             border: 1px solid #2bb673; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       i { | ||||
|         color: #2bb673; | ||||
|       } | ||||
| 
 | ||||
|       /*第二级菜单*/ | ||||
|       .submenu { | ||||
|         position: absolute; | ||||
|         width: 85.5px; | ||||
|         bottom: 45px; | ||||
| 
 | ||||
|         .subtitle { | ||||
|           background-color: #fff; | ||||
|           box-sizing: border-box; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .save_div { | ||||
|       margin-top: 15px; | ||||
|       text-align: center; | ||||
| 
 | ||||
|       .save_btn { | ||||
|         bottom: 20px; | ||||
|         left: 100px; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /*右边菜单内容*/ | ||||
|   .right { | ||||
|     float: left; | ||||
|     width: 63%; | ||||
|     background-color: #e8e7e7; | ||||
|     padding: 20px; | ||||
|     margin-left: 20px; | ||||
|     -webkit-box-sizing: border-box; | ||||
|     box-sizing: border-box; | ||||
| 
 | ||||
|     .configure_page { | ||||
|       .delete_btn { | ||||
|         text-align: right; | ||||
|         margin-bottom: 15px; | ||||
|       } | ||||
| 
 | ||||
|       .menu_content { | ||||
|         margin-top: 20px; | ||||
|       } | ||||
| 
 | ||||
|       .configur_content { | ||||
|         margin-top: 20px; | ||||
|         background-color: #fff; | ||||
|         padding: 20px 10px; | ||||
|         border-radius: 5px; | ||||
|       } | ||||
| 
 | ||||
|       .blue { | ||||
|         color: #29b6f6; | ||||
|         margin-top: 10px; | ||||
|       } | ||||
| 
 | ||||
|       .applet { | ||||
|         margin-bottom: 20px; | ||||
| 
 | ||||
|         span { | ||||
|           width: 20%; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       .input_width { | ||||
|         width: 40%; | ||||
|       } | ||||
| 
 | ||||
|       .material { | ||||
|         .input_width { | ||||
|           width: 30%; | ||||
|         } | ||||
| 
 | ||||
|         .el-textarea { | ||||
|           width: 80%; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .el-input { | ||||
|     width: 70%; | ||||
|     margin-right: 2%; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
| <!--素材样式--> | ||||
| <style lang="scss" scoped> | ||||
| .pagination { | ||||
|   text-align: right; | ||||
|   margin-right: 25px; | ||||
| } | ||||
| 
 | ||||
| .select-item { | ||||
|   width: 280px; | ||||
|   padding: 10px; | ||||
|   margin: 0 auto 10px auto; | ||||
|   border: 1px solid #eaeaea; | ||||
| } | ||||
| 
 | ||||
| .select-item2 { | ||||
|   padding: 10px; | ||||
|   margin: 0 auto 10px auto; | ||||
|   border: 1px solid #eaeaea; | ||||
| } | ||||
| 
 | ||||
| .ope-row { | ||||
|   padding-top: 10px; | ||||
|   text-align: center; | ||||
| } | ||||
| 
 | ||||
| .item-name { | ||||
|   font-size: 12px; | ||||
|   overflow: hidden; | ||||
|   text-overflow: ellipsis; | ||||
|   white-space: nowrap; | ||||
|   text-align: center; | ||||
| } | ||||
| </style> | ||||
|  |  | |||
|  | @ -0,0 +1,42 @@ | |||
| export default [ | ||||
|   { | ||||
|     value: 'view', | ||||
|     label: '跳转网页' | ||||
|   }, | ||||
|   { | ||||
|     value: 'miniprogram', | ||||
|     label: '跳转小程序' | ||||
|   }, | ||||
|   { | ||||
|     value: 'click', | ||||
|     label: '点击回复' | ||||
|   }, | ||||
|   { | ||||
|     value: 'article_view_limited', | ||||
|     label: '跳转图文消息' | ||||
|   }, | ||||
|   { | ||||
|     value: 'scancode_push', | ||||
|     label: '扫码直接返回结果' | ||||
|   }, | ||||
|   { | ||||
|     value: 'scancode_waitmsg', | ||||
|     label: '扫码回复' | ||||
|   }, | ||||
|   { | ||||
|     value: 'pic_sysphoto', | ||||
|     label: '系统拍照发图' | ||||
|   }, | ||||
|   { | ||||
|     value: 'pic_photo_or_album', | ||||
|     label: '拍照或者相册' | ||||
|   }, | ||||
|   { | ||||
|     value: 'pic_weixin', | ||||
|     label: '微信相册' | ||||
|   }, | ||||
|   { | ||||
|     value: 'location_select', | ||||
|     label: '选择地理位置' | ||||
|   } | ||||
| ] | ||||
		Loading…
	
		Reference in New Issue
	
	 Aix
						Aix