Pre Merge pull request !263 from 芙芙/master

pull/263/MERGE
芙芙 2025-08-31 03:31:00 +00:00 committed by Gitee
commit b74bdf88f9
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
5 changed files with 656 additions and 0 deletions

View File

@ -0,0 +1,53 @@
import request from '@/config/axios'
// 同步公众号模板
export const syncTemplate = (accountId) => {
return request.post({
url: '/mp/template/sync?accountId=' + accountId
})
}
// 获得公众号模板分页
export const getTemplatePage = (query) => {
return request.get({
url: '/mp/template/page',
params: query
})
}
// 获得公众号模板
export const getTemplate = (id) => {
return request.get({
url: '/mp/template/get?id=' + id
})
}
// 根据id获取模板
export const getTemplateContent = (id) => {
return request.get({
url: '/mp/template/contentGet?id=' + id
})
}
// 更新公众号模板
export const updateTemplate = (data) => {
return request.put({
url: '/mp/template/update',
data: data
})
}
// 删除公众号模板
export const deleteTemplate = (id) => {
return request.delete({
url: '/mp/template/delete?id=' + id
})
}
// 批量发送公众号模板
export const sendMsgBatchTemplate = (data) => {
return request.post({
url: '/mp/template/sendMsgBatch',
data: data
})
}

View File

@ -23,6 +23,14 @@ export const getUserPage = (query) => {
})
}
// 获得公众号粉丝分页
export const getUserPageEnhance = (query) => {
return request.get({
url: '/mp/user/pageEnhance',
params: query
})
}
// 同步公众号粉丝
export const syncUser = (accountId) => {
return request.post({

View File

@ -0,0 +1,228 @@
<template>
<Dialog v-model="dialogVisible" title="发送模板消息目标用户">
<el-form ref="formRef" v-loading="formLoading" :model="queryParams" label-width="100px">
<el-row :gutter="20">
<el-col :span="13">
<el-form-item label="用户标识" prop="url">
<el-input
v-model="queryParams.openid"
@keyup.enter="handleQuery"
placeholder="公众号用户的openId"
/>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item label="用户昵称" prop="appid">
<el-input
v-model="queryParams.nickname"
@keyup.enter="handleQuery"
placeholder="公众号用户昵称"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="11">
<el-form-item label="用户省份" prop="appid">
<el-input
v-model="queryParams.province"
@keyup.enter="handleQuery"
placeholder="公众号用户所属省份"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="用户城市" prop="appid">
<el-input
v-model="queryParams.city"
@keyup.enter="handleQuery"
placeholder="公众号用户所属城市"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="13">
<el-form-item label="用户标签">
<el-select
v-model="queryParams.tagId"
filterable
placeholder="选择用户分组"
:loading="wxUsersLoading"
>
<el-option
v-for="item in wxUserTags"
:key="item.id"
:label="item.name"
:value="item.tagId"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="2" />
<el-col :span="9">
<el-button @click="handleQuery"> <Icon icon="ep:search" />筛选 </el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" /> 重置</el-button>
</el-col>
</el-row>
</el-form>
<div class="user-list-send">
<div class="user-title-message">消息预览</div>
<div class="template-area">
<el-input
type="textarea"
disabled
autosize
v-model="templateContent"
placeholder="模版加载中..."
/>
</div>
<div class="user-title-message mb5">本消息将发送给</div>
<div class="user-list" v-loading="wxUsersLoading">
<div class="user-card" v-for="item in wxUserList" :key="item.openid">
<el-avatar :src="item.headimgurl" />
<div class="nickname">{{ item.nickname }}</div>
</div>
<span v-show="totalCount > 10">...</span>
等共 <span class="text-success">{{ totalCount }}</span> 位用户
</div>
</div>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
import * as MpTemplateApi from '@/api/mp/template'
import * as MpUserApi from '@/api/mp/user'
import * as MpTagApi from '@/api/mp/tag'
defineOptions({ name: 'MpUserForm' })
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const formLoading = ref(false) //
const wxUsersLoading = ref(false) //
const wxUserList = ref([]) //
const totalCount = ref() //
const wxUserTags = ref([]) //
const formRef = ref() // Ref
const templateContent = ref() //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
accountId: -1,
openid: '',
nickname: '',
province: '',
city: '',
tagId: []
})
const templateId = ref()
/** 打开弹窗 */
const open = async (id: number, accountId: number) => {
dialogVisible.value = true
resetForm()
try {
wxUsersLoading.value = true
queryParams.accountId = accountId
templateId.value = id
const userResponse = await MpUserApi.getUserPageEnhance(queryParams)
wxUserTags.value = await MpTagApi.getSimpleTagList()
totalCount.value = userResponse.total
wxUserList.value = userResponse.list
templateContent.value = await MpTemplateApi.getTemplateContent(id)
} finally {
wxUsersLoading.value = false
}
}
defineExpose({ open }) // open
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 查询列表 */
const getList = async () => {
try {
wxUsersLoading.value = true
console.log(queryParams)
const userData = await MpUserApi.getUserPageEnhance(queryParams)
wxUserList.value = userData.list
totalCount.value = userData.total
} finally {
wxUsersLoading.value = false
}
}
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
formLoading.value = true
try {
let newQueryParams = { ...queryParams }
newQueryParams.id = templateId.value
await MpTemplateApi.sendMsgBatchTemplate(newQueryParams)
message.success(t('common.success'))
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
const resetQuery = () => {
resetForm()
getList()
}
/** 重置表单 */
const resetForm = () => {
queryParams.pageNo = 1
queryParams.pageSize = 10
queryParams.openid = ''
queryParams.nickname = ''
queryParams.province = ''
queryParams.city = ''
queryParams.tagId = []
}
</script>
<style scoped>
.user-list-send {
margin-top: 20px;
border: 1px dotted gray;
padding: 10px;
}
.user-title-message {
font-size: 15px;
font-weight: bold;
}
.template-area {
margin: 20px 0;
}
.user-list {
display: flex;
flex-wrap: wrap;
align-items: center;
}
.user-card {
overflow: hidden;
max-width: 60px;
margin: 5px;
text-align: center;
}
.nickname {
color: #999999;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>

View File

@ -0,0 +1,180 @@
<template>
<Dialog v-model="dialogVisible" title="修改">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData.value"
:rules="formRules"
label-width="100px"
>
<el-row>
<el-col :span="16">
<el-form-item label="标题" prop="title">
<el-input v-model="formData.value.title" placeholder="标题" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="有效" prop="status">
<el-switch
v-model="formData.value.status"
placeholder="是否有效"
:active-value="0"
:inactive-value="1"
/>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="链接" prop="url">
<el-input v-model="formData.value.url" placeholder="跳转链接" />
</el-form-item>
<el-form-item label="小程序appid" prop="appid">
<el-input v-model="formData.value.appId" disabled placeholder="小程序appid" />
</el-form-item>
<el-form-item label="小程序路径" prop="pagePath">
<el-input v-model="formData.value.appPath" placeholder="小程序pagePath" />
</el-form-item>
<el-form-item label="模版名称" prop="templateId">
<el-input v-model="formData.value.templateId" placeholder="模版名称" />
</el-form-item>
<div class="message-fill">
<el-form-item class="form-group-title">消息填充数据请对照模板内容填写</el-form-item>
<el-form-item>
<el-input
type="textarea"
disabled
autosize
v-model="formData.value.content"
placeholder="模版"
/>
</el-form-item>
<el-row v-for="(item, index) in formData.value.messageData" :key="item.templateId">
<el-col :span="16">
<el-form-item :label="item.name" :prop="'data.' + index + '.value'">
<el-input
type="textarea"
autosize
rows="1"
v-model="item.value"
placeholder="填充内容"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="颜色">
<el-input type="color" v-model="item.color" placeholder="颜色" />
</el-form-item>
</el-col>
</el-row>
</div>
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
import * as MpTemplateApi from '@/api/mp/template'
defineOptions({ name: 'MpUserForm' })
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const formLoading = ref(false) //
const formData = reactive({
id: undefined,
templateId: '',
title: '',
messageData: [],
url: '',
appId: '',
appPath: '',
content: '',
status: true
})
//
const formRules = reactive({
title: [{ required: true, message: '模板标题不能为空', trigger: 'blur' }],
messageData: [{ required: true, message: '模板内容不能为空', trigger: 'blur' }],
templateId: [{ required: true, message: '模板名称不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
/** 打开弹窗 */
const open = async (id: number) => {
dialogVisible.value = true
resetForm()
//
if (id) {
formLoading.value = true
try {
const response = await MpTemplateApi.getTemplate(id)
formattingTemplate(response)
} finally {
formLoading.value = false
}
}
}
const formattingTemplate = (data) => {
//
if (data.messageData instanceof Array) {
formData.value = data
} else {
//
data.messageData = []
// ["{{first.DATA}}", "{{keyword1.DATA}}", "{{keyword2.DATA}}", "{{remark.DATA}}"]
let keysArray = data.content.match(/\{\{(\w*)\.DATA\}\}/g) || []
keysArray.map((item) => {
// .DATA
let name = item.replace('{{', '').replace('.DATA}}', '')
data.messageData.push({
name: name,
value: '',
color: '#000000'
})
console.log(data)
formData.value = data
})
}
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
await MpTemplateApi.updateTemplate(formData.value)
message.success(t('common.updateSuccess'))
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
nickname: undefined,
remark: undefined,
tagIds: []
}
formRef.value?.resetFields()
}
</script>
<style scoped>
.message-fill {
border: 1px dotted gray;
padding: 10px;
}
</style>

View File

@ -0,0 +1,187 @@
<template>
<!-- 搜索工作栏 -->
<ContentWrap>
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="公众号" prop="accountId">
<WxAccountSelect @change="onAccountChanged" />
</el-form-item>
<el-form-item label="模板名称" prop="templateId">
<el-input
v-model="queryParams.templateId"
placeholder="请输入名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="标题" prop="nickname">
<el-input
v-model="queryParams.title"
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>
<el-button
type="success"
plain
@click="handleSync"
v-hasPermi="['mp:template:sync']"
:disabled="queryParams.accountId === 0"
>
<Icon icon="ep:refresh" class="mr-5px" /> 同步公众号模板
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="编号" align="center" prop="id" width="130" />
<el-table-column label="模版名称" align="center" prop="templateId" />
<el-table-column label="标题" align="center" prop="title" />
<el-table-column label="模版内容" align="center" prop="content" width="350" />
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
type="primary"
link
@click="sendMessage(scope.row.id, queryParams.accountId)"
v-hasPermi="['mp:template:update']"
>
推送消息
</el-button>
<el-button
type="primary"
link
@click="openForm(scope.row.id)"
v-hasPermi="['mp:template:update']"
>
配置
</el-button>
<el-button
type="danger"
link
@click="handleDelete(scope.row.id)"
v-hasPermi="['mp:template:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗修改 -->
<TemplateForm ref="formRef" @success="getList" />
<SendMessage ref="sendRef" @success="getList" />
</template>
<script lang="ts" setup>
import * as MpTemplateApi from '@/api/mp/template'
import WxAccountSelect from '@/views/mp/components/wx-account-select'
import type { FormInstance } from 'element-plus'
import TemplateForm from './TemplateForm.vue'
import SendMessage from './SendMessage.vue'
defineOptions({ name: 'MpUser' })
const message = useMessage() //
const { t } = useI18n()
const loading = ref(true) //
const total = ref(0) //
const list = ref<any[]>([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
accountId: -1,
templateId: '',
title: ''
})
const queryFormRef = ref<FormInstance | null>(null) //
/** 侦听公众号变化 **/
const onAccountChanged = (id: number) => {
queryParams.accountId = id
queryParams.pageNo = 1
getList()
}
/** 查询列表 */
const getList = async () => {
try {
loading.value = true
const data = await MpTemplateApi.getTemplatePage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
const accountId = queryParams.accountId
queryFormRef.value?.resetFields()
queryParams.accountId = accountId
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref(null)
const openForm = (id: number) => {
formRef.value?.open(id)
}
/** 发送消息操作 */
const sendRef = ref(null)
const sendMessage = (id: number, accountId: number) => {
sendRef.value?.open(id, accountId)
}
/** 删除 */
const handleDelete = async (id) => {
//
await message.delConfirm()
//
await MpTemplateApi.deleteTemplate(id)
message.success(t('common.delSuccess'))
//
await getList()
}
/** 同步标签 */
const handleSync = async () => {
try {
await message.confirm('是否确认同步公众号模板?')
await MpTemplateApi.syncTemplate(queryParams.accountId)
message.success('开始从微信公众号同步模板信息,同步需要一段时间,建议稍后再查询')
await getList()
} catch {}
}
</script>