Compare commits

...

3 Commits

Author SHA1 Message Date
mkaoj697q f39e625538 李1
2 months ago
李嫚嫚 cc2f535039
2 months ago
李嫚嫚 553be20612
2 months ago

@ -1,33 +1,61 @@
<!-- Vue组件的模板部分开始 -->
<template>
<!-- 定义一个div元素作为Vue应用的根容器并赋予其id为"app" -->
<div id="app">
<!-- 使用Vue Router的<router-view>组件来显示当前路由匹配的组件 -->
<!-- <transition>组件用于在路由切换时添加过渡效果name属性指定了过渡效果的名称 -->
<transition name="fade">
<router-view />
<router-view /> <!-- 此处将渲染与当前URL匹配的组件 -->
</transition>
</div>
</template>
<!-- Vue组件的模板部分结束 -->
<style>
/* 为类名为image-sm的img元素设置最大宽度和高度 */
img.image-sm {
max-width: 80px;
/* 最大宽度为80像素 */
max-height: 80px;
/* 最大高度为80像素 */
}
/* 为el-col类下的el-select和el-date-editor元素设置宽度为100% */
/* 这通常用于Element UI框架中的布局和表单组件 */
.el-col .el-select,
.el-col .el-date-editor {
width: 100%;
/* 宽度占满父容器 */
}
/* 为类名为demo-table-expand的元素设置字体大小为0 */
/* 这可能是一个技巧,用于消除内部元素的默认间距 */
.demo-table-expand {
font-size: 0;
/* 字体大小设置为0 */
}
/* 为demo-table-expand类下的label元素设置宽度和颜色 */
.demo-table-expand label {
width: 90px;
/* 宽度为90像素 */
color: #99a9bf;
/* 颜色为深灰色 */
}
/* 为demo-table-expand类下的el-form-item元素设置外边距和宽度 */
/* 这通常用于表单布局,确保表单项正确对齐和分布 */
.demo-table-expand .el-form-item {
margin-right: 0;
/* 右侧外边距为0 */
margin-bottom: 0;
/* 底部外边距为0 */
width: 50%;
/* 宽度为父容器的50% */
}
/* 为类名为text-warning的元素设置文本颜色为警告色 */
.text-warning {
color: #e6a23c;
/* 颜色为橙色系的警告色 */
}
</style>

@ -1,32 +1,70 @@
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import VueCookie from 'vue-cookie'
// 从node_modules中引入Vue框架
import Vue from 'vue';
// 引入根组件App.vue
import App from './App.vue';
// 引入Vue Router实例用于管理前端路由
import router from './router';
// 引入Vuex状态管理实例
import store from './store';
// 引入vue-cookie插件用于在Vue应用中方便地操作cookie
import VueCookie from 'vue-cookie';
// 引入ElementUI组件库这是一个基于Vue的桌面端组件库
import ElementUI from 'element-ui';
import moment from 'moment'
// 引入moment.js库用于日期和时间的处理
import moment from 'moment';
// 引入ElementUI的样式文件这是ElementUI组件的默认样式
import 'element-ui/lib/theme-chalk/index.css';
import './assets/css/common.css'
import './assets/scss/index.scss'
import httpRequest from '@/utils/httpRequest' // api: https://github.com/axios/axios
import { isAuth } from '@/utils'
import VueClipboard from 'vue-clipboard2'
// 引入项目中自定义的common.css样式文件
import './assets/css/common.css';
// 引入项目中自定义的index.scss样式文件注意.scss文件需要sass-loader来编译
import './assets/scss/index.scss';
// 引入项目中自定义的httpRequest模块这个模块封装了axios用于发起HTTP请求
import httpRequest from '@/utils/httpRequest'; // @代表src目录的别名在webpack配置中定义
// 从utils模块中引入isAuth函数这个函数可能用于检查用户的登录状态或权限
import { isAuth } from '@/utils';
// 引入vue-clipboard2插件用于在Vue应用中方便地实现复制文本到剪贴板的功能
import VueClipboard from 'vue-clipboard2';
// 使用ElementUI组件库
Vue.use(ElementUI);
Vue.use(VueClipboard)
Vue.use(VueCookie)
Vue.config.productionTip = false
// 挂载全局
Vue.prototype.$http = httpRequest // ajax请求方法
Vue.prototype.isAuth = isAuth // 权限方法
// 使用vue-clipboard2插件
Vue.use(VueClipboard);
// 使用vue-cookie插件
Vue.use(VueCookie);
// 关闭Vue在启动时生成的生产提示
Vue.config.productionTip = false;
// 将httpRequest挂载到Vue的原型上这样在Vue组件中就可以通过this.$http来访问了
Vue.prototype.$http = httpRequest;
// 将isAuth函数挂载到Vue的原型上这样在Vue组件中就可以通过this.isAuth来访问了
Vue.prototype.isAuth = isAuth;
// 设置moment.js的语言环境为中文中国
moment.locale('zh-cn');
Vue.prototype.$moment = moment; //时间处理
// 将moment挂载到Vue的原型上这样在Vue组件中就可以通过this.$moment来访问了
Vue.prototype.$moment = moment;
// 创建Vue实例并传入router、store和渲染函数
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
router, // 路由实例
store, // 状态管理实例
// 渲染函数h是createElement函数的别名用于创建虚拟DOM节点
render: h => h(App) // 将App组件渲染为根节点
}).$mount('#app'); // 将Vue实例挂载到HTML中的id为app的元素上

@ -1,7 +1,9 @@
<template>
<main class="site-content" :class="{ 'site-content--tabs': $route.meta.isTab }">
<!-- 主入口标签页 s -->
<el-tabs v-if="$route.meta.isTab" v-model="mainTabsActiveName" :closable="true" @tab-click="selectedTabHandle" @tab-remove="removeTabHandle">
<!-- 如果当前路由元信息中包含isTab则显示标签页组件 -->
<el-tabs v-if="$route.meta.isTab" v-model="mainTabsActiveName" :closable="true" @tab-click="selectedTabHandle"
@tab-remove="removeTabHandle">
<!-- 标签页工具菜单包含关闭当前关闭其他关闭全部和刷新功能 -->
<el-dropdown class="site-tabs__tools" :show-timeout="0">
<i class="el-icon-arrow-down el-icon--right"></i>
<el-dropdown-menu slot="dropdown">
@ -11,17 +13,20 @@
<el-dropdown-item @click.native="refresh()">刷新当前标签页</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<!-- 循环渲染标签页 -->
<el-tab-pane v-for="item in mainTabs" :key="item.name" :label="item.title" :name="item.name">
<el-card :body-style="siteContentViewHeight">
<iframe v-if="item.type === 'iframe'" :src="item.iframeUrl" width="100%" height="100%" frameborder="0" scrolling="yes">
</iframe>
<!-- 如果标签页类型为iframe则渲染iframe -->
<iframe v-if="item.type === 'iframe'" :src="item.iframeUrl" width="100%" height="100%"
frameborder="0" scrolling="yes"></iframe>
<!-- 否则渲染router-view并使用keep-alive缓存组件 -->
<keep-alive v-else>
<router-view v-if="item.name === mainTabsActiveName" />
</keep-alive>
</el-card>
</el-tab-pane>
</el-tabs>
<!-- 主入口标签页 e -->
<!-- 如果当前路由元信息中不包含isTab则直接渲染router-view -->
<el-card v-else :body-style="siteContentViewHeight">
<keep-alive>
<router-view />
@ -33,63 +38,71 @@
<script>
import { isURL } from '@/utils/validate'
export default {
// refresh
inject: ['refresh'],
data() {
return {
//
}
},
computed: {
//
documentClientHeight: {
get() { return this.$store.state.common.documentClientHeight }
},
//
menuActiveName: {
get() { return this.$store.state.common.menuActiveName },
set(val) { this.$store.commit('common/updateMenuActiveName', val) }
},
//
mainTabs: {
get() { return this.$store.state.common.mainTabs },
set(val) { this.$store.commit('common/updateMainTabs', val) }
},
//
mainTabsActiveName: {
get() { return this.$store.state.common.mainTabsActiveName },
set(val) { this.$store.commit('common/updateMainTabsActiveName', val) }
},
//
siteContentViewHeight() {
var height = this.documentClientHeight - 50 - 30 - 2
if (this.$route.meta.isTab) {
height -= 40
// metaiframeUrlURL
return isURL(this.$route.meta.iframeUrl) ? { height: height + 'px' } : { minHeight: height + 'px' }
}
return { minHeight: height + 'px' }
}
},
methods: {
// tabs, tab
//
selectedTabHandle(tab) {
tab = this.mainTabs.filter(item => item.name === tab.name)
if (tab.length >= 1) {
this.$router.push({ name: tab[0].name, query: tab[0].query, params: tab[0].params })
}
},
// tabs, tab
// Vuex
removeTabHandle(tabName) {
this.$store.commit('common/removeTab', tabName)
},
// tabs,
//
tabsCloseCurrentHandle() {
this.removeTabHandle(this.mainTabsActiveName)
},
// tabs,
//
tabsCloseOtherHandle() {
this.mainTabs = this.mainTabs.filter(item => item.name === this.mainTabsActiveName)
},
// tabs,
//
tabsCloseAllHandle() {
this.mainTabs = []
this.menuActiveName = ''
this.$router.push({ name: 'home' })
},
// tabs,
// tabsRefreshCurrentHandlerefresh
tabsRefreshCurrentHandle() {
var tab = this.$route
this.removeTabHandle(tab.name)
@ -97,7 +110,7 @@ export default {
this.$router.push({ name: tab.name, query: tab.query, params: tab.params })
})
}
// refresh
}
}
</script>
</script>

@ -1,19 +1,27 @@
<template>
<!-- Element UI的对话框组件用于显示修改密码的表单 -->
<el-dialog title="修改密码" :visible.sync="visible" :append-to-body="true">
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="80px">
<!-- 表单组件用于数据绑定和验证 -->
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()"
label-width="80px">
<!-- 账号显示区域使用计算属性获取用户名 -->
<el-form-item label="账号">
<span>{{ userName }}</span>
</el-form-item>
<!-- 原密码输入框 -->
<el-form-item label="原密码" prop="password">
<el-input type="password" v-model="dataForm.password"></el-input>
</el-form-item>
<!-- 新密码输入框 -->
<el-form-item label="新密码" prop="newPassword">
<el-input type="password" v-model="dataForm.newPassword"></el-input>
</el-form-item>
<!-- 确认密码输入框带有自定义验证器以确保与新密码一致 -->
<el-form-item label="确认密码" prop="confirmPassword">
<el-input type="password" v-model="dataForm.confirmPassword"></el-input>
</el-form-item>
</el-form>
<!-- 对话框底部按钮区域 -->
<span slot="footer" class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="dataFormSubmit()"></el-button>
@ -22,9 +30,10 @@
</template>
<script>
import { clearLoginInfo } from '@/utils'
import { clearLoginInfo } from '@/utils' //
export default {
data() {
//
var validateConfirmPassword = (rule, value, callback) => {
if (this.dataForm.newPassword !== value) {
callback(new Error('确认密码与新密码不一致'))
@ -33,12 +42,15 @@ export default {
}
}
return {
//
visible: false,
//
dataForm: {
password: '',
newPassword: '',
confirmPassword: ''
},
//
dataRule: {
password: [
{ required: true, message: '原密码不能为空', trigger: 'blur' }
@ -54,49 +66,55 @@ export default {
}
},
computed: {
//
userName: {
get() { return this.$store.state.user.name }
},
//
mainTabs: {
get() { return this.$store.state.common.mainTabs },
set(val) { this.$store.commit('common/updateMainTabs', val) }
}
},
methods: {
//
//
init() {
this.visible = true
this.$nextTick(() => {
this.$refs['dataForm'].resetFields()
})
},
//
//
dataFormSubmit() {
//
this.$refs['dataForm'].validate((valid) => {
if (valid) {
//
this.$http({
url: this.$http.adornUrl('/sys/user/password'),
url: this.$http.adornUrl('/sys/user/password'), // URL
method: 'post',
data: this.$http.adornData({
data: this.$http.adornData({ //
'password': this.dataForm.password,
'newPassword': this.dataForm.newPassword
})
}).then(({ data }) => {
if (data && data.code === 200) {
//
this.$message({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
this.visible = false
this.visible = false //
this.$nextTick(() => {
this.mainTabs = []
clearLoginInfo()
this.$router.replace({ name: 'login' })
this.mainTabs = [] //
clearLoginInfo() //
this.$router.replace({ name: 'login' }) //
})
}
})
} else {
//
this.$message.error(data.msg)
}
})
@ -105,5 +123,4 @@ export default {
}
}
}
</script>
</script>

@ -1,102 +1,136 @@
<template>
<!-- 导航栏组件其类名根据navbarLayoutType动态设置 -->
<nav class="site-navbar" :class="'site-navbar--' + navbarLayoutType">
<!-- 导航栏头部 -->
<div class="site-navbar__header">
<!-- 点击h1元素将导航到首页 -->
<h1 class="site-navbar__brand" @click="$router.push({ name: 'home' })">
<!-- 大尺寸品牌标识 -->
<a class="site-navbar__brand-lg" href="javascript:;">微信管理系统</a>
<!-- 小尺寸品牌标识 -->
<a class="site-navbar__brand-mini" href="javascript:;">W</a>
</h1>
</div>
<!-- 导航栏主体 -->
<div class="site-navbar__body clearfix">
<!-- 左侧菜单用于折叠/展开侧边栏 -->
<el-menu class="site-navbar__menu" mode="horizontal">
<el-menu-item class="site-navbar__switch" index="0" @click="sidebarFold = !sidebarFold">
<i :class="sidebarFold?'el-icon-s-unfold':'el-icon-s-fold'"></i>
<!-- 根据sidebarFold的值动态显示折叠或展开图标 -->
<i :class="sidebarFold ? 'el-icon-s-unfold' : 'el-icon-s-fold'"></i>
</el-menu-item>
</el-menu>
<!-- 右侧菜单 -->
<el-menu class="site-navbar__menu site-navbar__menu--right" mode="horizontal">
<!-- 设置项 -->
<el-menu-item index="1" @click="$router.push({ name: 'theme' })">
<template slot="title">
<!-- 设置图标 -->
<i class="el-icon-setting"></i>
</template>
</el-menu-item>
<!-- 微信公众号选择器需权限验证 -->
<el-menu-item index="2" v-if="isAuth('wx:wxaccount:list')">
<template slot="title">
<!-- 微信公众号选择器组件 -->
<wx-account-selector></wx-account-selector>
</template>
</el-menu-item>
<!-- 用户头像下拉菜单 -->
<el-menu-item class="site-navbar__avatar" index="3">
<el-dropdown :show-timeout="0" placement="bottom">
<!-- 下拉菜单链接 -->
<span class="el-dropdown-link">
{{ userName }}
</span>
<!-- 下拉菜单内容 -->
<el-dropdown-menu slot="dropdown">
<!-- 修改密码项 -->
<el-dropdown-item @click.native="updatePasswordHandle()">修改密码</el-dropdown-item>
<!-- 退出项 -->
<el-dropdown-item @click.native="logoutHandle()">退出</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-menu-item>
</el-menu>
</div>
<!-- 弹窗, 修改密码 -->
<!-- 修改密码弹窗根据updatePassowrdVisible控制显示 -->
<update-password v-if="updatePassowrdVisible" ref="updatePassowrd"></update-password>
</nav>
</template>
<script>
//
import UpdatePassword from './main-navbar-update-password'
import WxAccountSelector from '@/components/wx-account-selector'
//
import { clearLoginInfo } from '@/utils'
export default {
data() {
return {
//
updatePassowrdVisible: false
}
},
components: {
UpdatePassword,WxAccountSelector
//
UpdatePassword, WxAccountSelector
},
computed: {
//
navbarLayoutType: {
get() { return this.$store.state.common.navbarLayoutType }
},
//
sidebarFold: {
get() { return this.$store.state.common.sidebarFold },
set(val) { this.$store.commit('common/updateSidebarFold', val) }
},
//
mainTabs: {
get() { return this.$store.state.common.mainTabs },
set(val) { this.$store.commit('common/updateMainTabs', val) }
},
//
userName: {
get() { return this.$store.state.user.name }
}
},
methods: {
//
//
updatePasswordHandle() {
//
this.updatePassowrdVisible = true
// DOM
this.$nextTick(() => {
this.$refs.updatePassowrd.init()
})
},
// 退
// 退
logoutHandle() {
//
this.$confirm(`确定进行[退出]操作?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 退
this.$http({
url: this.$http.adornUrl('/sys/logout'),
url: this.$http.adornUrl('/sys/logout'), // URL
method: 'post',
data: this.$http.adornData()
data: this.$http.adornData() //
}).then(({ data }) => {
//
if (data && data.code === 200) {
//
clearLoginInfo()
//
this.$router.push({ name: 'login' })
}
})
}).catch(() => { })
}).catch(() => { }) //
}
}
}
</script>
</script>

@ -1,50 +1,69 @@
<!-- 组件模板开始 -->
<template>
<el-submenu v-if="menu.list && menu.list.length >= 1" :index="menu.menuId + ''" :popper-class="'site-sidebar--' + sidebarLayoutSkin + '-popper'">
<!-- 如果menu对象有list属性且list数组长度大于等于1则渲染el-submenu -->
<el-submenu v-if="menu.list && menu.list.length >= 1" :index="menu.menuId + ''"
:popper-class="'site-sidebar--' + sidebarLayoutSkin + '-popper'">
<!-- 子菜单标题 -->
<template slot="title">
<!-- 图标使用menu对象的icon属性作为类名 -->
<i class="site-sidebar__menu-icon" :class="menu.icon"></i>
<!-- <icon-svg :name="menu.icon || ''" class="site-sidebar__menu-icon"></icon-svg> -->
<!-- 菜单名称 -->
<span>{{ menu.name }}</span>
</template>
<!-- 遍历menu的list属性为每个子菜单项渲染sub-menu组件 -->
<sub-menu v-for="item in menu.list" :key="item.menuId" :menu="item" :dynamicMenuRoutes="dynamicMenuRoutes">
</sub-menu>
</el-submenu>
<!-- 如果没有子菜单则渲染el-menu-item -->
<el-menu-item v-else :index="menu.menuId + ''" @click="gotoRouteHandle(menu)">
<!-- <icon-svg :name="menu.icon || ''" class="site-sidebar__menu-icon"></icon-svg> -->
<!-- 图标 -->
<i class="site-sidebar__menu-icon fa" :class="menu.icon"></i>
<!-- 菜单名称 -->
<span>{{ menu.name }}</span>
</el-menu-item>
</template>
<!-- 组件模板结束 -->
<script>
// SubMenu
import SubMenu from './main-sidebar-sub-menu'
export default {
//
name: 'sub-menu',
// props
props: {
menu: {
type: Object,
required: true
type: Object, // menu
required: true //
},
dynamicMenuRoutes: {
type: Array,
required: true
type: Array, //
required: true //
}
},
// 使
components: {
SubMenu
SubMenu // 使
},
//
computed: {
// Vuexstate
sidebarLayoutSkin: {
get() { return this.$store.state.common.sidebarLayoutSkin }
}
},
//
methods: {
// menuId()
// menuId
gotoRouteHandle(menu) {
// menu.menuId
var route = this.dynamicMenuRoutes.filter(item => item.meta.menuId === menu.menuId)
//
if (route.length >= 1) {
this.$router.push({ name: route[0].name })
}
}
}
}
</script>
</script>

@ -1,12 +1,21 @@
<template>
<!-- 侧边栏容器类名根据sidebarLayoutSkin动态设置 -->
<aside class="site-sidebar" :class="'site-sidebar--' + sidebarLayoutSkin">
<!-- 侧边栏内部容器 -->
<div class="site-sidebar__inner">
<el-menu :default-active="menuActiveName || 'home'" :collapse="sidebarFold" :collapseTransition="false" class="site-sidebar__menu">
<!-- Element UI的菜单组件用于构建导航菜单 -->
<el-menu :default-active="menuActiveName || 'home'" :collapse="sidebarFold" :collapseTransition="false"
class="site-sidebar__menu">
<!-- 首页菜单项 -->
<el-menu-item index="home" @click="$router.push({ name: 'home' })">
<!-- 图标 -->
<i class="site-sidebar__menu-icon el-icon-s-home"></i>
<!-- 菜单标题 -->
<span slot="title">首页</span>
</el-menu-item>
<sub-menu v-for="menu in menuList" :key="menu.menuId" :menu="menu" :dynamicMenuRoutes="dynamicMenuRoutes">
<!-- 遍历menuList为每个菜单项渲染sub-menu组件 -->
<sub-menu v-for="menu in menuList" :key="menu.menuId" :menu="menu"
:dynamicMenuRoutes="dynamicMenuRoutes">
</sub-menu>
</el-menu>
</div>
@ -14,77 +23,98 @@
</template>
<script>
// SubMenu
import SubMenu from './main-sidebar-sub-menu'
// isURL
import { isURL } from '@/utils/validate'
export default {
data() {
return {
//
dynamicMenuRoutes: []
}
},
components: {
SubMenu
SubMenu // SubMenu
},
computed: {
//
sidebarLayoutSkin: {
get() { return this.$store.state.common.sidebarLayoutSkin }
},
//
sidebarFold: {
get() { return this.$store.state.common.sidebarFold }
},
//
menuList: {
get() { return this.$store.state.common.menuList },
set(val) { this.$store.commit('common/updateMenuList', val) }
},
//
menuActiveName: {
get() { return this.$store.state.common.menuActiveName },
set(val) { this.$store.commit('common/updateMenuActiveName', val) }
},
//
mainTabs: {
get() { return this.$store.state.common.mainTabs },
set(val) { this.$store.commit('common/updateMainTabs', val) }
},
//
mainTabsActiveName: {
get() { return this.$store.state.common.mainTabsActiveName },
set(val) { this.$store.commit('common/updateMainTabsActiveName', val) }
}
},
watch: {
// routeHandle
$route: 'routeHandle'
},
created() {
// sessionStorage
this.menuList = JSON.parse(sessionStorage.getItem('menuList') || '[]')
this.dynamicMenuRoutes = JSON.parse(sessionStorage.getItem('dynamicMenuRoutes') || '[]')
//
this.routeHandle(this.$route)
},
methods: {
//
//
routeHandle(route) {
//
if (route.meta.isTab) {
// tab,
//
var tab = this.mainTabs.filter(item => item.name === route.name)[0]
//
if (!tab) {
//
if (route.meta.isDynamic) {
//
route = this.dynamicMenuRoutes.filter(item => item.name === route.name)[0]
//
if (!route) {
return console.error('未能找到可用标签页!')
}
}
//
tab = {
menuId: route.meta.menuId || route.name,
name: route.name,
title: route.meta.title,
type: isURL(route.meta.iframeUrl) ? 'iframe' : 'module',
iframeUrl: route.meta.iframeUrl || '',
params: route.params,
query: route.query
menuId: route.meta.menuId || route.name, // ID
name: route.name, //
title: route.meta.title, //
type: isURL(route.meta.iframeUrl) ? 'iframe' : 'module', // iframeUrlURL
iframeUrl: route.meta.iframeUrl || '', // iframe
params: route.params, //
query: route.query //
}
//
this.mainTabs = this.mainTabs.concat(tab)
}
//
this.menuActiveName = tab.menuId + ''
this.mainTabsActiveName = tab.name
}
}
}
}
</script>
</script>

@ -1,86 +1,120 @@
<!-- Vue组件的模板开始 -->
<template>
<div class="site-wrapper" :class="{ 'site-sidebar--fold': sidebarFold }" v-loading.fullscreen.lock="loading" element-loading-text="">
<!-- 外层容器根据sidebarFold的状态添加或移除'site-sidebar--fold' -->
<div class="site-wrapper" :class="{ 'site-sidebar--fold': sidebarFold }" <!-- loadingtrue -->
v-loading.fullscreen.lock="loading"
<!-- 自定义加载文本 -->
element-loading-text="拼命加载中">
<!-- 当loading不为true时渲染以下内容 -->
<template v-if="!loading">
<!-- 引入主导航栏组件 -->
<main-navbar />
<!-- 引入主侧边栏组件 -->
<main-sidebar />
<!-- 内容区域容器其最小高度由documentClientHeight决定 -->
<div class="site-content__wrapper" :style="{ 'min-height': documentClientHeight + 'px' }">
<!--
当store中的common.contentIsNeedRefresh为false时
渲染主内容区域组件
-->
<main-content v-if="!$store.state.common.contentIsNeedRefresh" />
</div>
</template>
</div>
<!-- Vue组件的模板结束 -->
</template>
<script>
import MainNavbar from './main-navbar'
import MainSidebar from './main-sidebar'
import MainContent from './main-content'
//
import MainNavbar from './main-navbar';
import MainSidebar from './main-sidebar';
import MainContent from './main-content';
export default {
//
provide() {
return {
//
//
refresh() {
this.$store.commit('common/updateContentIsNeedRefresh', true)
// contentIsNeedRefreshtrue
this.$store.commit('common/updateContentIsNeedRefresh', true);
// DOMcontentIsNeedRefreshfalse
this.$nextTick(() => {
this.$store.commit('common/updateContentIsNeedRefresh', false)
})
this.$store.commit('common/updateContentIsNeedRefresh', false);
});
}
}
};
},
//
data() {
return {
loading: true
}
loading: true // true
};
},
//
components: {
MainNavbar,
MainSidebar,
MainContent
},
//
computed: {
// store
documentClientHeight: {
get() { return this.$store.state.common.documentClientHeight },
set(val) { this.$store.commit('common/updateDocumentClientHeight', val) }
get() { return this.$store.state.common.documentClientHeight; },
set(val) { this.$store.commit('common/updateDocumentClientHeight', val); }
},
// store
sidebarFold: {
get() { return this.$store.state.common.sidebarFold }
get() { return this.$store.state.common.sidebarFold; }
},
// IDstoreuser
userId: {
get() { return this.$store.state.user.id },
set(val) { this.$store.commit('user/updateId', val) }
get() { return this.$store.state.user.id; },
set(val) { this.$store.commit('user/updateId', val); }
},
// storeuser
userName: {
get() { return this.$store.state.user.name },
set(val) { this.$store.commit('user/updateName', val) }
get() { return this.$store.state.user.name; },
set(val) { this.$store.commit('user/updateName', val); }
}
},
//
created() {
this.getUserInfo()
this.getUserInfo(); //
},
// DOM
mounted() {
this.resetDocumentClientHeight()
this.resetDocumentClientHeight(); //
},
//
methods: {
//
//
resetDocumentClientHeight() {
this.documentClientHeight = document.documentElement['clientHeight']
//
this.documentClientHeight = document.documentElement.clientHeight;
//
window.onresize = () => {
this.documentClientHeight = document.documentElement['clientHeight']
}
this.documentClientHeight = document.documentElement.clientHeight;
};
},
//
//
getUserInfo() {
// HTTP GET
this.$http({
url: this.$http.adornUrl('/sys/user/info'),
url: this.$http.adornUrl('/sys/user/info'), // adornUrlURL
method: 'get',
params: this.$http.adornParams()
params: this.$http.adornParams() // adornParams
}).then(({ data }) => {
//
if (data && data.code === 200) {
this.loading = false
this.userId = data.user.userId
this.userName = data.user.username
this.loading = false; //
this.userId = data.user.userId; // ID
this.userName = data.user.username; //
}
})
});
}
}
}
};
</script>

@ -1,27 +1,41 @@
<template>
<div class="mod-config">
<!-- 定义一个内联表单用于搜索和操作 -->
<el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
<el-form-item>
<!-- 输入框用于输入搜索关键词 -->
<el-input v-model="dataForm.key" placeholder="参数名" clearable></el-input>
</el-form-item>
<el-form-item>
<!-- 查询按钮触发数据列表的获取 -->
<el-button @click="getDataList()"></el-button>
<!-- 新增按钮根据权限显示 -->
<el-button v-if="isAuth('wx:wxaccount:save')" type="primary" @click="addOrUpdateHandle()"></el-button>
<el-button v-if="isAuth('wx:wxaccount:delete')" type="danger" @click="deleteHandle()" :disabled="dataListSelections.length <= 0"></el-button>
<!-- 批量删除按钮根据权限和选中项显示 -->
<el-button v-if="isAuth('wx:wxaccount:delete')" type="danger" @click="deleteHandle()"
:disabled="dataListSelections.length <= 0">批量删除</el-button>
</el-form-item>
</el-form>
<el-table :data="dataList" border v-loading="dataListLoading" @selection-change="selectionChangeHandle" style="width: 100%;">
<!-- 数据列表展示 -->
<el-table :data="dataList" border v-loading="dataListLoading" @selection-change="selectionChangeHandle"
style="width: 100%;">
<!-- 选择框列 -->
<el-table-column type="selection" header-align="center" align="center" width="50">
</el-table-column>
<!-- appid列 -->
<el-table-column prop="appid" header-align="center" align="center" label="appid">
</el-table-column>
<!-- 公众号名称列 -->
<el-table-column prop="name" header-align="center" align="center" label="公众号名称">
</el-table-column>
<!-- 类型列使用formatter格式化显示 -->
<el-table-column prop="type" header-align="center" align="center" label="类型" :formatter="accountTypeFormat">
</el-table-column>
<!-- 是否认证列使用插槽自定义显示 -->
<el-table-column prop="verified" header-align="center" align="center" label="是否认证">
<span slot-scope="scope">{{scope.row.verified?"是":"否"}}</span>
<span slot-scope="scope">{{ scope.row.verified ? "是" : "否" }}</span>
</el-table-column>
<!-- 操作列包含接入修改和删除按钮 -->
<el-table-column fixed="right" header-align="center" align="center" width="150" label="操作">
<template slot-scope="scope">
<el-button type="text" size="small" @click="accessInfo(scope.row)"></el-button>
@ -30,89 +44,83 @@
</template>
</el-table-column>
</el-table>
<!-- 弹窗, 新增 / 修改 -->
<!-- 新增/修改弹窗组件 -->
<add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList"></add-or-update>
<!-- 接入信息弹窗组件 -->
<account-access v-if="accountAccessVisible" ref="accountAccessDialog"></account-access>
</div>
</template>
<script>
import AddOrUpdate from './account/wx-account-add-or-update'
import AccountAccess from './account/wx-account-access-info'
import { mapState } from 'vuex'
import AddOrUpdate from './account/wx-account-add-or-update' // /
import AccountAccess from './account/wx-account-access-info' //
import { mapState } from 'vuex' // VuexmapState
export default {
data() {
return {
dataForm: {
key: ''
},
dataList: [],
dataListLoading: false,
dataListSelections: [],
addOrUpdateVisible: false,
accountAccessVisible:false
dataForm: { key: '' }, //
dataList: [], //
dataListLoading: false, //
dataListSelections: [], //
addOrUpdateVisible: false, // /
accountAccessVisible: false //
}
},
components: {
AddOrUpdate,AccountAccess
},
components: { AddOrUpdate, AccountAccess }, //
computed: mapState({
ACCOUNT_TYPES: state=>state.wxAccount.ACCOUNT_TYPES
ACCOUNT_TYPES: state => state.wxAccount.ACCOUNT_TYPES // Vuex
}),
activated() {
this.getDataList()
this.getDataList() //
},
methods: {
//
getDataList() {
this.dataListLoading = true
this.$http({
url: this.$http.adornUrl('/manage/wxAccount/list'),
url: this.$http.adornUrl('/manage/wxAccount/list'), //
method: 'get',
params: this.$http.adornParams({
'key': this.dataForm.key
})
params: this.$http.adornParams({ 'key': this.dataForm.key }) //
}).then(({ data }) => {
if (data && data.code === 200) {
this.dataList = data.list
this.$store.commit('wxAccount/updateAccountList', data.list)
this.dataList = data.list //
this.$store.commit('wxAccount/updateAccountList', data.list) // Vuex
} else {
this.dataList = []
this.dataList = [] //
}
this.dataListLoading = false
this.dataListLoading = false //
})
},
//
//
selectionChangeHandle(val) {
this.dataListSelections = val
this.dataListSelections = val //
},
// /
// /
addOrUpdateHandle(item) {
this.addOrUpdateVisible = true
this.addOrUpdateVisible = true //
this.$nextTick(() => {
this.$refs.addOrUpdate.init(item)
this.$refs.addOrUpdate.init(item) //
})
},
accessInfo(item){
this.accountAccessVisible = true
//
accessInfo(item) {
this.accountAccessVisible = true //
this.$nextTick(() => {
this.$refs.accountAccessDialog.init(item)
this.$refs.accountAccessDialog.init(item) //
})
},
//
//
deleteHandle(appid) {
var ids = appid ? [appid] : this.dataListSelections.map(item => {
return item.appid
})
var ids = appid ? [appid] : this.dataListSelections.map(item => item.appid) // appid
this.$confirm(`确定对[appid=${ids.join(',')}]进行[${appid ? '删除' : '批量删除'}]操作?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$http({
url: this.$http.adornUrl('/manage/wxAccount/delete'),
url: this.$http.adornUrl('/manage/wxAccount/delete'), //
method: 'post',
data: this.$http.adornData(ids, false)
data: this.$http.adornData(ids, false) //
}).then(({ data }) => {
if (data && data.code === 200) {
this.$message({
@ -120,18 +128,19 @@ export default {
type: 'success',
duration: 1500,
onClose: () => {
this.getDataList()
this.getDataList() //
}
})
} else {
this.$message.error(data.msg)
this.$message.error(data.msg) //
}
})
})
},
//
accountTypeFormat(row, column, cellValue) {
return this.ACCOUNT_TYPES[cellValue];
return this.ACCOUNT_TYPES[cellValue] //
}
}
}
</script>
</script>

@ -1,57 +1,73 @@
<template>
<!-- 使用element-ui的el-tabs组件创建一个标签页切换组件 -->
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
<el-tab-pane :label="'图片素材('+assetsCount.imageCount+')'" name="image" lazy>
<!-- 图片素材标签页使用el-tab-pane组件定义每个标签页的内容 -->
<el-tab-pane :label="'图片素材(' + assetsCount.imageCount + ')'" name="image" lazy>
<!-- 引入自定义的MaterialFile组件用于展示图片素材 -->
<material-file fileType="image" ref="imagePanel" @change="materialCount"></material-file>
</el-tab-pane>
<el-tab-pane :label="'语音素材('+assetsCount.voiceCount+')'" name="voice" lazy>
<!-- 语音素材标签页 -->
<el-tab-pane :label="'语音素材(' + assetsCount.voiceCount + ')'" name="voice" lazy>
<material-file fileType="voice" ref="voicePanel" @change="materialCount"></material-file>
</el-tab-pane>
<el-tab-pane :label="'视频素材('+assetsCount.videoCount+')'" name="video" lazy>
<!-- 视频素材标签页 -->
<el-tab-pane :label="'视频素材(' + assetsCount.videoCount + ')'" name="video" lazy>
<material-file fileType="video" ref="videoPanel" @change="materialCount"></material-file>
</el-tab-pane>
<el-tab-pane :label="'图文素材('+assetsCount.newsCount+')'" name="news" lazy>
<!-- 图文素材标签页使用自定义的MaterialNews组件 -->
<el-tab-pane :label="'图文素材(' + assetsCount.newsCount + ')'" name="news" lazy>
<material-news ref="newsPanel" @change="materialCount"></material-news>
</el-tab-pane>
</el-tabs>
</template>
<script>
export default {
data() {
return {
//
activeTab: 'image',
assetsCount:{
imageCount:'..',
videoCount:'..',
voiceCount:'..',
newsCount:'..'
//
assetsCount: {
imageCount: '..', //
videoCount: '..', //
voiceCount: '..', //
newsCount: '..' //
}
};
},
// MaterialFileMaterialNews
components: {
MaterialFile:()=>import('./assets/material-file'),
MaterialNews:()=>import('./assets/material-news')
MaterialFile: () => import('./assets/material-file'),
MaterialNews: () => import('./assets/material-news')
},
mounted(){
this.materialCount();
//
mounted() {
this.materialCount(); // materialCount
},
methods: {
//
handleTabClick(tab, event) {
this.$nextTick(()=>{
this.$refs[tab.name+'Panel'].init()
this.$nextTick(() => {
// DOMinit
this.$refs[tab.name + 'Panel'].init()
})
},
materialCount(){
//
materialCount() {
// 使$httpaxios
this.$http({
url: this.$http.adornUrl('/manage/wxAssets/materialCount')
url: this.$http.adornUrl('/manage/wxAssets/materialCount') // URL
}).then(({ data }) => {
if (data && data.code == 200) {
this.assetsCount=data.data
// assetsCount
this.assetsCount = data.data
} else {
//
this.$message.error(data.msg);
}
})
}
}
};
</script>

@ -1,24 +1,38 @@
<template>
<div>
<!-- 菜单项的输入框组底部有边框 -->
<div class="menu-input-group" style="border-bottom: 2px #e8e8e8 solid;">
<div class="menu-name">{{button.name}}</div>
<!-- 显示菜单名称 -->
<div class="menu-name">{{ button.name }}</div>
<!-- 删除菜单项的按钮点击时触发'delMenu'事件 -->
<div class="menu-del" @click="$emit('delMenu')"></div>
</div>
<!-- 菜单名称的输入框组 -->
<div class="menu-input-group">
<div class="menu-label">菜单名称</div>
<div class="menu-input">
<input type="text" name="name" placeholder="请输入菜单名称" class="menu-input-text" v-model="button.name" @input="checkMenuName(button.name)">
<!-- 输入框用于输入菜单名称绑定到button.name并监听输入事件 -->
<input type="text" name="name" placeholder="请输入菜单名称" class="menu-input-text" v-model="button.name"
@input="checkMenuName(button.name)">
<!-- 当菜单名称超过字数限制时显示 -->
<p class="menu-tips" style="color:#e15f63" v-show="menuNameBounds"></p>
<p class="menu-tips">字数不超过{{selectedMenuLevel==1?'5':'8'}}个汉字</p>
<!-- 显示菜单名称的字数限制 -->
<p class="menu-tips">字数不超过{{ selectedMenuLevel == 1 ? '5' : '8' }}个汉字</p>
</div>
</div>
<div v-show="!button.subButtons || button.subButtons.length==0">
<!-- 当没有子菜单或子菜单为空时显示 -->
<div v-show="!button.subButtons || button.subButtons.length == 0">
<!-- 菜单内容的输入框组 -->
<div class="menu-input-group">
<div class="menu-label">菜单内容</div>
<div class="menu-input">
<!-- 下拉选择框用于选择菜单类型 -->
<select v-model="button.type" name="type" class="menu-input-text">
<option value="view">跳转网页(view)</option>
<option value="media_id">发送消息(media_id)</option>
<!-- 注释掉的选项 -->
<!--<option value="view_limited">跳转公众号图文消息链接(view_limited)</option>-->
<option value="miniprogram">打开指定小程序(miniprogram)</option>
<option value="click">自定义点击事件(click)</option>
@ -31,7 +45,9 @@
</select>
</div>
</div>
<div class="menu-content" v-if="button.type=='view'">
<!-- 根据选择的菜单类型显示不同的内容输入框 -->
<div class="menu-content" v-if="button.type == 'view'">
<div class="menu-input-group">
<p class="menu-tips">订阅者点击该子菜单会跳到以下链接</p>
<div class="menu-label">页面地址</div>
@ -40,7 +56,7 @@
</div>
</div>
</div>
<div class="menu-content" v-else-if="button.type=='media_id'">
<div class="menu-content" v-else-if="button.type == 'media_id'">
<div class="menu-input-group">
<p class="menu-tips">订阅者点击该菜单会收到以下图文消息</p>
<div class="menu-label">media_id</div>
@ -49,18 +65,20 @@
</div>
</div>
</div>
<div class="menu-content" v-else-if="button.type=='miniprogram'">
<div class="menu-content" v-else-if="button.type == 'miniprogram'">
<div class="menu-input-group">
<p class="menu-tips">订阅者点击该子菜单会跳到以下小程序</p>
<div class="menu-label">小程序appId</div>
<div class="menu-input">
<input type="text" placeholder="小程序的appId仅认证公众号可配置" class="menu-input-text" v-model="button.appId">
<input type="text" placeholder="小程序的appId仅认证公众号可配置" class="menu-input-text"
v-model="button.appId">
</div>
</div>
<div class="menu-input-group">
<div class="menu-label">小程序路径</div>
<div class="menu-input">
<input type="text" placeholder="小程序的页面路径 pages/index/index" class="menu-input-text" v-model="button.pagePath">
<input type="text" placeholder="小程序的页面路径 pages/index/index" class="menu-input-text"
v-model="button.pagePath">
</div>
</div>
<div class="menu-input-group">
@ -83,13 +101,16 @@
</div>
</div>
</template>
<script>
export default {
props: {
// 1
selectedMenuLevel: {
type: Number,
default: 1
},
//
button: {
type: Object,
required: true
@ -97,25 +118,28 @@ export default {
},
data() {
return {
menuNameBounds: false,//
//
menuNameBounds: false,
}
},
methods: {
//
//
checkMenuName: function (val) {
if (this.selectedMenuLevel == 1 && this.getMenuNameLen(val) <= 10) {
// menuNameBounds
if (this.selectedMenuLevel == 1 && this.getMenuNameLen(val) <= 5) { // 510
this.menuNameBounds = false
} else if (this.selectedMenuLevel == 2 && this.getMenuNameLen(val) <= 16) {
} else if (this.selectedMenuLevel == 2 && this.getMenuNameLen(val) <= 8) {
this.menuNameBounds = false
} else {
this.menuNameBounds = true
}
},
//
//
getMenuNameLen: function (val) {
var len = 0;
for (var i = 0; i < val.length; i++) {
var a = val.charAt(i);
//
a.match(/[^\x00-\xff]/ig) != null ? len += 2 : len += 1;
}
return len;

@ -1,159 +1,200 @@
<template>
<div>
<!-- 应用菜单容器 -->
<div id="app-menu">
<!-- 预览窗 -->
<!-- 预览窗 -->
<div class="weixin-preview">
<div class="weixin-bd">
<div class="weixin-header">公众号菜单</div>
<!-- 菜单列表 -->
<ul class="weixin-menu" id="weixin-menu">
<li v-for="(btn,i) in menu.buttons" :key="i" class="menu-item" :class="{'current':selectedMenuIndex===i&&selectedMenuLevel==1}" @click="selectMenu(i)">
<!-- 循环遍历主菜单项 -->
<li v-for="(btn, i) in menu.buttons" :key="i" class="menu-item"
:class="{ 'current': selectedMenuIndex === i && selectedMenuLevel == 1 }"
@click="selectMenu(i)">
<div class="menu-item-title">
<span>{{ btn.name }}</span>
<span>{{ btn.name }}</span> <!-- 显示菜单项名称 -->
</div>
<!-- 子菜单列表 -->
<ul class="weixin-sub-menu">
<li v-for="(sub,i2) in btn.subButtons" :key="i2" class="menu-sub-item" :class="{'current':selectedMenuIndex===i&&selectedSubMenuIndex===i2&&selectedMenuLevel==2,'on-drag-over':onDragOverMenu==(i+'_'+i2)}" @click.stop="selectSubMenu(i,i2)" draggable="true" @dragstart="selectSubMenu(i,i2)" @dragover.prevent="onDragOverMenu=(i+'_'+i2)" @drop="onDrop(i,i2)">
<!-- 循环遍历子菜单项 -->
<li v-for="(sub, i2) in btn.subButtons" :key="i2" class="menu-sub-item" :class="{
'current': selectedMenuIndex === i && selectedSubMenuIndex === i2 &&
selectedMenuLevel == 2, 'on-drag-over': onDragOverMenu == (i + '_' + i2)
}"
@click.stop="selectSubMenu(i, i2)" draggable="true"
@dragstart="selectSubMenu(i, i2)"
@dragover.prevent="onDragOverMenu = (i + '_' + i2)" @drop="onDrop(i, i2)">
<div class="menu-item-title">
<span>{{sub.name}}</span>
<span>{{ sub.name }}</span> <!-- 显示子菜单项名称 -->
</div>
</li>
<li v-if="btn.subButtons.length<5" class="menu-sub-item" :class="{'on-drag-over':onDragOverMenu==(i+'_'+btn.subButtons.length)}" @click.stop="addMenu(2,i)" @dragover.prevent="onDragOverMenu=(i+'_'+btn.subButtons.length)" @drop="onDrop(i,btn.subButtons.length)">
<!-- 如果子菜单项少于5个显示添加按钮 -->
<li v-if="btn.subButtons.length < 5" class="menu-sub-item"
:class="{ 'on-drag-over': onDragOverMenu == (i + '_' + btn.subButtons.length) }"
@click.stop="addMenu(2, i)"
@dragover.prevent="onDragOverMenu = (i + '_' + btn.subButtons.length)"
@drop="onDrop(i, btn.subButtons.length)">
<div class="menu-item-title">
<i class="el-icon-plus"></i>
<i class="el-icon-plus"></i> <!-- 添加图标 -->
</div>
</li>
<!-- 菜单展开/收起箭头 -->
<i class="menu-arrow arrow_out"></i>
<i class="menu-arrow arrow_in"></i>
</ul>
</li>
<li class="menu-item" v-if="menu.buttons.length<3" @click="addMenu(1)"> <i class="el-icon-plus"></i></li>
<!-- 如果主菜单项少于3个显示添加按钮 -->
<li class="menu-item" v-if="menu.buttons.length < 3" @click="addMenu(1)">
<i class="el-icon-plus"></i> <!-- 添加图标 -->
</li>
</ul>
</div>
</div>
<!-- 菜单编辑器 -->
<div class="weixin-menu-detail" v-if="selectedMenuLevel>0">
<wx-menu-button-editor :button="selectedButton" :selectedMenuLevel="selectedMenuLevel" @delMenu="delMenu"></wx-menu-button-editor>
<div class="weixin-menu-detail" v-if="selectedMenuLevel > 0">
<!-- 菜单按钮编辑器组件 -->
<wx-menu-button-editor :button="selectedButton" :selectedMenuLevel="selectedMenuLevel"
@delMenu="delMenu"></wx-menu-button-editor>
</div>
</div>
<div class="weixin-btn-group" v-if="isAuth('wx:menu:save')" @click="updateWxMenu">
<!-- 按钮组根据权限显示 -->
<div class="weixin-btn-group" v-if="isAuth('wx:menu:save')">
<!-- 发布按钮 -->
<el-button type="success" icon="el-icon-upload">发布</el-button>
<!-- 清空按钮 -->
<el-button type="warning" icon="el-icon-delete" @click="delMenu"></el-button>
</div>
</div>
</template>
<script>
export default {
// wxMenuButtonEditor
components: {
wxMenuButtonEditor: () => import('./wx-menu-button-editor')
},
data() {
return {
menu: { 'buttons': [] },//
selectedMenuIndex: '',//
selectedSubMenuIndex: '',//
selectedMenuLevel: 0,//
selectedButton: '',//
onDragOverMenu:'' //
//
menu: { 'buttons': [] },
//
selectedMenuIndex: '',
//
selectedSubMenuIndex: '',
// 12
selectedMenuLevel: 0,
//
selectedButton: '',
//
onDragOverMenu: ''
}
},
mounted() {
//
this.getWxMenu();
},
methods: {
//
getWxMenu() {
this.$http({
url: this.$http.adornUrl('/manage/wxMenu/getMenu')
url: this.$http.adornUrl('/manage/wxMenu/getMenu') // API
}).then(({ data }) => {
if (data.code == 200) {
this.menu = data.data.menu;
this.menu = data.data.menu; //
} else {
this.$message({
type: 'error',
message: data.msg
message: data.msg //
});
}
});
},
//
//
selectMenu(i) {
this.selectedMenuLevel = 1
this.selectedSubMenuIndex = ''
this.selectedMenuIndex = i
this.selectedButton = this.menu.buttons[i]
this.selectedMenuLevel = 1; //
this.selectedSubMenuIndex = ''; //
this.selectedMenuIndex = i; //
this.selectedButton = this.menu.buttons[i]; //
},
//
selectSubMenu(i,i2) {
this.selectedMenuLevel = 2
this.selectedMenuIndex = i
this.selectedSubMenuIndex = i2
this.selectedButton = this.menu.buttons[i].subButtons[i2]
//
selectSubMenu(i, i2) {
this.selectedMenuLevel = 2; //
this.selectedMenuIndex = i; //
this.selectedSubMenuIndex = i2; //
this.selectedButton = this.menu.buttons[i].subButtons[i2]; //
},
//
addMenu(level,i) {
if (level == 1 && this.menu.buttons.length < 3) {
//
addMenu(level, i) {
if (level == 1 && this.menu.buttons.length < 3) { //
//
this.menu.buttons.push({
"type": "view",
"name": "菜单名称",
"subButtons": [],
"url": ""
})
this.selectMenu(this.menu.buttons.length - 1)
});
this.selectMenu(this.menu.buttons.length - 1); //
}
if (level == 2 && this.menu.buttons[i].subButtons.length < 5) {
if (level == 2 && this.menu.buttons[i].subButtons.length < 5) { //
//
this.menu.buttons[i].subButtons.push({
"type": "view",
"name": "子菜单名称",
"url": ""
})
this.selectSubMenu(i,this.menu.buttons[i].subButtons.length - 1)
});
this.selectSubMenu(i, this.menu.buttons[i].subButtons.length - 1); //
}
},
//
//
delMenu() {
if (this.selectedMenuLevel == 1 && confirm('删除后菜单下设置的内容将被删除')) {
this.menu.buttons.splice(this.selectedMenuIndex, 1);
this.unSelectMenu()
} else if (this.selectedMenuLevel == 2) {
this.menu.buttons[this.selectedMenuIndex].subButtons.splice(this.selectedSubMenuIndex, 1);
this.unSelectMenu()
if (this.selectedMenuLevel == 1 && confirm('删除后菜单下设置的内容将被删除')) { //
this.menu.buttons.splice(this.selectedMenuIndex, 1); //
this.unSelectMenu(); //
} else if (this.selectedMenuLevel == 2) { //
this.menu.buttons[this.selectedMenuIndex].subButtons.splice(this.selectedSubMenuIndex, 1); //
this.unSelectMenu(); //
}
},
unSelectMenu(){//
this.selectedMenuLevel = 0
this.selectedMenuIndex = ''
this.selectedSubMenuIndex = ''
this.selectedButton = ''
unSelectMenu() { //
this.selectedMenuLevel = 0; //
this.selectedMenuIndex = ''; //
this.selectedSubMenuIndex = ''; //
this.selectedButton = ''; //
},
//
updateWxMenu() {
this.$http({
url: this.$http.adornUrl('/manage/wxMenu/updateMenu'),
url: this.$http.adornUrl('/manage/wxMenu/updateMenu'), // API
data: this.menu,
method: 'post'
}).then(({ data }) => {
if (data.code == 200) {
this.$message.success('操作成功')
this.$message.success('操作成功'); //
} else {
this.$message.error(data.msg);
this.$message.error(data.msg); //
}
});
},
onDrop(i,i2){//
this.onDragOverMenu='';
if(i==this.selectedMenuIndex && i2==this.selectedSubMenuIndex) //
return
if(i!=this.selectedMenuIndex && this.menu.buttons[i].subButtons.length>=5){
this.$message.error('目标组已满');
return
//
onDrop(i, i2) {
this.onDragOverMenu = ''; //
if (i == this.selectedMenuIndex && i2 == this.selectedSubMenuIndex) //
return;
if (i != this.selectedMenuIndex && this.menu.buttons[i].subButtons.length >= 5) { //
this.$message.error('目标组已满'); //
return;
}
this.menu.buttons[i].subButtons.splice(i2,0,this.selectedButton)
let delSubIndex = this.selectedSubMenuIndex
if(i==this.selectedMenuIndex && i2<this.selectedSubMenuIndex)
delSubIndex++
this.menu.buttons[this.selectedMenuIndex].subButtons.splice(delSubIndex, 1);
this.unSelectMenu()
//
this.menu.buttons[i].subButtons.splice(i2, 0, this.selectedButton);
let delSubIndex = this.selectedSubMenuIndex; //
if (i == this.selectedMenuIndex && i2 < this.selectedSubMenuIndex) // +1
delSubIndex++;
this.menu.buttons[this.selectedMenuIndex].subButtons.splice(delSubIndex, 1); //
this.unSelectMenu(); //
}
}
}
</script>
<!-- 引入外部CSS文件 -->
<style src="@/assets/css/wx-menu.css"></style>

@ -1,14 +1,24 @@
<template>
<!-- 使用element-ui的Dialog组件用于显示回复消息的对话框 -->
<el-dialog title="消息回复" :close-on-click-modal="false" :visible.sync="visible">
<!-- 使用element-ui的Form组件用于构建表单 -->
<el-form :model="dataForm" :rules="dataRule" ref="dataForm">
<!-- 表单项用于输入回复内容 -->
<el-form-item prop="replyContent">
<el-input v-model="dataForm.replyContent" type="textarea" :rows="5" placeholder="回复内容" maxlength="600" show-word-limit :autosize="{ minRows: 5, maxRows: 30 }" autocomplete></el-input>
<el-button type="text" v-show="'text'==dataForm.replyType" @click="addLink"></el-button>
<!-- 使用element-ui的Input组件配置为textarea类型用于多行文本输入 -->
<el-input v-model="dataForm.replyContent" type="textarea" :rows="5" placeholder="回复内容" maxlength="600"
show-word-limit :autosize="{ minRows: 5, maxRows: 30 }" autocomplete></el-input>
<!-- 当回复类型为'text'时显示点击插入链接 -->
<el-button type="text" v-show="'text' == dataForm.replyType" @click="addLink"></el-button>
</el-form-item>
</el-form>
<!-- 对话框底部的操作按钮 -->
<span slot="footer" class="dialog-footer">
<!-- 取消按钮点击关闭对话框 -->
<el-button @click="visible = false">取消</el-button>
<el-button type="success" @click="dataFormSubmit()" :disabled="uploading">{{uploading?'发送中...':'发送'}}</el-button>
<!-- 发送按钮根据是否正在上传发送显示不同文字 -->
<el-button type="success" @click="dataFormSubmit()" :disabled="uploading">{{ uploading ? '发送中...' : '发送'
}}</el-button>
</span>
</el-dialog>
</template>
@ -17,13 +27,20 @@
export default {
data() {
return {
//
visible: false,
//
uploading: false,
//
dataForm: {
openid:'',
replyType:'text',
replyContent:''
// openid
openid: '',
// 'text'
replyType: 'text',
//
replyContent: ''
},
//
dataRule: {
replyContent: [
{ required: true, message: "回复内容不能为空", trigger: "blur" }
@ -31,54 +48,62 @@ export default {
}
}
},
components:{
WxMsgPreview:()=>import('@/components/wx-msg-preview')
// 使
components: {
WxMsgPreview: () => import('@/components/wx-msg-preview')
},
methods: {
// openid
init(openid) {
if(!openid)throw '参数异常'
this.dataForm.openid=openid
if (!openid) throw '参数异常'
this.dataForm.openid = openid
this.visible = true
},
//
//
dataFormSubmit() {
if(this.uploading)return
this.uploading=true
this.$refs['dataForm'].validate((valid) => {
if (this.uploading) return //
this.uploading = true //
this.$refs['dataForm'].validate((valid) => { //
if (valid) {
//
this.$http({
url: this.$http.adornUrl(`/manage/wxMsg/reply`),
method: 'post',
data: this.$http.adornData(this.dataForm)
url: this.$http.adornUrl(`/manage/wxMsg/reply`), //
method: 'post', //
data: this.$http.adornData(this.dataForm) //
}).then(({ data }) => {
if (data && data.code === 200) {
if (data && data.code === 200) { // 200
//
this.$message({
message: '回复成功',
type: 'success',
duration: 1500,
onClose: () => {
this.visible = false
}
})
this.$emit("success",{...this.dataForm});
this.dataForm.replyContent=''
} else {
this.$message.error(data.msg)
this.$emit("success", { ...this.dataForm }); //
this.dataForm.replyContent = '' //
} else { //
this.$message.error(data.msg) //
}
this.uploading=false
this.uploading = false //
})
}
})
},
//
addLink() {
//
this.dataForm.replyContent += '<a href="链接地址">链接文字</a>'
}
}
}
</script>
<style scoped>
.msg-container{
.msg-container {
background: #eee;
}
/* 注意:.msg-container样式在模板中未使用可能是遗留或备用 */
</style>

@ -1,44 +1,60 @@
<template>
<div class="mod-config">
<!-- 内联表单用于查询消息 -->
<el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
<!-- 时间选择器 -->
<el-form-item>
<el-select v-model="dataForm.startTime" placeholder="时间">
<el-option v-for="(name,key) in timeSelections" :key="key" :value="name" :label="key"></el-option>
<el-option v-for="(name, key) in timeSelections" :key="key" :value="name" :label="key"></el-option>
</el-select>
</el-form-item>
<!-- 消息类型选择器 -->
<el-form-item>
<el-select v-model="dataForm.msgTypes" placeholder="消息类型">
<el-option value="" label="不限类型"></el-option>
<el-option value="text,image,voice,shortvideo,video,news,music,location,link" label="消息"></el-option>
<el-option value="text,image,voice,shortvideo,video,news,music,location,link"
label="消息"></el-option>
<el-option value="event,transfer_customer_service" label="事件"></el-option>
</el-select>
</el-form-item>
<!-- 查询按钮 -->
<el-form-item>
<el-button @click="getDataList()"></el-button>
</el-form-item>
</el-form>
<!-- 提示信息 -->
<div class="text-gray">
24小时内消息可回复此后台展示消息有一分钟左右延迟如需畅聊请使用
<a href="https://mpkf.weixin.qq.com/" target="_blank">公众平台客服</a>
</div>
<!-- 消息列表加载时显示加载动画 -->
<div v-loading="dataListLoading">
<div class="msg-item" v-for="(msg,index) in dataList" :key="index">
<div class="avatar"><el-avatar shape="square" :size="60" :src="getUserInfo(msg.openid).headimgurl"></el-avatar></div>
<div class="msg-item" v-for="(msg, index) in dataList" :key="index">
<!-- 用户头像 -->
<div class="avatar"><el-avatar shape="square" :size="60"
:src="getUserInfo(msg.openid).headimgurl"></el-avatar></div>
<!-- 消息内容 -->
<div class="item-content">
<div class="flex justify-between margin-bottom">
<div class="text-cut">{{getUserInfo(msg.openid).nickname || '--'}}</div>
<div>{{$moment(msg.createTime).calendar()}}</div>
<div class="text-cut">{{ getUserInfo(msg.openid).nickname || '--' }}</div>
<div>{{ $moment(msg.createTime).calendar() }}</div>
<!-- 回复按钮如果消息在24小时内可点击 -->
<div class="reply-btn">
<div v-if="canReply(msg.createTime)" @click="replyHandle(msg.openid)" class="el-icon-s-promotion"></div>
<div v-if="canReply(msg.createTime)" @click="replyHandle(msg.openid)"
class="el-icon-s-promotion">回复</div>
</div>
</div>
<!-- 消息预览组件 -->
<wx-msg-preview :msg="msg" singleLine></wx-msg-preview>
</div>
</div>
</div>
<el-pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" :current-page="pageIndex" :page-sizes="[10, 20, 50, 100]" :page-size="pageSize" :total="totalCount" layout="total, sizes, prev, pager, next, jumper">
<!-- 分页组件 -->
<el-pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" :current-page="pageIndex"
:page-sizes="[10, 20, 50, 100]" :page-size="pageSize" :total="totalCount"
layout="total, sizes, prev, pager, next, jumper">
</el-pagination>
<!-- 弹窗, 消息回复 -->
<!-- 消息回复弹窗组件 -->
<wx-msg-reply ref="wxMsgReply" @success="onReplyed"></wx-msg-reply>
</div>
</template>
@ -48,29 +64,38 @@ const TIME_FORMAT = 'YYYY/MM/DD hh:mm:ss'
export default {
data() {
return {
timeSelections:{
'近24小时':this.$moment().subtract(1, 'days').format(TIME_FORMAT),
//
timeSelections: {
'近24小时': this.$moment().subtract(1, 'days').format(TIME_FORMAT),
'近3天': this.$moment().subtract(3, 'days').format(TIME_FORMAT),
'近7天': this.$moment().subtract(7, 'days').format(TIME_FORMAT),
'近30天': this.$moment().subtract(30, 'days').format(TIME_FORMAT),
},
//
dataForm: {
startTime: this.$moment().subtract(1, 'days').format(TIME_FORMAT),
msgTypes: ''
},
//
dataList: [],
userDataList:[],
//
userDataList: [],
//
pageIndex: 1,
pageSize: 20,
totalCount: 0,
//
dataListLoading: false,
// 使
dataListSelections: []
}
},
//
components: {
WxMsgReply:()=>import('./wx-msg-reply'),
WxMsgPreview:()=>import('@/components/wx-msg-preview')
WxMsgReply: () => import('./wx-msg-reply'),
WxMsgPreview: () => import('@/components/wx-msg-preview')
},
//
activated() {
this.getDataList()
},
@ -85,7 +110,7 @@ export default {
'page': this.pageIndex,
'limit': this.pageSize,
'msgTypes': this.dataForm.msgTypes,
'startTime':this.dataForm.startTime,
'startTime': this.dataForm.startTime,
'sidx': 'create_time',
'order': 'desc'
})
@ -101,64 +126,67 @@ export default {
this.dataListLoading = false
})
},
refreshUserList(msgList){
let openidList=msgList.map(msg=>msg.openid).filter(openid=>!this.userDataList.some(u=>u.openid==openid))
if(!openidList.length)return
openidList = Array.from(new Set(openidList))//
//
refreshUserList(msgList) {
let openidList = msgList.map(msg => msg.openid).filter(openid => !this.userDataList.some(u => u.openid == openid))
if (!openidList.length) return
openidList = Array.from(new Set(openidList)) //
this.$http({
url: this.$http.adornUrl('/manage/wxUser/listByIds'),
method: 'post',
data: this.$http.adornParams(openidList,false)
data: this.$http.adornParams(openidList, false)
}).then(({ data }) => {
if (data && data.code === 200) {
this.userDataList = this.userDataList.concat(data.data)
}
})
},
getUserInfo(openid){
return this.userDataList.find(u=>u.openid==openid) || {nickname:'--',headimgurl:''}
// openid
getUserInfo(openid) {
return this.userDataList.find(u => u.openid == openid) || { nickname: '--', headimgurl: '' }
},
// 24
canReply(time){
return new Date(time).getTime()>new Date().getTime()-24*60*60*1000
// 24
canReply(time) {
return new Date(time).getTime() > new Date().getTime() - 24 * 60 * 60 * 1000
},
//
//
sizeChangeHandle(val) {
this.pageSize = val
this.pageIndex = 1
this.getDataList()
},
//
//
currentChangeHandle(val) {
this.pageIndex = val
this.getDataList()
},
//
// 使
selectionChangeHandle(val) {
this.dataListSelections = val
},
//
//
replyHandle(openid) {
this.$nextTick(() => {
this.$refs.wxMsgReply.init(openid)
})
},
onReplyed(replyMsg){
//
onReplyed(replyMsg) {
this.dataList.unshift({
openid : replyMsg.openid,
msgType : replyMsg.replyType,
detail : {
content : replyMsg.replyContent
openid: replyMsg.openid,
msgType: replyMsg.replyType,
detail: {
content: replyMsg.replyContent
},
inOut : 1,
createTime : new Date()
inOut: 1,
createTime: new Date()
})
}
}
}
</script>
<style scoped>
.msg-item{
.msg-item {
border: 1px solid #DCDFE6;
display: flex;
justify-content: flex-start;
@ -166,19 +194,22 @@ export default {
margin-top: 20px;
padding: 10px 20px;
}
.avatar{
.avatar {
flex: 0;
display: inline-block;
min-width: 60px;
margin-right: 20px;
}
.item-content{
.item-content {
flex: 1;
line-height: 20px;
max-width: 100%;
overflow: hidden;
}
.reply-btn{
.reply-btn {
width: 50px;
}
</style>

@ -1,22 +1,33 @@
<template>
<!-- 弹窗对话框标题根据dataForm.id是否存在决定是新增还是修改 -->
<el-dialog :title="!dataForm.id ? '新增' : '修改'" :close-on-click-modal="false" :visible.sync="visible">
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="100px">
<!-- 表单绑定dataForm作为数据模型dataRule作为验证规则监听回车事件触发提交 -->
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()"
label-width="100px">
<!-- 表单项二维码类型使用单选按钮选择临时或永久 -->
<el-form-item label="二维码类型" prop="isTemp">
<el-radio v-model="dataForm.isTemp" :label="true"></el-radio>
<el-radio v-model="dataForm.isTemp" :label="false"></el-radio>
<!-- 如果不是临时二维码显示一个链接提醒用户注意永久二维码的限制 -->
<div>
<a class="text-warning" v-show="!dataForm.isTemp" target="_blank" href="https://developers.weixin.qq.com/doc/offiaccount/Account_Management/Generating_a_Parametric_QR_Code.html">10</a>
<a class="text-warning" v-show="!dataForm.isTemp" target="_blank"
href="https://developers.weixin.qq.com/doc/offiaccount/Account_Management/Generating_a_Parametric_QR_Code.html">注意永久二维码上限10万个且暂时无法删除旧的二维码</a>
</div>
</el-form-item>
<!-- 表单项场景值使用输入框输入任意字符串 -->
<el-form-item label="场景值" prop="sceneStr">
<el-input v-model="dataForm.sceneStr" placeholder="任意字符串" maxlength="64"></el-input>
</el-form-item>
<!-- 表单项失效时间仅当二维码为临时时显示 -->
<el-form-item label="失效时间/秒" prop="expireSeconds" v-if="dataForm.isTemp">
<el-input v-model="dataForm.expireSeconds" placeholder="单位最大259200030天"></el-input>
<div>最大30天当前设置<span class="text-warning">{{dataForm.expireSeconds/(24*3600)}}</span></div>
<!-- 显示当前设置的失效时间转换为天的结果 -->
<div>最大30天当前设置<span class="text-warning">{{ dataForm.expireSeconds / (24 * 3600) }}</span></div>
</el-form-item>
</el-form>
<!-- 弹窗底部按钮 -->
<span slot="footer" class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="dataFormSubmit()"></el-button>
@ -28,12 +39,15 @@
export default {
data() {
return {
//
visible: false,
//
dataForm: {
isTemp: true,
sceneStr: '',
expireSeconds: 2592000
isTemp: true, //
sceneStr: '', //
expireSeconds: 2592000 // 30
},
//
dataRule: {
isTemp: [
{ required: true, message: '二维码类型不能为空', trigger: 'blur' }
@ -48,34 +62,36 @@ export default {
}
},
methods: {
// id
init(id) {
this.dataForm.id = id || 0
this.visible = true
this.dataForm.id = id || 0 // id
this.visible = true //
this.$nextTick(() => {
this.$refs['dataForm'].resetFields()
this.$refs['dataForm'].resetFields() //
})
},
//
//
dataFormSubmit() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
if (valid) { //
// POST
this.$http({
url: this.$http.adornUrl(`/manage/wxQrCode/createTicket`),
url: this.$http.adornUrl(`/manage/wxQrCode/createTicket`), //
method: 'post',
data: this.$http.adornData(this.dataForm)
data: this.$http.adornData(this.dataForm) //
}).then(({ data }) => {
if (data && data.code === 200) {
if (data && data.code === 200) { //
this.$message({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
this.visible = false
this.$emit('refreshDataList')
this.visible = false //
this.$emit('refreshDataList') //
}
})
} else {
this.$message.error(data.msg)
} else { //
this.$message.error(data.msg) //
}
})
}
@ -83,4 +99,4 @@ export default {
}
}
}
</script>
</script>

@ -1,140 +1,168 @@
<template>
<div class="mod-config">
<!-- 表单用于查询二维码信息 -->
<el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
<el-form-item>
<el-input v-model="dataForm.sceneStr" placeholder="场景值" clearable></el-input>
</el-form-item>
<el-form-item>
<!-- 查询按钮 -->
<el-button @click="getDataList()"></el-button>
<!-- 新增按钮仅当用户有新增权限时显示 -->
<el-button v-if="isAuth('wx:wxqrcode:save')" type="primary" @click="addOrUpdateHandle()"></el-button>
<el-button v-if="isAuth('wx:wxqrcode:delete')" type="danger" @click="deleteHandle()" :disabled="dataListSelections.length <= 0"></el-button>
<!-- 批量删除按钮仅当用户有删除权限且至少选中一项时显示 -->
<el-button v-if="isAuth('wx:wxqrcode:delete')" type="danger" @click="deleteHandle()"
:disabled="dataListSelections.length <= 0">批量删除</el-button>
</el-form-item>
</el-form>
<el-table :data="dataList" border v-loading="dataListLoading" @selection-change="selectionChangeHandle" style="width: 100%;">
<!-- 表格用于显示二维码列表 -->
<el-table :data="dataList" border v-loading="dataListLoading" @selection-change="selectionChangeHandle"
style="width: 100%;">
<!-- 选择框列 -->
<el-table-column type="selection" header-align="center" align="center" width="50">
</el-table-column>
<!-- ID列 -->
<el-table-column prop="id" header-align="center" align="center" label="ID">
</el-table-column>
<!-- 类型列显示临时永久 -->
<el-table-column prop="isTemp" header-align="center" align="center" label="类型">
<span slot-scope="scope">{{scope.row.isTemp?'临时':'永久'}}</span>
<span slot-scope="scope">{{ scope.row.isTemp ? '临时' : '永久' }}</span>
</el-table-column>
<!-- 场景值列 -->
<el-table-column prop="sceneStr" header-align="center" align="center" label="场景值">
</el-table-column>
<!-- 二维码图片列显示二维码链接 -->
<el-table-column prop="ticket" header-align="center" align="center" show-overflow-tooltip label="二维码图片">
<a :href="'https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket='+scope.row.ticket" slot-scope="scope">{{scope.row.ticket}}</a>
<a :href="'https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=' + scope.row.ticket"
slot-scope="scope">{{ scope.row.ticket }}</a>
</el-table-column>
<!-- 解析后的地址列显示链接 -->
<el-table-column prop="url" header-align="center" align="center" show-overflow-tooltip label="解析后的地址">
<a :href="scope.row.url" slot-scope="scope">{{scope.row.url}}</a>
<a :href="scope.row.url" slot-scope="scope">{{ scope.row.url }}</a>
</el-table-column>
<!-- 失效时间列 -->
<el-table-column prop="expireTime" header-align="center" align="center" width="100" label="失效时间">
</el-table-column>
<!-- 操作列包含删除按钮 -->
<el-table-column fixed="right" header-align="center" align="center" width="150" label="操作">
<template slot-scope="scope">
<el-button type="text" size="small" @click="deleteHandle(scope.row.id)"></el-button>
</template>
</el-table-column>
</el-table>
<el-pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" :current-page="pageIndex" :page-sizes="[10, 20, 50, 100]" :page-size="pageSize" :total="totalPage" layout="total, sizes, prev, pager, next, jumper">
<!-- 分页组件 -->
<el-pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" :current-page="pageIndex"
:page-sizes="[10, 20, 50, 100]" :page-size="pageSize" :total="totalPage"
layout="total, sizes, prev, pager, next, jumper">
</el-pagination>
<!-- 弹窗, 新增 / 修改 -->
<!-- 弹窗组件用于新增/修改二维码信息 -->
<add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList"></add-or-update>
</div>
</template>
<script>
import AddOrUpdate from './wx-qrcode-add-or-update'
import AddOrUpdate from './wx-qrcode-add-or-update' // /
export default {
data() {
return {
//
dataForm: {
sceneStr: ''
},
//
dataList: [],
//
pageIndex: 1,
//
pageSize: 10,
//
totalPage: 0,
//
dataListLoading: false,
//
dataListSelections: [],
// /
addOrUpdateVisible: false
}
},
components: {
AddOrUpdate
AddOrUpdate // /
},
activated() {
this.getDataList()
this.getDataList() //
},
methods: {
//
getDataList() {
this.dataListLoading = true
this.$http({
url: this.$http.adornUrl('/manage/wxQrCode/list'),
this.dataListLoading = true //
this.$http({ //
url: this.$http.adornUrl('/manage/wxQrCode/list'), //
method: 'get',
params: this.$http.adornParams({
params: this.$http.adornParams({ //
'page': this.pageIndex,
'limit': this.pageSize,
'sceneStr': this.dataForm.sceneStr,
'sidx': 'id',
'order': 'desc'
})
}).then(({ data }) => {
}).then(({ data }) => { //
if (data && data.code === 200) {
this.dataList = data.page.list
this.totalPage = data.page.totalCount
this.dataList = data.page.list //
this.totalPage = data.page.totalCount //
} else {
this.dataList = []
this.totalPage = 0
this.dataList = [] //
this.totalPage = 0 //
}
this.dataListLoading = false
this.dataListLoading = false //
})
},
//
//
sizeChangeHandle(val) {
this.pageSize = val
this.pageIndex = 1
this.getDataList()
this.pageSize = val //
this.pageIndex = 1 //
this.getDataList() //
},
//
//
currentChangeHandle(val) {
this.pageIndex = val
this.getDataList()
this.pageIndex = val //
this.getDataList() //
},
//
//
selectionChangeHandle(val) {
this.dataListSelections = val
this.dataListSelections = val //
},
// /
// /
addOrUpdateHandle(id) {
this.addOrUpdateVisible = true
this.$nextTick(() => {
this.$refs.addOrUpdate.init(id)
this.addOrUpdateVisible = true //
this.$nextTick(() => { // DOM
this.$refs.addOrUpdate.init(id) // idnull
})
},
//
//
deleteHandle(id) {
var ids = id ? [id] : this.dataListSelections.map(item => item.id)
this.$confirm(`确定对[id=${ids.join(',')}]进行[${id ? '删除' : '批量删除'}]操作?(仅删存档)`, '提示', {
var ids = id ? [id] : this.dataListSelections.map(item => item.id) // id
this.$confirm(`确定对[id=${ids.join(',')}]进行[${id ? '删除' : '批量删除'}]操作?(仅删存档)`, '提示', { //
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$http({
}).then(() => { //
this.$http({ //
url: this.$http.adornUrl('/manage/wxQrCode/delete'),
method: 'post',
data: this.$http.adornData(ids, false)
}).then(({ data }) => {
data: this.$http.adornData(ids, false) // id
}).then(({ data }) => { //
if (data && data.code === 200) {
this.$message({
this.$message({ //
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => this.getDataList()
onClose: () => this.getDataList() //
})
} else {
this.$message.error(data.msg)
this.$message.error(data.msg) //
}
})
})
}
}

@ -1,100 +1,112 @@
<template>
<el-dialog :title="modeDesc[mode]+'用户标签'" :close-on-click-modal="false" :visible.sync="dialogVisible">
<!-- 使用Element UI的Dialog组件用于显示模态框 -->
<el-dialog :title="modeDesc[mode] + '用户标签'" <!-- mode -->
:close-on-click-modal="false" <!-- 点击模态框外部不关闭模态框 -->
:visible.sync="dialogVisible"> <!-- 双向绑定控制模态框的显示隐藏 -->
<div>
<el-select v-model="selectedTagid" filterable placeholder="请选择标签" style="width:100%">
<el-option v-for="tagid in tagidsInOption" :key="tagid" :label="getTagName(tagid)" :value="tagid"></el-option>
<!-- 使用Element UI的Select组件用于选择标签 -->
<el-select v-model="selectedTagid" <!-- ID -->
filterable <!-- 可过滤选项 -->
placeholder="请选择标签"
style="width:100%">
<!-- 遍历tagidsInOption数组生成下拉选项 -->
<el-option v-for="tagid in tagidsInOption" :key="tagid" :label="getTagName(tagid)" <!--
使用getTagName方法获取标签名称 -->
:value="tagid"></el-option>
</el-select>
<div style="margin-top:20px;">已选择用户数{{wxUsers.length}}</div>
<!-- 显示已选择用户的数量 -->
<div style="margin-top:20px;">已选择用户数{{ wxUsers.length }}</div>
</div>
<!-- 模态框底部操作按钮 -->
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible=false"></el-button>
<el-button type="primary" @click="dataFormSubmit()" :disabled="submitting">{{submitting?'保存中...':'确定'}}</el-button>
<el-button @click="dialogVisible = false">关闭</el-button> <!-- -->
<el-button type="primary" @click="dataFormSubmit()" :disabled="submitting">{{ submitting ? '保存中...' : '确定'
}}</el-button> <!-- submitting -->
</span>
</el-dialog>
</template>
<script>
import { mapState } from 'vuex'
import { mapState } from 'vuex' // VuexmapState
export default {
name:'wx-user-tagging',
props:{
wxUsers:Array,
name: 'wx-user-tagging', //
props: {
wxUsers: Array, // wxUsersopenidID
},
data(){
return{
mode:'tagging',//tagging | untagging
modeDesc:{
'tagging':'绑定',
'untagging':'解绑'
data() {
return {
mode: 'tagging', //
modeDesc: { //
'tagging': '绑定',
'untagging': '解绑'
},
selectedTagid:'',
dialogVisible:false,
submitting:false
selectedTagid: '', // ID
dialogVisible: false, //
submitting: false //
}
},
computed: mapState({
wxUserTags:state=>state.wxUserTags.tags,
/**
* 返回下拉选择框中的选项列表
* 假设 all= 全部标签intersection = 用户标签交集即所有用户都有的 union=用户标签并集即至少一个用户的
* 那么绑定时可选all-intersection的差集即所有用户都有的就不列出来了
* 解绑时可选union 即用户有的标签都列出来
*/
tagidsInOption(){
let userTags=this.wxUsers.map(u=>u.tagidList || [])//[[1,2],[],[1,3]]
if(this.mode=='tagging'){// -
let all = this.wxUserTags.map(item=>item.id)
return all.filter(tagid=>!userTags.every(tagsIdArray=>tagsIdArray.indexOf(tagid)>-1))
}else if(this.mode=='untagging'){//
let unionSet = new Set();
userTags.forEach(tagsIdArray=>{
tagsIdArray.forEach(tagid => unionSet.add(tagid))
});//unionSet
return Array.from(unionSet);//unionSet
wxUserTags: state => state.wxUserTags.tags, // Vuex
//
tagidsInOption() {
let userTags = this.wxUsers.map(u => u.tagidList || []) // ID
if (this.mode == 'tagging') { // -
let all = this.wxUserTags.map(item => item.id) // ID
return all.filter(tagid => !userTags.every(tagsIdArray => tagsIdArray.indexOf(tagid) > -1)) //
} else if (this.mode == 'untagging') { //
let unionSet = new Set(); // 使Set
userTags.forEach(tagsIdArray => {
tagsIdArray.forEach(tagid => unionSet.add(tagid)) // Set
});
return Array.from(unionSet); // Set
}
return []
return [] //
}
}),
methods:{
init(mode){
if('tagging'==mode || 'untagging'==mode){
this.mode=mode;
this.dialogVisible=true
}else{
throw('mode参数有误')
methods: {
//
init(mode) {
if ('tagging' == mode || 'untagging' == mode) {
this.mode = mode;
this.dialogVisible = true
} else {
throw ('mode参数有误') // mode
}
},
getTagName(tagid){
let tag = this.wxUserTags.find(item=>item.id==tagid)
return tag?tag.name : "?"
// ID
getTagName(tagid) {
let tag = this.wxUserTags.find(item => item.id == tagid) //
return tag ? tag.name : "?" // "?"
},
dataFormSubmit(){
if(this.submitting)return
if(!this.selectedTagid){
//
dataFormSubmit() {
if (this.submitting) return //
if (!this.selectedTagid) { //
this.$message.error('未选择标签')
return
}
this.submitting=true
let openidList=this.wxUsers.map(u=>u.openid)
this.$http({
url: this.$http.adornUrl(`/manage/wxUserTags/${this.mode=='tagging'?'batchTagging':'batchUnTagging'}`),
method: 'post',
data:this.$http.adornData({
tagid : this.selectedTagid,
openidList : openidList
this.submitting = true // true
let openidList = this.wxUsers.map(u => u.openid) // openid
this.$http({ // HTTP
url: this.$http.adornUrl(`/manage/wxUserTags/${this.mode == 'tagging' ? 'batchTagging' : 'batchUnTagging'}`), // URL
method: 'post', // 使POST
data: this.$http.adornData({ //
tagid: this.selectedTagid, // ID
openidList: openidList // openid
})
}).then(({ data }) => {
this.submitting=false
if (data && data.code === 200) {
}).then(({ data }) => { //
this.submitting = false // false
if (data && data.code === 200) { //
this.$message({
message: '操作成功,列表数据需稍后刷新查看',
type: 'success',
onClose: () =>this.dialogVisible=false
onClose: () => this.dialogVisible = false //
})
} else {
this.$message.error(data.msg)
} else { //
this.$message.error(data.msg) //
}
})
}
}

@ -1,67 +1,97 @@
<template>
<div class="mod-config">
<!-- 使用Element UI的表单组件设置为行内表单绑定dataForm数据模型并监听回车事件触发getDataList方法 -->
<el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
<!-- 用户标签选择器 -->
<el-form-item>
<el-select v-model="dataForm.tagid" filterable clearable placeholder="用户标签">
<el-option v-for="item in wxUserTags" :key="item.id" :label="item.name" :value="item.id"></el-option>
<!-- 循环渲染标签选项 -->
<el-option v-for="item in wxUserTags" :key="item.id" :label="item.name"
:value="item.id"></el-option>
</el-select>
</el-form-item>
<!-- 昵称输入框 -->
<el-form-item>
<el-input v-model="dataForm.nickname" placeholder="昵称" clearable></el-input>
</el-form-item>
<!-- 城市输入框 -->
<el-form-item>
<el-input v-model="dataForm.city" placeholder="城市" clearable></el-input>
</el-form-item>
<!-- 关注场景值输入框 -->
<el-form-item>
<el-input v-model="dataForm.qrSceneStr" placeholder="关注场景值" clearable></el-input>
</el-form-item>
<!-- 表单操作按钮 -->
<el-form-item>
<el-button @click="getDataList()"></el-button>
<el-button v-if="isAuth('wx:wxuser:save')" type="primary" @click="$refs.wxUserTagging.init('tagging')" :disabled="dataListSelections.length <= 0"></el-button>
<el-button v-if="isAuth('wx:wxuser:save')" type="primary" @click="$refs.wxUserTagging.init('untagging')" :disabled="dataListSelections.length <= 0"></el-button>
<el-button v-if="isAuth('wx:wxuser:delete')" type="danger" @click="deleteHandle()" :disabled="dataListSelections.length <= 0"></el-button>
<!-- 根据权限显示绑定/解绑标签按钮并设置禁用状态 -->
<el-button v-if="isAuth('wx:wxuser:save')" type="primary" @click="$refs.wxUserTagging.init('tagging')"
:disabled="dataListSelections.length <= 0">绑定标签</el-button>
<el-button v-if="isAuth('wx:wxuser:save')" type="primary" @click="$refs.wxUserTagging.init('untagging')"
:disabled="dataListSelections.length <= 0">解绑标签</el-button>
<!-- 根据权限显示批量删除按钮并设置禁用状态 -->
<el-button v-if="isAuth('wx:wxuser:delete')" type="danger" @click="deleteHandle()"
:disabled="dataListSelections.length <= 0">批量删除</el-button>
</el-form-item>
<!-- 其他操作按钮 -->
<el-form-item class="fr">
<el-button icon="el-icon-price-tag" type="success" @click="$refs.wxUserTagsEditor.show()"></el-button>
<el-button icon="el-icon-sort" type="success" @click="syncWxUsers()"></el-button>
<!-- 标签管理按钮 -->
<el-button icon="el-icon-price-tag" type="success"
@click="$refs.wxUserTagsEditor.show()">标签管理</el-button>
<!-- 同步粉丝按钮 -->
<el-button icon="el-icon-sort" type="success" @click="syncWxUsers()"></el-button>
</el-form-item>
</el-form>
<el-table :data="dataList" border v-loading="dataListLoading" @selection-change="selectionChangeHandle" style="width: 100%;">
<el-table-column type="selection" header-align="center" align="center" width="50">
</el-table-column>
<el-table-column prop="openid" header-align="center" align="center" label="openid">
</el-table-column>
<el-table-column prop="nickname" header-align="center" align="center" label="昵称">
</el-table-column>
<el-table-column prop="sex" header-align="center" align="center" label="性别" :formatter="sexFormat">
</el-table-column>
<el-table-column prop="city" header-align="center" align="center" label="城市">
</el-table-column>
<!-- 数据列表 -->
<el-table :data="dataList" border v-loading="dataListLoading" @selection-change="selectionChangeHandle"
style="width: 100%;">
<!-- 选择框列 -->
<el-table-column type="selection" header-align="center" align="center" width="50"></el-table-column>
<!-- openid列 -->
<el-table-column prop="openid" header-align="center" align="center" label="openid"></el-table-column>
<!-- 昵称列 -->
<el-table-column prop="nickname" header-align="center" align="center" label="昵称"></el-table-column>
<!-- 性别列使用formatter格式化显示 -->
<el-table-column prop="sex" header-align="center" align="center" label="性别"
:formatter="sexFormat"></el-table-column>
<!-- 城市列 -->
<el-table-column prop="city" header-align="center" align="center" label="城市"></el-table-column>
<!-- 头像列使用插槽显示图片 -->
<el-table-column prop="headimgurl" header-align="center" align="center" label="头像">
<img class="headimg" slot-scope="scope" v-if="scope.row.headimgurl" :src="scope.row.headimgurl" />
</el-table-column>
<!-- 标签列使用插槽显示多个标签 -->
<el-table-column prop="tagidList" header-align="center" align="center" label="标签" show-overflow-tooltip>
<template slot-scope="scope">
<span v-for="tagid in scope.row.tagidList" :key="tagid">{{getTagName(tagid)}} </span>
<span v-for="tagid in scope.row.tagidList" :key="tagid">{{ getTagName(tagid) }} </span>
</template>
</el-table-column>
<!-- 订阅时间列使用moment格式化显示 -->
<el-table-column prop="subscribeTime" header-align="center" align="center" label="订阅时间">
<template slot-scope="scope">{{$moment(scope.row.subscribeTime).calendar()}}</template>
</el-table-column>
<el-table-column prop="qrSceneStr" header-align="center" align="center" label="场景值">
<template slot-scope="scope">{{ $moment(scope.row.subscribeTime).calendar() }}</template>
</el-table-column>
<!-- 场景值列 -->
<el-table-column prop="qrSceneStr" header-align="center" align="center" label="场景值"></el-table-column>
<!-- 是否关注列使用插槽显示"是""否" -->
<el-table-column prop="subscribe" header-align="center" align="center" label="是否关注">
<span slot-scope="scope">{{scope.row.subscribe?"是":"否"}}</span>
<span slot-scope="scope">{{ scope.row.subscribe ? "是" : "否" }}</span>
</el-table-column>
<!-- 操作列包含删除按钮 -->
<el-table-column fixed="right" header-align="center" align="center" width="150" label="操作">
<template slot-scope="scope">
<el-button type="text" size="small" @click="deleteHandle(scope.row.openid)"></el-button>
</template>
</el-table-column>
</el-table>
<el-pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" :current-page="pageIndex" :page-sizes="[10, 20, 50, 100]" :page-size="pageSize" :total="totalPage" layout="total, sizes, prev, pager, next, jumper">
</el-pagination>
<wx-user-tags-manager ref="wxUserTagsEditor" :visible="showWxUserTagsEditor" @close="showWxUserTagsEditor=false"></wx-user-tags-manager>
<!-- 分页组件 -->
<el-pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" :current-page="pageIndex"
:page-sizes="[10, 20, 50, 100]" :page-size="pageSize" :total="totalPage"
layout="total, sizes, prev, pager, next, jumper"></el-pagination>
<!-- 标签管理器组件 -->
<wx-user-tags-manager ref="wxUserTagsEditor" :visible="showWxUserTagsEditor"
@close="showWxUserTagsEditor = false"></wx-user-tags-manager>
<!-- 用户标签绑定/解绑组件 -->
<wx-user-tagging ref="wxUserTagging" :wxUsers="dataListSelections"></wx-user-tagging>
</div>
</template>
@ -74,28 +104,28 @@ export default {
data() {
return {
dataForm: {
tagid:'',
tagid: '',
nickname: '',
city:'',
qrSceneStr:''
city: '',
qrSceneStr: ''
},
dataList: [],
pageIndex: 1,
pageSize: 10,
totalPage: 0,
showWxUserTagsEditor:false,
showWxUserTagsEditor: false,
dataListLoading: false,
dataListSelections: [],
}
},
components: {
WxUserTagsManager,WxUserTagging
WxUserTagsManager, WxUserTagging
},
activated() {
this.getDataList()
},
computed: mapState({
wxUserTags:state=>state.wxUserTags.tags
wxUserTags: state => state.wxUserTags.tags
}),
methods: {
//
@ -168,23 +198,23 @@ export default {
})
})
},
syncWxUsers(){
syncWxUsers() {
this.$http({
url: this.$http.adornUrl('/manage/wxUser/syncWxUsers'),
method: 'post',
}).then(({ data }) => {
if (data && data.code === 200) {
this.$message({
message: '同步任务已建立,请稍候刷新查看列表',
type: 'success',
duration: 1500
})
} else {
this.$message.error(data.msg)
}
})
url: this.$http.adornUrl('/manage/wxUser/syncWxUsers'),
method: 'post',
}).then(({ data }) => {
if (data && data.code === 200) {
this.$message({
message: '同步任务已建立,请稍候刷新查看列表',
type: 'success',
duration: 1500
})
} else {
this.$message.error(data.msg)
}
})
},
sexFormat(row, column, cellValue) {
let sexType = {
0: '未知',
@ -193,15 +223,15 @@ export default {
}
return sexType[cellValue];
},
getTagName(tagid){
let tag = this.wxUserTags.find(item=>item.id==tagid)
return tag?tag.name : "?"
getTagName(tagid) {
let tag = this.wxUserTags.find(item => item.id == tagid)
return tag ? tag.name : "?"
}
}
}
</script>
<style scoped>
.headimg{
.headimg {
width: 50px;
height: 50px;
border-radius: 8px;

Loading…
Cancel
Save