Explorar o código

!113 refactor: 抽离组件【公众号下拉选择】并重构相关页面
Merge pull request !113 from dhb52/dev

芋道源码 hai 1 ano
pai
achega
129766fca4

+ 15 - 0
.env.front

@@ -17,3 +17,18 @@ VITE_API_URL=/admin-api
 
 # 打包路径
 VITE_BASE_PATH=/
+
+# 项目本地运行端口号, 与.vscode/launch.json配合
+VITE_PORT=5173
+
+# 是否删除debugger
+VITE_DROP_DEBUGGER=false
+
+# 是否删除console.log
+VITE_DROP_CONSOLE=false
+
+# 是否sourcemap
+VITE_SOURCEMAP=true
+
+# 验证码的开关
+VITE_APP_CAPTCHA_ENABLE=false

+ 16 - 0
.vscode/launch.json

@@ -0,0 +1,16 @@
+{
+  // Use IntelliSense to learn about possible attributes.
+  // Hover to view descriptions of existing attributes.
+  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+  "version": "0.2.0",
+  "configurations": [
+    {
+      "type": "msedge",
+      "request": "launch",
+      "name": "Launch Edge against localhost",
+      "url": "http://localhost:5173",
+      "webRoot": "${workspaceFolder}/src",
+      "sourceMaps": true
+    }
+  ]
+}

+ 2 - 1
build/vite/index.ts

@@ -98,7 +98,8 @@ export function createVitePlugins() {
       deleteOriginFile: false //压缩后是否删除源文件
     }),
     ViteEjsPlugin(),
-    topLevelAwait({ // https://juejin.cn/post/7152191742513512485
+    topLevelAwait({
+      // https://juejin.cn/post/7152191742513512485
       // The export name of top-level await promise for each chunk module
       promiseExportName: '__tla',
       // The function to generate import names of top-level await promise in each chunk module

+ 1 - 1
package.json

@@ -127,7 +127,7 @@
     "vite-plugin-purge-icons": "^0.9.2",
     "vite-plugin-svg-icons": "^2.0.1",
     "vite-plugin-top-level-await": "^1.3.0",
-    "vite-plugin-vue-setup-extend": "^0.4.0",
+    "vite-plugin-vue-setup-extend-plus": "^0.1.0",
     "vite-plugin-windicss": "^1.8.10",
     "vue-tsc": "^1.2.0",
     "windicss": "^3.5.6"

+ 21 - 75
src/views/mp/autoReply/index.vue

@@ -3,28 +3,7 @@
 
   <!-- 搜索工作栏 -->
   <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="请选择公众号" class="!w-240px">
-          <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-form-item>
-    </el-form>
+    <WxAccountSelect @change="(accountId) => accountChanged(accountId)" />
   </ContentWrap>
 
   <!-- tab 切换 -->
@@ -181,20 +160,13 @@
   </ContentWrap>
 </template>
 <script setup name="MpAutoReply">
-import { ref, reactive, onMounted, nextTick } from 'vue'
 import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
 import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
 import WxMusic from '@/views/mp/components/wx-music/main.vue'
 import WxNews from '@/views/mp/components/wx-news/main.vue'
 import WxReplySelect from '@/views/mp/components/wx-reply/main.vue'
-import { getSimpleAccountList } from '@/api/mp/account'
-import {
-  createAutoReply,
-  deleteAutoReply,
-  getAutoReply,
-  getAutoReplyPage,
-  updateAutoReply
-} from '@/api/mp/autoReply'
+import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
+import * as MpAutoReplyApi from '@/api/mp/autoReply'
 
 import { DICT_TYPE, getDictOptions } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
@@ -202,7 +174,7 @@ import { ContentWrap } from '@/components/ContentWrap'
 
 const message = useMessage()
 
-const queryFormRef = ref()
+// const queryFormRef = ref()
 const formRef = ref()
 
 // tab 类型(1、关注时回复;2、消息回复;3、关键词回复)
@@ -240,43 +212,27 @@ const rules = {
   requestMatch: [{ required: true, message: '请求的关键字的匹配不能为空', trigger: 'blur' }]
 }
 
-const hackResetWxReplySelect = ref(false) // 重置 WxReplySelect 组件,解决无法清除的问题
+// 重置 WxReplySelect 组件,解决无法清除的问题
+const hackResetWxReplySelect = ref(false)
 
-// 公众号账号列表
-const accountList = ref([])
-
-onMounted(() => {
-  getSimpleAccountList().then((data) => {
-    accountList.value = data
-    // 默认选中第一个
-    if (accountList.value.length > 0) {
-      queryParams.accountId = accountList.value[0].id
-    }
-    // 加载数据
-    getList()
-  })
-})
+const accountChanged = (accountId) => {
+  queryParams.accountId = accountId
+  getList()
+}
 
 /** 查询列表 */
 const getList = async () => {
-  // 如果没有选中公众号账号,则进行提示。
-  if (!queryParams.accountId) {
-    message.error('未选中公众号,无法查询自动回复')
-    return false
-  }
-
   loading.value = false
-  // 处理查询参数
-  let params = {
-    ...queryParams,
-    type: type.value
-  }
-  // 执行查询
-  getAutoReplyPage(params).then((data) => {
+  try {
+    const data = await MpAutoReplyApi.getAutoReplyPage({
+      ...queryParams,
+      type: type.value
+    })
     list.value = data.list
     total.value = data.total
+  } finally {
     loading.value = false
-  })
+  }
 }
 
 /** 搜索按钮操作 */
@@ -285,16 +241,6 @@ const handleQuery = () => {
   getList()
 }
 
-/** 重置按钮操作 */
-const resetQuery = () => {
-  queryFormRef.value?.resetFields()
-  // 默认选中第一个
-  if (accountList.value.length > 0) {
-    queryParams.accountId = accountList.value[0].id
-  }
-  handleQuery()
-}
-
 const handleTabChange = (tabName) => {
   type.value = tabName
   handleQuery()
@@ -319,7 +265,7 @@ const handleUpdate = (row) => {
   resetEditor()
   console.log(row)
 
-  getAutoReply(row.id).then((data) => {
+  MpAutoReplyApi.getAutoReply(row.id).then((data) => {
     // 设置属性
     form.value = { ...data }
     delete form.value['responseMessageType']
@@ -370,13 +316,13 @@ const handleSubmit = () => {
     form.responseHqMusicUrl = objData.value.hqMusicUrl
 
     if (form.value.id !== undefined) {
-      updateAutoReply(form).then(() => {
+      MpAutoReplyApi.updateAutoReply(form).then(() => {
         message.success('修改成功')
         open.value = false
         getList()
       })
     } else {
-      createAutoReply(form).then(() => {
+      MpAutoReplyApi.createAutoReply(form).then(() => {
         message.success('新增成功')
         open.value = false
         getList()
@@ -414,7 +360,7 @@ const resetEditor = () => {
 
 const handleDelete = async (row) => {
   await message.confirm('是否确认删除此数据?')
-  await deleteAutoReply(row.id)
+  await MpAutoReplyApi.deleteAutoReply(row.id)
   await getList()
   message.success('删除成功')
 }

+ 39 - 0
src/views/mp/components/WxMpSelect.vue

@@ -0,0 +1,39 @@
+<template>
+  <el-select
+    v-model="accountId"
+    placeholder="请选择公众号"
+    class="!w-240px"
+    @change="accountChanged"
+  >
+    <el-option v-for="item in accountList" :key="item.id" :label="item.name" :value="item.id" />
+  </el-select>
+</template>
+
+<script lang="ts" setup name="WxMpSelect">
+import * as MpAccountApi from '@/api/mp/account'
+
+const accountId: Ref<number | undefined> = ref()
+const accountList: Ref<MpAccountApi.AccountVO[]> = ref([])
+
+const emit = defineEmits<{
+  (e: 'change', id: number | undefined): void
+}>()
+
+onMounted(async () => {
+  handleQuery()
+})
+
+const handleQuery = async () => {
+  const data = await MpAccountApi.getSimpleAccountList()
+  accountList.value = data
+  // 默认选中第一个
+  if (accountList.value.length > 0) {
+    accountId.value = accountList.value[0].id
+    emit('change', accountId.value)
+  }
+}
+
+const accountChanged = () => {
+  emit('change', accountId.value)
+}
+</script>

+ 44 - 0
src/views/mp/components/wx-account-select/main.vue

@@ -0,0 +1,44 @@
+<template>
+  <el-form class="-mb-15px" ref="queryFormRef" :inline="true" label-width="68px">
+    <el-form-item label="公众号" prop="accountId">
+      <el-select
+        v-model="accountId"
+        placeholder="请选择公众号"
+        class="!w-240px"
+        @change="accountChanged()"
+      >
+        <el-option v-for="item in accountList" :key="item.id" :label="item.name" :value="item.id" />
+      </el-select>
+    </el-form-item>
+    <el-form-item>
+      <slot name="actions"></slot>
+    </el-form-item>
+  </el-form>
+</template>
+
+<script setup name="WxAccountSelect">
+import * as MpAccountApi from '@/api/mp/account'
+const accountId = ref()
+const accountList = ref([])
+const queryFormRef = ref()
+
+const emit = defineEmits(['change'])
+
+onMounted(async () => {
+  handleQuery()
+})
+
+const handleQuery = async () => {
+  const data = await MpAccountApi.getSimpleAccountList()
+  accountList.value = data
+  // 默认选中第一个
+  if (accountList.value.length > 0) {
+    accountId.value = accountList.value[0].id
+    emit('change', accountId.value)
+  }
+}
+
+const accountChanged = () => {
+  emit('change', accountId.value)
+}
+</script>

+ 91 - 109
src/views/mp/components/wx-material-select/main.vue

@@ -14,7 +14,8 @@
           <p class="item-name">{{ item.name }}</p>
           <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>
@@ -48,7 +49,8 @@
         <el-table-column label="操作" align="center" fixed="right">
           <template #default="scope">
             <el-button type="primary" link @click="selectMaterialFun(scope.row)"
-              >选择<Icon icon="ep:plus" />
+              >选择
+              <Icon icon="ep:plus" />
             </el-button>
           </template>
         </el-table-column>
@@ -89,7 +91,8 @@
         >
           <template #default="scope">
             <el-button type="primary" link @click="selectMaterialFun(scope.row)"
-              >选择<Icon icon="akar-icons:circle-plus" />
+              >选择
+              <Icon icon="akar-icons:circle-plus" />
             </el-button>
           </template>
         </el-table-column>
@@ -110,7 +113,8 @@
             <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>
@@ -127,125 +131,101 @@
   </div>
 </template>
 
-<script lang="ts" name="WxMaterialSelect">
+<script lang="ts" setup name="WxMaterialSelect">
 import WxNews from '@/views/mp/components/wx-news/main.vue'
 import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
 import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
-import { getMaterialPage } from '@/api/mp/material'
-import { getFreePublishPage } from '@/api/mp/freePublish'
-import { getDraftPage } from '@/api/mp/draft'
+import * as MpMaterialApi from '@/api/mp/material'
+import * as MpFreePublishApi from '@/api/mp/freePublish'
+import * as MpDraftApi from '@/api/mp/draft'
 import { dateFormatter } from '@/utils/formatTime'
-import { defineComponent, PropType } from 'vue'
 
-export default defineComponent({
-  components: {
-    WxNews,
-    WxVoicePlayer,
-    WxVideoPlayer
+const props = defineProps({
+  objData: {
+    type: Object, // type - 类型;accountId - 公众号账号编号
+    required: true
   },
-  props: {
-    objData: {
-      type: Object, // type - 类型;accountId - 公众号账号编号
-      required: true
-    },
-    newsType: {
-      // 图文类型:1、已发布图文;2、草稿箱图文
-      type: String as PropType<string>,
-      default: '1'
-    }
-  },
-  setup(props, ctx) {
-    // 遮罩层
-    const loading = ref(false)
-    // 总条数
-    const total = ref(0)
-    // 数据列表
-    const list = ref([])
-    // 查询参数
-    const queryParams = reactive({
-      pageNo: 1,
-      pageSize: 10,
-      accountId: props.objData.accountId
-    })
-    const objDataRef = reactive(props.objData)
-    const newsTypeRef = ref(props.newsType)
+  newsType: {
+    // 图文类型:1、已发布图文;2、草稿箱图文
+    type: String as PropType<string>,
+    default: '1'
+  }
+})
 
-    const selectMaterialFun = (item) => {
-      ctx.emit('select-material', item)
-    }
-    /** 搜索按钮操作 */
-    const handleQuery = () => {
-      queryParams.pageNo = 1
-      getPage()
-    }
-    const getPage = () => {
-      loading.value = true
-      if (objDataRef.type === 'news' && newsTypeRef.value === '1') {
-        // 【图文】+ 【已发布】
-        getFreePublishPageFun()
-      } else if (objDataRef.type === 'news' && newsTypeRef.value === '2') {
-        // 【图文】+ 【草稿】
-        getDraftPageFun()
-      } else {
-        // 【素材】
-        getMaterialPageFun()
-      }
-    }
+const emit = defineEmits(['select-material'])
 
-    const getMaterialPageFun = async () => {
-      let data = await getMaterialPage({
-        ...queryParams,
-        type: objDataRef.type
-      })
-      list.value = data.list
-      total.value = data.total
-      loading.value = false
-    }
+// 遮罩层
+const loading = ref(false)
+// 总条数
+const total = ref(0)
+// 数据列表
+const list = ref([])
+// 查询参数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  accountId: props.objData.accountId
+})
+const objDataRef = reactive(props.objData)
+const newsTypeRef = ref(props.newsType)
 
-    const getFreePublishPageFun = async () => {
-      let data = await getFreePublishPage(queryParams)
-      data.list.forEach((item) => {
-        const newsItem = item.content.newsItem
-        newsItem.forEach((article) => {
-          article.picUrl = article.thumbUrl
-        })
-      })
-      list.value = data.list
-      total.value = data.total
-      loading.value = false
-    }
+const selectMaterialFun = (item) => {
+  emit('select-material', item)
+}
 
-    const getDraftPageFun = async () => {
-      let data = await getDraftPage(queryParams)
-      data.list.forEach((item) => {
-        const newsItem = item.content.newsItem
-        newsItem.forEach((article) => {
-          article.picUrl = article.thumbUrl
-        })
-      })
-      list.value = data.list
-      total.value = data.total
-      loading.value = false
+const getPage = async () => {
+  loading.value = true
+  try {
+    if (objDataRef.type === 'news' && newsTypeRef.value === '1') {
+      // 【图文】+ 【已发布】
+      await getFreePublishPageFun()
+    } else if (objDataRef.type === 'news' && newsTypeRef.value === '2') {
+      // 【图文】+ 【草稿】
+      await getDraftPageFun()
+    } else {
+      // 【素材】
+      await getMaterialPageFun()
     }
+  } finally {
+    loading.value = false
+  }
+}
+
+const getMaterialPageFun = async () => {
+  const data = await MpMaterialApi.getMaterialPage({
+    ...queryParams,
+    type: objDataRef.type
+  })
+  list.value = data.list
+  total.value = data.total
+}
 
-    onMounted(async () => {
-      getPage()
+const getFreePublishPageFun = async () => {
+  const data = await MpFreePublishApi.getFreePublishPage(queryParams)
+  data.list.forEach((item) => {
+    const newsItem = item.content.newsItem
+    newsItem.forEach((article) => {
+      article.picUrl = article.thumbUrl
     })
+  })
+  list.value = data.list
+  total.value = data.total
+}
 
-    return {
-      handleQuery,
-      dateFormatter,
-      selectMaterialFun,
-      getMaterialPageFun,
-      getPage,
-      formatDate,
-      queryParams,
-      objDataRef,
-      list,
-      total,
-      loading
-    }
-  }
+const getDraftPageFun = async () => {
+  const data = await MpDraftApi.getDraftPage(queryParams)
+  data.list.forEach((item) => {
+    const newsItem = item.content.newsItem
+    newsItem.forEach((article) => {
+      article.picUrl = article.thumbUrl
+    })
+  })
+  list.value = data.list
+  total.value = data.total
+}
+
+onMounted(async () => {
+  getPage()
 })
 </script>
 <style lang="scss" scoped>
@@ -276,6 +256,7 @@ p {
   .waterfall {
     column-count: 3;
   }
+
   p {
     color: red;
   }
@@ -285,6 +266,7 @@ p {
   .waterfall {
     column-count: 2;
   }
+
   p {
     color: orange;
   }

+ 14 - 58
src/views/mp/draft/index.vue

@@ -3,31 +3,13 @@
 
   <!-- 搜索工作栏 -->
   <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>
+    <WxAccountSelect @change="(accountId) => accountChanged(accountId)">
+      <template #actions>
         <el-button type="primary" plain @click="handleAdd" v-hasPermi="['mp:draft:create']">
           <Icon icon="ep:plus" />新增
         </el-button>
-      </el-form-item>
-    </el-form>
+      </template>
+    </WxAccountSelect>
   </ContentWrap>
 
   <!-- 列表 -->
@@ -35,7 +17,7 @@
     <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" />
+          <WxNews :articles="item.content.newsItem" />
           <!-- 操作按钮 -->
           <el-row class="ope-row">
             <el-button
@@ -239,7 +221,7 @@
           </div>
           <!--富文本编辑器组件-->
           <el-row>
-            <wx-editor
+            <WxEditor
               v-model="articlesAdd[isActiveAddNews].content"
               :account-id="uploadData.accountId"
               v-if="hackResetEditor"
@@ -258,14 +240,15 @@
 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 WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
 import { getAccessToken } from '@/utils/auth'
-import * as MpAccountApi from '@/api/mp/account'
 import * as MpDraftApi from '@/api/mp/draft'
 import * as MpFreePublishApi from '@/api/mp/freePublish'
-const message = useMessage() // 消息
 // 可以用改本地数据模拟,避免API调用超限
 // import drafts from './mock'
 
+const message = useMessage() // 消息
+
 const loading = ref(true) // 列表的加载中
 const total = ref(0) // 列表的总页数
 const list = ref([]) // 列表的数据
@@ -274,8 +257,6 @@ const queryParams = reactive({
   pageSize: 10,
   accountId: undefined
 })
-const queryFormRef = ref() // 搜索的表单
-const accountList = ref([]) // 公众号账号列表
 
 // ========== 文件上传 ==========
 const materialSelectRef = ref()
@@ -298,16 +279,11 @@ const operateMaterial = ref('add')
 const articlesMediaId = ref('')
 const hackResetEditor = ref(false)
 
-/** 初始化 **/
-onMounted(async () => {
-  accountList.value = await MpAccountApi.getSimpleAccountList()
-  // 选中第一个
-  if (accountList.value.length > 0) {
-    // @ts-ignore
-    queryParams.accountId = accountList.value[0].id
-  }
-  await getList()
-})
+/** 侦听公众号变化 **/
+const accountChanged = (accountId) => {
+  setAccountId(accountId)
+  getList()
+}
 
 // ======================== 列表查询 ========================
 /** 设置账号编号 */
@@ -341,26 +317,6 @@ const getList = async () => {
   }
 }
 
-/** 搜索按钮操作 */
-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 = () => {

+ 12 - 57
src/views/mp/freePublish/index.vue

@@ -3,28 +3,7 @@
 
   <!-- 搜索工作栏 -->
   <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="请选择公众号" class="!w-240px">
-          <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" class="mr-5px" /> 搜索</el-button>
-        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
-      </el-form-item>
-    </el-form>
+    <WxAccountSelect @change="(accountId) => accountChanged(accountId)" />
   </ContentWrap>
 
   <!-- 列表 -->
@@ -59,10 +38,11 @@
   </ContentWrap>
 </template>
 
-<script setup lang="ts" name="MpFreePublish">
+<script setup name="MpFreePublish">
 import * as FreePublishApi from '@/api/mp/freePublish'
-import * as MpAccountApi from '@/api/mp/account'
 import WxNews from '@/views/mp/components/wx-news/main.vue'
+import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
+
 const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
 
@@ -72,18 +52,17 @@ const list = ref([]) // 列表的数据
 const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
-  accountId: undefined // 当前页数
+  accountId: undefined
 })
-const queryFormRef = ref() // 搜索的表单
-const accountList = ref<MpAccountApi.AccountVO[]>([]) // 公众号账号列表
+
+/** 侦听公众号变化 **/
+const accountChanged = (accountId) => {
+  queryParams.accountId = accountId
+  getList()
+}
 
 /** 查询列表 */
 const getList = async () => {
-  // 如果没有选中公众号账号,则进行提示。
-  if (!queryParams.accountId) {
-    message.error('未选中公众号,无法查询已发表图文')
-    return false
-  }
   try {
     loading.value = true
     const data = await FreePublishApi.getFreePublishPage(queryParams)
@@ -94,22 +73,6 @@ const getList = async () => {
   }
 }
 
-/** 搜索按钮操作 */
-const handleQuery = () => {
-  queryParams.pageNo = 1
-  getList()
-}
-
-/** 重置按钮操作 */
-const resetQuery = () => {
-  queryFormRef.value.resetFields()
-  // 默认选中第一个
-  if (accountList.value.length > 0) {
-    queryParams.accountId = accountList.value[0].id
-  }
-  handleQuery()
-}
-
 /** 删除按钮操作 */
 const handleDelete = async (item) => {
   try {
@@ -122,16 +85,8 @@ const handleDelete = async (item) => {
     await getList()
   } catch {}
 }
-
-onMounted(async () => {
-  accountList.value = await MpAccountApi.getSimpleAccountList()
-  // 选中第一个
-  if (accountList.value.length > 0) {
-    queryParams.accountId = accountList.value[0].id
-  }
-  await getList()
-})
 </script>
+
 <style lang="scss" scoped>
 .ope-row {
   margin-top: 5px;

+ 126 - 128
src/views/mp/material/index.vue

@@ -2,26 +2,12 @@
   <doc-alert title="公众号素材" url="https://doc.iocoder.cn/mp/material/" />
   <!-- 搜索工作栏 -->
   <ContentWrap>
-    <el-form
-      class="-mb-15px"
-      :model="queryParams"
-      ref="queryFormRef"
-      :inline="true"
-      label-width="68px"
-    >
+    <el-form class="-mb-15px" :inline="true" label-width="68px">
       <el-form-item label="公众号" prop="accountId">
-        <el-select v-model="queryParams.accountId" placeholder="请选择公众号" class="!w-240px">
-          <el-option
-            v-for="item in accountList"
-            :key="item.id"
-            :label="item.name"
-            :value="item.id"
-          />
-        </el-select>
+        <WxMpSelect @change="(accountId) => accountChange(accountId)" />
       </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>
+        <slot name="actions"></slot>
       </el-form-item>
     </el-form>
   </ContentWrap>
@@ -31,11 +17,11 @@
       <!-- tab 1:图片  -->
       <el-tab-pane name="image">
         <template #label>
-          <span><Icon icon="ep:picture" />图片</span>
+          <span> <Icon icon="ep:picture" />图片 </span>
         </template>
         <div class="add_but" v-hasPermi="['mp:material:upload-permanent']">
           <el-upload
-            :action="actionUrl"
+            :action="uploadUrl"
             :headers="headers"
             multiple
             :limit="1"
@@ -58,7 +44,7 @@
               <img class="material-img" :src="item.url" />
               <div class="item-name">{{ item.name }}</div>
             </a>
-            <el-row class="ope-row" justify="center">
+            <el-row justify="center">
               <el-button
                 type="danger"
                 circle
@@ -82,11 +68,11 @@
       <!-- tab 2:语音  -->
       <el-tab-pane name="voice">
         <template #label>
-          <span><Icon icon="ep:microphone" />语音</span>
+          <span> <Icon icon="ep:microphone" />语音 </span>
         </template>
         <div class="add_but" v-hasPermi="['mp:material:upload-permanent']">
           <el-upload
-            :action="actionUrl"
+            :action="uploadUrl"
             :headers="headers"
             multiple
             :limit="1"
@@ -103,6 +89,8 @@
             </template>
           </el-upload>
         </div>
+
+        <!-- 列表 -->
         <el-table :data="list" stripe border v-loading="loading" style="margin-top: 10px">
           <el-table-column label="编号" align="center" prop="mediaId" />
           <el-table-column label="文件名" align="center" prop="name" />
@@ -111,9 +99,15 @@
               <WxVoicePlayer :url="scope.row.url" />
             </template>
           </el-table-column>
-          <el-table-column label="上传时间" align="center" prop="createTime" width="180">
+          <el-table-column
+            label="上传时间"
+            align="center"
+            prop="createTime"
+            :formatter="dateFormatter"
+            width="180"
+          >
             <template #default="scope">
-              <span>{{ formatDate(scope.row.createTime) }}</span>
+              <span>{{ scope.row.createTime }}</span>
             </template>
           </el-table-column>
           <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
@@ -145,7 +139,7 @@
       <!-- tab 3:视频 -->
       <el-tab-pane name="video">
         <template #label>
-          <span><Icon icon="ep:video-play" /> 视频</span>
+          <span> <Icon icon="ep:video-play" /> 视频 </span>
         </template>
         <div class="add_but" v-hasPermi="['mp:material:upload-permanent']">
           <el-button type="primary" plain @click="handleAddVideo">新建视频</el-button>
@@ -158,7 +152,7 @@
           v-loading="addMaterialLoading"
         >
           <el-upload
-            :action="actionUrl"
+            :action="uploadUrl"
             :headers="headers"
             multiple
             :limit="1"
@@ -197,11 +191,14 @@
             </el-row>
           </el-form>
           <template #footer>
+            <!-- <span class="dialog-footer"> -->
             <el-button @click="cancelVideo">取 消</el-button>
             <el-button type="primary" @click="submitVideo">提 交</el-button>
+            <!-- </span> -->
           </template>
         </el-dialog>
 
+        <!-- 列表 -->
         <el-table :data="list" stripe border v-loading="loading" style="margin-top: 10px">
           <el-table-column label="编号" align="center" prop="mediaId" />
           <el-table-column label="文件名" align="center" prop="name" />
@@ -212,16 +209,22 @@
               <WxVideoPlayer :url="scope.row.url" />
             </template>
           </el-table-column>
-          <el-table-column label="上传时间" align="center" prop="createTime" width="180">
+          <el-table-column
+            label="上传时间"
+            align="center"
+            :formatter="dateFormatter"
+            prop="createTime"
+            width="180"
+          >
             <template #default="scope">
-              <span>{{ formatDate(scope.row.createTime) }}</span>
+              <span>{{ scope.row.createTime }}</span>
             </template>
           </el-table-column>
           <el-table-column label="操作" align="center" fixed="right">
             <template #default="scope">
-              <el-button type="primary" link plain @click="handleDownload(scope.row)"
-                ><Icon icon="ep:download" />下载</el-button
-              >
+              <el-button type="primary" link plain @click="handleDownload(scope.row)">
+                <Icon icon="ep:download" />下载
+              </el-button>
               <el-button
                 type="primary"
                 link
@@ -246,23 +249,41 @@
     </el-tabs>
   </ContentWrap>
 </template>
-<script setup name="MpMaterial">
+
+<script lang="ts" setup name="MpMaterial">
 import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
 import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
-import { getSimpleAccountList } from '@/api/mp/account'
-import { getMaterialPage, deletePermanentMaterial } from '@/api/mp/material'
-import { getAccessToken } from '@/utils/auth'
-import { formatDate } from '@/utils/formatTime'
+import WxMpSelect from '@/views/mp/components/WxMpSelect.vue'
+import * as MpMaterialApi from '@/api/mp/material'
+import * as authUtil from '@/utils/auth'
+import { dateFormatter } from '@/utils/formatTime'
+import type {
+  FormInstance,
+  FormRules,
+  TabPaneName,
+  UploadInstance,
+  UploadProps,
+  UploadRawFile,
+  UploadUserFile
+} from 'element-plus'
 
 const BASE_URL = import.meta.env.VITE_BASE_URL
+const uploadUrl = BASE_URL + '/admin-api/mp/material/upload-permanent'
+const headers = { Authorization: 'Bearer ' + authUtil.getAccessToken() }
 
 const message = useMessage()
 
-const queryFormRef = ref()
-const uploadFormRef = ref()
-const uploadVideoRef = ref()
+const uploadFormRef = ref<FormInstance>()
+const uploadVideoRef = ref<UploadInstance>()
+
+const uploadRules: FormRules = {
+  title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
+  introduction: [{ required: true, message: '请输入描述', trigger: 'blur' }]
+}
 
-const type = ref('image')
+// 素材类型
+type MatertialType = 'image' | 'voice' | 'video'
+const type = ref<MatertialType>('image')
 // 遮罩层
 const loading = ref(false)
 // 总条数
@@ -270,17 +291,27 @@ const total = ref(0)
 // 数据列表
 const list = ref([])
 // 查询参数
-const queryParams = reactive({
+interface QueryParams {
+  pageNo: number
+  pageSize: number
+  accountId?: number
+  permanent: boolean
+}
+const queryParams: QueryParams = reactive({
   pageNo: 1,
   pageSize: 10,
   accountId: undefined,
   permanent: true
 })
 
-const actionUrl = BASE_URL + '/admin-api/mp/material/upload-permanent'
-const headers = { Authorization: 'Bearer ' + getAccessToken() }
-const fileList = ref([])
-const uploadData = reactive({
+const fileList = ref<UploadUserFile[]>([])
+
+interface UploadData {
+  type: MatertialType
+  title: string
+  introduction: string
+}
+const uploadData: UploadData = reactive({
   type: 'image',
   title: '',
   introduction: ''
@@ -289,96 +320,57 @@ const uploadData = reactive({
 // === 视频上传,独有变量 ===
 const dialogVideoVisible = ref(false)
 const addMaterialLoading = ref(false)
-const uploadRules = reactive({
-  // 视频上传的校验规则
-  title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
-  introduction: [{ required: true, message: '请输入描述', trigger: 'blur' }]
-})
-
-// 公众号账号列表
-const accountList = ref([])
 
-onMounted(() => {
-  getSimpleAccountList().then((data) => {
-    accountList.value = data
-    // 默认选中第一个
-    if (accountList.value.length > 0) {
-      setAccountId(accountList.value[0].id)
-    }
-    // 加载数据
-    getList()
-  })
-})
-
-// ======================== 列表查询 ========================
-/** 设置账号编号 */
-const setAccountId = (accountId) => {
+/** 侦听公众号变化 **/
+const accountChange = (accountId: number | undefined) => {
   queryParams.accountId = accountId
-  uploadData.accountId = accountId
+  getList()
 }
 
+// ======================== 列表查询 ========================
 /** 查询列表 */
-const getList = () => {
-  // 如果没有选中公众号账号,则进行提示。
-  if (!queryParams.accountId) {
-    message.error('未选中公众号,无法查询草稿箱')
-    return false
-  }
-
+const getList = async () => {
   loading.value = true
-  getMaterialPage({
-    ...queryParams,
-    type: type.value
-  })
-    .then((data) => {
-      list.value = data.list
-      total.value = data.total
-    })
-    .finally(() => {
-      loading.value = false
+  try {
+    const data = await MpMaterialApi.getMaterialPage({
+      ...queryParams,
+      type: type.value
     })
+    list.value = data.list
+    total.value = data.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 handleTabChange = (tabName) => {
+const handleTabChange = (tabName: TabPaneName) => {
   // 设置 type
-  uploadData.type = tabName
+  uploadData.type = tabName as MatertialType
+
+  // 提前情况数据,避免tab切换后显示垃圾数据
+  list.value = []
+  total.value = 0
+
   // 从第一页开始查询
   handleQuery()
 }
 
 // ======================== 文件上传 ========================
-const beforeImageUpload = (file) => {
-  const isType =
-    file.type === 'image/jpeg' ||
-    file.type === 'image/png' ||
-    file.type === 'image/gif' ||
-    file.type === 'image/bmp' ||
-    file.type === 'image/jpg'
+const beforeImageUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => {
+  const isType = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/jpg'].includes(
+    rawFile.type
+  )
   if (!isType) {
     message.error('上传图片格式不对!')
     return false
   }
-  const isLt = file.size / 1024 / 1024 < 2
+  const isLt = rawFile.size / 1024 / 1024 < 2
   if (!isLt) {
     message.error('上传图片大小不能超过 2M!')
     return false
@@ -387,13 +379,9 @@ const beforeImageUpload = (file) => {
   return true
 }
 
-const beforeVoiceUpload = (file) => {
-  const isType =
-    file.type === 'audio/mp3' ||
-    file.type === 'audio/wma' ||
-    file.type === 'audio/wav' ||
-    file.type === 'audio/amr'
-  const isLt = file.size / 1024 / 1024 < 2
+const beforeVoiceUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => {
+  const isType = ['audio/mp3', 'audio/wma', 'audio/wav', 'audio/amr'].includes(file.type)
+  const isLt = rawFile.size / 1024 / 1024 < 2
   if (!isType) {
     message.error('上传语音格式不对!')
     return false
@@ -406,22 +394,24 @@ const beforeVoiceUpload = (file) => {
   return true
 }
 
-const beforeVideoUpload = (file) => {
-  const isType = file.type === 'video/mp4'
+const beforeVideoUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => {
+  const isType = rawFile.type === 'video/mp4'
   if (!isType) {
     message.error('上传视频格式不对!')
     return false
   }
-  const isLt = file.size / 1024 / 1024 < 10
+
+  const isLt = rawFile.size / 1024 / 1024 < 10
   if (!isLt) {
     message.error('上传视频大小不能超过 10M!')
     return false
   }
+
   addMaterialLoading.value = true
   return true
 }
 
-const handleUploadSuccess = (response, file, fileList) => {
+const handleUploadSuccess: UploadProps['onSuccess'] = (response: any) => {
   loading.value = false
   addMaterialLoading.value = false
   if (response.code !== 0) {
@@ -440,17 +430,17 @@ const handleUploadSuccess = (response, file, fileList) => {
 }
 
 // 下载文件
-const handleDownload = (row) => {
+const handleDownload = (row: any) => {
   window.open(row.url, '_blank')
 }
 
 // 提交 video 新建的表单
 const submitVideo = () => {
-  uploadFormRef.value.validate((valid) => {
+  uploadFormRef.value?.validate((valid) => {
     if (!valid) {
       return false
     }
-    uploadVideoRef.value.submit()
+    uploadVideoRef.value?.submit()
   })
 }
 
@@ -474,9 +464,9 @@ const resetVideo = () => {
 }
 
 // ======================== 其它操作 ========================
-const handleDelete = async (item) => {
+const handleDelete = async (item: any) => {
   await message.confirm('此操作将永久删除该文件, 是否继续?')
-  await deletePermanentMaterial(item.id)
+  await MpMaterialApi.deletePermanentMaterial(item.id)
   message.alertSuccess('删除成功')
 }
 </script>
@@ -487,40 +477,48 @@ const handleDelete = async (item) => {
   width: 100%;
   column-gap: 10px;
   column-count: 5;
-  margin-top: 10px; /* 芋道源码:增加 10px,避免顶着上面 */
+  margin-top: 10px;
+  /* 芋道源码:增加 10px,避免顶着上面 */
 }
+
 .waterfall-item {
   padding: 10px;
   margin-bottom: 10px;
   break-inside: avoid;
   border: 1px solid #eaeaea;
 }
+
 .material-img {
   width: 100%;
 }
+
 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;
   }
 }
-/*瀑布流样式*/
 </style>

+ 19 - 61
src/views/mp/menu/index.vue

@@ -2,22 +2,7 @@
   <doc-alert title="公众号菜单" url="https://doc.iocoder.cn/mp/menu/" />
   <!-- 搜索工作栏 -->
   <ContentWrap>
-    <el-form class="-mb-15px" ref="queryFormRef" :inline="true" label-width="68px">
-      <el-form-item label="公众号" prop="accountId">
-        <el-select v-model="accountId" placeholder="请选择公众号" class="!w-240px">
-          <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-form-item>
-    </el-form>
+    <WxAccountSelect @change="(accountId) => accountChanged(accountId)" />
   </ContentWrap>
 
   <!-- 列表 -->
@@ -204,17 +189,15 @@ import { handleTree } from '@/utils/tree'
 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 * as MpAccountApi from '@/api/mp/account'
+import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
+import * as MpMenuApi from '@/api/mp/menu'
 import menuOptions from './menuOptions'
 const message = useMessage() // 消息
 
 // ======================== 列表查询 ========================
 const loading = ref(true) // 遮罩层
 const accountId = ref(undefined) // 公众号Id
-const name = ref('') // 公众号名
 const menuList = ref({ children: [] })
-const accountList = ref([]) // 公众号账号列表
 
 // ======================== 菜单操作 ========================
 const isActive = ref(-1) // 一级菜单点中样式
@@ -228,60 +211,34 @@ const showConfigureContent = ref(true) // 是否展示配置内容;如果有
 const hackResetWxReplySelect = ref(false) // 重置 WxReplySelect 组件
 const tempObj = ref({}) // 右边临时变量,作为中间值牵引关系
 
-const tempSelfObj = ref({
-  // 一些临时值放在这里进行判断,如果放在 tempObj,由于引用关系,menu 也会多了多余的参数
-})
+// 一些临时值放在这里进行判断,如果放在 tempObj,由于引用关系,menu 也会多了多余的参数
+const tempSelfObj = ref({})
 const dialogNewsVisible = ref(false) // 跳转图文时的素材选择弹窗
 
-onMounted(async () => {
-  accountList.value = await MpAccountApi.getSimpleAccountList()
-  // 选中第一个
-  if (accountList.value.length > 0) {
-    // @ts-ignore
-    setAccountId(accountList.value[0].id)
-  }
-  await getList()
-})
-
-// ======================== 列表查询 ========================
-/** 设置账号编号 */
-const setAccountId = (id) => {
+/** 侦听公众号变化 **/
+const accountChanged = (id) => {
   accountId.value = id
-  name.value = accountList.value.find((item) => item.id === accountId.value)?.name
+  getList()
 }
 
+/** 查询并转换菜单 **/
 const getList = async () => {
   loading.value = false
-  getMenuList(accountId.value)
-    .then((response) => {
-      const menuData = convertMenuList(response)
-      menuList.value = handleTree(menuData, 'id')
-    })
-    .finally(() => {
-      loading.value = false
-    })
+  try {
+    const data = await MpMenuApi.getMenuList(accountId.value)
+    const menuData = convertMenuList(data)
+    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 []
@@ -443,7 +400,7 @@ const handleSave = async () => {
   try {
     await message.confirm('确定要删除吗?')
     loading.value = true
-    await saveMenu(accountId.value, convertMenuFormList())
+    await MpMenuApi.saveMenu(accountId.value, convertMenuFormList())
     getList()
     message.notifySuccess('发布成功')
   } finally {
@@ -464,7 +421,7 @@ const handleDelete = async () => {
   try {
     await message.confirm('确定要删除吗?')
     loading.value = true
-    await deleteMenu(accountId.value)
+    await MpMenuApi.deleteMenu(accountId.value)
     handleQuery()
     message.notifySuccess('清空成功')
   } finally {
@@ -546,6 +503,7 @@ const deleteMaterial = () => {
   delete tempObj.value['replyArticles']
 }
 </script>
+
 <!--本组件样式-->
 <style lang="scss" scoped="scoped">
 /* 公共颜色变量 */

+ 13 - 69
src/views/mp/tag/index.vue

@@ -3,45 +3,16 @@
 
   <!-- 搜索工作栏 -->
   <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="请选择公众号" class="!w-240px">
-          <el-option
-            v-for="item in accountList"
-            :key="item.id"
-            :label="item.name"
-            :value="item.id"
-          />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="标签名称" prop="name">
-        <el-input
-          v-model="queryParams.name"
-          placeholder="请输入标签名称"
-          clearable
-          @keyup.enter="handleQuery"
-          class="!w-240px"
-        />
-      </el-form-item>
-      <el-form-item>
-        <el-form-item>
-          <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
-          <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
-        </el-form-item>
+    <WxAccountSelect @change="(accountId) => accountChanged(accountId)">
+      <template #actions>
         <el-button type="primary" plain @click="openForm('create')" v-hasPermi="['mp:tag:create']">
           <Icon icon="ep:plus" class="mr-5px" /> 新增
         </el-button>
         <el-button type="success" plain @click="handleSync" v-hasPermi="['mp:tag:sync']">
           <Icon icon="ep:refresh" class="mr-5px" /> 同步
         </el-button>
-      </el-form-item>
-    </el-form>
+      </template>
+    </WxAccountSelect>
   </ContentWrap>
 
   <!-- 列表 -->
@@ -92,8 +63,8 @@
 </template>
 <script setup lang="ts" name="MpTag">
 import { dateFormatter } from '@/utils/formatTime'
+import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
 import * as MpTagApi from '@/api/mp/tag'
-import * as MpAccountApi from '@/api/mp/account'
 import TagForm from './TagForm.vue'
 const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
@@ -104,19 +75,18 @@ const list = ref([]) // 列表的数据
 const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
-  accountId: undefined,
-  name: null
+  accountId: undefined
 })
-const queryFormRef = ref() // 搜索的表单
-const accountList = ref<MpAccountApi.AccountVO[]>([]) // 公众号账号列表
+
+/** 侦听公众号变化 **/
+const accountChanged = (accountId) => {
+  queryParams.pageNo = 1
+  queryParams.accountId = accountId
+  getList()
+}
 
 /** 查询列表 */
 const getList = async () => {
-  // 如果没有选中公众号账号,则进行提示。
-  if (!queryParams.accountId) {
-    await message.error('未选中公众号,无法查询标签')
-    return
-  }
   try {
     loading.value = true
     const data = await MpTagApi.getTagPage(queryParams)
@@ -127,22 +97,6 @@ const getList = async () => {
   }
 }
 
-/** 搜索按钮操作 */
-const handleQuery = () => {
-  queryParams.pageNo = 1
-  getList()
-}
-
-/** 重置按钮操作 */
-const resetQuery = () => {
-  queryFormRef.value.resetFields()
-  // 默认选中第一个
-  if (accountList.value.length > 0) {
-    queryParams.accountId = accountList.value[0].id
-  }
-  handleQuery()
-}
-
 /** 添加/修改操作 */
 const formRef = ref()
 const openForm = (type: string, id?: number) => {
@@ -172,14 +126,4 @@ const handleSync = async () => {
     await getList()
   } catch {}
 }
-
-/** 初始化 **/
-onMounted(async () => {
-  accountList.value = await MpAccountApi.getSimpleAccountList()
-  // 选中第一个
-  if (accountList.value.length > 0) {
-    queryParams.accountId = accountList.value[0].id
-  }
-  await getList()
-})
 </script>

+ 18 - 77
src/views/mp/user/index.vue

@@ -3,49 +3,13 @@
 
   <!-- 搜索工作栏 -->
   <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="请选择公众号" class="!w-240px">
-          <el-option
-            v-for="item in accountList"
-            :key="item.id"
-            :label="item.name"
-            :value="item.id"
-          />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="用户标识" prop="openid">
-        <el-input
-          v-model="queryParams.openid"
-          placeholder="请输入用户标识"
-          clearable
-          @keyup.enter="handleQuery"
-          class="!w-240px"
-        />
-      </el-form-item>
-      <el-form-item label="昵称" prop="nickname">
-        <el-input
-          v-model="queryParams.nickname"
-          placeholder="请输入昵称"
-          clearable
-          @keyup.enter="handleQuery"
-          class="!w-240px"
-        />
-      </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>
+    <WxAccountSelect @change="(accountId) => accountChanged(accountId)">
+      <template #actions>
         <el-button type="success" plain @click="handleSync" v-hasPermi="['mp:user:sync']">
           <Icon icon="ep:refresh" class="mr-5px" /> 同步
         </el-button>
-      </el-form-item>
-    </el-form>
+      </template>
+    </WxAccountSelect>
   </ContentWrap>
 
   <!-- 列表 -->
@@ -101,11 +65,12 @@
   <UserForm ref="formRef" @success="getList" />
 </template>
 <script lang="ts" setup name="MpUser">
+import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
 import { dateFormatter } from '@/utils/formatTime'
-import * as MpAccountApi from '@/api/mp/account'
 import * as MpUserApi from '@/api/mp/user'
 import * as MpTagApi from '@/api/mp/tag'
 import UserForm from './UserForm.vue'
+
 const message = useMessage() // 消息
 
 const loading = ref(true) // 列表的加载中
@@ -118,17 +83,22 @@ const queryParams = reactive({
   openid: null,
   nickname: null
 })
-const queryFormRef = ref() // 搜索的表单
-const accountList = ref([]) // 公众号账号列表
 const tagList = ref([]) // 公众号标签列表
 
+/** 初始化 */
+onMounted(async () => {
+  tagList.value = await MpTagApi.getSimpleTagList()
+})
+
+/** 侦听公众号变化 **/
+const accountChanged = (accountId) => {
+  queryParams.pageNo = 1
+  queryParams.accountId = accountId
+  getList()
+}
+
 /** 查询列表 */
 const getList = async () => {
-  // 如果没有选中公众号账号,则进行提示。
-  if (!queryParams.accountId) {
-    message.error('未选中公众号,无法查询用户')
-    return false
-  }
   try {
     loading.value = true
     const data = await MpUserApi.getUserPage(queryParams)
@@ -139,22 +109,6 @@ const getList = async () => {
   }
 }
 
-/** 搜索按钮操作 */
-const handleQuery = () => {
-  queryParams.pageNo = 1
-  getList()
-}
-
-/** 重置按钮操作 */
-const resetQuery = () => {
-  queryFormRef.value.resetFields()
-  // 默认选中第一个
-  if (accountList.value.length > 0) {
-    queryParams.accountId = accountList.value[0].id
-  }
-  handleQuery()
-}
-
 /** 添加/修改操作 */
 const formRef = ref()
 const openForm = (id: number) => {
@@ -171,17 +125,4 @@ const handleSync = async () => {
     await getList()
   } catch {}
 }
-
-/** 初始化 */
-onMounted(async () => {
-  // 加载标签
-  tagList.value = await MpTagApi.getSimpleTagList()
-
-  // 加载账号
-  accountList.value = await MpAccountApi.getSimpleAccountList()
-  if (accountList.value.length > 0) {
-    queryParams.accountId = accountList.value[0].id
-  }
-  await getList()
-})
 </script>