mirror of
				https://gitee.com/hhyykk/ipms-sjy-ui.git
				synced 2025-10-31 02:08:45 +08:00 
			
		
		
		
	Merge remote-tracking branch 'yudao/dev' into dev-to-dev
This commit is contained in:
		| @@ -64,6 +64,7 @@ const include = [ | ||||
|   'element-plus/es/components/dropdown-menu/style/index', | ||||
|   'element-plus/es/components/dropdown-item/style/index', | ||||
|   'element-plus/es/components/skeleton/style/index', | ||||
|  | ||||
|   'element-plus/es/components/skeleton/style/css', | ||||
|   'element-plus/es/components/backtop/style/css', | ||||
|   'element-plus/es/components/menu/style/css', | ||||
| @@ -76,7 +77,21 @@ const include = [ | ||||
|   'element-plus/es/components/badge/style/css', | ||||
|   'element-plus/es/components/breadcrumb/style/css', | ||||
|   'element-plus/es/components/breadcrumb-item/style/css', | ||||
|   'element-plus/es/components/image/style/css' | ||||
|   'element-plus/es/components/image/style/css', | ||||
|   'element-plus/es/components/tag/style/css', | ||||
|   'element-plus/es/components/dialog/style/css', | ||||
|   'element-plus/es/components/form/style/css', | ||||
|   'element-plus/es/components/form-item/style/css', | ||||
|   'element-plus/es/components/card/style/css', | ||||
|   'element-plus/es/components/tooltip/style/css', | ||||
|   'element-plus/es/components/radio-group/style/css', | ||||
|   'element-plus/es/components/radio/style/css', | ||||
|   'element-plus/es/components/input-number/style/css', | ||||
|   'element-plus/es/components/tree-select/style/css', | ||||
|   'element-plus/es/components/drawer/style/css', | ||||
|   'element-plus/es/components/image-viewer/style/css', | ||||
|   'element-plus/es/components/upload/style/css', | ||||
|   'element-plus/es/components/switch/style/css' | ||||
| ] | ||||
|  | ||||
| const exclude = ['@iconify/json'] | ||||
|   | ||||
| @@ -17,17 +17,17 @@ export interface CategoryVO { | ||||
|    */ | ||||
|   name: string | ||||
|   /** | ||||
|    * 分类图片 | ||||
|    * 移动端分类图 | ||||
|    */ | ||||
|   picUrl: string | ||||
|   /** | ||||
|    * PC 端分类图 | ||||
|    */ | ||||
|   bigPicUrl?: string | ||||
|   /** | ||||
|    * 分类排序 | ||||
|    */ | ||||
|   sort?: number | ||||
|   /** | ||||
|    * 分类描述 | ||||
|    */ | ||||
|   description?: string | ||||
|   sort: number | ||||
|   /** | ||||
|    * 开启状态 | ||||
|    */ | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import request from '@/config/axios' | ||||
|  | ||||
| export interface AccountVO { | ||||
|   id?: number | ||||
|   id: number | ||||
|   name: string | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -4,27 +4,30 @@ | ||||
|       ref="formRef" | ||||
|       :model="formData" | ||||
|       :rules="formRules" | ||||
|       label-width="80px" | ||||
|       label-width="120px" | ||||
|       v-loading="formLoading" | ||||
|     > | ||||
|       <el-form-item label="上级分类" prop="parentId"> | ||||
|         <el-tree-select | ||||
|           v-model="formData.parentId" | ||||
|           :data="categoryTree" | ||||
|           :props="{ label: 'name', value: 'id' }" | ||||
|           :render-after-expand="false" | ||||
|           placeholder="请选择上级分类" | ||||
|           check-strictly | ||||
|           default-expand-all | ||||
|         /> | ||||
|         <el-select v-model="formData.parentId" placeholder="请选择上级分类"> | ||||
|           <el-option :key="0" label="顶级分类" :value="0" /> | ||||
|           <el-option | ||||
|             v-for="item in categoryList" | ||||
|             :key="item.id" | ||||
|             :label="item.name" | ||||
|             :value="item.id" | ||||
|           /> | ||||
|         </el-select> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="分类名称" prop="name"> | ||||
|         <el-input v-model="formData.name" placeholder="请输入分类名称" /> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="分类图片" prop="picUrl"> | ||||
|       <el-form-item label="移动端分类图" prop="picUrl"> | ||||
|         <UploadImg v-model="formData.picUrl" :limit="1" :is-show-tip="false" /> | ||||
|         <div v-if="formData.parentId === 0" style="font-size: 10px">推荐 200x100 图片分辨率</div> | ||||
|         <div v-else style="font-size: 10px">推荐 100x100 图片分辨率</div> | ||||
|         <div style="font-size: 10px" class="pl-10px">推荐 180x180 图片分辨率</div> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="PC 端分类图" prop="bigPicUrl"> | ||||
|         <UploadImg v-model="formData.bigPicUrl" :limit="1" :is-show-tip="false" /> | ||||
|         <div style="font-size: 10px" class="pl-10px">推荐 468x340 图片分辨率</div> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="分类排序" prop="sort"> | ||||
|         <el-input-number v-model="formData.sort" controls-position="right" :min="0" /> | ||||
| @@ -40,9 +43,6 @@ | ||||
|           </el-radio> | ||||
|         </el-radio-group> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="分类描述"> | ||||
|         <el-input v-model="formData.description" type="textarea" placeholder="请输入分类描述" /> | ||||
|       </el-form-item> | ||||
|     </el-form> | ||||
|     <template #footer> | ||||
|       <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> | ||||
| @@ -53,7 +53,6 @@ | ||||
| <script setup lang="ts" name="ProductCategory"> | ||||
| import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' | ||||
| import { CommonStatusEnum } from '@/utils/constants' | ||||
| import { handleTree } from '@/utils/tree' | ||||
| import * as ProductCategoryApi from '@/api/mall/product/category' | ||||
| const { t } = useI18n() // 国际化 | ||||
| const message = useMessage() // 消息弹窗 | ||||
| @@ -66,8 +65,8 @@ const formData = ref({ | ||||
|   id: undefined, | ||||
|   name: '', | ||||
|   picUrl: '', | ||||
|   status: CommonStatusEnum.ENABLE, | ||||
|   description: '' | ||||
|   bigPicUrl: '', | ||||
|   status: CommonStatusEnum.ENABLE | ||||
| }) | ||||
| const formRules = reactive({ | ||||
|   parentId: [{ required: true, message: '请选择上级分类', trigger: 'blur' }], | ||||
| @@ -77,7 +76,7 @@ const formRules = reactive({ | ||||
|   status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }] | ||||
| }) | ||||
| const formRef = ref() // 表单 Ref | ||||
| const categoryTree = ref<any[]>([]) // 分类树 | ||||
| const categoryList = ref<any[]>([]) // 分类树 | ||||
|  | ||||
| /** 打开弹窗 */ | ||||
| const open = async (type: string, id?: number) => { | ||||
| @@ -95,7 +94,7 @@ const open = async (type: string, id?: number) => { | ||||
|     } | ||||
|   } | ||||
|   // 获得分类树 | ||||
|   await getTree() | ||||
|   categoryList.value = await ProductCategoryApi.getCategoryList({ parentId: 0 }) | ||||
| } | ||||
| defineExpose({ open }) // 提供 open 方法,用于打开弹窗 | ||||
|  | ||||
| @@ -131,17 +130,9 @@ const resetForm = () => { | ||||
|     id: undefined, | ||||
|     name: '', | ||||
|     picUrl: '', | ||||
|     status: CommonStatusEnum.ENABLE, | ||||
|     description: '' | ||||
|     bigPicUrl: '', | ||||
|     status: CommonStatusEnum.ENABLE | ||||
|   } | ||||
|   formRef.value?.resetFields() | ||||
| } | ||||
|  | ||||
| /** 获得分类树 */ | ||||
| const getTree = async () => { | ||||
|   const data = await ProductCategoryApi.getCategoryList({}) | ||||
|   const tree = handleTree(data, 'id', 'parentId') | ||||
|   const menu = { id: 0, name: '顶级分类', children: tree } | ||||
|   categoryTree.value = [menu] | ||||
| } | ||||
| </script> | ||||
|   | ||||
| @@ -36,9 +36,9 @@ | ||||
|   <ContentWrap> | ||||
|     <el-table v-loading="loading" :data="list" row-key="id" default-expand-all> | ||||
|       <el-table-column label="分类名称" prop="name" sortable /> | ||||
|       <el-table-column label="分类图片" align="center" prop="picUrl"> | ||||
|       <el-table-column label="移动端分类图" align="center" prop="picUrl"> | ||||
|         <template #default="scope"> | ||||
|           <img v-if="scope.row.picUrl" :src="scope.row.picUrl" alt="分类图片" class="h-100px" /> | ||||
|           <img v-if="scope.row.picUrl" :src="scope.row.picUrl" alt="移动端分类图" class="h-100px" /> | ||||
|         </template> | ||||
|       </el-table-column> | ||||
|       <el-table-column label="分类排序" align="center" prop="sort" /> | ||||
|   | ||||
| @@ -103,6 +103,7 @@ import ReplyTable from './components/ReplyTable.vue' | ||||
| import { MsgType } from './components/types' | ||||
| const message = useMessage() // 消息 | ||||
|  | ||||
| const accountId = ref(-1) // 公众号ID | ||||
| const msgType = ref<MsgType>(MsgType.Keyword) // 消息类型 | ||||
| const RequestMessageTypes = ['text', 'image', 'voice', 'video', 'shortvideo', 'location', 'link'] // 允许选择的请求消息类型 | ||||
| const loading = ref(true) // 遮罩层 | ||||
| @@ -110,15 +111,10 @@ const total = ref(0) // 总条数 | ||||
| const list = ref<any[]>([]) // 自动回复列表 | ||||
| const formRef = ref<FormInstance | null>(null) // 表单 ref | ||||
| // 查询参数 | ||||
| interface QueryParams { | ||||
|   pageNo: number | ||||
|   pageSize: number | ||||
|   accountId: number | ||||
| } | ||||
| const queryParams: QueryParams = reactive({ | ||||
| const queryParams = reactive({ | ||||
|   pageNo: 1, | ||||
|   pageSize: 10, | ||||
|   accountId: 0 | ||||
|   accountId: accountId | ||||
| }) | ||||
|  | ||||
| const dialogTitle = ref('') // 弹出层标题 | ||||
| @@ -127,7 +123,7 @@ const replyForm = ref<any>({}) // 表单参数 | ||||
| // 回复消息 | ||||
| const reply = ref<Reply>({ | ||||
|   type: ReplyType.Text, | ||||
|   accountId: 0 | ||||
|   accountId: -1 | ||||
| }) | ||||
| // 表单校验 | ||||
| const rules = { | ||||
| @@ -137,8 +133,9 @@ const rules = { | ||||
|  | ||||
| /** 侦听账号变化 */ | ||||
| const onAccountChanged = (id: number) => { | ||||
|   queryParams.accountId = id | ||||
|   accountId.value = id | ||||
|   reply.value.accountId = id | ||||
|   queryParams.pageNo = 1 | ||||
|   getList() | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -8,13 +8,14 @@ | ||||
| import * as MpAccountApi from '@/api/mp/account' | ||||
|  | ||||
| const account: MpAccountApi.AccountVO = reactive({ | ||||
|   id: undefined, | ||||
|   id: -1, | ||||
|   name: '' | ||||
| }) | ||||
| const accountList: Ref<MpAccountApi.AccountVO[]> = ref([]) | ||||
|  | ||||
| const accountList = ref<MpAccountApi.AccountVO[]>([]) | ||||
|  | ||||
| const emit = defineEmits<{ | ||||
|   (e: 'change', id: number, name: string): void | ||||
|   (e: 'change', id: number, name: string) | ||||
| }>() | ||||
|  | ||||
| const handleQuery = async () => { | ||||
|   | ||||
							
								
								
									
										51
									
								
								src/views/mp/components/wx-msg/components/MsgEvent.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/views/mp/components/wx-msg/components/MsgEvent.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| <template> | ||||
|   <div> | ||||
|     <div v-if="item.event === 'subscribe'"> | ||||
|       <el-tag type="success">关注</el-tag> | ||||
|     </div> | ||||
|     <div v-else-if="item.event === 'unsubscribe'"> | ||||
|       <el-tag type="danger">取消关注</el-tag> | ||||
|     </div> | ||||
|     <div v-else-if="item.event === 'CLICK'"> | ||||
|       <el-tag>点击菜单</el-tag> | ||||
|       【{{ item.eventKey }}】 | ||||
|     </div> | ||||
|     <div v-else-if="item.event === 'VIEW'"> | ||||
|       <el-tag>点击菜单链接</el-tag> | ||||
|       【{{ item.eventKey }}】 | ||||
|     </div> | ||||
|     <div v-else-if="item.event === 'scancode_waitmsg'"> | ||||
|       <el-tag>扫码结果</el-tag> | ||||
|       【{{ item.eventKey }}】 | ||||
|     </div> | ||||
|     <div v-else-if="item.event === 'scancode_push'"> | ||||
|       <el-tag>扫码结果</el-tag> | ||||
|       【{{ item.eventKey }}】 | ||||
|     </div> | ||||
|     <div v-else-if="item.event === 'pic_sysphoto'"> | ||||
|       <el-tag>系统拍照发图</el-tag> | ||||
|     </div> | ||||
|     <div v-else-if="item.event === 'pic_photo_or_album'"> | ||||
|       <el-tag>拍照或者相册</el-tag> | ||||
|     </div> | ||||
|     <div v-else-if="item.event === 'pic_weixin'"> | ||||
|       <el-tag>微信相册</el-tag> | ||||
|     </div> | ||||
|     <div v-else-if="item.event === 'location_select'"> | ||||
|       <el-tag>选择地理位置</el-tag> | ||||
|     </div> | ||||
|     <div v-else> | ||||
|       <el-tag type="danger">未知事件类型</el-tag> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| const props = defineProps<{ | ||||
|   item: any | ||||
| }>() | ||||
|  | ||||
| const item = ref(props.item) | ||||
| </script> | ||||
|  | ||||
| <style scoped></style> | ||||
							
								
								
									
										110
									
								
								src/views/mp/components/wx-msg/components/MsgList.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								src/views/mp/components/wx-msg/components/MsgList.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,110 @@ | ||||
| <template> | ||||
|   <div class="execution" v-for="item in props.list" :key="item.id"> | ||||
|     <div | ||||
|       class="avue-comment" | ||||
|       :class="{ 'avue-comment--reverse': item.sendFrom === SendFrom.MpBot }" | ||||
|     > | ||||
|       <div class="avatar-div"> | ||||
|         <img :src="getAvatar(item.sendFrom)" class="avue-comment__avatar" /> | ||||
|         <div class="avue-comment__author"> | ||||
|           {{ getNickname(item.sendFrom) }} | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="avue-comment__main"> | ||||
|         <div class="avue-comment__header"> | ||||
|           <div class="avue-comment__create_time">{{ formatDate(item.createTime) }}</div> | ||||
|         </div> | ||||
|         <div | ||||
|           class="avue-comment__body" | ||||
|           :style="item.sendFrom === SendFrom.MpBot ? 'background: #6BED72;' : ''" | ||||
|         > | ||||
|           <!-- 【事件】区域 --> | ||||
|           <MsgEvent v-if="item.type === MsgType.Event" :item="item" /> | ||||
|           <!-- 【消息】区域 --> | ||||
|           <div v-else-if="item.type === MsgType.Text">{{ item.content }}</div> | ||||
|           <div v-else-if="item.type === MsgType.Voice"> | ||||
|             <WxVoicePlayer :url="item.mediaUrl" :content="item.recognition" /> | ||||
|           </div> | ||||
|           <div v-else-if="item.type === MsgType.Image"> | ||||
|             <a target="_blank" :href="item.mediaUrl"> | ||||
|               <img :src="item.mediaUrl" style="width: 100px" /> | ||||
|             </a> | ||||
|           </div> | ||||
|           <div | ||||
|             v-else-if="item.type === MsgType.Video || item.type === 'shortvideo'" | ||||
|             style="text-align: center" | ||||
|           > | ||||
|             <WxVideoPlayer :url="item.mediaUrl" /> | ||||
|           </div> | ||||
|           <div v-else-if="item.type === MsgType.Link" class="avue-card__detail"> | ||||
|             <el-link type="success" :underline="false" target="_blank" :href="item.url"> | ||||
|               <div class="avue-card__title"><i class="el-icon-link"></i>{{ item.title }}</div> | ||||
|             </el-link> | ||||
|             <div class="avue-card__info" style="height: unset">{{ item.description }}</div> | ||||
|           </div> | ||||
|           <!-- TODO 芋艿:待完善 --> | ||||
|           <div v-else-if="item.type === MsgType.Location"> | ||||
|             <WxLocation | ||||
|               :label="item.label" | ||||
|               :location-y="item.locationY" | ||||
|               :location-x="item.locationX" | ||||
|             /> | ||||
|           </div> | ||||
|           <div v-else-if="item.type === MsgType.News" style="width: 300px"> | ||||
|             <!-- TODO 芋艿:待测试;详情页也存在类似的情况 --> | ||||
|             <WxNews :articles="item.articles" /> | ||||
|           </div> | ||||
|           <div v-else-if="item.type === MsgType.Music"> | ||||
|             <WxMusic | ||||
|               :title="item.title" | ||||
|               :description="item.description" | ||||
|               :thumb-media-url="item.thumbMediaUrl" | ||||
|               :music-url="item.musicUrl" | ||||
|               :hq-music-url="item.hqMusicUrl" | ||||
|             /> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts" name="MsgList"> | ||||
| import WxVideoPlayer from '@/views/mp/components/wx-video-play' | ||||
| import WxVoicePlayer from '@/views/mp/components/wx-voice-play' | ||||
| import WxNews from '@/views/mp/components/wx-news' | ||||
| import WxLocation from '@/views/mp/components/wx-location' | ||||
| import WxMusic from '@/views/mp/components/wx-music' | ||||
| import MsgEvent from './MsgEvent.vue' | ||||
| import { formatDate } from '@/utils/formatTime' | ||||
| import { MsgType, User } from '../types' | ||||
| import avatarWechat from '@/assets/imgs/wechat.png' | ||||
|  | ||||
| const props = defineProps<{ | ||||
|   list: any[] | ||||
|   accountId: number | ||||
|   user: User | ||||
| }>() | ||||
|  | ||||
| enum SendFrom { | ||||
|   User = 1, | ||||
|   MpBot = 2 | ||||
| } | ||||
|  | ||||
| const getAvatar = (sendFrom: SendFrom) => | ||||
|   sendFrom === SendFrom.User ? props.user.avatar : avatarWechat | ||||
|  | ||||
| const getNickname = (sendFrom: SendFrom) => | ||||
|   sendFrom === SendFrom.User ? props.user.nickname : '公众号' | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| /* 因为 joolun 实现依赖 avue 组件,该页面使用了 comment.scss、card.scc  */ | ||||
| @import '../comment.scss'; | ||||
| @import '../card.scss'; | ||||
|  | ||||
| .avatar-div { | ||||
|   text-align: center; | ||||
|   width: 80px; | ||||
| } | ||||
| </style> | ||||
| @@ -7,123 +7,22 @@ | ||||
| --> | ||||
| <template> | ||||
|   <ContentWrap> | ||||
|     <div class="msg-div" :id="'msg-div' + nowStr"> | ||||
|     <div class="msg-div" ref="msgDivRef"> | ||||
|       <!-- 加载更多 --> | ||||
|       <div v-loading="loading"></div> | ||||
|       <div v-if="!loading"> | ||||
|         <div class="el-table__empty-block" v-if="loadMore" @click="loadingMore" | ||||
|         <div class="el-table__empty-block" v-if="hasMore" @click="loadMore" | ||||
|           ><span class="el-table__empty-text">点击加载更多</span></div | ||||
|         > | ||||
|         <div class="el-table__empty-block" v-if="!loadMore" | ||||
|         <div class="el-table__empty-block" v-if="!hasMore" | ||||
|           ><span class="el-table__empty-text">没有更多了</span></div | ||||
|         > | ||||
|       </div> | ||||
|  | ||||
|       <!-- 消息列表 --> | ||||
|       <div class="execution" v-for="item in list" :key="item.id"> | ||||
|         <div class="avue-comment" :class="item.sendFrom === 2 ? 'avue-comment--reverse' : ''"> | ||||
|           <div class="avatar-div"> | ||||
|             <img | ||||
|               :src="item.sendFrom === 1 ? user.avatar : mp.avatar" | ||||
|               class="avue-comment__avatar" | ||||
|             /> | ||||
|             <div class="avue-comment__author" | ||||
|               >{{ item.sendFrom === 1 ? user.nickname : mp.nickname }} | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="avue-comment__main"> | ||||
|             <div class="avue-comment__header"> | ||||
|               <div class="avue-comment__create_time">{{ formatDate(item.createTime) }}</div> | ||||
|             </div> | ||||
|             <div | ||||
|               class="avue-comment__body" | ||||
|               :style="item.sendFrom === 2 ? 'background: #6BED72;' : ''" | ||||
|             > | ||||
|               <!-- 【事件】区域 --> | ||||
|               <div v-if="item.type === MsgType.Event && item.event === 'subscribe'"> | ||||
|                 <el-tag type="success">关注</el-tag> | ||||
|               </div> | ||||
|               <div v-else-if="item.type === MsgType.Event && item.event === 'unsubscribe'"> | ||||
|                 <el-tag type="danger">取消关注</el-tag> | ||||
|               </div> | ||||
|               <div v-else-if="item.type === MsgType.Event && item.event === 'CLICK'"> | ||||
|                 <el-tag>点击菜单</el-tag> | ||||
|                 【{{ item.eventKey }}】 | ||||
|               </div> | ||||
|               <div v-else-if="item.type === MsgType.Event && item.event === 'VIEW'"> | ||||
|                 <el-tag>点击菜单链接</el-tag> | ||||
|                 【{{ item.eventKey }}】 | ||||
|               </div> | ||||
|               <div v-else-if="item.type === MsgType.Event && item.event === 'scancode_waitmsg'"> | ||||
|                 <el-tag>扫码结果</el-tag> | ||||
|                 【{{ item.eventKey }}】 | ||||
|               </div> | ||||
|               <div v-else-if="item.type === MsgType.Event && item.event === 'scancode_push'"> | ||||
|                 <el-tag>扫码结果</el-tag> | ||||
|                 【{{ item.eventKey }}】 | ||||
|               </div> | ||||
|               <div v-else-if="item.type === MsgType.Event && item.event === 'pic_sysphoto'"> | ||||
|                 <el-tag>系统拍照发图</el-tag> | ||||
|               </div> | ||||
|               <div v-else-if="item.type === MsgType.Event && item.event === 'pic_photo_or_album'"> | ||||
|                 <el-tag>拍照或者相册</el-tag> | ||||
|               </div> | ||||
|               <div v-else-if="item.type === MsgType.Event && item.event === 'pic_weixin'"> | ||||
|                 <el-tag>微信相册</el-tag> | ||||
|               </div> | ||||
|               <div v-else-if="item.type === MsgType.Event && item.event === 'location_select'"> | ||||
|                 <el-tag>选择地理位置</el-tag> | ||||
|               </div> | ||||
|               <div v-else-if="item.type === MsgType.Event"> | ||||
|                 <el-tag type="danger">未知事件类型</el-tag> | ||||
|               </div> | ||||
|               <!-- 【消息】区域 --> | ||||
|               <div v-else-if="item.type === MsgType.Text">{{ item.content }}</div> | ||||
|               <div v-else-if="item.type === MsgType.Voice"> | ||||
|                 <WxVoicePlayer :url="item.mediaUrl" :content="item.recognition" /> | ||||
|               </div> | ||||
|               <div v-else-if="item.type === MsgType.Image"> | ||||
|                 <a target="_blank" :href="item.mediaUrl"> | ||||
|                   <img :src="item.mediaUrl" style="width: 100px" /> | ||||
|                 </a> | ||||
|               </div> | ||||
|               <div | ||||
|                 v-else-if="item.type === MsgType.Video || item.type === 'shortvideo'" | ||||
|                 style="text-align: center" | ||||
|               > | ||||
|                 <WxVideoPlayer :url="item.mediaUrl" /> | ||||
|               </div> | ||||
|               <div v-else-if="item.type === MsgType.Link" class="avue-card__detail"> | ||||
|                 <el-link type="success" :underline="false" target="_blank" :href="item.url"> | ||||
|                   <div class="avue-card__title"><i class="el-icon-link"></i>{{ item.title }}</div> | ||||
|                 </el-link> | ||||
|                 <div class="avue-card__info" style="height: unset">{{ item.description }}</div> | ||||
|               </div> | ||||
|               <!-- TODO 芋艿:待完善 --> | ||||
|               <div v-else-if="item.type === MsgType.Location"> | ||||
|                 <WxLocation | ||||
|                   :label="item.label" | ||||
|                   :location-y="item.locationY" | ||||
|                   :location-x="item.locationX" | ||||
|                 /> | ||||
|               </div> | ||||
|               <div v-else-if="item.type === MsgType.News" style="width: 300px"> | ||||
|                 <!-- TODO 芋艿:待测试;详情页也存在类似的情况 --> | ||||
|                 <WxNews :articles="item.articles" /> | ||||
|               </div> | ||||
|               <div v-else-if="item.type === MsgType.Music"> | ||||
|                 <WxMusic | ||||
|                   :title="item.title" | ||||
|                   :description="item.description" | ||||
|                   :thumb-media-url="item.thumbMediaUrl" | ||||
|                   :music-url="item.musicUrl" | ||||
|                   :hq-music-url="item.hqMusicUrl" | ||||
|                 /> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <MsgList :list="list" :account-id="accountId" :user="user" /> | ||||
|     </div> | ||||
|  | ||||
|     <div class="msg-send" v-loading="sendLoading"> | ||||
|       <WxReplySelect ref="replySelectRef" v-model="reply" /> | ||||
|       <el-button type="success" class="send-but" @click="sendMsg">发送(S)</el-button> | ||||
| @@ -132,18 +31,12 @@ | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts" name="WxMsg"> | ||||
| import WxReplySelect from '@/views/mp/components/wx-reply' | ||||
| import WxVideoPlayer from '@/views/mp/components/wx-video-play' | ||||
| import WxVoicePlayer from '@/views/mp/components/wx-voice-play' | ||||
| import WxNews from '@/views/mp/components/wx-news' | ||||
| import WxLocation from '@/views/mp/components/wx-location' | ||||
| import WxMusic from '@/views/mp/components/wx-music' | ||||
| import WxReplySelect, { Reply, ReplyType } from '@/views/mp/components/wx-reply' | ||||
| import MsgList from './components/MsgList.vue' | ||||
| import { getMessagePage, sendMessage } from '@/api/mp/message' | ||||
| import { getUser } from '@/api/mp/user' | ||||
| import { formatDate } from '@/utils/formatTime' | ||||
| import profile from '@/assets/imgs/profile.jpg' | ||||
| import wechat from '@/assets/imgs/wechat.png' | ||||
| import { MsgType } from './types' | ||||
| import { User } from './types' | ||||
|  | ||||
| const message = useMessage() // 消息弹窗 | ||||
|  | ||||
| @@ -154,61 +47,41 @@ const props = defineProps({ | ||||
|   } | ||||
| }) | ||||
|  | ||||
| const nowStr = ref(new Date().getTime()) // 当前的时间戳,用于每次消息加载后,回到原位置;具体见 :id="'msg-div' + nowStr" 处 | ||||
| const accountId = ref(-1) // 公众号ID,需要通过userId初始化 | ||||
| const loading = ref(false) // 消息列表是否正在加载中 | ||||
| const loadMore = ref(true) // 是否可以加载更多 | ||||
| const hasMore = ref(true) // 是否可以加载更多 | ||||
| const list = ref<any[]>([]) // 消息列表 | ||||
| const queryParams = reactive({ | ||||
|   pageNo: 1, // 当前页数 | ||||
|   pageSize: 14, // 每页显示多少条 | ||||
|   accountId: undefined | ||||
|   accountId: accountId | ||||
| }) | ||||
|  | ||||
| interface User { | ||||
|   nickname: string | ||||
|   avatar: string | ||||
|   accountId: number | ||||
| } | ||||
| // 由于微信不再提供昵称,直接使用“用户”展示 | ||||
| const user: User = reactive({ | ||||
|   nickname: '用户', | ||||
|   avatar: profile, | ||||
|   accountId: 0 // 公众号账号编号 | ||||
| }) | ||||
|  | ||||
| interface Mp { | ||||
|   nickname: string | ||||
|   avatar: string | ||||
| } | ||||
| const mp: Mp = reactive({ | ||||
|   nickname: '公众号', | ||||
|   avatar: wechat | ||||
|   accountId: accountId // 公众号账号编号 | ||||
| }) | ||||
|  | ||||
| // ========= 消息发送 ========= | ||||
| const sendLoading = ref(false) // 发送消息是否加载中 | ||||
| interface Reply { | ||||
|   type: MsgType | ||||
|   accountId: number | null | ||||
|   articles: any[] | ||||
| } | ||||
|  | ||||
| // 微信发送消息 | ||||
| const reply = ref<Reply>({ | ||||
|   type: MsgType.Text, | ||||
|   accountId: null, | ||||
|   type: ReplyType.Text, | ||||
|   accountId: -1, | ||||
|   articles: [] | ||||
| }) | ||||
|  | ||||
| const replySelectRef = ref<InstanceType<typeof WxReplySelect> | null>(null) | ||||
| const replySelectRef = ref<InstanceType<typeof WxReplySelect> | null>(null) // WxReplySelect组件ref,用于消息发送成功后清除内容 | ||||
| const msgDivRef = ref() // 消息显示窗口ref,用于滚动到底部 | ||||
|  | ||||
| /** 完成加载 */ | ||||
| onMounted(async () => { | ||||
|   const data = await getUser(props.userId) | ||||
|   user.nickname = data.nickname?.length > 0 ? data.nickname : user.nickname | ||||
|   user.avatar = user.avatar?.length > 0 ? data.avatar : user.avatar | ||||
|   user.accountId = data.accountId | ||||
|   queryParams.accountId = data.accountId | ||||
|   accountId.value = data.accountId | ||||
|   reply.value.accountId = data.accountId | ||||
|  | ||||
|   refreshChange() | ||||
| @@ -216,11 +89,15 @@ onMounted(async () => { | ||||
|  | ||||
| // 执行发送 | ||||
| const sendMsg = async () => { | ||||
|   if (!reply) { | ||||
|   if (!unref(reply)) { | ||||
|     return | ||||
|   } | ||||
|   // 公众号限制:客服消息,公众号只允许发送一条 | ||||
|   if (reply.value.type === MsgType.News && reply.value.articles.length > 1) { | ||||
|   if ( | ||||
|     reply.value.type === ReplyType.News && | ||||
|     reply.value.articles && | ||||
|     reply.value.articles.length > 1 | ||||
|   ) { | ||||
|     reply.value.articles = [reply.value.articles[0]] | ||||
|     message.success('图文消息条数限制在 1 条以内,已默认发送第一条') | ||||
|   } | ||||
| @@ -229,18 +106,18 @@ const sendMsg = async () => { | ||||
|   sendLoading.value = false | ||||
|  | ||||
|   list.value = [...list.value, ...[data]] | ||||
|   scrollToBottom() | ||||
|   await scrollToBottom() | ||||
|  | ||||
|   // 发送后清空数据 | ||||
|   replySelectRef.value?.clear() | ||||
| } | ||||
|  | ||||
| const loadingMore = () => { | ||||
| const loadMore = () => { | ||||
|   queryParams.pageNo++ | ||||
|   getPage(queryParams, null) | ||||
| } | ||||
|  | ||||
| const getPage = async (page, params) => { | ||||
| const getPage = async (page: any, params: any = null) => { | ||||
|   loading.value = true | ||||
|   let dataTemp = await getMessagePage( | ||||
|     Object.assign( | ||||
| @@ -254,62 +131,45 @@ const getPage = async (page, params) => { | ||||
|     ) | ||||
|   ) | ||||
|  | ||||
|   const msgDiv = document.getElementById('msg-div' + nowStr.value) | ||||
|   let scrollHeight = 0 | ||||
|   if (msgDiv) { | ||||
|     scrollHeight = msgDiv.scrollHeight | ||||
|   } | ||||
|   const scrollHeight = msgDivRef.value?.scrollHeight ?? 0 | ||||
|   // 处理数据 | ||||
|   const data = dataTemp.list.reverse() | ||||
|   list.value = [...data, ...list.value] | ||||
|   loading.value = false | ||||
|   if (data.length < queryParams.pageSize || data.length === 0) { | ||||
|     loadMore.value = false | ||||
|     hasMore.value = false | ||||
|   } | ||||
|   queryParams.pageNo = page.pageNo | ||||
|   queryParams.pageSize = page.pageSize | ||||
|   // 滚动到原来的位置 | ||||
|   if (queryParams.pageNo === 1) { | ||||
|     // 定位到消息底部 | ||||
|     scrollToBottom() | ||||
|     await scrollToBottom() | ||||
|   } else if (data.length !== 0) { | ||||
|     // 定位滚动条 | ||||
|     await nextTick(() => { | ||||
|       if (scrollHeight !== 0) { | ||||
|         let div = document.getElementById('msg-div' + nowStr.value) | ||||
|         if (div && msgDiv) { | ||||
|           msgDiv.scrollTop = div.scrollHeight - scrollHeight - 100 | ||||
|         } | ||||
|     await nextTick() | ||||
|     if (scrollHeight !== 0) { | ||||
|       if (msgDivRef.value) { | ||||
|         msgDivRef.value.scrollTop = msgDivRef.value.scrollHeight - scrollHeight - 100 | ||||
|       } | ||||
|     }) | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| const refreshChange = () => { | ||||
|   getPage(queryParams, null) | ||||
|   getPage(queryParams) | ||||
| } | ||||
|  | ||||
| /** 定位到消息底部 */ | ||||
| const scrollToBottom = () => { | ||||
|   nextTick(() => { | ||||
|     let div = document.getElementById('msg-div' + nowStr.value) | ||||
|     if (div) { | ||||
|       div.scrollTop = div.scrollHeight | ||||
|     } | ||||
|   }) | ||||
| const scrollToBottom = async () => { | ||||
|   await nextTick() | ||||
|   if (msgDivRef.value) { | ||||
|     msgDivRef.value.scrollTop = msgDivRef.value.scrollHeight | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| /* 因为 joolun 实现依赖 avue 组件,该页面使用了 comment.scss、card.scc  */ | ||||
| @import './comment.scss'; | ||||
| @import './card.scss'; | ||||
|  | ||||
| .msg-main { | ||||
|   margin-top: -30px; | ||||
|   padding: 10px; | ||||
| } | ||||
|  | ||||
| .msg-div { | ||||
|   height: 50vh; | ||||
|   overflow: auto; | ||||
| @@ -322,11 +182,6 @@ const scrollToBottom = () => { | ||||
|   padding: 10px; | ||||
| } | ||||
|  | ||||
| .avatar-div { | ||||
|   text-align: center; | ||||
|   width: 80px; | ||||
| } | ||||
|  | ||||
| .send-but { | ||||
|   float: right; | ||||
|   margin-top: 8px; | ||||
|   | ||||
| @@ -9,3 +9,9 @@ export enum MsgType { | ||||
|   Music = 'music', | ||||
|   News = 'news' | ||||
| } | ||||
|  | ||||
| export interface User { | ||||
|   nickname: string | ||||
|   avatar: string | ||||
|   accountId: number | ||||
| } | ||||
|   | ||||
| @@ -55,6 +55,6 @@ defineExpose({ | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| /* 因为 joolun 实现依赖 avue 组件,该页面使用了 card.scc  */ | ||||
| @import url('../wx-msg/card.scss'); | ||||
| /* 因为 joolun 实现依赖 avue 组件,该页面使用了 card.scss  */ | ||||
| @import '../wx-msg/card.scss'; | ||||
| </style> | ||||
|   | ||||
| @@ -51,7 +51,7 @@ | ||||
|       > | ||||
|         <WxMaterialSelect | ||||
|           type="image" | ||||
|           :account-id="accountId" | ||||
|           :account-id="accountId!" | ||||
|           @select-material="onMaterialSelected" | ||||
|         /> | ||||
|       </el-dialog> | ||||
| @@ -93,11 +93,11 @@ const showImageDialog = ref(false) | ||||
| const fileList = ref<UploadFiles>([]) | ||||
| interface UploadData { | ||||
|   type: UploadType | ||||
|   accountId: number | undefined | ||||
|   accountId: number | ||||
| } | ||||
| const uploadData: UploadData = reactive({ | ||||
|   type: UploadType.Image, | ||||
|   accountId: accountId | ||||
|   accountId: accountId! | ||||
| }) | ||||
|  | ||||
| /** 素材选择完成事件*/ | ||||
|   | ||||
| @@ -125,7 +125,7 @@ | ||||
|   </el-container> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| <script setup lang="ts" name="NewsForm"> | ||||
| import { Editor } from '@/components/Editor' | ||||
| import { createEditorConfig } from '../editor-config' | ||||
| import CoverSelect from './CoverSelect.vue' | ||||
|   | ||||
| @@ -76,30 +76,17 @@ import { | ||||
|  | ||||
| const message = useMessage() // 消息 | ||||
|  | ||||
| const accountId = ref<number>(0) | ||||
| const accountId = ref(-1) | ||||
| provide('accountId', accountId) | ||||
|  | ||||
| const loading = ref(true) // 列表的加载中 | ||||
| const list = ref<any[]>([]) // 列表的数据 | ||||
| const total = ref(0) // 列表的总页数 | ||||
| interface QueryParams { | ||||
|   pageNo: number | ||||
|   pageSize: number | ||||
|   accountId: number | ||||
| } | ||||
| const queryParams: QueryParams = reactive({ | ||||
|  | ||||
| const queryParams = reactive({ | ||||
|   pageNo: 1, | ||||
|   pageSize: 10, | ||||
|   accountId: 0 | ||||
| }) | ||||
|  | ||||
| interface UploadData { | ||||
|   type: 'image' | 'video' | 'audio' | ||||
|   accountId: number | ||||
| } | ||||
| const uploadData: UploadData = reactive({ | ||||
|   type: 'image', | ||||
|   accountId: 0 | ||||
|   accountId: accountId | ||||
| }) | ||||
|  | ||||
| // ========== 草稿新建 or 修改 ========== | ||||
| @@ -111,7 +98,8 @@ const isSubmitting = ref(false) | ||||
|  | ||||
| /** 侦听公众号变化 **/ | ||||
| const onAccountChanged = (id: number) => { | ||||
|   setAccountId(id) | ||||
|   accountId.value = id | ||||
|   queryParams.pageNo = 1 | ||||
|   getList() | ||||
| } | ||||
|  | ||||
| @@ -124,12 +112,6 @@ const onBeforeDialogClose = async (onDone: () => {}) => { | ||||
| } | ||||
|  | ||||
| // ======================== 列表查询 ======================== | ||||
| /** 设置账号编号 */ | ||||
| const setAccountId = (id: number) => { | ||||
|   queryParams.accountId = id | ||||
|   uploadData.accountId = id | ||||
| } | ||||
|  | ||||
| /** 查询列表 */ | ||||
| const getList = async () => { | ||||
|   loading.value = true | ||||
| @@ -170,10 +152,10 @@ const onSubmitNewsItem = async () => { | ||||
|   isSubmitting.value = true | ||||
|   try { | ||||
|     if (isCreating.value) { | ||||
|       await MpDraftApi.createDraft(queryParams.accountId, newsList.value) | ||||
|       await MpDraftApi.createDraft(accountId.value, newsList.value) | ||||
|       message.notifySuccess('新增成功') | ||||
|     } else { | ||||
|       await MpDraftApi.updateDraft(queryParams.accountId, mediaId.value, newsList.value) | ||||
|       await MpDraftApi.updateDraft(accountId.value, mediaId.value, newsList.value) | ||||
|       message.notifySuccess('更新成功') | ||||
|     } | ||||
|   } finally { | ||||
| @@ -185,7 +167,6 @@ const onSubmitNewsItem = async () => { | ||||
|  | ||||
| // ======================== 草稿箱发布 ======================== | ||||
| const onPublish = async (item: Article) => { | ||||
|   const accountId = queryParams.accountId | ||||
|   const mediaId = item.mediaId | ||||
|   const content = | ||||
|     '你正在通过发布的方式发表内容。 发布不占用群发次数,一天可多次发布。' + | ||||
| @@ -193,7 +174,7 @@ const onPublish = async (item: Article) => { | ||||
|     '发布后,你可以前往发表记录获取链接,也可以将发布内容添加到自定义菜单、自动回复、话题和页面模板中。' | ||||
|   try { | ||||
|     await message.confirm(content) | ||||
|     await MpFreePublishApi.submitFreePublish(accountId, mediaId) | ||||
|     await MpFreePublishApi.submitFreePublish(accountId.value, mediaId) | ||||
|     message.notifySuccess('发布成功') | ||||
|     await getList() | ||||
|   } catch {} | ||||
| @@ -201,11 +182,10 @@ const onPublish = async (item: Article) => { | ||||
|  | ||||
| /** 删除按钮操作 */ | ||||
| const onDelete = async (item: Article) => { | ||||
|   const accountId = queryParams.accountId | ||||
|   const mediaId = item.mediaId | ||||
|   try { | ||||
|     await message.confirm('此操作将永久删除该草稿, 是否继续?') | ||||
|     await MpDraftApi.deleteDraft(accountId, mediaId) | ||||
|     await MpDraftApi.deleteDraft(accountId.value, mediaId) | ||||
|     message.notifySuccess('删除成功') | ||||
|     await getList() | ||||
|   } catch {} | ||||
|   | ||||
| @@ -59,20 +59,16 @@ const loading = ref(true) // 列表的加载中 | ||||
| const total = ref(0) // 列表的总页数 | ||||
| const list = ref<any[]>([]) // 列表的数据 | ||||
|  | ||||
| interface QueryParams { | ||||
|   pageNo: number | ||||
|   pageSize: number | ||||
|   accountId: number | ||||
| } | ||||
| const queryParams: QueryParams = reactive({ | ||||
| const queryParams = reactive({ | ||||
|   pageNo: 1, | ||||
|   pageSize: 10, | ||||
|   accountId: 0 | ||||
|   accountId: -1 | ||||
| }) | ||||
|  | ||||
| /** 侦听公众号变化 **/ | ||||
| const onAccountChanged = (id: number) => { | ||||
|   queryParams.accountId = id | ||||
|   queryParams.pageNo = 1 | ||||
|   getList() | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -100,16 +100,10 @@ const loading = ref(false) // 遮罩层 | ||||
| const list = ref<any[]>([]) // 总条数 | ||||
| const total = ref(0) // 数据列表 | ||||
| // 查询参数 | ||||
| interface QueryParams { | ||||
|   pageNo: number | ||||
|   pageSize: number | ||||
|   accountId: number | ||||
|   permanent: boolean | ||||
| } | ||||
| const queryParams: QueryParams = reactive({ | ||||
| const queryParams = reactive({ | ||||
|   pageNo: 1, | ||||
|   pageSize: 10, | ||||
|   accountId: 0, | ||||
|   accountId: -1, | ||||
|   permanent: true | ||||
| }) | ||||
| const showCreateVideo = ref(false) // 是否新建视频的弹窗 | ||||
| @@ -117,6 +111,7 @@ const showCreateVideo = ref(false) // 是否新建视频的弹窗 | ||||
| /** 侦听公众号变化 **/ | ||||
| const onAccountChanged = (id: number) => { | ||||
|   queryParams.accountId = id | ||||
|   queryParams.pageNo = 1 | ||||
|   getList() | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
|     item-key="id" | ||||
|     ghost-class="draggable-ghost" | ||||
|     :animation="400" | ||||
|     @end="onDragEnd" | ||||
|     @end="onParentDragEnd" | ||||
|   > | ||||
|     <template #item="{ element: parent, index: x }"> | ||||
|       <div class="menu_bottom"> | ||||
| @@ -23,6 +23,7 @@ | ||||
|             item-key="id" | ||||
|             ghost-class="draggable-ghost" | ||||
|             :animation="400" | ||||
|             @end="onChildDragEnd" | ||||
|           > | ||||
|             <template #item="{ element: child, index: y }"> | ||||
|               <div class="subtitle menu_bottom"> | ||||
| @@ -118,42 +119,49 @@ const subMenuClicked = (child: Menu, x: number, y: number) => { | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * 处理一级菜单展开后被拖动 | ||||
|  * 处理一级菜单展开后被拖动,激活(展开)原来活动的一级菜单 | ||||
|  * | ||||
|  * @param oldIndex: 一级菜单拖动前的位置 | ||||
|  * @param newIndex: 一级菜单拖动后的位置 | ||||
|  */ | ||||
| const onDragEnd = ({ oldIndex, newIndex }) => { | ||||
| const onParentDragEnd = ({ oldIndex, newIndex }) => { | ||||
|   // 二级菜单没有展开,直接返回 | ||||
|   if (props.activeIndex === '__MENU_NOT_SELECTED__') { | ||||
|     return | ||||
|   } | ||||
|  | ||||
|   let newParent = props.parentIndex | ||||
|   if (props.parentIndex === oldIndex) { | ||||
|     newParent = newIndex | ||||
|   } else if (props.parentIndex === newIndex) { | ||||
|     newParent = oldIndex | ||||
|   } else { | ||||
|     // 如果展开的二级菜单下标`props.parentIndex`不是被移动的菜单的前后下标。 | ||||
|     // 那么使用一个辅助素组来模拟菜单移动,然后找到展开的二级菜单的新下标`newParent` | ||||
|     let positions = new Array<boolean>(menuList.value.length).fill(false) | ||||
|     positions[props.parentIndex] = true | ||||
|     positions.splice(oldIndex, 1) | ||||
|     positions.splice(newIndex, 0, true) | ||||
|     newParent = positions.indexOf(true) | ||||
|   } | ||||
|   // 使用一个辅助数组来模拟菜单移动,然后找到展开的二级菜单的新下标`newParent` | ||||
|   let positions = new Array<boolean>(menuList.value.length).fill(false) | ||||
|   positions[props.parentIndex] = true | ||||
|   const [out] = positions.splice(oldIndex, 1) // 移出菜单,保存到变量out | ||||
|   positions.splice(newIndex, 0, out) // 把out变量插入被移出的菜单 | ||||
|   const newParentIndex = positions.indexOf(true) | ||||
|  | ||||
|   // 找到菜单元素,触发一级菜单点击 | ||||
|   const parent = menuList.value[newParent] | ||||
|   emit('menu-clicked', parent, newParent) | ||||
|   const parent = menuList.value[newParentIndex] | ||||
|   emit('menu-clicked', parent, newParentIndex) | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * 处理二级菜单展开后被拖动,激活被拖动的菜单 | ||||
|  * | ||||
|  * @param newIndex 二级菜单拖动后的位置 | ||||
|  */ | ||||
| const onChildDragEnd = ({ newIndex }) => { | ||||
|   const x = props.parentIndex | ||||
|   const y = newIndex | ||||
|   const children = menuList.value[x]?.children | ||||
|   if (children && children?.length > 0) { | ||||
|     const child = children[y] | ||||
|     emit('submenu-clicked', child, x, y) | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .menu_bottom { | ||||
|   position: relative; | ||||
|   display: inline-block; | ||||
|   display: block; | ||||
|   float: left; | ||||
|   width: 85.5px; | ||||
|   text-align: center; | ||||
|   | ||||
| @@ -65,7 +65,7 @@ const MENU_NOT_SELECTED = '__MENU_NOT_SELECTED__' | ||||
|  | ||||
| // ======================== 列表查询 ======================== | ||||
| const loading = ref(false) // 遮罩层 | ||||
| const accountId = ref<number>(0) | ||||
| const accountId = ref(-1) | ||||
| const accountName = ref<string>('') | ||||
| const menuList = ref<Menu[]>([]) | ||||
|  | ||||
| @@ -339,7 +339,7 @@ div { | ||||
|  | ||||
|   .left { | ||||
|     position: relative; | ||||
|     display: inline-block; | ||||
|     display: block; | ||||
|     float: left; | ||||
|     width: 350px; | ||||
|     height: 715px; | ||||
|   | ||||
| @@ -93,20 +93,12 @@ const total = ref(0) // 数据的总页数 | ||||
| const list = ref<any[]>([]) // 当前页的列表数据 | ||||
|  | ||||
| // 搜索参数 | ||||
| interface QueryParams { | ||||
|   pageNo: number | ||||
|   pageSize: number | ||||
|   openid: string | undefined | ||||
|   accountId: number | ||||
|   type: MsgType | undefined | ||||
|   createTime: string[] | [] | ||||
| } | ||||
| const queryParams: QueryParams = reactive({ | ||||
| const queryParams = reactive({ | ||||
|   pageNo: 1, | ||||
|   pageSize: 10, | ||||
|   openid: undefined, | ||||
|   accountId: 0, | ||||
|   type: undefined, | ||||
|   openid: '', | ||||
|   accountId: -1, | ||||
|   type: MsgType.Text, | ||||
|   createTime: [] | ||||
| }) | ||||
| const queryFormRef = ref<FormInstance | null>(null) // 搜索的表单 | ||||
| @@ -120,6 +112,7 @@ const messageBox = reactive({ | ||||
| /** 侦听accountId */ | ||||
| const onAccountChanged = (id: number) => { | ||||
|   queryParams.accountId = id | ||||
|   queryParams.pageNo = 1 | ||||
|   handleQuery() | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -84,7 +84,7 @@ const dateRange = ref([ | ||||
|   beginOfDay(new Date(new Date().getTime() - 3600 * 1000 * 24 * 7)), | ||||
|   endOfDay(new Date(new Date().getTime() - 3600 * 1000 * 24)) | ||||
| ]) | ||||
| const accountId = ref() // 选中的公众号编号 | ||||
| const accountId = ref(-1) // 选中的公众号编号 | ||||
| const accountList = ref<MpAccountApi.AccountVO[]>([]) // 公众号账号列表 | ||||
|  | ||||
| const xAxisDate = ref([] as any[]) // X 轴的日期范围 | ||||
| @@ -232,7 +232,7 @@ const getAccountList = async () => { | ||||
|   accountList.value = await MpAccountApi.getSimpleAccountList() | ||||
|   // 默认选中第一个 | ||||
|   if (accountList.value.length > 0) { | ||||
|     accountId.value = accountList.value[0].id | ||||
|     accountId.value = accountList.value[0].id! | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -95,23 +95,18 @@ const loading = ref(true) // 列表的加载中 | ||||
| const total = ref(0) // 列表的总页数 | ||||
| const list = ref<any[]>([]) // 列表的数据 | ||||
|  | ||||
| interface QueryParams { | ||||
|   pageNo: number | ||||
|   pageSize: number | ||||
|   accountId: number | ||||
| } | ||||
| const queryParams: QueryParams = reactive({ | ||||
| const queryParams = reactive({ | ||||
|   pageNo: 1, | ||||
|   pageSize: 10, | ||||
|   accountId: 0 | ||||
|   accountId: -1 | ||||
| }) | ||||
|  | ||||
| const formRef = ref<InstanceType<typeof TagForm> | null>(null) | ||||
|  | ||||
| /** 侦听公众号变化 **/ | ||||
| const onAccountChanged = (id: number) => { | ||||
|   queryParams.pageNo = 1 | ||||
|   queryParams.accountId = id | ||||
|   queryParams.pageNo = 1 | ||||
|   getList() | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -113,27 +113,20 @@ const loading = ref(true) // 列表的加载中 | ||||
| const total = ref(0) // 列表的总页数 | ||||
| const list = ref<any[]>([]) // 列表的数据 | ||||
|  | ||||
| interface QueryParams { | ||||
|   pageNo: number | ||||
|   pageSize: number | ||||
|   accountId: number | ||||
|   openid: string | null | ||||
|   nickname: string | null | ||||
| } | ||||
| const queryParams: QueryParams = reactive({ | ||||
| const queryParams = reactive({ | ||||
|   pageNo: 1, | ||||
|   pageSize: 10, | ||||
|   accountId: 0, | ||||
|   openid: null, | ||||
|   nickname: null | ||||
|   accountId: -1, | ||||
|   openid: '', | ||||
|   nickname: '' | ||||
| }) | ||||
| const queryFormRef = ref<FormInstance | null>(null) // 搜索的表单 | ||||
| const tagList = ref<any[]>([]) // 公众号标签列表 | ||||
|  | ||||
| /** 侦听公众号变化 **/ | ||||
| const onAccountChanged = (id: number) => { | ||||
|   queryParams.pageNo = 1 | ||||
|   queryParams.accountId = id | ||||
|   queryParams.pageNo = 1 | ||||
|   getList() | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 puhui999
					puhui999