fix: 新增公众号管理相关页面,引入配置文件修改。

This commit is contained in:
tianyongbao
2025-01-09 15:25:49 +08:00
parent 6dc87b0067
commit bb0823e751
46 changed files with 8735 additions and 98 deletions

View File

@@ -0,0 +1,172 @@
<template>
<div>
<avue-crud
ref="crud"
:page="page"
:data="tableData"
:table-loading="tableLoading"
:option="tableOption"
:permission="permissionList"
@on-load="getPageF"
@refresh-change="refreshChange"
@row-update="handleUpdate"
@row-save="handleSave"
@row-del="handleDel"
@sort-change="sortChange"
@search-change="searchChange"
@selection-change="selectionChange"
>
<template #sex="scope">
<el-tag size="small" effect="light" :type="scope.row.sex == '1' ? 'success' : scope.row.sex == '2' ? 'danger' : 'warning'">
{{ scope.row.$sex || '未知' }}
</el-tag>
</template>
</avue-crud>
</div>
</template>
<script setup name="WxMaUser">
import { checkPermi, checkRole } from '@/utils/permission'
import { getPage, addObj, putObj, delObj } from '@/api/wxma/wxuser'
import { tableOption } from '@/const/crud/wxma/wxuser'
const { proxy } = getCurrentInstance()
const data = reactive({
tableData: [],
page: {
total: 0, // 总页数
currentPage: 1, // 当前页数
pageSize: 20, // 每页显示多少条
ascs: [],
descs: 'create_time'
},
paramsSearch: {},
tableLoading: false,
selectionData: [],
dialogTagging: false
})
const { page, tableData, tableLoading } = toRefs(data)
const permissionList = computed(() => {
return {
addBtn: checkPermi(['wxma:wxuser:add']),
delBtn: checkPermi(['wxma:wxuser:del']),
editBtn: checkPermi(['wxma:wxuser:edit']),
viewBtn: checkPermi(['wxma:wxuser:get'])
}
})
function selectionChange(list) {
data.selectionData = list
}
function searchChange(params, done) {
params = proxy.filterForm(params)
data.paramsSearch = params
data.page.currentPage = 1
getPageF(data.page, params)
done()
}
function sortChange(val) {
const prop = val.prop ? val.prop.replace(/([A-Z])/g, '_$1').toLowerCase() : ''
if (val.order == 'ascending') {
data.page.descs = []
data.page.ascs = prop
} else if (val.order == 'descending') {
data.page.ascs = []
data.page.descs = prop
} else {
data.page.ascs = []
data.page.descs = []
}
getPageF(data.page)
}
function getPageF(page, params) {
data.tableLoading = true
getPage(
Object.assign(
{
current: page.currentPage,
size: page.pageSize,
descs: data.page.descs,
ascs: data.page.ascs,
appType: '1'
},
params,
data.paramsSearch
)
)
.then((response) => {
data.tableData = response.data.records
data.page.total = response.data.total
data.page.currentPage = page.currentPage
data.page.pageSize = page.pageSize
data.tableLoading = false
})
.catch(() => {
data.tableLoading = false
})
}
function handleDel(row, index) {
proxy
.$confirm('是否确认删除', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(function () {
return delObj(row.id)
})
.then(() => {
proxy.$message({
showClose: true,
message: '删除成功',
type: 'success'
})
getPageF(data.page)
})
.catch(() => {})
}
function handleUpdate(row, index, done, loading) {
putObj(row)
.then(() => {
proxy.$message({
showClose: true,
message: '修改成功',
type: 'success'
})
done()
getPageF(data.page)
})
.catch(() => {
loading()
})
}
function handleSave(row, done, loading) {
addObj(row)
.then(() => {
proxy.$message({
showClose: true,
message: '添加成功',
type: 'success'
})
done()
getPageF(data.page)
})
.catch(() => {
loading()
})
}
function refreshChange(page) {
getPageF(data.page)
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,404 @@
<template>
<div class="app-container">
<el-tabs v-model="type" @tab-click="handleClick">
<el-tab-pane name="1">
<template #label>
<span>关注时回复</span>
</template>
<avue-crud
style="padding: 0"
ref="crud"
:page="page"
:data="tableData"
:permission="permissionList"
:table-loading="tableLoading"
:option="tableOption1"
@refresh-change="refreshChange"
>
<template #menu-left="">
<div class="add_but" v-if="tableData.length <= 0">
<el-button type="primary" icon="Plus" @click="handleAdd"> </el-button>
</div>
</template>
<template #menu="scope">
<el-button link icon="el-icon-edit" plain type="primary" @click="handleEdit(scope.row)">编辑</el-button>
<el-button link icon="el-icon-delete" type="danger" plain @click="handleDel(scope.row)">删除</el-button>
</template>
</avue-crud>
</el-tab-pane>
<el-tab-pane name="2">
<template #label>
<span><i class="el-icon-chat-line-round"></i> 消息回复</span>
</template>
<avue-crud
ref="crud"
:page="page"
:data="tableData"
:permission="permissionList"
:table-loading="tableLoading"
:option="tableOption2"
@refresh-change="refreshChange"
@sort-change="sortChange"
>
<template #menu-left>
<div class="add_but">
<el-button type="primary" icon="Plus" @click="handleAdd"> </el-button>
</div>
</template>
<template #menu="scope">
<el-button link icon="el-icon-edit" type="primary" plain @click="handleEdit(scope.row)">编辑</el-button>
<el-button link icon="el-icon-delete" type="danger" plain @click="handleDel(scope.row)">删除</el-button>
</template>
</avue-crud>
</el-tab-pane>
<el-tab-pane name="3">
<template #label>
<span><i class="el-icon-news"></i> 关键词回复</span>
</template>
<avue-crud
ref="crud"
:page="page"
:data="tableData"
:permission="permissionList"
:table-loading="tableLoading"
:option="tableOption3"
@on-load="getPageF"
@refresh-change="refreshChange"
@sort-change="sortChange"
@search-change="searchChange"
>
<template #menu-left>
<div class="add_but">
<el-button type="primary" icon="Plus" @click="handleAdd"> </el-button>
</div>
</template>
<template #menu="scope">
<el-button link icon="el-icon-edit" type="primary" plain @click="handleEdit(scope.row)">编辑</el-button>
<el-button link icon="el-icon-delete" type="danger" plain @click="handleDel(scope.row)">删除</el-button>
</template>
</avue-crud>
</el-tab-pane>
</el-tabs>
<el-dialog :title="handleType == 'add' ? '新增回复消息' : '修改回复消息'" v-model="dialog1Visible" width="50%">
<el-form label-width="100px">
<el-form-item label="请求消息类型" v-if="type == '2'">
<el-select v-model="objData.reqType" placeholder="请选择">
<template v-for="item in dictData.get('wx_req_type')" :key="item.value">
<el-option v-if="item.value !== 'event'" :label="item.label" :value="item.value" :disabled="item.disabled" />
</template>
</el-select>
</el-form-item>
<el-form-item label="匹配类型" v-if="type == '3'">
<el-select v-model="objData.repMate" placeholder="请选择" style="width: 100px">
<el-option v-for="item in dictData.get('wx_rep_mate')" :key="item.value" :label="item.label" :value="item.value"> </el-option>
</el-select>
</el-form-item>
<el-form-item label="关键词" v-if="type == '3'">
<el-input placeholder="请输入内容" v-model="objData.reqKey" clearable> </el-input>
</el-form-item>
<el-form-item label="回复消息">
<div v-if="hackResetWxReplySelect" style="width: 100%">
<WxReplySelect :objData="objData"></WxReplySelect>
</div>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialog1Visible = false"> </el-button>
<el-button type="primary" @click="handleSubmit"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup name="WxMpAutoreply">
/**
* 消息自动回复
*/
import { getPage, getObj, addObj, putObj, delObj } from '@/api/wxmp/wxautoreply'
import { tableOption1, tableOption2, tableOption3 } from '@/const/crud/wxmp/wxautoreply'
import WxReplySelect from '@/components/wx-reply/main.vue'
import { checkPermi, checkRole } from '@/utils/permission'
import { computed, getCurrentInstance, nextTick, onMounted, reactive } from 'vue'
const { proxy } = getCurrentInstance()
const data = reactive({
tableData: [],
page: {
total: 0, // 总页数
currentPage: 1, // 当前页数
pageSize: 20, // 每页显示多少条
ascs: [], //升序字段
descs: [] //降序字段
},
paramsSearch: {},
tableLoading: false,
dialog1Visible: false,
type: '1', //类型1、关注时回复2、消息回复3、关键词回复
objData: {
repType: 'text'
},
handleType: null,
dictData: new Map(),
hackResetWxReplySelect: false
})
const { tableData, tableLoading, page, dialog1Visible, objData, type, handleType, dictData, hackResetWxReplySelect } = toRefs(data)
const permissionList = computed(() => {
return {
addBtn: checkPermi(['wxmp:wxautoreply:add']),
delBtn: checkPermi(['wxmp:wxautoreply:del']),
editBtn: checkPermi(['wxmp:wxautoreply:edit']),
viewBtn: checkPermi(['wxmp:wxautoreply:get'])
}
})
function onCreated() {
getPageF(data.page)
data.dictData.set('wx_rep_mate', [
{
value: '1',
label: '全匹配'
},
{
value: '2',
label: '半匹配'
}
])
data.dictData.set('wx_req_type', [
{
value: 'text',
label: '文本'
},
{
value: 'image',
label: '图片'
},
{
value: 'voice',
label: '语音'
},
{
value: 'video',
label: '视频'
},
{
value: 'shortvideo',
label: '小视频'
},
{
value: 'location',
label: '地理位置'
},
{
value: 'link',
label: '链接消息'
},
{
value: 'event',
label: '事件推送'
}
])
}
onCreated()
onMounted(() => {})
function handleAdd() {
data.hackResetWxReplySelect = false //销毁组件
nextTick(() => {
data.hackResetWxReplySelect = true //重建组件
})
data.handleType = 'add'
data.dialog1Visible = true
data.objData = {
repType: 'text'
}
}
function handleEdit(row) {
data.hackResetWxReplySelect = false //销毁组件
nextTick(() => {
data.hackResetWxReplySelect = true //重建组件
})
data.handleType = 'edit'
data.dialog1Visible = true
data.objData = Object.assign({}, row)
}
function handleClick(tab, event) {
data.tableData = []
data.page.currentPage = 1
data.type = tab.paneName
getPageF(data.page)
}
function searchChange(params, done) {
params = /* Warn: Unknown source: filterForm */ proxy.filterForm(params)
data.paramsSearch = params
data.page.currentPage = 1
getPageF(data.page, params)
done()
}
function sortChange(val) {
const prop = val.prop ? val.prop.replace(/([A-Z])/g, '_$1').toLowerCase() : ''
if (val.order == 'ascending') {
data.page.descs = []
data.page.ascs = prop
} else if (val.order == 'descending') {
data.page.ascs = []
data.page.descs = prop
} else {
data.page.ascs = []
data.page.descs = []
}
getPageF(data.page)
}
function getPageF(page, params) {
data.tableLoading = true
getPage(
Object.assign(
{
current: page.currentPage,
size: page.pageSize,
descs: data.page.descs,
ascs: data.page.ascs,
type: data.type
},
params,
data.paramsSearch
)
)
.then((response) => {
data.tableData = response.data.records
data.page.total = response.data.total
data.page.currentPage = page.currentPage
data.page.pageSize = page.pageSize
data.tableLoading = false
if (data.type == '2') {
const wxReqType = data.dictData.get('wx_req_type')
for (let i = 0; i < wxReqType.length; i++) {
wxReqType[i].disabled = false
for (let j = 0; j < data.tableData.length; j++) {
if (wxReqType[i].value == data.tableData[j].reqType) {
wxReqType[i].disabled = true
}
}
}
}
})
.catch(() => {
data.tableLoading = false
})
}
function handleDel(row, index) {
proxy
.$confirm('是否确认删除此数据', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(function () {
return delObj(row.id)
})
.then(() => {
proxy.$message({
showClose: true,
message: '删除成功',
type: 'success'
})
getPageF(data.page)
})
.catch(() => {})
}
function handleSubmit(row) {
if (data.handleType == 'add') {
addObj(
Object.assign(
{
type: data.type
},
data.objData
)
).then(() => {
proxy.$message({
showClose: true,
message: '添加成功',
type: 'success'
})
getPageF(data.page)
data.dialog1Visible = false
})
}
if (data.handleType == 'edit') {
putObj(data.objData).then(() => {
proxy.$message({
showClose: true,
message: '修改成功',
type: 'success'
})
getPageF(data.page)
data.dialog1Visible = false
})
}
}
function handleUpdate(row, index, done, loading) {
putObj(row)
.then(() => {
proxy.$message({
showClose: true,
message: '修改成功',
type: 'success'
})
done()
getPageF(data.page)
})
.catch(() => {
loading()
})
}
function handleSave(row, done, loading) {
addObj(
Object.assign(
{
type: data.type
},
data.objData
)
)
.then(() => {
proxy.$message({
showClose: true,
message: '添加成功',
type: 'success'
})
getPageF(data.page)
data.dialog1Visible = false
})
.catch(() => {
done()
})
}
function refreshChange(page) {
data.objData = {
repType: 'text'
}
getPageF(data.page)
}
</script>
<style lang="scss" scoped>
// 去掉 ruoyi 的样式
.avue-crud :deep(.el-card__body) {
padding: 0 !important;
}
</style>

View File

@@ -0,0 +1,793 @@
<template>
<div class="app-container">
<div class="add_but">
<el-button type="primary" @click="handleAddNews"> </el-button>
</div>
<el-dialog
:title="operateMaterial == 'add' ? '新建图文' : '修改图文'"
append-to-body
:before-close="dialogNewsClose"
:close-on-click-modal="false"
v-model="dialogNewsVisible"
width="80%"
top="20px"
>
<div class="left">
<div class="select-item">
<div v-for="(news, index) in articlesAdd" :key="news.id">
<div class="news-main father" v-if="index == 0" :class="{ activeAddNews: isActiveAddNews == index }" @click="activeNews(index)">
<div class="news-content">
<el-image class="material-img" :src="news.thumbUrl">
<template #error><div class="flex-c material-img">未设置</div> </template>
</el-image>
<div class="news-content-title">{{ news.title }}</div>
</div>
<div class="child" v-if="articlesAdd.length > 1">
<el-button size="small" icon="Bottom" @click="downNews(index)">下移</el-button>
<el-button v-if="operateMaterial == 'add'" size="small" type="danger" icon="Delete" @click="minusNews(index)">删除 </el-button>
</div>
</div>
<div class="news-main-item father" v-if="index > 0" :class="{ activeAddNews: isActiveAddNews == index }" @click="activeNews(index)">
<div class="news-content-item">
<div class="news-content-item-title">{{ news.title }}</div>
<div class="news-content-item-img">
<el-image class="material-img" style="height: 55px" :src="news.thumbUrl">
<template #error><div class="flex-c material-img" style="background: #eeeeee">未设置</div> </template>
</el-image>
</div>
</div>
<div class="child">
<el-button v-if="articlesAdd.length > index + 1" size="small" icon="Bottom" @click="downNews(index)">下移 </el-button>
<el-button size="small" icon="Top" @click="upNews(index)">上移</el-button>
<el-button v-if="operateMaterial == 'add'" size="small" type="danger" icon="Delete" @click="minusNews(index)">删除 </el-button>
</div>
</div>
</div>
<div v-if="articlesAdd.length < 8 && operateMaterial == 'add'" class="news-main-plus">
<el-button @click="plusNews" type="primary" icon="Plus" circle />
</div>
</div>
</div>
<div class="right" v-loading="addMaterialLoading">
<!--富文本编辑器组件-->
<el-tabs v-model="activeName">
<el-tab-pane label="封面标题" name="title">
<div>
<div class="thumb-div">
<div class="input-tt" style="display: flex">封面</div>
<div>
<img
class="material-img"
v-if="articlesAdd[isActiveAddNews].thumbUrl"
:src="articlesAdd[isActiveAddNews].thumbUrl"
:class="isActiveAddNews == 0 ? 'avatar' : 'avatar1'"
/>
<el-icon v-else class="avatar-uploader-icon" :class="isActiveAddNews == 0 ? 'avatar' : 'avatar1'"><Plus /></el-icon>
<div class="thumb-but">
<el-upload
:action="actionUrl"
:headers="headers"
multiple
:limit="1"
:on-success="handleUploadSuccess"
:file-list="fileList"
:before-upload="beforeThumbImageUpload"
:data="uploadData"
>
<template #trigger>
<el-button size="small" type="primary"> 本地上传 </el-button>
</template>
<el-button size="small" type="primary" @click="openMaterial" style="margin-left: 5px">素材库选择 </el-button>
<template #tip>
<div class="el-upload__tip">支持bmp/png/jpeg/jpg/gif格式大小不超过2M</div>
</template>
</el-upload>
</div>
</div>
</div>
<el-dialog title="选择图片" v-model="dialogImageVisible" width="80%" append-to-body>
<WxMaterialSelect :objData="{ repType: 'image' }" @selectMaterial="selectMaterial"></WxMaterialSelect>
</el-dialog>
<div class="digest">
<div class="input-tt">标题</div>
<el-input v-model="articlesAdd[isActiveAddNews].title" placeholder="请输入标题"></el-input>
<div class="input-tt">作者</div>
<el-input v-model="articlesAdd[isActiveAddNews].author" placeholder="请输入作者"></el-input>
<div class="input-tt">原文地址</div>
<el-input v-model="articlesAdd[isActiveAddNews].contentSourceUrl" placeholder="请输入原文地址"></el-input>
</div>
</div>
<div class="input-tt">摘要</div>
<el-input :rows="5" type="textarea" v-model="articlesAdd[isActiveAddNews].digest" placeholder="请输入摘要" maxlength="120"></el-input>
</el-tab-pane>
<el-tab-pane label="内容详情" name="content">
<div style="padding-bottom: 20px">
<WxEditor v-model="articlesAdd[isActiveAddNews].content" :minHeight="350" :uploadData="uploadData" v-if="hackResetEditor" />
</div>
</el-tab-pane>
</el-tabs>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogNewsVisible = false"> </el-button>
<el-button type="primary" @click="subNews"> </el-button>
</div>
</template>
</el-dialog>
<div class="waterfall" v-loading="tableLoading">
<template v-for="item in tableData" :key="item.id">
<div v-if="item.content && item.content.newsItem" class="waterfall-item">
<WxNews :objData="item.content.newsItem"></WxNews>
<el-row class="ope-row">
<el-button type="success" icon="Promotion" size="small" @click="handlePublishNews(item)">发布</el-button>
<el-button type="primary" size="small" plain icon="el-icon-edit" @click="handleEditNews(item)">编辑</el-button>
<el-button type="danger" size="small" plain icon="el-icon-delete" @click="delMaterial(item)">删除</el-button>
</el-row>
</div>
</template>
</div>
<div v-if="tableData.length <= 0 && !tableLoading" class="el-table__empty-block">
<el-empty />
</div>
<el-pagination
@size-change="sizeChange"
@current-change="currentChange"
v-model:current-page="page.currentPage"
:page-sizes="[10, 20]"
:page-size="page.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="page.total"
class="pagination"
>
</el-pagination>
</div>
</template>
<script setup name="WxMpDraft">
import { getPage, addObj, delObj, putObj, publish } from '@/api/wxmp/wxdraft'
import { getPage as getPage1 } from '@/api/wxmp/wxmaterial'
import WxEditor from '@/components/WxEditor/WxEditor.vue'
import WxNews from '@/components/wx-news/main.vue'
import WxMaterialSelect from '@/components/wx-material-select/main.vue'
import { getToken } from '@/utils/auth'
import { getCurrentInstance, nextTick, onMounted, reactive } from 'vue'
const { proxy } = getCurrentInstance()
const data = reactive({
tableData: [],
page: {
total: 0, // 总页数
currentPage: 1, // 当前页数
pageSize: 10 // 每页显示多少条
},
page1: {
total: 0, // 总页数
currentPage: 1, // 当前页数
pageSize: 10 // 每页显示多少条
},
tableLoading: false,
fileList: [],
dialogVideoVisible: false,
dialogNewsVisible: false,
addMaterialLoading: false,
uploadData: {
mediaType: 'image',
title: '',
introduction: ''
},
articlesAdd: [
{
title: '',
thumbMediaId: '',
author: '',
digest: '',
showCoverPic: '',
content: '',
contentSourceUrl: '',
needOpenComment: '',
onlyFansCanComment: '',
thumbUrl: ''
}
],
isActiveAddNews: 0,
dialogImageVisible: false,
imageListData: [],
tableLoading1: false,
operateMaterial: 'add',
articlesMediaId: '',
hackResetEditor: false,
actionUrl: ref(import.meta.env.VITE_APP_BASE_API + '/wxmaterial/materialFileUpload'), // 上传的图片服务器地址
headers: {
Authorization: 'Bearer ' + getToken()
},
activeName: 'title'
})
const {
activeName,
materialType,
tableData,
tableLoading,
page,
page1,
fileList,
dialogVideoVisible,
dialogNewsVisible,
addMaterialLoading,
uploadData,
uploadRules,
articlesAdd,
isActiveAddNews,
dialogImageVisible,
imageListData,
tableLoading1,
operateMaterial,
articlesMediaId,
hackResetEditor,
actionUrl,
headers
} = toRefs(data)
onMounted(() => {
getPageF(data.page)
})
function getPageF(page, params) {
data.tableData = []
data.tableLoading = true
getPage(
Object.assign(
{
current: page.currentPage,
size: page.pageSize
},
params
)
)
.then((response) => {
data.tableData = response.data.items
data.page.total = response.data.totalCount
data.page.currentPage = page.currentPage
data.page.pageSize = page.pageSize
data.tableLoading = false
})
.catch(() => {
data.tableLoading = false
})
}
function sizeChange(val) {
data.page.currentPage = 1
data.page.pageSize = val
getPageF(data.page)
}
function currentChange(val) {
data.page.currentPage = val
getPageF(data.page)
}
function delMaterial(item) {
proxy
.$confirm('此操作将永久删除该草稿, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
data.tableLoading = true
delObj({
id: item.mediaId
})
.then((response) => {
data.tableLoading = false
if (response.code == 200) {
getPageF(data.page)
} else {
data.tableLoading = false
proxy.$message.error('删除出错:' + response.msg)
}
})
.catch(() => {
data.tableLoading = false
})
})
}
function getPage1F() {
data.tableLoading1 = true
getPage1({
current: data.page1.currentPage,
size: data.page1.pageSize,
type: 'image'
})
.then((response) => {
data.tableLoading1 = false
data.imageListData = response.data.items
data.page1.total = response.data.totalCount
})
.catch(() => {
data.tableLoading1 = false
})
}
function openMaterial() {
data.imageListData = null
data.page1.currentPage = 1
getPage1F()
data.dialogImageVisible = true
}
function dialogNewsClose(done) {
/* Warn: Unknown source: $confirm */
proxy
.$confirm('修改内容可能还未保存,确定关闭吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
data.hackResetEditor = false //销毁组件
nextTick(() => {
data.hackResetEditor = true //重建组件
})
data.isActiveAddNews = 0
done()
})
.catch(() => {})
}
function downNews(index) {
const temp = data.articlesAdd[index]
data.articlesAdd[index] = data.articlesAdd[index + 1]
data.articlesAdd[index + 1] = temp
data.isActiveAddNews = index + 1
}
function upNews(index) {
const temp = data.articlesAdd[index]
data.articlesAdd[index] = data.articlesAdd[index - 1]
data.articlesAdd[index - 1] = temp
data.isActiveAddNews = index - 1
}
function activeNews(index) {
data.hackResetEditor = false //销毁组件
nextTick(() => {
data.hackResetEditor = true //重建组件
})
data.isActiveAddNews = index
}
function minusNews(index) {
proxy
.$confirm('确定删除该图文吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
data.articlesAdd.splice(index, 1)
if (data.isActiveAddNews == index) {
data.isActiveAddNews = 0
}
})
.catch(() => {})
}
function plusNews() {
data.articlesAdd.push({
title: '',
thumbMediaId: '',
author: '',
digest: '',
showCoverPic: '',
content: '',
contentSourceUrl: '',
needOpenComment: '',
onlyFansCanComment: '',
thumbUrl: ''
})
data.isActiveAddNews = data.articlesAdd.length - 1
}
function subNews() {
data.addMaterialLoading = true
if (data.operateMaterial == 'add') {
addObj({
articles: data.articlesAdd
})
.then((response) => {
data.addMaterialLoading = false
data.dialogNewsVisible = false
if (response.code == 200) {
data.isActiveAddNews = 0
data.articlesAdd = [
{
title: '',
thumbMediaId: '',
author: '',
digest: '',
showCoverPic: '',
content: '',
contentSourceUrl: '',
needOpenComment: '',
onlyFansCanComment: '',
thumbUrl: ''
}
]
data.page.currentPage = 1
getPageF(data.page)
} else {
proxy.$message.error('新增图文出错:' + response.msg)
}
})
.catch(() => {
data.addMaterialLoading = false
})
}
if (data.operateMaterial == 'edit') {
putObj({
articles: data.articlesAdd,
mediaId: data.articlesMediaId
})
.then((response) => {
data.addMaterialLoading = false
data.dialogNewsVisible = false
if (response.code == 200) {
data.isActiveAddNews = 0
data.articlesAdd = [
{
title: '',
thumbMediaId: '',
author: '',
digest: '',
showCoverPic: '',
content: '',
contentSourceUrl: '',
needOpenComment: '',
onlyFansCanComment: '',
thumbUrl: ''
}
]
getPageF(data.page)
} else {
proxy.$message.error('修改图文出错:' + response.msg)
}
})
.catch(() => {
data.addMaterialLoading = false
})
}
}
function handleEditNews(item) {
data.hackResetEditor = false //销毁组件
nextTick(() => {
data.hackResetEditor = true //重建组件
})
if (data.operateMaterial == 'add') {
data.isActiveAddNews = 0
}
data.operateMaterial = 'edit'
data.articlesAdd = JSON.parse(JSON.stringify(item.content.newsItem))
data.articlesMediaId = item.mediaId
data.dialogNewsVisible = true
}
function handleAddNews() {
data.hackResetEditor = false //销毁组件
nextTick(() => {
data.hackResetEditor = true //重建组件
})
if (data.operateMaterial == 'edit') {
data.isActiveAddNews = 0
data.articlesAdd = [
{
title: '',
thumbMediaId: '',
author: '',
digest: '',
showCoverPic: '',
content: '',
contentSourceUrl: '',
needOpenComment: '',
onlyFansCanComment: '',
thumbUrl: ''
}
]
}
data.operateMaterial = 'add'
data.dialogNewsVisible = true
}
function beforeThumbImageUpload(file) {
data.addMaterialLoading = true
const isType = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/gif' || file.type === 'image/bmp' || file.type === 'image/jpg'
const isLt = file.size / 1024 / 1024 < 2
if (!isType) {
proxy.$message.error('上传图片格式不对!')
}
if (!isLt) {
proxy.$message.error('上传图片大小不能超过2M!')
}
data.addMaterialLoading = false
return isType && isLt
}
function handleUploadSuccess(response, file, fileList) {
data.tableLoading = false
data.addMaterialLoading = false
if (response.code == 200) {
data.dialogVideoVisible = false
data.fileList = []
data.uploadData.title = ''
data.uploadData.introduction = ''
data.articlesAdd[data.isActiveAddNews].thumbMediaId = response.data.mediaId
data.articlesAdd[data.isActiveAddNews].thumbUrl = response.data.url
} else {
proxy.$message.error('上传出错:' + response.msg)
}
}
function selectMaterial(item) {
data.dialogImageVisible = false
data.articlesAdd[data.isActiveAddNews].thumbMediaId = item.mediaId
data.articlesAdd[data.isActiveAddNews].thumbUrl = item.url
}
function handlePublishNews(item) {
proxy
.$confirm(
'你正在通过发布的方式发表内容。 发布不占用群发次数,一天可多次发布。已发布内容不会推送给用户,也不会展示在公众号主页中。 发布后,你可以前往发表记录获取链接,也可以将发布内容添加到自定义菜单、自动回复、话题和页面模板中。',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
.then(() => {
data.tableLoading = true
publish(item.mediaId)
.then((response) => {
data.tableLoading = false
proxy.$message.success('发布任务提交成功')
getPageF(data.page)
})
.catch(() => {
data.tableLoading = false
})
})
.catch(() => {})
}
</script>
<style lang="scss" scoped>
.pagination {
float: right;
margin-right: 25px;
}
.add_but {
padding-bottom: 10px;
}
.ope-row {
margin-top: 5px;
text-align: center;
border-top: 1px solid #eaeaea;
padding-top: 5px;
}
.item-name {
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center;
}
.el-upload__tip {
margin-left: 5px;
}
.left {
display: inline-block;
width: 35%;
vertical-align: top;
margin-top: 10px;
}
.right {
display: inline-block;
width: 60%;
min-height: 500px;
}
.avatar-uploader {
width: 20%;
display: inline-block;
}
.avatar-uploader .el-upload {
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
text-align: unset !important;
}
.avatar-uploader .el-upload:hover {
border-color: #165dff;
}
.avatar-uploader-icon {
border: 1px solid #d9d9d9;
font-size: 28px;
color: #8c939d;
width: 120px;
height: 120px;
line-height: 120px;
text-align: center;
}
.avatar {
width: 220px;
height: 120px;
}
.avatar1 {
width: 120px;
height: 120px;
}
.digest {
width: 70%;
display: inline-block;
vertical-align: top;
padding-left: 20px;
}
.waterfall {
width: 100%;
column-gap: 10px;
column-count: 5;
margin: 0 auto;
}
.waterfall-item {
padding: 10px;
margin-bottom: 10px;
break-inside: avoid;
border: 1px solid #eaeaea;
}
p {
line-height: 30px;
}
@media (min-width: 992px) and (max-width: 1300px) {
.waterfall {
column-count: 3;
}
p {
color: red;
}
}
@media (min-width: 768px) and (max-width: 991px) {
.waterfall {
column-count: 2;
}
p {
color: orange;
}
}
@media (max-width: 767px) {
.waterfall {
column-count: 1;
}
}
.news-main {
background-color: #ffffff;
width: 100%;
margin: auto;
height: 120px;
}
.news-content {
background-color: #acadae;
width: 100%;
height: 120px;
position: relative;
}
.news-content-title {
display: inline-block;
font-size: 15px;
color: #ffffff;
position: absolute;
min-height: 25px;
left: 0px;
bottom: 0px;
padding: 5px;
background-color: rgba(0, 0, 0, 0.5);
width: 100%;
opacity: 0.65;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.news-main-item {
background-color: #ffffff;
padding: 5px 0px;
border-bottom: 1px solid #eaeaea;
width: 100%;
margin: auto;
}
.news-content-item {
position: relative;
padding: 5px;
display: flex;
align-items: center;
}
.news-content-item-title {
display: inline-block;
font-size: 12px;
align-items: center;
width: 70%;
}
.news-content-item-img {
display: inline-block;
width: 25%;
// background-color: #acadae;
}
.input-tt {
padding: 5px;
}
.activeAddNews {
position: relative;
overflow: visible;
cursor: pointer;
}
.activeAddNews::before {
position: absolute;
inset: 0;
z-index: 1;
content: '';
border: 5px solid #2bb673;
}
.news-main-plus {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
height: 50px;
}
.icon-plus {
margin: 10px;
font-size: 25px;
}
.select-item {
width: 60%;
margin: 0 auto 10px auto;
border: 1px solid #eaeaea;
}
.father .child {
display: none;
text-align: center;
position: relative;
bottom: 25px;
height: 0;
}
.father:hover .child {
display: block;
z-index: 3;
}
.thumb-div {
display: inline-block;
width: 30%;
text-align: center;
}
.thumb-but {
margin: 5px;
}
.material-img {
width: 100%;
height: 100%;
min-width: 50px;
min-height: 50px;
}
.flex-c {
display: flex;
justify-content: center;
align-items: center;
}
</style>

View File

@@ -0,0 +1,331 @@
<template>
<div class="app-container">
<div class="waterfall" v-loading="tableLoading">
<template v-for="item in tableData" :key="item.id">
<div v-if="item.content && item.content.newsItem" class="waterfall-item">
<WxNews :objData="item.content.newsItem"></WxNews>
<el-row class="ope-row flex-c">
<el-button type="danger" icon="el-icon-delete" circle @click="delMaterial(item)"></el-button>
</el-row>
</div>
</template>
</div>
<div v-if="tableData.length <= 0 && !tableLoading" class="el-table__empty-block">
<el-empty />
</div>
<el-pagination
@size-change="sizeChange"
@current-change="currentChange"
v-model:current-page="page.currentPage"
:page-sizes="[10, 20]"
:page-size="page.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="page.total"
class="pagination"
>
</el-pagination>
</div>
</template>
<script setup name="WxMpFreePublish">
import { getPage, delObj } from '@/api/wxmp/wxfreepublish'
import WxNews from '@/components/wx-news/main.vue'
import { getToken } from '@/utils/auth'
import { getCurrentInstance, onMounted, reactive, ref, watch } from 'vue'
const { proxy } = getCurrentInstance()
const tree = ref(null)
const data = reactive({
tableData: [],
page: {
total: 0, // 总页数
currentPage: 1, // 当前页数
pageSize: 10 // 每页显示多少条
},
page1: {
total: 0, // 总页数
currentPage: 1, // 当前页数
pageSize: 10 // 每页显示多少条
},
tableLoading: false,
headers: {
Authorization: 'Bearer ' + getToken()
}
})
const { tableData, tableLoading, page, page1, headers } = toRefs(data)
watch(
() => proxy.filterText,
(val) => {
tree.value.filter(val)
}
)
onMounted(() => {
getPageF(data.page)
})
function getPageF(page, params) {
data.tableData = []
data.tableLoading = true
getPage(
Object.assign(
{
current: page.currentPage,
size: page.pageSize
},
params
)
)
.then((response) => {
data.tableData = response.data.items
data.page.total = response.data.totalCount
data.page.currentPage = page.currentPage
data.page.pageSize = page.pageSize
data.tableLoading = false
})
.catch(() => {
data.tableLoading = false
})
}
function sizeChange(val) {
data.page.currentPage = 1
data.page.pageSize = val
getPageF(data.page)
}
function currentChange(val) {
data.page.currentPage = val
getPageF(data.page)
}
function delMaterial(item) {
proxy
.$confirm('删除后用户将无法访问此页面,确定删除?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
data.tableLoading = true
delObj({
id: item.articleId
})
.then((response) => {
data.tableLoading = false
if (response.code == 200) {
getPageF(data.page)
} else {
data.tableLoading = false
proxy.$message.error('删除出错:' + response.msg)
}
})
.catch(() => {
data.tableLoading = false
})
})
}
</script>
<style lang="scss" scoped>
.pagination {
float: right;
margin-right: 25px;
}
.add_but {
padding: 10px;
}
.ope-row {
margin-top: 5px;
text-align: center;
border-top: 1px solid #eaeaea;
padding-top: 5px;
}
.item-name {
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center;
}
.el-upload__tip {
margin-left: 5px;
}
.left {
display: inline-block;
width: 35%;
vertical-align: top;
margin-top: 200px;
}
.right {
display: inline-block;
width: 60%;
margin-top: -40px;
}
.avatar-uploader {
width: 20%;
display: inline-block;
}
.avatar-uploader .el-upload {
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
text-align: unset !important;
}
.avatar-uploader .el-upload:hover {
border-color: #165dff;
}
.avatar-uploader-icon {
border: 1px solid #d9d9d9;
font-size: 28px;
color: #8c939d;
width: 120px;
height: 120px;
line-height: 120px;
text-align: center;
}
.avatar {
width: 230px;
height: 120px;
}
.avatar1 {
width: 120px;
height: 120px;
}
.digest {
width: 60%;
display: inline-block;
vertical-align: top;
}
.waterfall {
width: 100%;
column-gap: 10px;
column-count: 5;
margin: 0 auto;
}
.waterfall-item {
padding: 10px;
margin-bottom: 10px;
break-inside: avoid;
border: 1px solid #eaeaea;
}
p {
line-height: 30px;
}
@media (min-width: 992px) and (max-width: 1300px) {
.waterfall {
column-count: 3;
}
p {
color: red;
}
}
@media (min-width: 768px) and (max-width: 991px) {
.waterfall {
column-count: 2;
}
p {
color: orange;
}
}
@media (max-width: 767px) {
.waterfall {
column-count: 1;
}
}
.news-main {
background-color: #ffffff;
width: 100%;
margin: auto;
height: 120px;
}
.news-content {
background-color: #acadae;
width: 100%;
height: 120px;
position: relative;
}
.news-content-title {
display: inline-block;
font-size: 15px;
color: #ffffff;
position: absolute;
left: 0px;
bottom: 0px;
background-color: black;
width: 98%;
padding: 1%;
opacity: 0.65;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
height: 25px;
}
.news-main-item {
background-color: #ffffff;
padding: 5px 0px;
border-top: 1px solid #eaeaea;
width: 100%;
margin: auto;
}
.news-content-item {
position: relative;
margin-left: -3px;
}
.news-content-item-title {
display: inline-block;
font-size: 12px;
width: 70%;
}
.news-content-item-img {
display: inline-block;
width: 25%;
background-color: #acadae;
}
.input-tt {
padding: 5px;
}
.activeAddNews {
border: 5px solid #2bb673;
}
.news-main-plus {
width: 280px;
text-align: center;
margin: auto;
height: 50px;
}
.icon-plus {
margin: 10px;
font-size: 25px;
}
.select-item {
width: 60%;
padding: 10px;
margin: 0 auto 10px auto;
border: 1px solid #eaeaea;
}
.father .child {
display: none;
text-align: center;
position: relative;
bottom: 25px;
}
.father:hover .child {
display: block;
}
.thumb-div {
display: inline-block;
width: 30%;
text-align: center;
}
.thumb-but {
margin: 5px;
}
.material-img {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,886 @@
<template>
<div class="app-container">
<el-tabs v-model="materialType" @tab-click="handleClick">
<el-tab-pane name="image">
<template #label>
<span>图片</span>
</template>
<div class="add_but">
<el-upload
:action="actionUrl"
:headers="headers"
multiple
:limit="1"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
:file-list="fileList"
:before-upload="beforeImageUpload"
:data="uploadData"
>
<el-button type="primary" icon="Upload">点击上传</el-button>
<template #tip>
<div class="el-upload__tip">支持bmp/png/jpeg/jpg/gif格式大小不超过2M</div>
</template>
</el-upload>
</div>
<div class="waterfall" v-loading="tableLoading">
<div class="waterfall-item" v-for="item in tableData" :key="item.id">
<a target="_blank" :href="item.url">
<img class="material-img" :src="item.url" />
<div class="item-name">{{ item.name }}</div>
</a>
<el-row class="ope-row flex-c">
<el-button type="danger" icon="el-icon-delete" circle @click="delMaterial(item)"></el-button>
</el-row>
</div>
</div>
<div v-if="tableData.length <= 0 && !tableLoading" class="el-table__empty-block">
<el-empty />
</div>
<el-pagination
@size-change="sizeChange"
@current-change="currentChange"
v-model:current-page="page.currentPage"
:page-sizes="[10, 20]"
:page-size="page.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="page.total"
class="pagination"
>
</el-pagination>
</el-tab-pane>
<el-tab-pane name="voice">
<template #label>
<span>语音</span>
</template>
<div class="add_but">
<el-upload
:action="actionUrl"
:headers="headers"
multiple
:limit="1"
:on-success="handleUploadSuccess"
:file-list="fileList"
:before-upload="beforeVoiceUpload"
:data="uploadData"
>
<el-button type="primary" icon="Upload">点击上传</el-button>
<template #tip>
<div class="el-upload__tip">格式支持mp3/wma/wav/amr文件大小不超过2M播放长度不超过60s</div>
</template>
</el-upload>
</div>
<el-table :data="tableData" stripe border v-loading="tableLoading">
<el-table-column prop="mediaId" label="media_id"> </el-table-column>
<el-table-column prop="name" label="名称"> </el-table-column>
<el-table-column prop="updateTime" label="更新时间"> </el-table-column>
<el-table-column fixed="right" label="操作">
<template v-slot="scope">
<el-button link icon="el-icon-download" type="primary" @click="handleDown(scope.row)">下载</el-button>
<el-button link icon="el-icon-delete" type="danger" @click="delMaterial(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
@size-change="sizeChange"
@current-change="currentChange"
v-model:current-page="page.currentPage"
:page-sizes="[10, 20]"
:page-size="page.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="page.total"
class="pagination"
>
</el-pagination>
</el-tab-pane>
<el-tab-pane name="video">
<template #label>
<span>视频</span>
</template>
<div class="add_but">
<el-button type="primary" icon="Plus" @click="handleAddVideo">新建</el-button>
</div>
<el-dialog title="新建视频" v-model="dialogVideoVisible">
<el-upload
ref="uploadVideo"
:action="actionUrl"
:headers="headers"
multiple
:limit="1"
:on-success="handleUploadSuccess"
:file-list="fileList"
:before-upload="beforeVideoUpload"
:auto-upload="false"
:data="uploadData"
>
<template #trigger>
<el-button type="primary">选择视频</el-button>
</template>
<div class="el-upload__tip">格式支持MP4文件大小不超过10MB</div>
</el-upload>
<el-form :model="uploadData" :rules="uploadRules" ref="uploadForm">
<el-form-item label="标题" prop="title">
<el-input v-model="uploadData.title" placeholder="标题将展示在相关播放页面,建议填写清晰、准确、生动的标题"></el-input>
</el-form-item>
<el-form-item label="描述" prop="introduction">
<el-input
:rows="3"
type="textarea"
v-model="uploadData.introduction"
placeholder="介绍语将展示在相关播放页面,建议填写简洁明确、有信息量的内容"
></el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogVideoVisible = false"> </el-button>
<el-button type="primary" @click="subVideo"> </el-button>
</div>
</template>
</el-dialog>
<el-table :data="tableData" stripe border v-loading="tableLoading">
<el-table-column prop="mediaId" label="media_id"> </el-table-column>
<el-table-column prop="name" label="名称"> </el-table-column>
<el-table-column prop="updateTime" label="更新时间"> </el-table-column>
<el-table-column fixed="right" label="操作">
<template v-slot="scope">
<el-button link icon="el-icon-view" type="primary" @click="handleInfo(scope.row)">查看</el-button>
<el-button link icon="el-icon-delete" type="danger" @click="delMaterial(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
@size-change="sizeChange"
@current-change="currentChange"
v-model:current-page="page.currentPage"
:page-sizes="[10, 20]"
:page-size="page.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="page.total"
class="pagination"
>
</el-pagination>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script setup name="WxMpMaterial">
import { getPage, addObj, delObj, getMaterialOther, getMaterialVideo } from '@/api/wxmp/wxmaterial'
import { checkPermi, checkRole } from '@/utils/permission'
import WxEditor from '@/components/WxEditor/WxEditor.vue'
import WxNews from '@/components/wx-news/main.vue'
import WxMaterialSelect from '@/components/wx-material-select/main.vue'
import { getToken } from '@/utils/auth'
const { proxy } = getCurrentInstance()
const uploadVideo = ref(null)
const data = reactive({
materialType: 'image',
tableData: [],
page: {
total: 0, // 总页数
currentPage: 1, // 当前页数
pageSize: 10 // 每页显示多少条
},
page1: {
total: 0, // 总页数
currentPage: 1, // 当前页数
pageSize: 10 // 每页显示多少条
},
tableLoading: false,
fileList: [],
dialogVideoVisible: false,
dialogNewsVisible: false,
addMaterialLoading: false,
uploadData: {
mediaType: 'image',
title: '',
introduction: ''
},
uploadRules: {
title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
introduction: [{ required: true, message: '请输入描述', trigger: 'blur' }]
},
articlesAdd: [
{
title: '',
thumbMediaId: '',
author: '',
digest: '',
showCoverPic: '',
content: '',
contentSourceUrl: '',
needOpenComment: '',
onlyFansCanComment: '',
thumbUrl: ''
}
],
isActiveAddNews: 0,
dialogImageVisible: false,
imageListData: [],
tableLoading1: false,
operateMaterial: 'add',
articlesMediaId: '',
hackResetEditor: false,
actionUrl: ref(import.meta.env.VITE_APP_BASE_API + '/wxmaterial/materialFileUpload'), // 上传的图片服务器地址
headers: {
Authorization: 'Bearer ' + getToken()
}
})
const {
materialType,
tableData,
tableLoading,
page,
page1,
fileList,
dialogVideoVisible,
dialogNewsVisible,
addMaterialLoading,
uploadData,
uploadRules,
articlesAdd,
isActiveAddNews,
dialogImageVisible,
imageListData,
tableLoading1,
operateMaterial,
articlesMediaId,
hackResetEditor,
actionUrl,
headers
} = toRefs(data)
onMounted(() => {
getPageF(data.page)
})
function dialogNewsClose(done) {
proxy
.$confirm('修改内容可能还未保存,确定关闭吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
data.hackResetEditor = false //销毁组件
nextTick(() => {
data.hackResetEditor = true //重建组件
})
data.isActiveAddNews = 0
done()
})
.catch(() => {})
}
function downNews(index) {
const temp = data.articlesAdd[index]
data.articlesAdd[index] = data.articlesAdd[index + 1]
data.articlesAdd[index + 1] = temp
data.isActiveAddNews = index + 1
}
function upNews(index) {
const temp = data.articlesAdd[index]
data.articlesAdd[index] = data.articlesAdd[index - 1]
data.articlesAdd[index - 1] = temp
data.isActiveAddNews = index - 1
}
function getPage1() {
data.tableLoading1 = true
getPageF({
current: data.page1.currentPage,
size: data.page1.pageSize,
type: 'image'
})
.then((response) => {
data.tableLoading1 = false
data.imageListData = response.data.items
data.page1.total = response.data.totalCount
})
.catch(() => {
data.tableLoading1 = false
})
}
function selectMaterial(item) {
data.dialogImageVisible = false
data.articlesAdd[data.isActiveAddNews].thumbMediaId = item.mediaId
data.articlesAdd[data.isActiveAddNews].thumbUrl = item.url
}
function openMaterial() {
data.imageListData = null
data.page1.currentPage = 1
getPage1()
data.dialogImageVisible = true
}
function activeNews(index) {
data.hackResetEditor = false //销毁组件
nextTick(() => {
data.hackResetEditor = true //重建组件
})
data.isActiveAddNews = index
}
function minusNews(index) {
proxy
.$confirm('确定删除该图文吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
data.articlesAdd.splice(index, 1)
if (data.isActiveAddNews == index) {
data.isActiveAddNews = 0
}
})
.catch(() => {})
}
function plusNews() {
data.articlesAdd.push({
title: '',
thumbMediaId: '',
author: '',
digest: '',
showCoverPic: '',
content: '',
contentSourceUrl: '',
needOpenComment: '',
onlyFansCanComment: '',
thumbUrl: ''
})
data.isActiveAddNews = data.articlesAdd.length - 1
}
function handleInfo(row) {
data.tableLoading = true
getMaterialVideo({
mediaId: row.mediaId
})
.then((response) => {
data.tableLoading = false
if (response.code == 200) {
const downUrl = response.data.downUrl
window.open(downUrl, '_blank')
} else {
proxy.$message.error('获取微信视频素材出错:' + response.data.msg)
}
})
.catch(() => {
data.tableLoading = false
})
}
function handleDown(row) {
data.tableLoading = true
getMaterialOther({
mediaId: row.mediaId,
fileName: row.name
})
.then((response) => {
data.tableLoading = false
const url = window.URL.createObjectURL(new Blob([response.data]))
const link = document.createElement('a')
link.style.display = 'none'
link.href = url
link.setAttribute('download', row.name)
document.body.appendChild(link)
link.click()
})
.catch(() => {
data.tableLoading = false
})
}
function subNews() {
data.addMaterialLoading = true
if (data.operateMaterial == 'add') {
addObj({
articles: data.articlesAdd
})
.then((response) => {
data.addMaterialLoading = false
data.dialogNewsVisible = false
if (response.code == 200) {
data.isActiveAddNews = 0
data.articlesAdd = [
{
title: '',
thumbMediaId: '',
author: '',
digest: '',
showCoverPic: '',
content: '',
contentSourceUrl: '',
needOpenComment: '',
onlyFansCanComment: '',
thumbUrl: ''
}
]
data.page.currentPage = 1
getPageF(data.page)
} else {
proxy.$message.error('新增图文出错:' + response.data.msg)
}
})
.catch(() => {
data.addMaterialLoading = false
})
}
if (data.operateMaterial == 'edit') {
// materialNewsUpdate({
// articles: data.articlesAdd,
// mediaId: data.articlesMediaId,
// })
// .then((response) => {
// data.addMaterialLoading = false;
// data.dialogNewsVisible = false;
// if (response.code == 200) {
// data.isActiveAddNews = 0;
// data.articlesAdd = [
// {
// title: "",
// thumbMediaId: "",
// author: "",
// digest: "",
// showCoverPic: "",
// content: "",
// contentSourceUrl: "",
// needOpenComment: "",
// onlyFansCanComment: "",
// thumbUrl: "",
// },
// ];
// getPageF(data.page);
// } else {
// proxy.$message.error("修改图文出错:" + response.data.msg);
// }
// })
// .catch(() => {
// data.addMaterialLoading = false;
// });
}
}
function subVideo() {
proxy.$refs.uploadForm.validate((valid) => {
if (valid) {
uploadVideo.value.submit()
} else {
return false
}
})
}
function handleAddVideo() {
data.dialogVideoVisible = true
}
function handleEditNews(item) {
data.hackResetEditor = false //销毁组件
nextTick(() => {
data.hackResetEditor = true //重建组件
})
if (data.operateMaterial == 'add') {
data.isActiveAddNews = 0
}
data.operateMaterial = 'edit'
data.articlesAdd = JSON.parse(JSON.stringify(item.content.articles))
data.articlesMediaId = item.mediaId
data.dialogNewsVisible = true
}
function handleAddNews() {
data.hackResetEditor = false //销毁组件
nextTick(() => {
data.hackResetEditor = true //重建组件
})
if (data.operateMaterial == 'edit') {
data.isActiveAddNews = 0
data.articlesAdd = [
{
title: '',
thumbMediaId: '',
author: '',
digest: '',
showCoverPic: '',
content: '',
contentSourceUrl: '',
needOpenComment: '',
onlyFansCanComment: '',
thumbUrl: ''
}
]
}
data.operateMaterial = 'add'
data.dialogNewsVisible = true
}
function beforeThumbImageUpload(file) {
data.addMaterialLoading = true
const isType = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/gif' || file.type === 'image/bmp' || file.type === 'image/jpg'
const isLt = file.size / 1024 / 1024 < 2
if (!isType) {
proxy.$message.error('上传图片格式不对!')
}
if (!isLt) {
proxy.$message.error('上传图片大小不能超过2M!')
}
data.addMaterialLoading = false
return isType && isLt
}
function beforeImageUpload(file) {
data.tableLoading = true
const isType = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/gif' || file.type === 'image/bmp' || file.type === 'image/jpg'
const isLt = file.size / 1024 / 1024 < 2
if (!isType) {
proxy.$message.error('上传图片格式不对!')
}
if (!isLt) {
proxy.$message.error('上传图片大小不能超过2M!')
}
data.tableLoading = false
return isType && isLt
}
function beforeVoiceUpload(file) {
data.tableLoading = true
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
if (!isType) {
proxy.$message.error('上传语音格式不对!')
}
if (!isLt) {
proxy.$message.error('上传语音大小不能超过2M!')
}
data.tableLoading = false
return isType && isLt
}
function beforeVideoUpload(file) {
data.addMaterialLoading = true
const isType = file.type === 'video/mp4'
const isLt = file.size / 1024 / 1024 < 10
if (!isType) {
proxy.$message.error('上传视频格式不对!')
}
if (!isLt) {
proxy.$message.error('上传视频大小不能超过10M!')
}
data.addMaterialLoading = false
return isType && isLt
}
function delMaterial(item) {
proxy
.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
data.tableLoading = true
delObj({
id: item.mediaId
})
.then((response) => {
data.tableLoading = false
if (response.code == 200) {
getPageF(data.page)
} else {
data.tableLoading = false
proxy.$message.error('删除出错:' + response.data.msg)
}
})
.catch(() => {
data.tableLoading = false
})
})
}
function handleUploadError(response) {
proxy.$message.error('上传出错:' + response)
}
function handleUploadSuccess(response, file, fileList) {
data.tableLoading = false
data.addMaterialLoading = false
if (response.code == 200) {
data.dialogVideoVisible = false
data.fileList = []
data.uploadData.title = ''
data.uploadData.introduction = ''
if (data.materialType != 'news') {
data.page.currentPage = 1
getPageF(data.page)
} else {
data.articlesAdd[data.isActiveAddNews].thumbMediaId = response.data.mediaId
data.articlesAdd[data.isActiveAddNews].thumbUrl = response.data.url
}
} else {
data.fileList = []
proxy.$message.error('上传出错:' + response.msg)
}
}
function handleClick(tab, event) {
const materialType = tab.paneName
data.page.currentPage = 1
data.materialType = materialType
getPageF(data.page)
data.uploadData.mediaType = materialType
if (materialType == 'news') {
data.uploadData.mediaType = 'image'
}
}
function getPageF(page, params) {
data.tableData = []
data.tableLoading = true
getPage(
Object.assign(
{
current: page.currentPage,
size: page.pageSize,
type: data.materialType
},
params
)
)
.then((response) => {
data.tableData = response.data.items
data.page.total = response.data.totalCount
data.page.currentPage = page.currentPage
data.page.pageSize = page.pageSize
data.tableLoading = false
})
.catch(() => {
data.tableLoading = false
})
}
function sizeChange(val) {
data.page.currentPage = 1
data.page.pageSize = val
getPageF(data.page)
}
function currentChange(val) {
data.page.currentPage = val
getPageF(data.page)
}
function refreshChange(page) {
getPageF(data.page)
}
</script>
<style lang="scss" scoped>
.pagination {
float: right;
margin-right: 25px;
}
.add_but {
padding: 10px;
}
.ope-row {
margin-top: 5px;
text-align: center;
border-top: 1px solid #eaeaea;
padding-top: 5px;
}
.item-name {
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center;
}
.el-upload__tip {
margin-left: 5px;
}
.left {
display: inline-block;
width: 35%;
vertical-align: top;
margin-top: 200px;
}
.right {
display: inline-block;
width: 60%;
margin-top: -40px;
}
.avatar-uploader {
width: 20%;
display: inline-block;
}
.avatar-uploader .el-upload {
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
text-align: unset !important;
}
.avatar-uploader .el-upload:hover {
border-color: #409eff;
}
.avatar-uploader-icon {
border: 1px solid #d9d9d9;
font-size: 28px;
color: #8c939d;
width: 120px;
height: 120px;
line-height: 120px;
text-align: center;
}
.avatar {
width: 230px;
height: 120px;
}
.avatar1 {
width: 120px;
height: 120px;
}
.digest {
width: 60%;
display: inline-block;
vertical-align: top;
}
.waterfall {
width: 100%;
column-gap: 10px;
column-count: 5;
margin: 0 auto;
}
.waterfall-item {
padding: 10px;
margin-bottom: 10px;
break-inside: avoid;
border: 1px solid #eaeaea;
}
p {
line-height: 30px;
}
@media (min-width: 992px) and (max-width: 1300px) {
.waterfall {
column-count: 3;
}
p {
color: red;
}
}
@media (min-width: 768px) and (max-width: 991px) {
.waterfall {
column-count: 2;
}
p {
color: orange;
}
}
@media (max-width: 767px) {
.waterfall {
column-count: 1;
}
}
.news-main {
background-color: #ffffff;
width: 100%;
margin: auto;
height: 120px;
}
.news-content {
background-color: #acadae;
width: 100%;
height: 120px;
position: relative;
}
.news-content-title {
display: inline-block;
font-size: 15px;
color: #ffffff;
position: absolute;
left: 0px;
bottom: 0px;
background-color: black;
width: 98%;
padding: 1%;
opacity: 0.65;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
height: 25px;
}
.news-main-item {
background-color: #ffffff;
padding: 5px 0px;
border-top: 1px solid #eaeaea;
width: 100%;
margin: auto;
}
.news-content-item {
position: relative;
margin-left: -3px;
}
.news-content-item-title {
display: inline-block;
font-size: 12px;
width: 70%;
}
.news-content-item-img {
display: inline-block;
width: 25%;
background-color: #acadae;
}
.input-tt {
padding: 5px;
}
.activeAddNews {
border: 5px solid #2bb673;
}
.news-main-plus {
width: 280px;
text-align: center;
margin: auto;
height: 50px;
}
.icon-plus {
margin: 10px;
font-size: 25px;
}
.select-item {
width: 60%;
padding: 10px;
margin: 0 auto 10px auto;
border: 1px solid #eaeaea;
}
.father .child {
display: none;
text-align: center;
position: relative;
bottom: 25px;
}
.father:hover .child {
display: block;
}
.thumb-div {
display: inline-block;
width: 30%;
text-align: center;
}
.thumb-but {
margin: 5px;
}
.material-img {
width: 100%;
height: 100%;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,564 @@
<template>
<div class="app-container">
<div class="public-account-management clearfix" v-loading="saveLoading">
<!--左边配置菜单-->
<div class="left">
<div class="weixin-hd">
<div class="weixin-title">
{{ menuName }}
</div>
</div>
<div class="weixin-menu menu_main clearfix">
<div class="menu_bottom" v-for="(item, i) of menu.button" :key="i">
<!-- 一级菜单 -->
<div @click="menuFun(i, item)" class="menu_item el-icon-s-fold" :class="{ active: isActive == i }">
{{ item.name }}
</div>
<!--以下为二级菜单-->
<div class="submenu" v-if="isSubMenuFlag == i">
<template v-for="(subItem, k) in item.sub_button" :key="k">
<div v-if="item.sub_button" class="subtitle menu_bottom">
<div class="menu_subItem" :class="{ active: isSubMenuActive == i + '' + k }" @click="subMenuFun(subItem, i, k)">
{{ subItem.name }}
</div>
</div>
</template>
<!-- 二级菜单加号 当长度 小于 5 才显示二级菜单的加号 -->
<div class="menu_bottom menu_addicon" v-if="!item.sub_button || item.sub_button.length < 5" @click="addSubMenu(i, item)">
<el-icon><Plus /></el-icon>
</div>
</div>
</div>
<!-- 一级菜单加号 -->
<div class="menu_bottom menu_addicon" v-if="menuKeyLength < 3" @click="addMenu">
<el-icon><Plus /></el-icon>
</div>
</div>
<div class="save_div">
<!--<el-button class="save_btn" type="warning" size="small" @click="saveFun">保存菜单</el-button>-->
<el-button class="save_btn" type="success" size="small" @click="saveAndReleaseFun">保存并发布菜单</el-button>
</div>
</div>
<!--右边配置-->
<div v-if="showRightFlag" class="right">
<div class="configure_page">
<div class="delete_btn">
<el-button size="small" type="danger" icon="el-icon-delete" @click="deleteMenu(tempObj)">删除当前菜单</el-button>
</div>
<div>
<span>菜单名称</span>
<el-input class="input_width" v-model="tempObj.name" placeholder="请输入菜单名称" :maxlength="nameMaxlength" clearable></el-input>
</div>
<div v-if="showConfigurContent">
<div class="menu_content">
<span>菜单内容</span>
<el-select v-model="tempObj.type" clearable placeholder="请选择" class="menu_option">
<el-option v-for="item in menuOptions" :label="item.label" :value="item.value" :key="item.value"></el-option>
</el-select>
</div>
<div class="configur_content" v-if="tempObj.type == 'view'">
<span>跳转链接</span>
<el-input class="input_width" v-model="tempObj.url" placeholder="请输入链接" clearable></el-input>
</div>
<div class="configur_content" v-if="tempObj.type == 'miniprogram'">
<div class="applet">
<span>小程序的appid</span>
<el-input class="input_width" v-model="tempObj.appid" placeholder="请输入小程序的appid" clearable></el-input>
</div>
<div class="applet">
<span>小程序的页面路径</span>
<el-input class="input_width" v-model="tempObj.pagepath" placeholder="请输入小程序的页面路径pages/index" clearable></el-input>
</div>
<div class="applet">
<span>备用网页</span>
<el-input class="input_width" v-model="tempObj.url" placeholder="不支持小程序的老版本客户端将打开本网页" clearable></el-input>
</div>
<p class="blue">tips:需要和公众号进行关联才可以把小程序绑定带微信菜单上哟</p>
</div>
<div class="configur_content" v-if="tempObj.type == 'article_view_limited'">
<el-row>
<div class="select-item" v-if="tempObj && tempObj.content && tempObj.content.articles">
<WxNews :objData="tempObj.content.articles"></WxNews>
<el-row class="ope-row">
<el-button type="danger" icon="el-icon-delete" circle @click="deleteTempObj"></el-button>
</el-row>
</div>
<div v-if="!tempObj.content || !tempObj.content.articles">
<el-row>
<el-col :span="24" style="text-align: center">
<el-button type="success" @click="openMaterial"
>素材库选择<el-icon class="el-icon--right"><CircleCheck /></el-icon
></el-button>
</el-col>
</el-row>
</div>
<el-dialog title="选择图文" v-model="dialogNewsVisible" width="90%">
<WxMaterialSelect :objData="{ repType: 'news' }" @selectMaterial="selectMaterial"></WxMaterialSelect>
</el-dialog>
</el-row>
</div>
<div class="configur_content" v-if="tempObj.type == 'click' || tempObj.type == 'scancode_waitmsg'">
<WxReplySelect v-model:objData="tempObj" v-if="hackResetWxReplySelect"></WxReplySelect>
</div>
</div>
</div>
</div>
<!--一进页面就显示的默认页面当点击左边按钮的时候就不显示了-->
<div v-if="!showRightFlag" class="right">
<p>请选择菜单配置</p>
</div>
</div>
</div>
</template>
<script setup name="WxMpMenuDetail">
import { save, saveAndRelease, getList } from '@/api/wxmp/wxmenu'
import WxReplySelect from '@/components/wx-reply/main.vue'
import WxNews from '@/components/wx-news/main.vue'
import WxMaterialSelect from '@/components/wx-material-select/main.vue'
import { computed, getCurrentInstance, nextTick, onMounted, reactive } from 'vue'
const { proxy } = getCurrentInstance()
const data = reactive({
showRightFlag: false, //右边配置显示默认详情还是配置详情
menu: {
button: []
}, //横向菜单
isActive: -1, // 一级菜单点中样式
isSubMenuActive: -1, // 一级菜单点中样式
isSubMenuFlag: -1, // 二级菜单显示标志
tempObj: {}, //右边临时变量,作为中间值牵引关系
tempSelfObj: {
//一些临时值放在这里进行判断如果放在tempObj由于引用关系menu也会多了多余的参数
},
visible2: false, //素材内容 "选择素材"按钮弹框显示隐藏
tableData: [], //素材内容弹框数据,
menuName: '',
showConfigurContent: true,
nameMaxlength: 0, //名称最大长度
menuOptions: [
{
value: 'view',
label: '跳转网页'
},
{
value: 'miniprogram',
label: '跳转小程序'
},
{
value: 'click',
label: '点击回复'
},
{
value: 'article_view_limited',
label: '跳转图文消息'
},
{
value: 'scancode_push',
label: '扫码直接返回结果'
},
{
value: 'scancode_waitmsg',
label: '扫码回复'
},
{
value: 'pic_sysphoto',
label: '系统拍照发图'
},
{
value: 'pic_photo_or_album',
label: '拍照或者相册'
},
{
value: 'pic_weixin',
label: '微信相册'
},
{
value: 'location_select',
label: '选择地理位置'
}
],
dialogNewsVisible: false,
saveLoading: false,
hackResetWxReplySelect: false
})
const {
showRightFlag,
menu,
isActive,
isSubMenuActive,
isSubMenuFlag,
tempObj,
tempSelfObj,
menuName,
showConfigurContent,
nameMaxlength,
menuOptions,
dialogNewsVisible,
saveLoading,
hackResetWxReplySelect
} = toRefs(data)
const menuKeyLength = computed(() => {
return data.menu.button.length
})
function onCreated() {
getMenuFun()
}
onCreated()
onMounted(() => {})
function deleteTempObj() {}
function openMaterial() {
data.dialogNewsVisible = true
}
function selectMaterial(item) {
if (item.content.articles.length > 1) {
proxy.$alert('您选择的是多图文,将默认跳转第一篇', '提示', {
confirmButtonText: '确定'
})
}
data.dialogNewsVisible = false
data.tempObj.article_id = item.articleId
data.tempObj.mediaName = item.name
data.tempObj.url = item.url
item.mediaType = data.tempObj.mediaType
item.content.articles = item.content.articles.slice(0, 1)
data.tempObj.content = item.content
}
function getMenuFun() {
getList().then((res) => {
data.menu.button = JSON.parse(res.msg).button
})
}
function handleClick(tab, event) {
data.tempObj.mediaType = tab.paneName
}
function saveAndReleaseFun() {
proxy
.$confirm('确定要保证并发布该菜单吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
data.saveLoading = true
saveAndRelease({
strWxMenu: data.menu
})
.then((response) => {
data.saveLoading = false
if (response.code == 200) {
proxy.$message({
showClose: true,
message: '发布成功',
type: 'success'
})
} else {
proxy.$message.error(response.data.msg)
}
})
.catch(() => {
data.saveLoading = false
})
})
.catch(() => {})
}
function menuFun(i, item) {
data.hackResetWxReplySelect = false //销毁组件
nextTick(() => {
data.hackResetWxReplySelect = true //重建组件
})
data.showRightFlag = true //右边菜单
data.tempObj = item //这个如果放在顶部flag会没有。因为重新赋值了。
data.tempSelfObj.grand = '1' //表示一级菜单
data.tempSelfObj.index = i //表示一级菜单索引
data.isActive = i //一级菜单选中样式
data.isSubMenuFlag = i //二级菜单显示标志
data.isSubMenuActive = -1 //二级菜单去除选中样式
data.nameMaxlength = 4
if (item.sub_button && item.sub_button.length > 0) {
data.showConfigurContent = false
} else {
data.showConfigurContent = true
}
}
function subMenuFun(subItem, index, k) {
data.hackResetWxReplySelect = false //销毁组件
nextTick(() => {
data.hackResetWxReplySelect = true //重建组件
})
data.showRightFlag = true //右边菜单
data.tempObj = subItem //将点击的数据放到临时变量,对象有引用作用
data.tempSelfObj.grand = '2' //表示二级菜单
data.tempSelfObj.index = index //表示一级菜单索引
data.tempSelfObj.secondIndex = k //表示二级菜单索引
data.isSubMenuActive = index + '' + k //二级菜单选中样式
data.isActive = -1 //一级菜单去除样式
data.showConfigurContent = true
data.nameMaxlength = 7
}
function addMenu() {
const menuKeyLengthTemp = menuKeyLength.value
const addButton = {
name: '菜单名称',
sub_button: []
}
data.menu.button[menuKeyLengthTemp] = addButton
menuFun(menuKeyLength.value - 1, addButton)
}
function addSubMenu(i, item) {
if (!item.sub_button || item.sub_button.length <= 0) {
item.sub_button = []
data.showConfigurContent = false
}
const subMenuKeyLength = item.sub_button.length //获取二级菜单key长度
const addButton = {
name: '子菜单名称'
}
item.sub_button[subMenuKeyLength] = addButton
subMenuFun(item.sub_button[subMenuKeyLength], i, subMenuKeyLength)
}
function deleteMenu(obj) {
proxy
.$confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
deleteData() // 删除菜单数据
data.tempObj = {}
data.showRightFlag = false
data.isActive = -1
data.isSubMenuActive = -1
})
.catch(() => {})
}
function deleteData() {
// 一级菜单的删除方法
if (data.tempSelfObj.grand == '1') {
data.menu.button.splice(data.tempSelfObj.index, 1)
}
// 二级菜单的删除方法
if (data.tempSelfObj.grand == '2') {
data.menu.button[data.tempSelfObj.index].sub_button.splice(data.tempSelfObj.secondIndex, 1)
}
proxy.$message({
type: 'success',
message: '删除成功!'
})
}
</script>
<style lang="scss" scoped="scoped">
.clearfix {
*zoom: 1;
}
.clearfix::after {
content: '';
display: table;
clear: both;
}
div {
text-align: left;
}
.weixin-hd {
color: #fff;
text-align: center;
position: relative;
bottom: 426px;
left: 0px;
width: 300px;
height: 64px;
background: transparent url(assets/menu_head.png) no-repeat 0 0;
background-position: 0 0;
background-size: 100%;
}
.weixin-title {
color: #fff;
font-size: 14px;
width: 100%;
text-align: center;
position: absolute;
top: 33px;
left: 0px;
}
.weixin-menu {
background: transparent url(assets/menu_foot.png) no-repeat 0 0;
padding-left: 43px;
font-size: 12px;
}
.menu_option {
width: 40% !important;
}
.public-account-management {
min-width: 1200px;
width: 1200px;
margin: 0 auto;
.left {
float: left;
display: inline-block;
width: 350px;
height: 715px;
background: url('assets/iphone_backImg.png') no-repeat;
background-size: 100% auto;
padding: 518px 25px 88px;
position: relative;
box-sizing: border-box;
/*第一级菜单*/
.menu_main {
.menu_bottom {
position: relative;
float: left;
display: inline-block;
box-sizing: border-box;
width: 85.5px;
text-align: center;
border: 1px solid #ebedee;
background-color: #fff;
cursor: pointer;
&.menu_addicon {
height: 46px;
line-height: 46px;
}
.menu_item {
height: 44px;
line-height: 44px;
text-align: center;
box-sizing: border-box;
width: 100%;
&.active {
border: 1px solid #2bb673;
}
}
.menu_subItem {
height: 44px;
line-height: 44px;
text-align: center;
box-sizing: border-box;
&.active {
border: 1px solid #2bb673;
}
}
}
i {
color: #2bb673;
}
/*第二级菜单*/
.submenu {
position: absolute;
width: 85.5px;
bottom: 45px;
.subtitle {
background-color: #fff;
box-sizing: border-box;
}
}
}
.save_div {
margin-top: 15px;
text-align: center;
.save_btn {
bottom: 20px;
left: 100px;
}
}
}
/*右边菜单内容*/
.right {
float: left;
width: 63%;
background-color: #e8e7e7;
padding: 20px;
margin-left: 20px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
.configure_page {
.delete_btn {
text-align: right;
margin-bottom: 15px;
}
.menu_content {
margin-top: 20px;
}
.configur_content {
margin-top: 20px;
background-color: #fff;
padding: 20px 10px;
border-radius: 5px;
}
.blue {
color: #29b6f6;
margin-top: 10px;
}
.applet {
margin-bottom: 20px;
span {
width: 20%;
}
}
.material {
.input_width {
width: 30%;
}
.el-textarea {
width: 80%;
}
}
}
}
}
</style>
<style lang="scss" scoped>
.public-account-management {
.el-input {
width: 70%;
margin-right: 2%;
}
}
</style>
<style lang="scss" scoped>
.pagination {
text-align: right;
margin-right: 25px;
}
.select-item {
width: 280px;
padding: 10px;
margin: 0 auto 10px auto;
border: 1px solid #eaeaea;
}
.select-item2 {
padding: 10px;
margin: 0 auto 10px auto;
border: 1px solid #eaeaea;
}
.ope-row {
padding-top: 10px;
text-align: center;
}
.item-name {
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center;
}
</style>

View File

@@ -0,0 +1,222 @@
<template>
<div>
<avue-crud
ref="crud"
:page="page"
:data="tableData"
:permission="permissionList"
:table-loading="tableLoading"
:option="tableOption"
@on-load="getPageF"
@refresh-change="refreshChange"
@row-update="handleUpdate"
@row-save="handleSave"
@row-del="handleDel"
@sort-change="sortChange"
@search-change="searchChange"
>
<template #appName="scope">
<el-tag type="success" size="small">{{ scope.row.appName }}</el-tag>
</template>
<template #nickName="scope">
<el-badge :value="scope.row.countMsg" class="count-msg">
<img class="head-img" :src="scope.row.headimgUrl" />
<div class="nick-name">{{ scope.row.nickName }}</div>
</el-badge>
</template>
<template #readFlag="scope">
<el-tag :type="scope.row.readFlag == '1' ? 'success' : 'danger'" size="small">{{ scope.row.$readFlag }}</el-tag>
</template>
<template #repContent="scope">
<div v-if="scope.row.repType == 'event' && scope.row.repEvent == 'subscribe'">
<el-tag type="success" size="small">关注</el-tag>
</div>
<div v-if="scope.row.repType == 'event' && scope.row.repEvent == 'unsubscribe'">
<el-tag type="danger" size="small">取消关注</el-tag>
</div>
<div v-if="scope.row.repType == 'event' && scope.row.repEvent == 'CLICK'"><el-tag size="small">点击菜单</el-tag>{{ scope.row.repName }}</div>
<div v-if="scope.row.repType == 'event' && scope.row.repEvent == 'VIEW'"><el-tag size="small">点击菜单链接</el-tag>{{ scope.row.repUrl }}</div>
<div v-if="scope.row.repType == 'event' && scope.row.repEvent == 'scancode_waitmsg'">
<el-tag size="small">扫码结果</el-tag>{{ scope.row.repContent }}
</div>
<div v-if="scope.row.repType == 'text'">{{ scope.row.repContent }}</div>
<div v-if="scope.row.repType == 'image'">
<a target="_blank" :href="scope.row.repUrl"><img :src="scope.row.repUrl" style="width: 100px" /></a>
</div>
<div v-if="scope.row.repType == 'voice'">
<WxVoicePlayer :objData="scope.row"></WxVoicePlayer>
</div>
<div v-if="scope.row.repType == 'video'">
<WxVideoPlayer :objData="scope.row" style="margin-top: 40px"></WxVideoPlayer>
</div>
<div v-if="scope.row.repType == 'shortvideo'">
<WxVideoPlayer :objData="scope.row" style="margin-top: 40px"></WxVideoPlayer>
</div>
<div v-if="scope.row.repType == 'link'">
<el-tag size="small">链接</el-tag><a :href="scope.row.repUrl" target="_blank">{{ scope.row.repName }}</a>
</div>
</template>
<template #menu="scope">
<el-button link icon="el-icon-chat-line-round" type="primary" plain @click="wxMsgDo(scope.row, scope.index)">消息</el-button>
</template>
</avue-crud>
<el-dialog title="用户消息" v-model="dialogMsgVisible" width="700px">
<WxMsg :wxUserId="wxUserId" v-if="dialogMsgVisible"></WxMsg>
</el-dialog>
</div>
</template>
<script setup name="WxMpMsg">
import { getPage, getObj, addObj, putObj, delObj } from '@/api/wxmp/wxmsg'
import { tableOption } from '@/const/crud/wxmp/wxmsg'
import { checkPermi, checkRole } from '@/utils/permission'
import WxMsg from '@/components/wx-msg/main.vue'
import WxVideoPlayer from '@/components/wx-video-play/main.vue'
import WxVoicePlayer from '@/components/wx-voice-play/main.vue'
const { proxy } = getCurrentInstance()
const data = reactive({
dialogMsgVisible: false,
tableData: [],
page: {
total: 0, // 总页数
currentPage: 1, // 当前页数
pageSize: 20, // 每页显示多少条
ascs: [], //升序字段
descs: 'create_time' //降序字段
},
paramsSearch: {},
tableLoading: false,
wxUserId: ''
})
const { tableData, tableLoading, page, dialogMsgVisible, wxUserId } = toRefs(data)
const permissionList = computed(() => {
return {
addBtn: checkPermi(['wxmp:wxmsg:add']),
delBtn: checkPermi(['wxmp:wxmsg:del']),
editBtn: checkPermi(['wxmp:wxmsg:edit']),
viewBtn: checkPermi(['wxmp:wxmsg:get'])
}
})
function wxMsgDo(row) {
data.wxUserId = row.wxUserId
data.dialogMsgVisible = true
row.readFlag = '1'
}
function searchChange(params, done) {
params = proxy.filterForm(params)
data.paramsSearch = params
data.page.currentPage = 1
getPageF(data.page, params)
done()
}
function sortChange(val) {
const prop = val.prop ? val.prop.replace(/([A-Z])/g, '_$1').toLowerCase() : ''
if (val.order == 'ascending') {
data.page.descs = []
data.page.ascs = prop
} else if (val.order == 'descending') {
data.page.ascs = []
data.page.descs = prop
} else {
data.page.ascs = []
data.page.descs = []
}
getPageF(data.page)
}
function getPageF(page, params) {
data.tableLoading = true
getPage(
Object.assign(
{
current: page.currentPage,
size: page.pageSize,
descs: data.page.descs,
ascs: data.page.ascs,
type: '1'
},
params,
data.paramsSearch
)
)
.then((response) => {
data.tableData = response.data.records
data.page.total = response.data.total
data.page.currentPage = page.currentPage
data.page.pageSize = page.pageSize
data.tableLoading = false
})
.catch(() => {
data.tableLoading = false
})
}
function handleDel(row, index) {
proxy
.$confirm('是否确认删除此数据', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(function () {
return delObj(row.id)
})
.then(() => {
proxy.$message({
showClose: true,
message: '删除成功',
type: 'success'
})
getPageF(data.page)
})
.catch(() => {})
}
function handleUpdate(row, index, done, loading) {
putObj(row)
.then(() => {
proxy.$message({
showClose: true,
message: '修改成功',
type: 'success'
})
done()
getPageF(data.page)
})
.catch(() => {
loading()
})
}
function handleSave(row, done, loading) {
addObj(row)
.then(() => {
proxy.$message({
showClose: true,
message: '添加成功',
type: 'success'
})
done()
getPageF(data.page)
})
.catch(() => {
loading()
})
}
function refreshChange(page) {
getPageF(data.page)
}
</script>
<style lang="scss" scoped>
.head-img {
width: 50px;
}
</style>

View File

@@ -0,0 +1,303 @@
<template>
<div>
<el-card class="box-card">
<template #header>
<div class="clearfix">
<span class="demonstration">时间范围</span>
<el-date-picker
format="YYYY-MM-DD"
v-model="dateValues"
type="daterange"
:picker-options="datePickerOptions"
@change="changeDate"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
>
</el-date-picker>
</div>
</template>
<el-row justify="center">
<el-col :span="12">
<div id="userSummaryChart" :style="{ width: '80%', height: '378px' }"></div>
</el-col>
<el-col :span="12">
<div id="userCumulateChart" :style="{ width: '80%', height: '378px' }"></div>
</el-col>
<el-col :span="12">
<div id="interfaceSummaryChart" :style="{ width: '80%', height: '378px' }"></div>
</el-col>
<el-col :span="12">
<div id="interfaceSummaryChart2" :style="{ width: '80%', height: '378px' }"></div>
</el-col>
</el-row>
</el-card>
</div>
</template>
<script setup name="WxMpSummary">
import { dayjs } from 'element-plus'
// 引入基本模板
import * as echarts from 'echarts'
import { getInterfaceSummary, getUserSummary, getUserCumulate } from '@/api/wxmp/wxsummary'
import { getCurrentInstance, onMounted, reactive } from 'vue'
const { proxy } = getCurrentInstance()
const data = reactive({
datePickerOptions: {
disabledDate(date) {
return date >= new Date(new Date() - 3600 * 1000 * 24)
}
},
startDate: dayjs().add(-7, 'days').format('YYYY-MM-DD'),
endDate: dayjs().add(-1, 'days').format('YYYY-MM-DD'),
dateValues: [dayjs().add(-7, 'days').format('YYYY-MM-DD'), dayjs().add(-1, 'days').format('YYYY-MM-DD')],
xAxisData: [],
seriesData1: [],
seriesData2: [],
seriesData3: [],
seriesData4: [],
seriesData5: [],
seriesData6: [],
seriesData7: []
})
const {
datePickerOptions,
startDate,
endDate,
dateValues,
xAxisData,
seriesData1,
seriesData2,
seriesData3,
seriesData4,
seriesData5,
seriesData6,
seriesData7
} = toRefs(data)
onMounted(() => {
getSummary()
})
function changeDate() {
const startDate = new Date(data.dateValues[0])
const endDate = new Date(data.dateValues[1])
if (dayjs(endDate).diff(dayjs(startDate), 'day') > 6) {
proxy.$message.error('时间间隔7天以内请重新选择')
return false
} else {
data.startDate = dayjs(startDate).format('YYYY-MM-DD')
data.endDate = dayjs(endDate).format('YYYY-MM-DD')
data.xAxisData = []
data.seriesData1 = []
data.seriesData2 = []
data.seriesData5 = []
data.seriesData6 = []
getSummary()
}
}
function getSummary() {
const days = dayjs(data.endDate).diff(dayjs(data.startDate), 'day') //相差天数
for (let i = 0; i <= days; i++) {
data.xAxisData.push(dayjs(data.startDate).add(i, 'days').format('YYYY-MM-DD'))
data.seriesData1.push(0)
data.seriesData2.push(0)
data.seriesData5.push(0)
data.seriesData6.push(0)
}
getUserSummary({
startDate: data.startDate,
endDate: data.endDate
})
.then((response) => {
data.xAxisData.forEach((item, index, arr) => {
response.data.forEach((item2, index2, arr2) => {
if (item2.refDate.indexOf(item) >= 0) {
data.seriesData5[index] = data.seriesData5[index] + item2.newUser
data.seriesData6[index] = data.seriesData6[index] + item2.cancelUser
}
})
})
// 基于准备好的dom初始化echarts实例
const userSummaryChart = echarts.init(document.getElementById('userSummaryChart'))
// 绘制图表
userSummaryChart.setOption({
title: { text: '用户增减数据' },
color: ['#67C23A', '#F56C6C'],
legend: {
data: ['新增用户', '取消关注的用户']
},
tooltip: {},
xAxis: {
data: data.xAxisData
},
yAxis: {},
series: [
{
name: '新增用户',
type: 'bar',
label: {
normal: {
show: true
}
},
barGap: 0,
data: data.seriesData5
},
{
name: '取消关注的用户',
type: 'bar',
label: {
normal: {
show: true
}
},
data: data.seriesData6
}
]
})
})
.catch(() => {})
getUserCumulate({
startDate: data.startDate,
endDate: data.endDate
})
.then((response) => {
response.data.forEach((item, index, arr) => {
data.seriesData7[index] = item.cumulateUser
})
// 基于准备好的dom初始化echarts实例
const userCumulateChart = echarts.init(document.getElementById('userCumulateChart'))
// 绘制图表
userCumulateChart.setOption({
title: { text: '累计用户数据' },
legend: {
data: ['累计用户量']
},
xAxis: {
type: 'category',
data: data.xAxisData
},
yAxis: {
type: 'value'
},
tooltip: {
trigger: 'axis'
},
series: [
{
name: '累计用户量',
data: data.seriesData7,
type: 'line',
smooth: true
}
]
})
})
.catch(() => {})
//获取接口数据
getInterfaceSummary({
startDate: data.startDate,
endDate: data.endDate
})
.then((response) => {
response.data.forEach((item, index, arr) => {
data.seriesData1[index] = item.callbackCount
data.seriesData2[index] = item.maxTimeCost
data.seriesData3[index] = item.totalTimeCost
data.seriesData4[index] = item.failCount
})
// 基于准备好的dom初始化echarts实例
const interfaceSummaryChart = echarts.init(document.getElementById('interfaceSummaryChart'))
// 绘制图表
interfaceSummaryChart.setOption({
title: { text: '接口分析数据' },
color: ['#67C23A', '#F56C6C'],
legend: {
data: ['被动回复用户消息的次数', '失败次数']
},
tooltip: {},
xAxis: {
data: data.xAxisData
},
yAxis: {},
series: [
{
name: '被动回复用户消息的次数',
type: 'bar',
label: {
normal: {
show: true
}
},
barGap: 0,
data: data.seriesData1
},
{
name: '失败次数',
type: 'bar',
label: {
normal: {
show: true
}
},
data: data.seriesData4
}
]
})
// 基于准备好的dom初始化echarts实例
const interfaceSummaryChart2 = echarts.init(document.getElementById('interfaceSummaryChart2'))
// 绘制图表
interfaceSummaryChart2.setOption({
title: { text: '接口分析数据' },
color: ['#E6A23C', '#409EFF'],
legend: {
data: ['最大耗时', '总耗时']
},
tooltip: {},
xAxis: {
data: data.xAxisData
},
yAxis: {},
series: [
{
name: '最大耗时',
type: 'bar',
label: {
normal: {
show: true
}
},
data: data.seriesData2
},
{
name: '总耗时',
type: 'bar',
label: {
normal: {
show: true
}
},
data: data.seriesData3
}
]
})
})
.catch(() => {})
}
</script>
<style lang="scss" scoped>
.demonstration {
font-size: 15px;
margin-right: 10px;
}
</style>

View File

@@ -0,0 +1,382 @@
<template>
<div>
<avue-crud
ref="crud"
:page="page"
:data="tableData"
:table-loading="tableLoading"
:option="tableOption"
:permission="permissionList"
@on-load="getPageF"
@refresh-change="refreshChange"
@row-update="handleUpdate"
@row-save="handleSave"
@row-del="handleDel"
@sort-change="sortChange"
@search-change="searchChange"
@selection-change="selectionChange"
>
<template #subscribe="scope">
<el-tag size="small" effect="dark" :type="scope.row.subscribe == '1' ? 'success' : scope.row.subscribe == '0' ? 'danger' : 'warning'">
{{ scope.row.$subscribe }}
</el-tag>
</template>
<template #sex="scope">
<el-tag size="small" effect="light" :type="scope.row.sex == '1' ? 'success' : scope.row.sex == '2' ? 'danger' : 'warning'">
{{ scope.row.$sex || '未知' }}
</el-tag>
</template>
<template #latitude="scope">
<el-link
v-if="scope.row.longitude"
type="primary"
target="_blank"
:href="
'https://map.qq.com/?type=marker&isopeninfowin=1&markertype=1&pointx=' +
scope.row.longitude +
'&pointy=' +
scope.row.latitude +
'&name=' +
scope.row.nickName +
'&ref=joolun'
"
>
<i class="el-icon-map-location"></i>
</el-link>
</template>
<template #menu-left>
<el-button
type="success"
@click="
dialogTagging = true
taggingType = 'tagging'
"
icon="el-icon-document"
v-hasPermi="['wxmp:wxuser:tagging']"
>打标签
</el-button>
<el-button
type="warning"
@click="
dialogTagging = true
taggingType = 'unTagging'
"
icon="el-icon-document"
v-hasPermi="['wxmp:wxuser:tagging']"
>去除标签
</el-button>
<el-button type="danger" @click="synchroWxUserF" icon="el-icon-refresh" v-hasPermi="['wxmp:wxuser:synchro']">同步用户 </el-button>
<el-dialog :title="taggingType == 'tagging' ? '请选择要打的标签' : '请选择要去除的标签'" v-model="dialogTagging" width="30%">
<el-checkbox-group v-model="checkedTags">
<el-checkbox v-for="tag in userTagsData" :label="tag.id" :key="tag.id">{{ tag.name }} </el-checkbox>
</el-checkbox-group>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogTagging = false"> </el-button>
<el-button type="primary" @click="toTagging"> </el-button>
</span>
</template>
</el-dialog>
</template>
<template #menu="scope">
<el-button link v-hasPermi="['wxmp:wxuser:edit:remark']" icon="el-icon-edit" type="primary" plain @click="updateRemarkF(scope.row, scope.index)"
>修改备注
</el-button>
<el-button link v-hasPermi="['wxmp:wxuser:index']" icon="el-icon-chat-line-round" type="primary" plain @click="wxMsgDo(scope.row, scope.index)"
>消息
</el-button>
</template>
<template #tagidListSearch="scope">
<el-select v-model="scope.row.tagidList" placeholder="请选择">
<el-option v-for="item in userTagsData" :key="item.id" :label="item.name" :value="item.id"> </el-option>
</el-select>
</template>
</avue-crud>
<el-dialog title="用户消息" v-model="dialogMsgVisible" width="700px">
<WxMsg :wxUserId="wxUserId" v-if="dialogMsgVisible"></WxMsg>
</el-dialog>
</div>
</template>
<script setup name="WxMpUser">
import { checkPermi, checkRole } from '@/utils/permission'
import { getPage, getObj, addObj, putObj, delObj, synchroWxUser, updateRemark, tagging } from '@/api/wxmp/wxuser'
import { getList as listUserTags } from '@/api/wxmp/wxusertags'
import { tableOption } from '@/const/crud/wxmp/wxuser'
import WxMsg from '@/components/wx-msg/main.vue'
const { proxy } = getCurrentInstance()
const crud = ref(null)
const data = reactive({
tableData: [],
page: {
total: 0, // 总页数
currentPage: 1, // 当前页数
pageSize: 20, // 每页显示多少条
ascs: [],
descs: 'subscribe_time'
},
paramsSearch: {},
tableLoading: false,
selectionData: [],
dialogTagging: false,
checkedTags: [],
userTagsData: [],
taggingType: '',
dialogMsgVisible: false,
wxUserId: ''
})
const { tableData, tableLoading, page, dialogTagging, checkedTags, userTagsData, taggingType, dialogMsgVisible, wxUserId } = toRefs(data)
const permissionList = computed(() => {
return {
addBtn: checkPermi(['wxmp:wxuser:add']),
delBtn: checkPermi(['wxmp:wxuser:del']),
editBtn: checkPermi(['wxmp:wxuser:edit']),
viewBtn: checkPermi(['wxmp:wxuser:get'])
}
})
onMounted(() => {
listUserTagsF()
})
function listUserTagsF() {
data.tableLoading = true
listUserTags()
.then((response) => {
if (response.code == 200) {
const userTagsData = response.data
data.userTagsData = userTagsData
crud.value.DIC.tagidList = userTagsData
} else {
proxy.$message.error('获取用户标签出错:' + response.msg)
}
data.tableLoading = false
getPageF(data.page)
})
.catch(() => {
data.tableLoading = false
getPageF(data.page)
})
}
function wxMsgDo(row) {
data.wxUserId = row.id
data.dialogMsgVisible = true
}
function toTagging() {
const openIdList = []
for (let i = 0; i < data.selectionData.length; i++) {
openIdList.push(data.selectionData[i].openId)
}
if (data.checkedTags.length <= 0) {
proxy.$message.error('请选择标签')
return
}
if (openIdList.length <= 0) {
proxy.$message.error('请选择用户')
return
}
data.tableLoading = true
tagging({
taggingType: data.taggingType,
tagIds: data.checkedTags,
openIds: openIdList
})
.then((response) => {
data.tableLoading = false
data.checkedTags = []
crud.value.selectClear()
if (response.code == 200) {
getPageF(data.page)
} else {
proxy.$message.error('打标签出错:' + response.msg)
}
})
.catch(() => {
data.tableLoading = false
})
data.dialogTagging = false
}
function selectionChange(list) {
data.selectionData = list
}
function searchChange(params, done) {
params = proxy.filterForm(params)
data.paramsSearch = params
data.page.currentPage = 1
getPageF(data.page, params)
done()
}
function synchroWxUserF() {
proxy
.$confirm('同步用户需要一定时间,用户量越大、用时越久,请耐心等待,勿重复提交;确认此操作吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
data.tableLoading = true
synchroWxUser()
.then((response) => {
data.tableLoading = false
if (response.code == 200) {
getPageF(data.page)
} else {
proxy.$message.error('同步微信用户出错:' + response.msg)
}
})
.catch(() => {
data.tableLoading = false
})
setTimeout(() => {
if (data.tableLoading) {
data.tableLoading = false
proxy.$alert('同步请求发送成功,系统正在处理中,请稍后刷新查看同步结果,用户量越大用时越久,请耐心等待,勿重复提交', '提示', {
confirmButtonText: '确定'
})
}
}, 3000)
})
.catch(() => {})
}
function sortChange(val) {
const prop = val.prop ? val.prop.replace(/([A-Z])/g, '_$1').toLowerCase() : ''
if (val.order == 'ascending') {
data.page.descs = []
data.page.ascs = prop
} else if (val.order == 'descending') {
data.page.ascs = []
data.page.descs = prop
} else {
data.page.ascs = []
data.page.descs = []
}
getPageF(data.page)
}
function getPageF(page, params) {
data.tableLoading = true
getPage(
Object.assign(
{
current: page.currentPage,
size: page.pageSize,
descs: data.page.descs,
ascs: data.page.ascs,
appType: '2'
},
params,
data.paramsSearch
)
)
.then((response) => {
data.tableData = response.data.records
data.page.total = response.data.total
data.page.currentPage = page.currentPage
data.page.pageSize = page.pageSize
data.tableLoading = false
})
.catch(() => {
data.tableLoading = false
})
}
function updateRemarkF(row, index) {
proxy
.$prompt('请输入备注', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /\S/,
inputErrorMessage: '输入不能为空'
})
.then(({ value }) => {
data.tableLoading = true
row.remark = value
updateRemark(row)
.then((response) => {
data.tableLoading = false
if (response.code == 200) {
data.tableData.splice(index, 1, Object.assign({}, row))
proxy.$message({
showClose: true,
message: '修改成功',
type: 'success'
})
getPageF(data.page)
} else {
proxy.$message.error(response.msg)
}
})
.catch(() => {
data.tableLoading = false
})
})
}
function handleDel(row, index) {
proxy
.$confirm('是否确认删除', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(function () {
return delObj(row.id)
})
.then(() => {
proxy.$message({
showClose: true,
message: '删除成功',
type: 'success'
})
getPageF(data.page)
})
.catch(() => {})
}
function handleUpdate(row, index, done, loading) {
putObj(row)
.then(() => {
proxy.$message({
showClose: true,
message: '修改成功',
type: 'success'
})
done()
getPageF(data.page)
})
.catch(() => {
loading()
})
}
function handleSave(row, done, loading) {
addObj(row)
.then(() => {
proxy.$message({
showClose: true,
message: '添加成功',
type: 'success'
})
done()
getPageF(data.page)
})
.catch(() => {
loading()
})
}
function refreshChange(page) {
getPageF(data.page)
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,151 @@
<template>
<div>
<avue-crud
ref="crud"
:page="page"
:data="tableData"
:table-loading="tableLoading"
:option="tableOption"
@on-load="getPage"
@refresh-change="refreshChange"
@row-update="handleUpdate"
@row-save="handleSave"
@row-del="handleDel"
@sort-change="sortChange"
@search-change="searchChange"
>
</avue-crud>
<el-dialog title="用户消息" v-model="dialogMsgVisible" width="40%">
<WxMsg :wxUserId="wxUserId" v-if="dialogMsgVisible"></WxMsg>
</el-dialog>
</div>
</template>
<script setup name="WxMpUserTags">
import WxMsg from '@/components/wx-msg/main.vue'
import { getList, addObj, putObj, delObj } from '@/api/wxmp/wxusertags'
import { tableOption } from '@/const/crud/wxmp/wxusertags'
import { checkPermi, checkRole } from '@/utils/permission'
const { proxy } = getCurrentInstance()
const data = reactive({
tableData: [],
page: {
total: 0, // 总页数
currentPage: 1, // 当前页数
pageSize: 20, // 每页显示多少条
ascs: [],
descs: 'subscribe_time'
},
paramsSearch: {},
tableLoading: false,
dialogMsgVisible: false,
wxUserId: ''
})
const { page, tableData, tableLoading, dialogMsgVisible, wxUserId } = toRefs(data)
const permissionList = computed(() => {
return {
addBtn: checkPermi(['wxmp:wxusertags:add']),
delBtn: checkPermi(['wxmp:wxusertags:del']),
editBtn: checkPermi(['wxmp:wxusertags:edit']),
viewBtn: checkPermi(['wxmp:wxusertags:get'])
}
})
function searchChange(params, done) {
params = proxy.filterForm(params)
data.paramsSearch = params
data.page.currentPage = 1
getPage(data.page, params)
done()
}
function sortChange(val) {
const prop = val.prop ? val.prop.replace(/([A-Z])/g, '_$1').toLowerCase() : ''
if (val.order == 'ascending') {
data.page.descs = []
data.page.ascs = prop
} else if (val.order == 'descending') {
data.page.ascs = []
data.page.descs = prop
} else {
data.page.ascs = []
data.page.descs = []
}
getPage(data.page)
}
function getPage(page, params) {
data.tableLoading = true
getList(Object.assign({}, params, data.paramsSearch))
.then((response) => {
data.tableData = response.data
data.tableLoading = false
})
.catch(() => {
data.tableLoading = false
})
}
function handleDel(row, index) {
proxy
.$confirm('是否确认删除', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(function () {
return delObj({
id: row.id
})
})
.then(() => {
proxy.$message({
showClose: true,
message: '删除成功',
type: 'success'
})
getPage(data.page)
})
.catch(() => {})
}
function handleUpdate(row, index, done, loading) {
putObj(Object.assign({}, row))
.then(() => {
proxy.$message({
showClose: true,
message: '修改成功',
type: 'success'
})
done()
getPage(data.page)
})
.catch(() => {
loading()
})
}
function handleSave(row, done, loading) {
addObj(Object.assign({}, row))
.then(() => {
proxy.$message({
showClose: true,
message: '添加成功',
type: 'success'
})
done()
getPage(data.page)
})
.catch(() => {
loading()
})
}
function refreshChange(page) {
getPage(data.page)
}
</script>
<style lang="scss" scoped></style>