#8

Closed
pgf2eulj7 wants to merge 0 commits from branch4 into branch1

@ -1,33 +1,48 @@
<template>
<!-- 根元素应用的主容器 -->
<div id="app">
<!-- 使用fade过渡效果来切换路由视图 -->
<transition name="fade">
<router-view />
<router-view /> <!-- 路由出口用于渲染匹配的组件 -->
</transition>
</div>
</template>
<style>
/* 样式定义 */
/* 定义小图片的样式 */
img.image-sm {
max-width: 80px;
max-height: 80px;
max-width: 80px; /* 最大宽度 */
max-height: 80px; /* 最大高度 */
}
/* 为el-col内的el-select和el-date-editor设置宽度 */
.el-col .el-select,
.el-col .el-date-editor {
width: 100%;
width: 100%; /* 宽度设置为父容器的100% */
}
/* 设置表格展开行的样式 */
.demo-table-expand {
font-size: 0;
font-size: 0; /* 将字体大小设为0常用于清除子元素的默认间隙 */
}
/* 设置表格展开行内label的样式 */
.demo-table-expand label {
width: 90px;
color: #99a9bf;
width: 90px; /* 宽度 */
color: #99a9bf; /* 文字颜色 */
}
/* 设置el-form-item在表格展开行中的样式 */
.demo-table-expand .el-form-item {
margin-right: 0;
margin-bottom: 0;
width: 50%;
margin-right: 0; /* 右侧外边距 */
margin-bottom: 0; /* 底部外边距 */
width: 50%; /* 宽度设置为父容器的50% */
}
/* 定义警告文本的样式 */
.text-warning {
color: #e6a23c;
color: #e6a23c; /* 文字颜色 */
}
</style>

@ -1,32 +1,68 @@
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import VueCookie from 'vue-cookie'
// 导入Vue框架
import Vue from 'vue';
// 导入根组件
import App from './App.vue';
// 导入Vue Router实例用于页面路由管理
import router from './router';
// 导入Vuex状态管理实例
import store from './store';
// 导入vue-cookie插件用于处理cookie
import VueCookie from 'vue-cookie';
// 导入Element UI组件库
import ElementUI from 'element-ui';
import moment from 'moment'
// 导入moment.js库用于日期和时间处理
import moment from 'moment';
// 导入Element UI的CSS样式
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'
// 导入全局CSS样式
import './assets/css/common.css';
// 导入全局SCSS样式
import './assets/scss/index.scss';
// 导入自定义的httpRequest工具基于axios的封装注意这里注释的api链接可能不准确因为httpRequest的具体实现未在代码中给出
import httpRequest from '@/utils/httpRequest';
// 导入自定义的权限验证工具
import { isAuth } from '@/utils';
// 导入vue-clipboard2插件用于复制文本到剪贴板
import VueClipboard from 'vue-clipboard2';
// 使用Element UI组件库
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;
// 挂载全局属性和方法
// 全局ajax请求方法
Vue.prototype.$http = httpRequest;
// 全局权限验证方法
Vue.prototype.isAuth = isAuth;
// 设置moment.js的语言环境为中文
moment.locale('zh-cn');
Vue.prototype.$moment = moment; //时间处理
// 将moment挂载到Vue原型上作为全局的时间处理方法
Vue.prototype.$moment = moment;
// 创建Vue实例并挂载到#app元素上
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
router, // 注入路由使得我们可以通过this.$router访问路由实例
store, // 注入store使得我们可以通过this.$store访问状态管理实例
render: h => h(App) // 渲染App组件
}).$mount('#app'); // 挂载到DOM上的#app元素

@ -1,5 +1,7 @@
import Vue from 'vue'
import Vuex from 'vuex'
// 引入各个模块的 Vuex 状态管理文件
import common from './modules/common'
import user from './modules/user'
import article from './modules/article'
@ -7,18 +9,26 @@ import message from './modules/message'
import wxUserTags from './modules/wxUserTags'
import wxAccount from './modules/wxAccount'
// 注册 Vuex 插件,使其可用于 Vue 中
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
common,
user,
article,
message,
wxUserTags,
wxAccount
},
mutations: {
},
strict: true
// 使用 modules 来组织不同的子模块
modules: {
// 引入并注册各个模块
common, // 公共模块,可能用于存储一些通用的状态
user, // 用户模块,存储用户信息相关的状态
article, // 文章模块,存储与文章相关的状态
message, // 消息模块,存储消息相关的状态
wxUserTags, // 微信用户标签模块,管理微信用户标签的状态
wxAccount // 微信账号模块,管理微信账号相关的状态
},
// mutations 用于同步修改状态,这里没有定义任何 mutations可根据需求进行扩展
mutations: {
// 这里可以添加全局的 mutation但目前没有定义
},
// 启用严格模式,开发环境下会对状态的修改进行检查,确保只能通过 mutation 修改状态
strict: true
})

@ -1,32 +1,59 @@
import Vue from 'vue'
import Vue from 'vue';
export default {
namespaced: true,
state: {
ACCOUNT_TYPES:{
1:'订阅号',
2:'服务号'
},
accountList:[],
selectedAppid:''
},
mutations: {
updateAccountList (state, list) {
state.accountList = list
if(!list.length)return
if(!state.selectedAppid){
let appidCookie = Vue.cookie.get('appid')
let selectedAppid = appidCookie?appidCookie:list[0].appid
this.commit('wxAccount/selectAccount',selectedAppid)
}
},
selectAccount (state, appid) {
Vue.cookie.set('appid',appid)
let oldAppid = state.selectedAppid
state.selectedAppid = appid
if(oldAppid){//切换账号时刷新网页
location.reload();
}
},
}
}
// Vuex模块启用命名空间
namespaced: true,
// Vuex的state用来存储应用状态
state: {
// 账户类型映射数字ID到账户类型名称
ACCOUNT_TYPES: {
1: '订阅号',
2: '服务号'
},
// 存储账户列表
accountList: [],
// 当前选中的Appid用来标识选择的账号
selectedAppid: ''
},
// Vuex的mutations用来修改state
mutations: {
// 更新账户列表
updateAccountList (state, list) {
// 更新state中的accountList
state.accountList = list;
// 如果列表为空,直接返回
if (!list.length) return;
// 如果当前没有选中的Appid则从cookie或列表中选择一个默认Appid
if (!state.selectedAppid) {
let appidCookie = Vue.cookie.get('appid');
// 获取cookie中的appid如果有则使用cookie中的appid否则使用列表中的第一个Appid
let selectedAppid = appidCookie ? appidCookie : list[0].appid;
// 通过commit调用mutation更新选中的账号
this.commit('wxAccount/selectAccount', selectedAppid);
}
},
// 选择某个账号切换Appid
selectAccount (state, appid) {
// 更新cookie中的appid保存选中的Appid
Vue.cookie.set('appid', appid);
// 记录上一个选中的Appid
let oldAppid = state.selectedAppid;
// 更新当前选中的Appid
state.selectedAppid = appid;
// 如果选中的Appid发生变化则刷新页面
if (oldAppid) {
location.reload();
}
}
}
};

@ -1,12 +1,20 @@
export default {
namespaced: true,
state: {
tags:[]
},
mutations: {
updateTags (state, tags) {
state.tags = tags
}
}
// 启用 Vuex 模块的命名空间,避免命名冲突
namespaced: true,
// state 存储模块的状态
state: {
// tags 用来存储标签数据的数组
tags: []
},
// mutations 用来修改 state 中的状态
mutations: {
// 更新 tags 数组的内容
updateTags (state, tags) {
// 将传入的 tags 更新到 state 中
state.tags = tags;
}
}
};

@ -4,74 +4,81 @@ import router from '@/router'
import qs from 'qs'
import merge from 'lodash/merge'
import { clearLoginInfo } from '@/utils'
const baseUrl = '/wx'
const baseUrl = '/wx' // 设置请求的基础路径
// 创建axios实例
const http = axios.create({
timeout: 1000 * 30,
withCredentials: true,
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
timeout: 1000 * 30, // 设置请求超时为30秒
withCredentials: true, // 允许携带跨域请求的cookie
headers: {
'Content-Type': 'application/json; charset=utf-8' // 默认请求头为json格式
}
})
/**
* 请求拦截
*/
* 请求拦截器
* 在每个请求发送之前加入token从cookie中获取
*/
http.interceptors.request.use(config => {
config.headers['token'] = Vue.cookie.get('token') // 请求头带上token
return config
config.headers['token'] = Vue.cookie.get('token') // 在请求头中加入token
return config // 返回请求配置
}, error => {
return Promise.reject(error)
return Promise.reject(error) // 请求出错时返回Promise拒绝
})
/**
* 响应拦截
*/
* 响应拦截器
* 对响应数据进行拦截处理
* 如果返回的状态码为401未授权则清除登录信息并跳转到登录页
*/
http.interceptors.response.use(response => {
if (response.data && response.data.code === 401) { // 401, token失效
clearLoginInfo()
router.push({ name: 'login' })
}
return response
if (response.data && response.data.code === 401) { // 判断返回的code是否为401代表token失效
clearLoginInfo() // 清除登录信息
router.push({ name: 'login' }) // 跳转到登录页面
}
return response // 返回响应数据
}, error => {
return Promise.reject(error)
return Promise.reject(error) // 响应出错时返回Promise拒绝
})
/**
* 请求地址处理
* @param {*} actionName action方法名称
*/
* 请求地址处理函数
* @param {*} actionName 接口的名称拼接成完整的URL
* @returns {string} 拼接后的完整URL
*/
http.adornUrl = (actionName) => {
// 非生产环境 && 开启代理, 接口前缀统一使用[/proxyApi/]前缀做代理拦截!
return baseUrl + actionName
// 在开发环境下,如果开启了代理,则请求路径会带上代理前缀
return baseUrl + actionName // 返回完整的请求URL
}
/**
* get请求参数处理
* @param {*} params 参数对象
* @param {*} openDefultParams 是否开启默认参数?
*/
* get请求的参数处理
* @param {*} params 请求的参数对象
* @param {*} openDefultParams 是否开启默认参数
* @returns {object} 处理后的参数对象
*/
http.adornParams = (params = {}, openDefultParams = true) => {
var defaults = {
't': new Date().getTime()
}
return openDefultParams ? merge(defaults, params) : params
const defaults = {
't': new Date().getTime() // 添加时间戳参数,防止缓存
}
return openDefultParams ? merge(defaults, params) : params // 合并默认参数和传入的参数
}
/**
* post请求数据处理
* @param {*} data 数据对象
* @param {*} openDefultdata 是否开启默认数据?
* @param {*} contentType 数据格式
* json: 'application/json; charset=utf-8'
* form: 'application/x-www-form-urlencoded; charset=utf-8'
*/
* post请求的数据处理
* @param {*} data 请求的数据对象
* @param {*} openDefultdata 是否开启默认数据
* @param {*} contentType 数据格式类型'json''form'
* @returns {string} 处理后的数据
*/
http.adornData = (data = {}, openDefultdata = true, contentType = 'json') => {
var defaults = {
't': new Date().getTime()
}
data = openDefultdata ? merge(defaults, data) : data
return contentType === 'json' ? JSON.stringify(data) : qs.stringify(data)
const defaults = {
't': new Date().getTime() // 添加时间戳参数,防止缓存
}
data = openDefultdata ? merge(defaults, data) : data // 合并默认数据和传入的数据
// 根据不同的contentType处理数据格式
return contentType === 'json' ? JSON.stringify(data) : qs.stringify(data)
}
export default http
export default http // 导出axios实例供其他模块使用

@ -3,56 +3,76 @@ import router from '@/router'
import store from '@/store'
/**
* 获取uuid
*/
* 获取UUID
* 生成一个标准的UUID例如xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
* 使用随机数和指定格式的规则生成UUID
*/
export function getUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
return (c === 'x' ? (Math.random() * 16 | 0) : ('r&0x3' | '0x8')).toString(16)
})
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
return (c === 'x' ? (Math.random() * 16 | 0) : ('r&0x3' | '0x8')).toString(16)
})
}
/**
* 是否有权限
* @param {*} key
*/
* 检查是否有某个权限
* @param {*} key 权限的标识符
* @returns {boolean} 如果权限列表中包含该权限返回true否则返回false
*/
export function isAuth(key) {
return JSON.parse(sessionStorage.getItem('permissions') || '[]').indexOf(key) !== -1 || false
// 从 sessionStorage 中获取权限列表,并转换为数组,如果没有权限列表,默认返回空数组
return JSON.parse(sessionStorage.getItem('permissions') || '[]').indexOf(key) !== -1 || false
}
/**
* 树形数据转换
* @param {*} data
* @param {*} id
* @param {*} pid
*/
* 将平面数据转换为树形数据
* @param {*} data 原始平面数据
* @param {*} id 唯一标识符字段默认为'id'
* @param {*} pid 父级标识符字段默认为'parentId'
* @returns {Array} 转换后的树形数据
*/
export function treeDataTranslate(data, id = 'id', pid = 'parentId') {
var res = []
var temp = {}
for (var i = 0; i < data.length; i++) {
temp[data[i][id]] = data[i]
}
for (var k = 0; k < data.length; k++) {
if (temp[data[k][pid]] && data[k][id] !== data[k][pid]) {
if (!temp[data[k][pid]]['children']) {
temp[data[k][pid]]['children'] = []
}
if (!temp[data[k][pid]]['_level']) {
temp[data[k][pid]]['_level'] = 1
}
data[k]['_level'] = temp[data[k][pid]]._level + 1
temp[data[k][pid]]['children'].push(data[k])
} else {
res.push(data[k])
}
}
return res
var res = [] // 存储最终的树形结构
var temp = {} // 临时存储每个节点,以便快速查找父节点
// 将数据转换为临时对象key为节点的id值为节点本身
for (var i = 0; i < data.length; i++) {
temp[data[i][id]] = data[i]
}
// 遍历数据根据pid将节点组织成树形结构
for (var k = 0; k < data.length; k++) {
// 如果节点的父节点存在并且当前节点的id不等于父节点的id
if (temp[data[k][pid]] && data[k][id] !== data[k][pid]) {
// 如果父节点没有'children'属性,则初始化为数组
if (!temp[data[k][pid]]['children']) {
temp[data[k][pid]]['children'] = []
}
// 如果父节点没有'_level'属性则设置为1
if (!temp[data[k][pid]]['_level']) {
temp[data[k][pid]]['_level'] = 1
}
// 当前节点的级别为父节点的级别+1
data[k]['_level'] = temp[data[k][pid]]._level + 1
// 将当前节点推送到父节点的children数组中
temp[data[k][pid]]['children'].push(data[k])
} else {
// 如果当前节点是根节点,直接推送到结果数组中
res.push(data[k])
}
}
return res // 返回转换后的树形数据
}
/**
* 清除登录信息
*/
* 清除登录信息
* 用于用户退出时清理本地存储的登录信息
*/
export function clearLoginInfo() {
Vue.cookie.delete('token')
//store.commit('resetStore')
router.options.isAddDynamicMenuRoutes = false
// 删除cookie中的'token'
Vue.cookie.delete('token')
// 目前注释掉了重置store的操作若需要可以解除注释
// store.commit('resetStore')
// 重置动态菜单路由标志
router.options.isAddDynamicMenuRoutes = false
}

@ -1,31 +1,36 @@
/**
* 邮箱
* @param {*} s
*/
* 验证邮箱格式
* @param {*} s - 需要验证的邮箱地址
* @returns {boolean} - 返回是否是有效的邮箱地址
*/
export function isEmail(s) {
return /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((.[a-zA-Z0-9_-]{2,3}){1,2})$/.test(s)
}
/**
* 手机号码
* @param {*} s
*/
export function isMobile(s) {
return /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((\.[a-zA-Z0-9_-]{2,3}){1,2})$/.test(s)
}
/**
* 验证手机号码格式中国手机号
* @param {*} s - 需要验证的手机号
* @returns {boolean} - 返回是否是有效的手机号码
*/
export function isMobile(s) {
return /^1[0-9]{10}$/.test(s)
}
/**
* 电话号码
* @param {*} s
*/
export function isPhone(s) {
}
/**
* 验证固定电话号码格式
* @param {*} s - 需要验证的电话号码
* @returns {boolean} - 返回是否是有效的电话号码包括区号和本地号码
*/
export function isPhone(s) {
return /^([0-9]{3,4}-)?[0-9]{7,8}$/.test(s)
}
/**
* URL地址
* @param {*} s
*/
export function isURL(s) {
}
/**
* 验证URL地址格式
* @param {*} s - 需要验证的URL地址
* @returns {boolean} - 返回是否是有效的URL地址包括http或https协议
*/
export function isURL(s) {
return /^http[s]?:\/\/.*/.test(s)
}
}

@ -1,61 +1,81 @@
<template>
<!-- 404 错误页面的模板 -->
<div class="site-wrapper site-page--not-found">
<div class="site-content__wrapper">
<div class="site-content">
<h2 class="not-found-title">400</h2>
<p class="not-found-desc">抱歉您访问的页面<em>失联</em> ...</p>
<el-button @click="$router.go(-1)"></el-button>
<el-button type="primary" class="not-found-btn-gohome" @click="$router.push({ name: 'home' })">进入首页</el-button>
</div>
</div>
<div class="site-content__wrapper">
<div class="site-content">
<!-- 错误代码 -->
<h2 class="not-found-title">400</h2>
<!-- 错误描述 -->
<p class="not-found-desc">抱歉您访问的页面<em>失联</em> ...</p >
<!-- 返回上一页按钮 -->
<el-button @click="$router.go(-1)"></el-button>
<!-- 返回首页按钮 -->
<el-button type="primary" class="not-found-btn-gohome" @click="$router.push({ name: 'home' })">进入首页</el-button>
</div>
</template>
<script>
export default {
}
</script>
<style lang="scss">
.site-wrapper.site-page--not-found {
position: absolute;
</div>
</div>
</template>
<script>
export default {
// JavaScript
}
</script>
<style lang="scss">
/* 整个404页面的外部容器 */
.site-wrapper.site-page--not-found {
position: absolute; /* 定位到页面的绝对位置 */
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: hidden;
overflow: hidden; /* 防止内容溢出 */
/* 内容包裹层 */
.site-content__wrapper {
padding: 0;
margin: 0;
background-color: #fff;
padding: 0;
margin: 0;
background-color: #fff; /* 设置背景色为白色 */
}
/* 页面内容容器 */
.site-content {
position: fixed;
top: 15%;
left: 50%;
z-index: 2;
padding: 30px;
text-align: center;
transform: translate(-50%, 0);
position: fixed; /* 固定位置 */
top: 15%; /* 距离顶部15% */
left: 50%; /* 距离左边50% */
z-index: 2; /* 确保内容在其他元素之上 */
padding: 30px;
text-align: center; /* 内容居中 */
transform: translate(-50%, 0); /* 将内容水平居中 */
}
/* 错误标题 */
.not-found-title {
margin: 20px 0 15px;
font-size: 10em;
font-weight: 400;
color: rgb(55, 71, 79);
margin: 20px 0 15px;
font-size: 10em; /* 大字体 */
font-weight: 400;
color: rgb(55, 71, 79); /* 文字颜色 */
}
/* 错误描述 */
.not-found-desc {
margin: 0 0 30px;
font-size: 26px;
text-transform: uppercase;
color: rgb(118, 131, 143);
> em {
font-style: normal;
color: #ee8145;
}
margin: 0 0 30px;
font-size: 26px;
text-transform: uppercase; /* 将文本转换为大写 */
color: rgb(118, 131, 143); /* 文字颜色 */
/* 强调标签 <em> 样式 */
> em {
font-style: normal;
color: #ee8145; /* 设置颜色 */
}
}
/* 返回首页按钮的左边距 */
.not-found-btn-gohome {
margin-left: 30px;
margin-left: 30px;
}
}
}
</style>
</style>

@ -1,12 +1,18 @@
<template>
<!-- 模块容器 div -->
<div class="mod-home">
<h3>欢迎使用微信管理系统</h3>
<!-- 欢迎标题 -->
<h3>欢迎使用微信管理系统</h3>
</div>
</template>
<style>
.mod-home {
</template>
<style>
/* 样式部分 */
.mod-home {
/* 设置行高,增加文本的可读性 */
line-height: 2.5;
/* 使文本水平居中对齐 */
text-align: center;
}
</style>
}
</style>

@ -1,184 +1,220 @@
<template>
<div class="site-wrapper site-page--login">
<div class="site-content__wrapper">
<div class="site-content">
<div class="brand-info">
<h2 class="brand-info__text">微信后台管理系统</h2>
<p class="brand-info__intro">微信公众号后台管理系统</p>
</div>
<div class="login-main">
<h3 class="login-title">管理员登录</h3>
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" status-icon>
<el-form-item prop="userName">
<el-input v-model="dataForm.userName" placeholder="帐号"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="dataForm.password" type="password" placeholder="密码"></el-input>
</el-form-item>
<el-form-item prop="captcha">
<el-row :gutter="20">
<el-col :span="14">
<el-input v-model="dataForm.captcha" placeholder="验证码">
</el-input>
</el-col>
<el-col :span="10" class="login-captcha">
<img :src="captchaPath" @click="getCaptcha()" alt="">
</el-col>
</el-row>
</el-form-item>
<el-form-item>
<el-button class="login-btn-submit" type="primary" @click="dataFormSubmit()"></el-button>
</el-form-item>
</el-form>
</div>
</div>
</div>
<!-- 页面容器背景层 -->
<div class="site-content__wrapper">
<!-- 页面内容容器 -->
<div class="site-content">
<!-- 品牌信息部分 -->
<div class="brand-info">
<h2 class="brand-info__text">微信后台管理系统</h2>
<p class="brand-info__intro">微信公众号后台管理系统</p >
</div>
</template>
<!-- 登录表单 -->
<div class="login-main">
<h3 class="login-title">管理员登录</h3>
<!-- 表单绑定了 model validation 规则 -->
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" status-icon>
<!-- 用户名输入框 -->
<el-form-item prop="userName">
<el-input v-model="dataForm.userName" placeholder="帐号"></el-input>
</el-form-item>
<!-- 密码输入框 -->
<el-form-item prop="password">
<el-input v-model="dataForm.password" type="password" placeholder="密码"></el-input>
</el-form-item>
<!-- 验证码输入框 -->
<el-form-item prop="captcha">
<el-row :gutter="20">
<el-col :span="14">
<el-input v-model="dataForm.captcha" placeholder="验证码"></el-input>
</el-col>
<el-col :span="10" class="login-captcha">
<!-- 验证码图片点击刷新验证码 -->
< img :src="captchaPath" @click="getCaptcha()" alt="">
</el-col>
</el-row>
</el-form-item>
<!-- 登录按钮 -->
<el-form-item>
<el-button class="login-btn-submit" type="primary" @click="dataFormSubmit()"></el-button>
</el-form-item>
</el-form>
</div>
</div>
</div>
</div>
</template>
<script>
import { getUUID } from '@/utils'
import { getUUID } from '@/utils' // UUID
export default {
data() {
return {
dataForm: {
userName: '',
password: '',
uuid: '',
captcha: ''
},
dataRule: {
userName: [
{ required: true, message: '帐号不能为空', trigger: 'blur' }
],
password: [
{ required: true, message: '密码不能为空', trigger: 'blur' }
],
captcha: [
{ required: true, message: '验证码不能为空', trigger: 'blur' }
]
},
captchaPath: ''
}
},
created() {
this.getCaptcha()
},
methods: {
//
dataFormSubmit() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
this.$http({
url: this.$http.adornUrl('/sys/login'),
method: 'post',
data: this.$http.adornData({
'username': this.dataForm.userName,
'password': this.dataForm.password,
'uuid': this.dataForm.uuid,
'captcha': this.dataForm.captcha
})
}).then(({ data }) => {
if (data && data.code === 200) {
this.$cookie.set('token', data.token)
this.$router.replace({ name: 'home' })
} else {
this.getCaptcha()
this.$message.error(data.msg)
}
})
}
})
},
//
getCaptcha() {
this.dataForm.uuid = getUUID()
this.captchaPath = this.$http.adornUrl(`/captcha?uuid=${this.dataForm.uuid}`)
}
}
data() {
return {
//
dataForm: {
userName: '', //
password: '', //
uuid: '', // UUID
captcha: '' //
},
//
dataRule: {
userName: [
{ required: true, message: '帐号不能为空', trigger: 'blur' }
],
password: [
{ required: true, message: '密码不能为空', trigger: 'blur' }
],
captcha: [
{ required: true, message: '验证码不能为空', trigger: 'blur' }
]
},
captchaPath: '' //
}
},
created() {
//
this.getCaptcha()
},
methods: {
//
dataFormSubmit() {
//
this.$refs['dataForm'].validate((valid) => {
if (valid) {
//
this.$http({
url: this.$http.adornUrl('/sys/login'), //
method: 'post',
data: this.$http.adornData({
'username': this.dataForm.userName,
'password': this.dataForm.password,
'uuid': this.dataForm.uuid,
'captcha': this.dataForm.captcha
})
}).then(({ data }) => {
if (data && data.code === 200) {
// token
this.$cookie.set('token', data.token)
this.$router.replace({ name: 'home' })
} else {
//
this.getCaptcha()
this.$message.error(data.msg)
}
})
}
})
},
// UUID
getCaptcha() {
this.dataForm.uuid = getUUID() // UUID
//
this.captchaPath = this.$http.adornUrl(`/captcha?uuid=${this.dataForm.uuid}`)
}
}
}
</script>
<style lang="scss">
.site-wrapper.site-page--login {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(38, 50, 56, 0.5);
overflow: hidden;
&:before {
position: fixed;
top: 0;
left: 0;
z-index: -1;
width: 100%;
height: 100%;
content: "";
background-color: #fa8bff;
background-image: linear-gradient(
45deg,
#fa8bff 0%,
#2bd2ff 52%,
#2bff88 90%
);
background-size: cover;
}
.site-content__wrapper {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
padding: 0;
margin: 0;
overflow-x: hidden;
overflow-y: auto;
background-color: transparent;
}
.site-content {
min-height: 100%;
padding: 30px 500px 30px 30px;
}
.brand-info {
margin: 220px 100px 0 90px;
color: #fff;
}
.brand-info__text {
margin: 0 0 22px 0;
font-size: 48px;
font-weight: 400;
text-transform: uppercase;
}
.brand-info__intro {
margin: 10px 0;
font-size: 16px;
line-height: 1.58;
opacity: 0.6;
}
.login-main {
position: absolute;
top: 0;
right: 0;
padding: 150px 60px 180px;
width: 470px;
min-height: 100%;
background-color: #fff;
}
.login-title {
font-size: 16px;
}
.login-captcha {
overflow: hidden;
> img {
width: 100%;
cursor: pointer;
}
}
.login-btn-submit {
width: 100%;
margin-top: 38px;
}
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(38, 50, 56, 0.5); /* 半透明背景 */
overflow: hidden;
/* 页面背景渐变效果 */
&:before {
position: fixed;
top: 0;
left: 0;
z-index: -1; /* 确保背景层在最底层 */
width: 100%;
height: 100%;
content: "";
background-color: #fa8bff;
background-image: linear-gradient(
45deg,
#fa8bff 0%,
#2bd2ff 52%,
#2bff88 90%
);
background-size: cover;
}
.site-content__wrapper {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
padding: 0;
margin: 0;
overflow-x: hidden;
overflow-y: auto;
background-color: transparent;
}
.site-content {
min-height: 100%;
padding: 30px 500px 30px 30px;
}
/* 品牌信息样式 */
.brand-info {
margin: 220px 100px 0 90px;
color: #fff;
}
.brand-info__text {
margin: 0 0 22px 0;
font-size: 48px;
font-weight: 400;
text-transform: uppercase;
}
.brand-info__intro {
margin: 10px 0;
font-size: 16px;
line-height: 1.58;
opacity: 0.6;
}
/* 登录表单样式 */
.login-main {
position: absolute;
top: 0;
right: 0;
padding: 150px 60px 180px;
width: 470px;
min-height: 100%;
background-color: #fff;
}
.login-title {
font-size: 16px;
}
/* 验证码图片样式 */
.login-captcha {
overflow: hidden;
> img {
width: 100%;
cursor: pointer; /* 鼠标指针变成手形,表示可以点击 */
}
}
/* 登录按钮样式 */
.login-btn-submit {
width: 100%;
margin-top: 38px;
}
}
</style>

@ -1,33 +1,53 @@
<template>
<el-form>
<h2>布局设置</h2>
<el-form-item label="导航条类型">
<el-radio-group v-model="navbarLayoutType">
<el-radio label="default" border>default</el-radio>
<el-radio label="inverse" border>inverse</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="侧边栏皮肤">
<el-radio-group v-model="sidebarLayoutSkin">
<el-radio label="light" border>light</el-radio>
<el-radio label="dark" border>dark</el-radio>
</el-radio-group>
</el-form-item>
<!-- 布局设置表单标题 -->
<h2>布局设置</h2>
<!-- 导航条类型设置项 -->
<el-form-item label="导航条类型">
<!-- 导航条类型选择框使用 radio 按钮选择 'default' 'inverse' -->
<el-radio-group v-model="navbarLayoutType">
<el-radio label="default" border>default</el-radio>
<el-radio label="inverse" border>inverse</el-radio>
</el-radio-group>
</el-form-item>
<!-- 侧边栏皮肤设置项 -->
<el-form-item label="侧边栏皮肤">
<!-- 侧边栏皮肤选择框使用 radio 按钮选择 'light' 'dark' -->
<el-radio-group v-model="sidebarLayoutSkin">
<el-radio label="light" border>light</el-radio>
<el-radio label="dark" border>dark</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
</template>
</template>
<script>
export default {
computed: {
navbarLayoutType: {
get() { return this.$store.state.common.navbarLayoutType },
set(val) { this.$store.commit('common/updateNavbarLayoutType', val) }
},
sidebarLayoutSkin: {
get() { return this.$store.state.common.sidebarLayoutSkin },
set(val) { this.$store.commit('common/updateSidebarLayoutSkin', val) }
}
}
computed: {
//
navbarLayoutType: {
// getter Vuex navbarLayoutType
get() {
return this.$store.state.common.navbarLayoutType
},
// setter Vuex navbarLayoutType
set(val) {
this.$store.commit('common/updateNavbarLayoutType', val)
}
},
//
sidebarLayoutSkin: {
// getter Vuex sidebarLayoutSkin
get() {
return this.$store.state.common.sidebarLayoutSkin
},
// setter Vuex sidebarLayoutSkin
set(val) {
this.$store.commit('common/updateSidebarLayoutSkin', val)
}
}
}
}
</script>

@ -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,19 @@
<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>
<!-- 否则使用keep-alive缓存组件并显示当前激活的标签页内容 -->
<keep-alive v-else>
<router-view v-if="item.name === mainTabsActiveName" />
</keep-alive>
</el-card>
</el-tab-pane>
</el-tabs>
<!-- 主入口标签页 e -->
<!-- 如果当前路由不包含isTab则显示单个页面 -->
<el-card v-else :body-style="siteContentViewHeight">
<keep-alive>
<router-view />
@ -31,73 +35,80 @@
</template>
<script>
import { isURL } from '@/utils/validate'
import { isURL } from '@/utils/validate' // URL
export default {
inject: ['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
//
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,
tabsRefreshCurrentHandle() {
var tab = this.$route
this.removeTabHandle(tab.name)
this.$nextTick(() => {
this.$router.push({ name: tab.name, query: tab.query, params: tab.params })
})
}
// methodstemplate
// tabsRefreshCurrentHandle refresh() methodstabsRefreshCurrentHandle
// tabsRefreshCurrentHandle() {
// var tab = this.$route
// this.removeTabHandle(tab.name)
// this.$nextTick(() => {
// this.$router.push({ name: tab.name, query: tab.query, params: tab.params })
// })
// }
}
}
</script>
</script>

@ -1,19 +1,26 @@
<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-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 +29,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 +41,15 @@ export default {
}
}
return {
//
visible: false,
//
dataForm: {
password: '',
newPassword: '',
confirmPassword: ''
password: '', //
newPassword: '', //
confirmPassword: '' //
},
//
dataRule: {
password: [
{ required: true, message: '原密码不能为空', trigger: 'blur' }
@ -48,55 +59,65 @@ export default {
],
confirmPassword: [
{ required: true, message: '确认密码不能为空', trigger: 'blur' },
{ validator: validateConfirmPassword, trigger: 'blur' }
{ validator: validateConfirmPassword, trigger: 'blur' } //
]
}
}
},
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'),
method: 'post',
data: this.$http.adornData({
url: this.$http.adornUrl('/sys/user/password'), //
method: 'post', //
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.$nextTick(() => {
//
this.mainTabs = []
//
clearLoginInfo()
//
this.$router.replace({ name: 'login' })
})
}
})
} else {
//
this.$message.error(data.msg)
}
})
@ -105,5 +126,4 @@ export default {
}
}
}
</script>
</script>

@ -1,60 +1,76 @@
<template>
<!-- 导航栏组件类名根据navbarLayoutType动态设置 -->
<nav class="site-navbar" :class="'site-navbar--' + navbarLayoutType">
<!-- 导航栏头部包含品牌标识 -->
<div class="site-navbar__header">
<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">
<!-- 侧边栏折叠/展开图标根据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'
import UpdatePassword from './main-navbar-update-password' //
import WxAccountSelector from '@/components/wx-account-selector' //
import { clearLoginInfo } from '@/utils' //
export default {
data() {
return {
updatePassowrdVisible: false
updatePassowrdVisible: false // /
}
},
components: {
UpdatePassword,WxAccountSelector
UpdatePassword, //
WxAccountSelector //
},
computed: {
// navbarLayoutTypesidebarFoldmainTabsuserName
navbarLayoutType: {
get() { return this.$store.state.common.navbarLayoutType }
},
@ -71,26 +87,28 @@ export default {
}
},
methods: {
//
//
updatePasswordHandle() {
this.updatePassowrdVisible = true
this.$nextTick(() => {
this.$refs.updatePassowrd.init()
})
},
// 退
// 退
logoutHandle() {
this.$confirm(`确定进行[退出]操作?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 退
this.$http({
url: this.$http.adornUrl('/sys/logout'),
method: 'post',
data: this.$http.adornData()
}).then(({ data }) => {
if (data && data.code === 200) {
//
clearLoginInfo()
this.$router.push({ name: 'login' })
}
@ -99,4 +117,4 @@ export default {
}
}
}
</script>
</script>

@ -1,50 +1,38 @@
<template>
<el-submenu v-if="menu.list && menu.list.length >= 1" :index="menu.menuId + ''" :popper-class="'site-sidebar--' + sidebarLayoutSkin + '-popper'">
<template slot="title">
<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>
<sub-menu v-for="item in menu.list" :key="item.menuId" :menu="item" :dynamicMenuRoutes="dynamicMenuRoutes">
</sub-menu>
</el-submenu>
<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',
name: 'sub-menu', //
props: {
menu: {
type: Object,
required: true
type: Object, //
required: true //
},
dynamicMenuRoutes: {
type: Array,
required: true
type: Array, //
required: true //
}
},
components: {
SubMenu
SubMenu // SubMenu
},
computed: {
sidebarLayoutSkin: {
get() { return this.$store.state.common.sidebarLayoutSkin }
sidebarLayoutSkin() {
// Vuex storesidebarLayoutSkin
return this.$store.state.common.sidebarLayoutSkin
}
},
methods: {
// menuId()
// menuId
gotoRouteHandle(menu) {
// dynamicMenuRoutesmenuIdmenumenuId
var route = this.dynamicMenuRoutes.filter(item => item.meta.menuId === menu.menuId)
// 使$router.push
if (route.length >= 1) {
this.$router.push({ name: route[0].name })
}
}
}
}
</script>
</script>

@ -1,69 +1,78 @@
<template>
<!-- 侧边栏容器根据sidebarLayoutSkin动态设置类名 -->
<aside class="site-sidebar" :class="'site-sidebar--' + sidebarLayoutSkin">
<div class="site-sidebar__inner">
<!-- 侧边栏菜单默认激活项为menuActiveName或'home'可折叠无折叠动画 -->
<el-menu :default-active="menuActiveName || 'home'" :collapse="sidebarFold" :collapseTransition="false" class="site-sidebar__menu">
<!-- 首页菜单项点击后跳转到'home'路由 -->
<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>
<!-- 菜单项标题使用slot="title"指定但在这里直接写了Element UI内部会处理 -->
<span>首页</span>
</el-menu-item>
<sub-menu v-for="menu in menuList" :key="menu.menuId" :menu="menu" :dynamicMenuRoutes="dynamicMenuRoutes">
</sub-menu>
<!-- 遍历menuList为每个菜单项渲染一个sub-menu组件 -->
<sub-menu v-for="menu in menuList" :key="menu.menuId" :menu="menu" :dynamicMenuRoutes="dynamicMenuRoutes"></sub-menu>
</el-menu>
</div>
</aside>
</template>
<script>
import SubMenu from './main-sidebar-sub-menu'
import { isURL } from '@/utils/validate'
import SubMenu from './main-sidebar-sub-menu' // SubMenu
import { isURL } from '@/utils/validate' // isURL
export default {
data() {
return {
dynamicMenuRoutes: []
dynamicMenuRoutes: [] //
}
},
components: {
SubMenu
SubMenu // SubMenu
},
computed: {
// Vuex store
sidebarLayoutSkin: {
get() { return this.$store.state.common.sidebarLayoutSkin }
get() { return this.$store.state.common.sidebarLayoutSkin } //
},
sidebarFold: {
get() { return this.$store.state.common.sidebarFold }
get() { return this.$store.state.common.sidebarFold } //
},
menuList: {
get() { return this.$store.state.common.menuList },
set(val) { this.$store.commit('common/updateMenuList', val) }
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) }
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) }
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) }
get() { return this.$store.state.common.mainTabsActiveName }, //
set(val) { this.$store.commit('common/updateMainTabsActiveName', val) } //
}
},
watch: {
// routeHandle
$route: 'routeHandle'
},
created() {
// sessionStoragerouteHandle
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,
// tabtab
var tab = this.mainTabs.filter(item => item.name === route.name)[0]
if (!tab) {
// tabtab
if (route.meta.isDynamic) {
route = this.dynamicMenuRoutes.filter(item => item.name === route.name)[0]
if (!route) {
@ -74,17 +83,18 @@ export default {
menuId: route.meta.menuId || route.name,
name: route.name,
title: route.meta.title,
type: isURL(route.meta.iframeUrl) ? 'iframe' : 'module',
type: isURL(route.meta.iframeUrl) ? 'iframe' : 'module', // iframeUrltab
iframeUrl: route.meta.iframeUrl || '',
params: route.params,
query: route.query
}
this.mainTabs = this.mainTabs.concat(tab)
this.mainTabs = this.mainTabs.concat(tab) // tabmainTabs
}
//
this.menuActiveName = tab.menuId + ''
this.mainTabsActiveName = tab.name
}
}
}
}
</script>
</script>

@ -1,9 +1,16 @@
<template>
<div class="site-wrapper" :class="{ 'site-sidebar--fold': sidebarFold }" v-loading.fullscreen.lock="loading" element-loading-text="">
<!-- 站点包装器根据sidebarFold状态添加类名同时绑定全屏加载指示器 -->
<div class="site-wrapper" :class="{ 'site-sidebar--fold': sidebarFold }"
v-loading.fullscreen.lock="loading" element-loading-text="拼命加载中">
<!-- 当loading为false时显示以下内容 -->
<template v-if="!loading">
<!-- 导航栏组件 -->
<main-navbar />
<!-- 侧边栏组件 -->
<main-sidebar />
<!-- 站点内容包装器动态设置最小高度 -->
<div class="site-content__wrapper" :style="{ 'min-height': documentClientHeight + 'px' }">
<!-- 如果内容不需要刷新则显示主要内容组件 -->
<main-content v-if="!$store.state.common.contentIsNeedRefresh" />
</div>
</template>
@ -11,31 +18,38 @@
</template>
<script>
//
import MainNavbar from './main-navbar'
import MainSidebar from './main-sidebar'
import MainContent from './main-content'
export default {
// refresh
provide() {
return {
//
refresh() {
//
this.$store.commit('common/updateContentIsNeedRefresh', true)
// DOM
this.$nextTick(() => {
this.$store.commit('common/updateContentIsNeedRefresh', false)
})
}
}
},
//
data() {
return {
loading: true
loading: true //
}
},
//
components: {
MainNavbar,
MainSidebar,
MainContent
},
// Vuex store
computed: {
documentClientHeight: {
get() { return this.$store.state.common.documentClientHeight },
@ -53,29 +67,38 @@ export default {
set(val) { this.$store.commit('user/updateName', val) }
}
},
//
created() {
this.getUserInfo()
this.getUserInfo() //
},
//
mounted() {
this.resetDocumentClientHeight()
this.resetDocumentClientHeight() //
},
//
methods: {
//
//
resetDocumentClientHeight() {
//
this.documentClientHeight = document.documentElement['clientHeight']
//
window.onresize = () => {
this.documentClientHeight = document.documentElement['clientHeight']
}
},
//
//
getUserInfo() {
// HTTP GET
this.$http({
url: this.$http.adornUrl('/sys/user/info'),
method: 'get',
params: this.$http.adornParams()
}).then(({ data }) => {
// 200
if (data && data.code === 200) {
//
this.loading = false
// ID
this.userId = data.user.userId
this.userName = data.user.username
}
@ -83,4 +106,4 @@ export default {
}
}
}
</script>
</script>

@ -1,127 +1,150 @@
<template>
<!-- Dialog弹框用于配置云存储 -->
<el-dialog title="云存储配置" :close-on-click-modal="false" :visible.sync="visible">
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="120px">
<el-form-item size="mini" label="存储类型">
<el-radio-group v-model="dataForm.type">
<el-radio :label="1">七牛</el-radio>
<el-radio :label="2">阿里云</el-radio>
<el-radio :label="3">腾讯云</el-radio>
</el-radio-group>
</el-form-item>
<template v-if="dataForm.type === 1">
<el-form-item label="域名">
<el-input v-model="dataForm.qiniuDomain" placeholder="七牛绑定的域名"></el-input>
</el-form-item>
<el-form-item label="路径前缀">
<el-input v-model="dataForm.qiniuPrefix" placeholder="不设置默认为空"></el-input>
</el-form-item>
<el-form-item label="AccessKey">
<el-input v-model="dataForm.qiniuAccessKey" placeholder="七牛AccessKey"></el-input>
</el-form-item>
<el-form-item label="SecretKey">
<el-input v-model="dataForm.qiniuSecretKey" placeholder="七牛SecretKey"></el-input>
</el-form-item>
<el-form-item label="空间名">
<el-input v-model="dataForm.qiniuBucketName" placeholder="七牛存储空间名"></el-input>
</el-form-item>
</template>
<template v-else-if="dataForm.type === 2">
<el-form-item label="域名">
<el-input v-model="dataForm.aliyunDomain" placeholder="阿里云绑定的域名"></el-input>
</el-form-item>
<el-form-item label="路径前缀">
<el-input v-model="dataForm.aliyunPrefix" placeholder="不设置默认为空"></el-input>
</el-form-item>
<el-form-item label="EndPoint">
<el-input v-model="dataForm.aliyunEndPoint" placeholder="阿里云EndPoint"></el-input>
</el-form-item>
<el-form-item label="AccessKeyId">
<el-input v-model="dataForm.aliyunAccessKeyId" placeholder="阿里云AccessKeyId"></el-input>
</el-form-item>
<el-form-item label="AccessKeySecret">
<el-input v-model="dataForm.aliyunAccessKeySecret" placeholder="阿里云AccessKeySecret"></el-input>
</el-form-item>
<el-form-item label="BucketName">
<el-input v-model="dataForm.aliyunBucketName" placeholder="阿里云BucketName"></el-input>
</el-form-item>
</template>
<template v-else-if="dataForm.type === 3">
<el-form-item label="域名">
<el-input v-model="dataForm.qcloudDomain" placeholder="腾讯云绑定的域名"></el-input>
</el-form-item>
<el-form-item label="路径前缀">
<el-input v-model="dataForm.qcloudPrefix" placeholder="不设置默认为空"></el-input>
</el-form-item>
<el-form-item label="AppId">
<el-input v-model="dataForm.qcloudAppId" placeholder="腾讯云AppId"></el-input>
</el-form-item>
<el-form-item label="SecretId">
<el-input v-model="dataForm.qcloudSecretId" placeholder="腾讯云SecretId"></el-input>
</el-form-item>
<el-form-item label="SecretKey">
<el-input v-model="dataForm.qcloudSecretKey" placeholder="腾讯云SecretKey"></el-input>
</el-form-item>
<el-form-item label="BucketName">
<el-input v-model="dataForm.qcloudBucketName" placeholder="腾讯云BucketName"></el-input>
</el-form-item>
<el-form-item label="Bucket所属地区">
<el-input v-model="dataForm.qcloudRegion" placeholder="如sh可选值 华南gz 华北tj 华东sh"></el-input>
</el-form-item>
</template>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="dataFormSubmit()"></el-button>
</span>
<!-- 表单组件model 绑定 dataFormrules 绑定 dataRule -->
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="120px">
<!-- 存储类型选择项 -->
<el-form-item size="mini" label="存储类型">
<el-radio-group v-model="dataForm.type">
<el-radio :label="1">七牛</el-radio>
<el-radio :label="2">阿里云</el-radio>
<el-radio :label="3">腾讯云</el-radio>
</el-radio-group>
</el-form-item>
<!-- 当选择七牛云时显示七牛相关配置项 -->
<template v-if="dataForm.type === 1">
<el-form-item label="域名">
<el-input v-model="dataForm.qiniuDomain" placeholder="七牛绑定的域名"></el-input>
</el-form-item>
<el-form-item label="路径前缀">
<el-input v-model="dataForm.qiniuPrefix" placeholder="不设置默认为空"></el-input>
</el-form-item>
<el-form-item label="AccessKey">
<el-input v-model="dataForm.qiniuAccessKey" placeholder="七牛AccessKey"></el-input>
</el-form-item>
<el-form-item label="SecretKey">
<el-input v-model="dataForm.qiniuSecretKey" placeholder="七牛SecretKey"></el-input>
</el-form-item>
<el-form-item label="空间名">
<el-input v-model="dataForm.qiniuBucketName" placeholder="七牛存储空间名"></el-input>
</el-form-item>
</template>
<!-- 当选择阿里云时显示阿里云相关配置项 -->
<template v-else-if="dataForm.type === 2">
<el-form-item label="域名">
<el-input v-model="dataForm.aliyunDomain" placeholder="阿里云绑定的域名"></el-input>
</el-form-item>
<el-form-item label="路径前缀">
<el-input v-model="dataForm.aliyunPrefix" placeholder="不设置默认为空"></el-input>
</el-form-item>
<el-form-item label="EndPoint">
<el-input v-model="dataForm.aliyunEndPoint" placeholder="阿里云EndPoint"></el-input>
</el-form-item>
<el-form-item label="AccessKeyId">
<el-input v-model="dataForm.aliyunAccessKeyId" placeholder="阿里云AccessKeyId"></el-input>
</el-form-item>
<el-form-item label="AccessKeySecret">
<el-input v-model="dataForm.aliyunAccessKeySecret" placeholder="阿里云AccessKeySecret"></el-input>
</el-form-item>
<el-form-item label="BucketName">
<el-input v-model="dataForm.aliyunBucketName" placeholder="阿里云BucketName"></el-input>
</el-form-item>
</template>
<!-- 当选择腾讯云时显示腾讯云相关配置项 -->
<template v-else-if="dataForm.type === 3">
<el-form-item label="域名">
<el-input v-model="dataForm.qcloudDomain" placeholder="腾讯云绑定的域名"></el-input>
</el-form-item>
<el-form-item label="路径前缀">
<el-input v-model="dataForm.qcloudPrefix" placeholder="不设置默认为空"></el-input>
</el-form-item>
<el-form-item label="AppId">
<el-input v-model="dataForm.qcloudAppId" placeholder="腾讯云AppId"></el-input>
</el-form-item>
<el-form-item label="SecretId">
<el-input v-model="dataForm.qcloudSecretId" placeholder="腾讯云SecretId"></el-input>
</el-form-item>
<el-form-item label="SecretKey">
<el-input v-model="dataForm.qcloudSecretKey" placeholder="腾讯云SecretKey"></el-input>
</el-form-item>
<el-form-item label="BucketName">
<el-input v-model="dataForm.qcloudBucketName" placeholder="腾讯云BucketName"></el-input>
</el-form-item>
<el-form-item label="Bucket所属地区">
<el-input v-model="dataForm.qcloudRegion" placeholder="如sh可选值 华南gz 华北tj 华东sh"></el-input>
</el-form-item>
</template>
</el-form>
<!-- 弹窗底部按钮 -->
<span slot="footer" class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="dataFormSubmit()"></el-button>
</span>
</el-dialog>
</template>
</template>
<script>
export default {
data() {
return {
visible: false,
dataForm: {},
dataRule: {}
}
},
methods: {
init(id) {
this.visible = true
this.$http({
url: this.$http.adornUrl('/sys/oss/config'),
method: 'get',
params: this.$http.adornParams()
}).then(({ data }) => {
this.dataForm = data && data.code === 200 ? data.config : []
})
},
//
dataFormSubmit() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
this.$http({
url: this.$http.adornUrl('/sys/oss/saveConfig'),
method: 'post',
data: this.$http.adornData(this.dataForm)
}).then(({ data }) => {
if (data && data.code === 200) {
this.$message({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
this.visible = false
}
})
} else {
this.$message.error(data.msg)
}
})
}
})
}
}
//
data() {
return {
// /
visible: false,
//
dataForm: {},
//
dataRule: {}
}
},
methods: {
// id
init(id) {
this.visible = true //
//
this.$http({
url: this.$http.adornUrl('/sys/oss/config'), //
method: 'get', // GET
params: this.$http.adornParams() //
}).then(({ data }) => {
//
this.dataForm = data && data.code === 200 ? data.config : []
})
},
//
dataFormSubmit() {
//
this.$refs['dataForm'].validate((valid) => {
if (valid) {
//
this.$http({
url: this.$http.adornUrl('/sys/oss/saveConfig'), //
method: 'post', // POST
data: this.$http.adornData(this.dataForm) //
}).then(({ data }) => {
if (data && data.code === 200) {
//
this.$message({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
this.visible = false //
}
})
} else {
//
this.$message.error(data.msg)
}
})
}
})
}
}
}
</script>

@ -1,87 +1,105 @@
<template>
<div @click="selectFile">
<input type="file" ref="fileInput" v-show="false" @change="onFileChange" />
<div>{{uploading?infoText:'上传文件'}}</div>
</div>
</template>
<script>
// 使
// 使 <script src="https://unpkg.com/cos-js-sdk-v5@0.5.23/dist/cos-js-sdk-v5.min.js" async></script>
<!-- 触发文件选择的区域点击时触发 selectFile 方法 -->
<div @click="selectFile">
<!-- 隐藏的文件输入框通过 click 触发文件选择 -->
<input type="file" ref="fileInput" v-show="false" @change="onFileChange" />
<!-- 显示上传状态文本如果正在上传则显示 infoText否则显示 "上传文件" -->
<div>{{uploading ? infoText : '上传文件'}}</div>
</div>
</template>
<script>
// 使
// 使 SDKhttps://unpkg.com/cos-js-sdk-v5@0.5.23/dist/cos-js-sdk-v5.min.js
var cos;
export default {
name: "oss-uploader",
data() {
return {
uploading: false,
infoText:"上传中...",
cosConfig:[]
}
},
mounted(){
this.$http({
url: this.$http.adornUrl('/sys/oss/config'),
method: 'get',
params: this.$http.adornParams()
}).then(({data}) => {
if(data && data.code === 200){
this.cosConfig = data.config
cos=new COS({
SecretId: data.config.qcloudSecretId,
SecretKey: data.config.qcloudSecretKey,
});
}else{
this.$message.error('请先配置云存储相关信息!')
}
})
},
methods: {
selectFile() {//
if (!this.uploading) {
this.$refs.fileInput.click();
}
},
onFileChange() {
let file = this.$refs.fileInput.files[0];
this.uploading = true;
let now = new Date();
let path=now.toISOString().slice(0,10)+'/'+now.getTime()+file.name.substr(file.name.lastIndexOf('.'))
cos.putObject({
Bucket: this.cosConfig.qcloudBucketName, /* 必须 */
Region: this.cosConfig.qcloudRegion, /* 必须 */
Key: path, /* 必须 */
Body: file, //
onProgress: (progressData)=> {
this.infoText='上传中:'+progressData.percent*100+'%'
}
}, (err, data)=> {
console.log(err || data);
this.uploading = false;
if(data){
this.infoText='上传文件'
let fileUrl='https://'+this.cosConfig.qcloudBucketName+'.cos.'+this.cosConfig.qcloudRegion+'.myqcloud.com/'+path;
this.saveUploadResult(fileUrl)
}else {
this.$message.error('文件上传失败',err)
}
});
},
saveUploadResult(url){
this.$http({
url: this.$http.adornUrl('/sys/oss/upload'),
method: 'post',
data:{
url:url
}
}).then(({data})=>{
this.$emit('uploaded', url)
})
}
}
name: "oss-uploader", //
data() {
return {
uploading: false, //
infoText: "上传中...", //
cosConfig: [] // COS
}
</script>
<style scoped>
</style>
},
mounted() {
// COS
this.$http({
url: this.$http.adornUrl('/sys/oss/config'),
method: 'get',
params: this.$http.adornParams() //
}).then(({ data }) => {
if (data && data.code === 200) {
//
this.cosConfig = data.config;
// COS
cos = new COS({
SecretId: data.config.qcloudSecretId,
SecretKey: data.config.qcloudSecretKey,
});
} else {
//
this.$message.error('请先配置云存储相关信息!');
}
})
},
methods: {
//
selectFile() {
//
if (!this.uploading) {
this.$refs.fileInput.click();
}
},
//
onFileChange() {
let file = this.$refs.fileInput.files[0]; //
this.uploading = true; // true
let now = new Date();
//
let path = now.toISOString().slice(0, 10) + '/' + now.getTime() + file.name.substr(file.name.lastIndexOf('.'));
// COS
cos.putObject({
Bucket: this.cosConfig.qcloudBucketName, //
Region: this.cosConfig.qcloudRegion, //
Key: path, //
Body: file, //
onProgress: (progressData) => { //
//
this.infoText = '上传中:' + (progressData.percent * 100).toFixed(2) + '%';
}
}, (err, data) => {
//
console.log(err || data);
this.uploading = false; // false
if (data) {
//
this.infoText = '上传文件';
// 访 URL
let fileUrl = 'https://' + this.cosConfig.qcloudBucketName + '.cos.' + this.cosConfig.qcloudRegion + '.myqcloud.com/' + path;
this.saveUploadResult(fileUrl); //
} else {
//
this.$message.error('文件上传失败', err);
}
});
},
// 访 URL
saveUploadResult(url) {
this.$http({
url: this.$http.adornUrl('/sys/oss/upload'),
method: 'post',
data: { url: url } // URL
}).then(({ data }) => {
// `uploaded` URL
this.$emit('uploaded', url);
})
}
}
}
</script>
<style scoped>
/* 样式部分为空 */
</style>

@ -1,59 +1,73 @@
<template>
<!-- 点击外部div触发文件选择操作 -->
<div @click="selectFile">
<input type="file" ref="fileInput" v-show="false" @change="onFileChange" />
<div>{{uploading?infoText:'上传文件'}}</div>
<!-- 隐藏的文件上传input选择文件后触发onFileChange方法 -->
<input type="file" ref="fileInput" v-show="false" @change="onFileChange" />
<!-- 显示上传中的状态或默认的上传文件文本 -->
<div>{{uploading ? infoText : '上传文件'}}</div>
</div>
</template>
<script>
export default {
name: "oss-uploader",
</template>
<script>
export default {
name: "oss-uploader", // "oss-uploader"
data() {
return {
uploading: false,
infoText: "上传中...",
cosConfig: []
}
return {
uploading: false, // false
infoText: "上传中...", //
cosConfig: [] //
}
},
mounted() {
this.$http({
url: this.$http.adornUrl('/sys/oss/config'),
method: 'get',
params: this.$http.adornParams()
}).then(({ data }) => {
if (data && data.code === 200 && data.config.type) {
this.cosConfig = data.config
} else {
this.$message.error('请先配置云存储相关信息!')
}
})
//
this.$http({
url: this.$http.adornUrl('/sys/oss/config'), // API
method: 'get', // 使GET
params: this.$http.adornParams() //
}).then(({ data }) => {
//
if (data && data.code === 200 && data.config.type) {
this.cosConfig = data.config //
} else {
//
this.$message.error('请先配置云存储相关信息!')
}
})
},
methods: {
selectFile() {//
if (!this.uploading) {
this.$refs.fileInput.click();
}
},
onFileChange() {
let file = this.$refs.fileInput.files[0];
this.uploading = true;
let formData = new FormData();
formData.append("file", file)
this.$http({
url: this.$http.adornUrl('/sys/oss/upload'),
method: 'post',
data: formData
}).then(({ data }) => {
console.log(data)
if (data && data.code === 200) {
this.$emit('uploaded', data.url)
} else {
this.$message.error("文件上传失败:" + data.msg)
}
this.uploading = false;
})
}
//
selectFile() {
if (!this.uploading) {
// input
this.$refs.fileInput.click();
}
},
//
onFileChange() {
let file = this.$refs.fileInput.files[0]; //
this.uploading = true; // true
let formData = new FormData();
formData.append("file", file); // formData
// POST
this.$http({
url: this.$http.adornUrl('/sys/oss/upload'), // API
method: 'post', // 使POST
data: formData //
}).then(({ data }) => {
//
console.log(data); // 便
if (data && data.code === 200) {
// 'uploaded'url
this.$emit('uploaded', data.url)
} else {
//
this.$message.error("文件上传失败:" + data.msg)
}
this.uploading = false; // false
})
}
}
}
}
</script>
</script>

@ -39,108 +39,79 @@
<script>
export default {
data() {
return {
dataForm: {},
dataList: [],
pageIndex: 1,
pageSize: 10,
totalCount: 0,
dataListLoading: false,
dataListSelections: [],
configVisible: false,
uploadVisible: false
}
},
components: {
Config: () => import('./oss-config'),
OssUploader: () => import('./oss-uploader')
},
activated() {
this.getDataList()
},
methods: {
//
getDataList() {
this.dataListLoading = true
this.$http({
url: this.$http.adornUrl('/sys/oss/list'),
method: 'get',
params: this.$http.adornParams({
'page': this.pageIndex,
'limit': this.pageSize,
'sidx': 'id',
'order': 'desc'
})
}).then(({ data }) => {
if (data && data.code === 200) {
this.dataList = data.page.list
this.totalCount = data.page.totalCount
} else {
this.dataList = []
this.totalCount = 0
}
this.dataListLoading = false
})
},
//
sizeChangeHandle(val) {
this.pageSize = val
this.pageIndex = 1
this.getDataList()
},
//
currentChangeHandle(val) {
this.pageIndex = val
this.getDataList()
},
//
selectionChangeHandle(val) {
this.dataListSelections = val
},
//
configHandle() {
this.configVisible = true
this.$nextTick(() => {
this.$refs.config.init()
})
},
//
uploadHandle() {
this.uploadVisible = true
this.$nextTick(() => {
this.$refs.upload.init()
})
},
//
deleteHandle(id) {
var ids = id ? [id] : this.dataListSelections.map(item => item.id)
this.$confirm(`确定对[id=${ids.join(',')}]进行[${id ? '删除' : '批量删除'}]操作?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$http({
url: this.$http.adornUrl('/sys/oss/delete'),
method: 'post',
data: this.$http.adornData(ids, false)
}).then(({ data }) => {
if (data && data.code === 200) {
this.$message({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => this.getDataList()
})
} else {
this.$message.error(data.msg)
}
})
}).catch(() => { })
},
isImageUrl(url) {
return url && /.*\.(gif|jpg|jpeg|png|GIF|JPEG|JPG|PNG)/.test(url)
}
}
data() {
return {
//
dataForm: {},
//
dataList: [],
//
pageIndex: 1,
//
pageSize: 10,
//
totalCount: 0,
//
dataListLoading: false,
//
dataListSelections: [],
//
configVisible: false,
//
uploadVisible: false
}
},
components: {
//
Config: () => import('./oss-config'),
//
OssUploader: () => import('./oss-uploader')
},
//
activated() {
this.getDataList();
},
methods: {
//
getDataList() {
this.dataListLoading = true;
this.$http({
url: this.$http.adornUrl('/sys/oss/list'),
method: 'get',
params: this.$http.adornParams({
'page': this.pageIndex, //
'limit': this.pageSize, //
'sidx': 'id', //
'order': 'desc' //
})
}).then(({ data }) => {
// dataListtotalCount
if (data && data.code === 200) {
this.dataList = data.page.list;
this.totalCount = data.page.totalCount;
} else {
//
this.dataList = [];
this.totalCount = 0;
}
this.dataListLoading = false; //
});
},
//
sizeChangeHandle(val) {
this.pageSize = val;
this.pageIndex = 1; //
this.getDataList(); //
},
//
currentChangeHandle(val) {
this.pageIndex = val;
this.getDataList(); //
},
//
selectionChangeHandle(val) {
this.dataListSelections = val;
},
}
}
</script>

@ -1,96 +1,117 @@
<template>
<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="80px">
<el-form-item label="参数名" prop="paramKey">
<el-input v-model="dataForm.paramKey" placeholder="参数名"></el-input>
</el-form-item>
<el-form-item label="参数值" prop="paramValue">
<el-input v-model="dataForm.paramValue" placeholder="参数值"></el-input>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="dataForm.remark" placeholder="备注"></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>
</span>
</el-dialog>
<!-- 弹窗组件显示标题是新增修改取决于dataForm.id -->
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="80px">
<!-- 表单组件绑定数据模型为dataForm表单校验规则为dataRule按回车键提交表单 -->
<el-form-item label="参数名" prop="paramKey">
<!-- 参数名输入框验证参数名 -->
<el-input v-model="dataForm.paramKey" placeholder="参数名"></el-input>
</el-form-item>
<el-form-item label="参数值" prop="paramValue">
<!-- 参数值输入框验证参数值 -->
<el-input v-model="dataForm.paramValue" placeholder="参数值"></el-input>
</el-form-item>
<el-form-item label="备注" prop="remark">
<!-- 备注输入框 -->
<el-input v-model="dataForm.remark" placeholder="备注"></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>
<!-- 确定按钮点击时提交表单 -->
</span>
</el-dialog>
</template>
<script>
export default {
data() {
return {
visible: false,
dataForm: {
id: 0,
paramKey: '',
paramValue: '',
remark: ''
},
dataRule: {
paramKey: [
{ required: true, message: '参数名不能为空', trigger: 'blur' }
],
paramValue: [
{ required: true, message: '参数值不能为空', trigger: 'blur' }
]
}
}
},
methods: {
init(id) {
this.dataForm.id = id || 0
this.visible = true
this.$nextTick(() => {
this.$refs['dataForm'].resetFields()
if (this.dataForm.id) {
this.$http({
url: this.$http.adornUrl(`/sys/config/info/${this.dataForm.id}`),
method: 'get',
params: this.$http.adornParams()
}).then(({ data }) => {
if (data && data.code === 200) {
this.dataForm.paramKey = data.config.paramKey
this.dataForm.paramValue = data.config.paramValue
this.dataForm.remark = data.config.remark
}
})
}
})
},
//
dataFormSubmit() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
this.$http({
url: this.$http.adornUrl(`/sys/config/${!this.dataForm.id ? 'save' : 'update'}`),
method: 'post',
data: this.$http.adornData({
'id': this.dataForm.id || undefined,
'paramKey': this.dataForm.paramKey,
'paramValue': this.dataForm.paramValue,
'remark': this.dataForm.remark
})
}).then(({ data }) => {
if (data && data.code === 200) {
this.$message({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
this.visible = false
this.$emit('refreshDataList')
}
})
} else {
this.$message.error(data.msg)
}
})
}
})
}
}
data() {
return {
visible: false, //
dataForm: {
id: 0, // ID0ID
paramKey: '', //
paramValue: '',//
remark: '' //
},
dataRule: {
paramKey: [
{ required: true, message: '参数名不能为空', trigger: 'blur' }
],
paramValue: [
{ required: true, message: '参数值不能为空', trigger: 'blur' }
]
}
}
},
methods: {
// ID
init(id) {
this.dataForm.id = id || 0
this.visible = true
this.$nextTick(() => {
this.$refs['dataForm'].resetFields() //
if (this.dataForm.id) {
//
this.$http({
url: this.$http.adornUrl(`/sys/config/info/${this.dataForm.id}`),
method: 'get',
params: this.$http.adornParams()
}).then(({ data }) => {
if (data && data.code === 200) {
this.dataForm.paramKey = data.config.paramKey
this.dataForm.paramValue = data.config.paramValue
this.dataForm.remark = data.config.remark
}
})
}
})
},
//
dataFormSubmit() {
//
this.$refs['dataForm'].validate((valid) => {
if (valid) {
//
this.$http({
url: this.$http.adornUrl(`/sys/config/${!this.dataForm.id ? 'save' : 'update'}`),
method: 'post',
data: this.$http.adornData({
'id': this.dataForm.id || undefined, // IDID
'paramKey': this.dataForm.paramKey,
'paramValue': this.dataForm.paramValue,
'remark': this.dataForm.remark
})
}).then(({ data }) => {
if (data && data.code === 200) {
//
this.$message({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
this.visible = false
this.$emit('refreshDataList') //
}
})
} else {
//
this.$message.error(data.msg)
}
})
}
})
}
}
}
</script>

@ -1,134 +1,172 @@
<template>
<div class="mod-config">
<el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
<el-form-item>
<el-input v-model="dataForm.paramKey" placeholder="参数名" clearable></el-input>
</el-form-item>
<el-form-item>
<el-button @click="getDataList()"></el-button>
<el-button type="primary" @click="addOrUpdateHandle()"></el-button>
<el-button 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-column type="selection" header-align="center" align="center" width="50">
</el-table-column>
<el-table-column prop="id" header-align="center" align="center" width="80" label="ID">
</el-table-column>
<el-table-column prop="paramKey" header-align="center" align="center" label="参数名">
</el-table-column>
<el-table-column prop="paramValue" header-align="center" align="center" label="参数值">
</el-table-column>
<el-table-column prop="remark" header-align="center" align="center" 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="addOrUpdateHandle(scope.row.id)"></el-button>
<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="totalCount" layout="total, sizes, prev, pager, next, jumper">
</el-pagination>
<!-- 弹窗, 新增 / 修改 -->
<add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList"></add-or-update>
</div>
<!-- 表单部分包含参数名的查询框和操作按钮 -->
<el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
<!-- 表单项使用 inline 布局按回车触发查询 -->
<el-form-item>
<el-input v-model="dataForm.paramKey" placeholder="参数名" clearable></el-input>
<!-- 输入框绑定 model dataForm.paramKey允许清除内容 -->
</el-form-item>
<el-form-item>
<!-- 查询按钮点击时触发 getDataList 方法 -->
<el-button @click="getDataList()"></el-button>
<!-- 新增按钮点击时触发 addOrUpdateHandle 方法 -->
<el-button type="primary" @click="addOrUpdateHandle()"></el-button>
<!-- 批量删除按钮点击时触发 deleteHandle 方法只有当有选中的数据时可用 -->
<el-button 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-column type="selection" header-align="center" align="center" width="50"></el-table-column>
<!-- ID列 -->
<el-table-column prop="id" header-align="center" align="center" width="80" label="ID"></el-table-column>
<!-- 参数名列 -->
<el-table-column prop="paramKey" header-align="center" align="center" label="参数名"></el-table-column>
<!-- 参数值列 -->
<el-table-column prop="paramValue" header-align="center" align="center" label="参数值"></el-table-column>
<!-- 备注列 -->
<el-table-column prop="remark" header-align="center" align="center" label="备注"></el-table-column>
<!-- 操作列包含修改和删除按钮 -->
<el-table-column fixed="right" header-align="center" align="center" width="150" label="操作">
<template slot-scope="scope">
<!-- 修改按钮点击时触发 addOrUpdateHandle 方法传入当前行的 id -->
<el-button type="text" size="small" @click="addOrUpdateHandle(scope.row.id)"></el-button>
<!-- 删除按钮点击时触发 deleteHandle 方法传入当前行的 id -->
<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="totalCount" 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 './config-add-or-update'
export default {
data() {
return {
dataForm: {
paramKey: ''
},
dataList: [],
pageIndex: 1,
pageSize: 10,
totalCount: 0,
dataListLoading: false,
dataListSelections: [],
addOrUpdateVisible: false
}
},
components: {
AddOrUpdate
},
activated() {
this.getDataList()
},
methods: {
//
getDataList() {
this.dataListLoading = true
this.$http({
url: this.$http.adornUrl('/sys/config/list'),
method: 'get',
params: this.$http.adornParams({
'page': this.pageIndex,
'limit': this.pageSize,
'paramKey': this.dataForm.paramKey
})
}).then(({ data }) => {
if (data && data.code === 200) {
this.dataList = data.page.list
this.totalCount = data.page.totalCount
} else {
this.dataList = []
this.totalCount = 0
}
this.dataListLoading = false
})
},
//
sizeChangeHandle(val) {
this.pageSize = val
this.pageIndex = 1
this.getDataList()
},
//
currentChangeHandle(val) {
this.pageIndex = val
this.getDataList()
},
//
selectionChangeHandle(val) {
this.dataListSelections = val
},
// /
addOrUpdateHandle(id) {
this.addOrUpdateVisible = true
this.$nextTick(() => {
this.$refs.addOrUpdate.init(id)
})
},
//
deleteHandle(id) {
var ids = id ? [id] : this.dataListSelections.map(item => item.id)
this.$confirm(`确定对[id=${ids.join(',')}]进行[${id ? '删除' : '批量删除'}]操作?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$http({
url: this.$http.adornUrl('/sys/config/delete'),
method: 'post',
data: this.$http.adornData(ids, false)
}).then(({ data }) => {
if (data && data.code === 200) {
this.$message({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => this.getDataList()
})
} else {
this.$message.error(data.msg)
}
})
}).catch(() => { })
}
}
data() {
return {
//
dataForm: {
paramKey: ''
},
//
dataList: [],
//
pageIndex: 1,
//
pageSize: 10,
//
totalCount: 0,
//
dataListLoading: false,
//
dataListSelections: [],
// /
addOrUpdateVisible: false
}
},
components: {
AddOrUpdate
},
activated() {
//
this.getDataList()
},
methods: {
//
getDataList() {
this.dataListLoading = true
this.$http({
url: this.$http.adornUrl('/sys/config/list'),
method: 'get',
params: this.$http.adornParams({
'page': this.pageIndex,
'limit': this.pageSize,
'paramKey': this.dataForm.paramKey
})
}).then(({ data }) => {
if (data && data.code === 200) {
this.dataList = data.page.list
this.totalCount = data.page.totalCount
} else {
this.dataList = []
this.totalCount = 0
}
this.dataListLoading = false
})
},
//
sizeChangeHandle(val) {
this.pageSize = val
this.pageIndex = 1
this.getDataList()
},
//
currentChangeHandle(val) {
this.pageIndex = val
this.getDataList()
},
//
selectionChangeHandle(val) {
this.dataListSelections = val
},
// /
addOrUpdateHandle(id) {
this.addOrUpdateVisible = true
this.$nextTick(() => {
// /
this.$refs.addOrUpdate.init(id)
})
},
//
deleteHandle(id) {
var ids = id ? [id] : this.dataListSelections.map(item => item.id)
//
this.$confirm(`确定对[id=${ids.join(',')}]进行[${id ? '删除' : '批量删除'}]操作?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$http({
url: this.$http.adornUrl('/sys/config/delete'),
method: 'post',
data: this.$http.adornData(ids, false)
}).then(({ data }) => {
if (data && data.code === 200) {
this.$message({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => this.getDataList()
})
} else {
this.$message.error(data.msg)
}
})
}).catch(() => { })
}
}
}
</script>

@ -1,90 +1,107 @@
<template>
<div class="mod-log">
<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-form-item>
</el-form>
<el-table :data="dataList" border v-loading="dataListLoading" style="width: 100%">
<el-table-column prop="id" header-align="center" align="center" width="80" label="ID">
</el-table-column>
<el-table-column prop="username" header-align="center" align="center" label="用户名">
</el-table-column>
<el-table-column prop="operation" header-align="center" align="center" label="用户操作">
</el-table-column>
<el-table-column prop="method" header-align="center" align="center" width="150" :show-overflow-tooltip="true" label="请求方法">
</el-table-column>
<el-table-column prop="params" header-align="center" align="center" width="150" :show-overflow-tooltip="true" label="请求参数">
</el-table-column>
<el-table-column prop="time" header-align="center" align="center" label="执行时长(毫秒)">
</el-table-column>
<el-table-column prop="ip" header-align="center" align="center" width="150" label="IP地址">
</el-table-column>
<el-table-column prop="createDate" header-align="center" align="center" width="180" label="创建时间">
</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="totalCount" layout="total, sizes, prev, pager, next, jumper">
</el-pagination>
</div>
<!-- 搜索表单部分 -->
<el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
<!-- 输入框绑定到 dataForm.key用于搜索用户操作或者用户名 -->
<el-form-item>
<el-input v-model="dataForm.key" placeholder="用户名/用户操作" clearable></el-input>
</el-form-item>
<el-form-item>
<!-- 查询按钮点击时触发 getDataList 方法 -->
<el-button @click="getDataList()"></el-button>
</el-form-item>
</el-form>
<!-- 表格显示数据 -->
<el-table :data="dataList" border v-loading="dataListLoading" style="width: 100%">
<!-- 表格的列定义 -->
<el-table-column prop="id" header-align="center" align="center" width="80" label="ID"></el-table-column>
<el-table-column prop="username" header-align="center" align="center" label="用户名"></el-table-column>
<el-table-column prop="operation" header-align="center" align="center" label="用户操作"></el-table-column>
<el-table-column prop="method" header-align="center" align="center" width="150" :show-overflow-tooltip="true" label="请求方法"></el-table-column>
<el-table-column prop="params" header-align="center" align="center" width="150" :show-overflow-tooltip="true" label="请求参数"></el-table-column>
<el-table-column prop="time" header-align="center" align="center" label="执行时长(毫秒)"></el-table-column>
<el-table-column prop="ip" header-align="center" align="center" width="150" label="IP地址"></el-table-column>
<el-table-column prop="createDate" header-align="center" align="center" width="180" label="创建时间"></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="totalCount" layout="total, sizes, prev, pager, next, jumper">
</el-pagination>
</div>
</template>
<script>
export default {
data() {
return {
dataForm: {
key: ''
},
dataList: [],
pageIndex: 1,
pageSize: 10,
totalCount: 0,
dataListLoading: false,
selectionDataList: []
}
},
created() {
this.getDataList()
},
methods: {
//
getDataList() {
this.dataListLoading = true
this.$http({
url: this.$http.adornUrl('/sys/log/list'),
method: 'get',
params: this.$http.adornParams({
'page': this.pageIndex,
'limit': this.pageSize,
'key': this.dataForm.key,
'sidx': 'id',
'order': 'desc'
})
}).then(({ data }) => {
if (data && data.code === 200) {
this.dataList = data.page.list
this.totalCount = data.page.totalCount
} else {
this.dataList = []
this.totalCount = 0
}
this.dataListLoading = false
})
},
//
sizeChangeHandle(val) {
this.pageSize = val
this.pageIndex = 1
this.getDataList()
},
//
currentChangeHandle(val) {
this.pageIndex = val
this.getDataList()
}
}
data() {
return {
// 'key'
dataForm: {
key: ''
},
//
dataList: [],
//
pageIndex: 1,
//
pageSize: 10,
//
totalCount: 0,
//
dataListLoading: false,
// 使
selectionDataList: []
}
},
created() {
//
this.getDataList()
},
methods: {
//
getDataList() {
//
this.dataListLoading = true
this.$http({
url: this.$http.adornUrl('/sys/log/list'), //
method: 'get',
params: this.$http.adornParams({
//
'page': this.pageIndex,
'limit': this.pageSize,
'key': this.dataForm.key, //
'sidx': 'id', // id
'order': 'desc' //
})
}).then(({ data }) => {
//
if (data && data.code === 200) {
this.dataList = data.page.list //
this.totalCount = data.page.totalCount //
} else {
//
this.dataList = []
this.totalCount = 0
}
//
this.dataListLoading = false
})
},
//
sizeChangeHandle(val) {
this.pageSize = val //
this.pageIndex = 1 //
this.getDataList() //
},
//
currentChangeHandle(val) {
this.pageIndex = val //
this.getDataList() //
}
}
}
</script>

@ -1,218 +1,253 @@
<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="80px">
<el-form-item label="类型" prop="type">
<el-radio-group v-model="dataForm.type">
<el-radio v-for="(type, index) in dataForm.typeList" :label="index" :key="index">{{ type }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="dataForm.typeList[dataForm.type] + '名称'" prop="name">
<el-input v-model="dataForm.name" :placeholder="dataForm.typeList[dataForm.type] + '名称'"></el-input>
</el-form-item>
<el-form-item label="上级菜单" prop="parentName">
<el-popover ref="menuListPopover" placement="bottom-start" trigger="click">
<el-tree :data="menuList" :props="menuListTreeProps" node-key="menuId" ref="menuListTree" @current-change="menuListTreeCurrentChangeHandle" :default-expand-all="true" :highlight-current="true" :expand-on-click-node="false">
</el-tree>
</el-popover>
<el-input v-model="dataForm.parentName" v-popover:menuListPopover :readonly="true" placeholder="点击选择上级菜单" class="menu-list__input"></el-input>
</el-form-item>
<el-form-item v-if="dataForm.type === 1" label="菜单路由" prop="url">
<el-input v-model="dataForm.url" placeholder="菜单路由"></el-input>
</el-form-item>
<el-form-item v-if="dataForm.type !== 0" label="授权标识" prop="perms">
<el-input v-model="dataForm.perms" placeholder="多个用逗号分隔, 如: user:list,user:create"></el-input>
</el-form-item>
<el-form-item v-if="dataForm.type !== 2" label="菜单图标" prop="icon">
<el-row>
<el-col :span="12">
<el-input v-model="dataForm.icon" placeholder="菜单图标名称" class="icon-list__input"></el-input>
</el-col>
<el-col :span="12" class="icon-list__tips">
<el-form-item v-if="dataForm.type !== 2" label="排序号" prop="orderNum">
<el-input-number v-model="dataForm.orderNum" controls-position="right" :min="0" label="排序号"></el-input-number>
</el-form-item>
</el-col>
</el-row>
</el-form-item>
<div>参考ElementUI图标库, <a href="https://element.eleme.cn/#/zh-CN/component/icon" target="_blank">找图标</a></div>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="dataFormSubmit()"></el-button>
</span>
<!-- 表单组件绑定数据模型和验证规则 -->
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="80px">
<!-- 菜单类型选择 -->
<el-form-item label="类型" prop="type">
<el-radio-group v-model="dataForm.type">
<!-- 循环渲染可选择的菜单类型 -->
<el-radio v-for="(type, index) in dataForm.typeList" :label="index" :key="index">{{ type }}</el-radio>
</el-radio-group>
</el-form-item>
<!-- 菜单名称输入框 -->
<el-form-item :label="dataForm.typeList[dataForm.type] + '名称'" prop="name">
<el-input v-model="dataForm.name" :placeholder="dataForm.typeList[dataForm.type] + '名称'"></el-input>
</el-form-item>
<!-- 上级菜单选择 -->
<el-form-item label="上级菜单" prop="parentName">
<!-- 弹出菜单树选择 -->
<el-popover ref="menuListPopover" placement="bottom-start" trigger="click">
<el-tree :data="menuList" :props="menuListTreeProps" node-key="menuId" ref="menuListTree"
@current-change="menuListTreeCurrentChangeHandle" :default-expand-all="true"
:highlight-current="true" :expand-on-click-node="false">
</el-tree>
</el-popover>
<!-- 显示上级菜单选择 -->
<el-input v-model="dataForm.parentName" v-popover:menuListPopover :readonly="true"
placeholder="点击选择上级菜单" class="menu-list__input"></el-input>
</el-form-item>
<!-- 菜单路由仅在选择菜单类型时显示 -->
<el-form-item v-if="dataForm.type === 1" label="菜单路由" prop="url">
<el-input v-model="dataForm.url" placeholder="菜单路由"></el-input>
</el-form-item>
<!-- 授权标识仅在类型不为目录时显示 -->
<el-form-item v-if="dataForm.type !== 0" label="授权标识" prop="perms">
<el-input v-model="dataForm.perms" placeholder="多个用逗号分隔, 如: user:list,user:create"></el-input>
</el-form-item>
<!-- 菜单图标仅在类型不为按钮时显示 -->
<el-form-item v-if="dataForm.type !== 2" label="菜单图标" prop="icon">
<el-row>
<el-col :span="12">
<el-input v-model="dataForm.icon" placeholder="菜单图标名称" class="icon-list__input"></el-input>
</el-col>
<el-col :span="12" class="icon-list__tips">
<!-- 排序号输入框仅在类型不为按钮时显示 -->
<el-form-item v-if="dataForm.type !== 2" label="排序号" prop="orderNum">
<el-input-number v-model="dataForm.orderNum" controls-position="right" :min="0" label="排序号"></el-input-number>
</el-form-item>
</el-col>
</el-row>
</el-form-item>
<!-- 提示用户参考图标库 -->
<div>参考ElementUI图标库, 找图标</div>
</el-form>
<!-- 弹窗底部按钮 -->
<span slot="footer" class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="dataFormSubmit()"></el-button>
</span>
</el-dialog>
</template>
<script>
import { treeDataTranslate } from '@/utils'
export default {
</template>
<script>
//
import { treeDataTranslate } from '@/utils'
export default {
data() {
var validateUrl = (rule, value, callback) => {
if (this.dataForm.type === 1 && !/\S/.test(value)) {
callback(new Error('菜单URL不能为空'))
} else {
callback()
}
}
return {
visible: false,
dataForm: {
id: 0,
type: 1,
typeList: ['目录', '菜单', '按钮'],
name: '',
parentId: 0,
parentName: '',
url: '',
perms: '',
orderNum: 0,
icon: '',
},
dataRule: {
name: [
{ required: true, message: '菜单名称不能为空', trigger: 'blur' }
],
parentName: [
{ required: true, message: '上级菜单不能为空', trigger: 'change' }
],
url: [
{ validator: validateUrl, trigger: 'blur' }
]
},
menuList: [],
menuListTreeProps: {
label: 'name',
children: 'children'
}
}
// URL
var validateUrl = (rule, value, callback) => {
// URL
if (this.dataForm.type === 1 && !/\S/.test(value)) {
callback(new Error('菜单URL不能为空'))
} else {
callback() //
}
}
return {
visible: false, //
dataForm: {
id: 0, // ID0
type: 1, // 0-1-2-
typeList: ['目录', '菜单', '按钮'], //
name: '', //
parentId: 0, // ID
parentName: '', //
url: '', //
perms: '', //
orderNum: 0, //
icon: '', //
},
//
dataRule: {
name: [
{ required: true, message: '菜单名称不能为空', trigger: 'blur' } //
],
parentName: [
{ required: true, message: '上级菜单不能为空', trigger: 'change' } //
],
url: [
{ validator: validateUrl, trigger: 'blur' } // URL
]
},
menuList: [], // 使
menuListTreeProps: {
label: 'name', //
children: 'children' //
}
}
},
methods: {
init(id) {
this.dataForm.id = id || 0
this.$http({
url: this.$http.adornUrl('/sys/menu/select'),
method: 'get',
params: this.$http.adornParams()
}).then(({ data }) => {
this.menuList = treeDataTranslate(data.menuList, 'menuId')
}).then(() => {
this.visible = true
this.$nextTick(() => {
this.$refs['dataForm'].resetFields()
})
}).then(() => {
if (!this.dataForm.id) {
//
this.menuListTreeSetCurrentNode()
} else {
//
this.$http({
url: this.$http.adornUrl(`/sys/menu/info/${this.dataForm.id}`),
method: 'get',
params: this.$http.adornParams()
}).then(({ data }) => {
this.dataForm.id = data.menu.menuId
this.dataForm.type = data.menu.type
this.dataForm.name = data.menu.name
this.dataForm.parentId = data.menu.parentId
this.dataForm.url = data.menu.url
this.dataForm.perms = data.menu.perms
this.dataForm.orderNum = data.menu.orderNum
this.dataForm.icon = data.menu.icon
this.menuListTreeSetCurrentNode()
})
}
})
},
//
menuListTreeCurrentChangeHandle(data, node) {
this.dataForm.parentId = data.menuId
this.dataForm.parentName = data.name
},
//
menuListTreeSetCurrentNode() {
this.$refs.menuListTree.setCurrentKey(this.dataForm.parentId)
this.dataForm.parentName = (this.$refs.menuListTree.getCurrentNode() || {})['name']
},
//
dataFormSubmit() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
this.$http({
url: this.$http.adornUrl(`/sys/menu/${!this.dataForm.id ? 'save' : 'update'}`),
method: 'post',
data: this.$http.adornData({
'menuId': this.dataForm.id || undefined,
'type': this.dataForm.type,
'name': this.dataForm.name,
'parentId': this.dataForm.parentId,
'url': this.dataForm.url,
'perms': this.dataForm.perms,
'orderNum': this.dataForm.orderNum,
'icon': this.dataForm.icon
})
}).then(({ data }) => {
if (data && data.code === 200) {
this.$message({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
this.visible = false
this.$emit('refreshDataList')
}
})
} else {
this.$message.error(data.msg)
}
})
}
})
}
}
}
</script>
<style lang="scss">
.mod-menu {
//
init(id) {
this.dataForm.id = id || 0 // ID
this.$http({
url: this.$http.adornUrl('/sys/menu/select'),
method: 'get',
params: this.$http.adornParams()
}).then(({ data }) => {
//
this.menuList = treeDataTranslate(data.menuList, 'menuId')
}).then(() => {
this.visible = true //
this.$nextTick(() => {
this.$refs['dataForm'].resetFields() //
})
}).then(() => {
if (!this.dataForm.id) {
//
this.menuListTreeSetCurrentNode()
} else {
//
this.$http({
url: this.$http.adornUrl(`/sys/menu/info/${this.dataForm.id}`),
method: 'get',
params: this.$http.adornParams()
}).then(({ data }) => {
//
this.dataForm.id = data.menu.menuId
this.dataForm.type = data.menu.type
this.dataForm.name = data.menu.name
this.dataForm.parentId = data.menu.parentId
this.dataForm.url = data.menu.url
this.dataForm.perms = data.menu.perms
this.dataForm.orderNum = data.menu.orderNum
this.dataForm.icon = data.menu.icon
this.menuListTreeSetCurrentNode() //
})
}
})
},
//
menuListTreeCurrentChangeHandle(data, node) {
this.dataForm.parentId = data.menuId // ID
this.dataForm.parentName = data.name //
},
//
menuListTreeSetCurrentNode() {
this.$refs.menuListTree.setCurrentKey(this.dataForm.parentId) //
this.dataForm.parentName = (this.$refs.menuListTree.getCurrentNode() || {})['name'] //
},
//
dataFormSubmit() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
//
this.$http({
url: this.$http.adornUrl(`/sys/menu/${!this.dataForm.id ? 'save' : 'update'}`),
method: 'post',
data: this.$http.adornData({
'menuId': this.dataForm.id || undefined,
'type': this.dataForm.type,
'name': this.dataForm.name,
'parentId': this.dataForm.parentId,
'url': this.dataForm.url,
'perms': this.dataForm.perms,
'orderNum': this.dataForm.orderNum,
'icon': this.dataForm.icon
})
}).then(({ data }) => {
if (data && data.code === 200) {
//
this.$message({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
this.visible = false //
this.$emit('refreshDataList') //
}
})
} else {
//
this.$message.error(data.msg)
}
})
}
})
}
}
}
</script>
<style lang="scss">
.mod-menu {
.menu-list__input,
.icon-list__input {
> .el-input__inner {
cursor: pointer;
}
> .el-input__inner {
cursor: pointer; //
}
}
&__icon-popover {
width: 458px;
overflow: hidden;
width: 458px; //
overflow: hidden; //
}
&__icon-inner {
width: 478px;
max-height: 258px;
overflow-x: hidden;
overflow-y: auto;
width: 478px; //
max-height: 258px; //
overflow-x: hidden; //
overflow-y: auto; //
}
&__icon-list {
width: 458px;
padding: 0;
margin: -8px 0 0 -8px;
> .el-button {
padding: 8px;
margin: 8px 0 0 8px;
> span {
display: inline-block;
vertical-align: middle;
width: 18px;
height: 18px;
font-size: 18px;
}
}
width: 458px; //
padding: 0; //
margin: -8px 0 0 -8px; //
> .el-button {
padding: 8px; //
margin: 8px 0 0 8px; //
> span {
display: inline-block; // 使
vertical-align: middle; //
width: 18px; //
height: 18px; //
font-size: 18px; //
}
}
}
.icon-list__tips {
font-size: 18px;
text-align: center;
color: #e6a23c;
cursor: pointer;
font-size: 18px; //
text-align: center; //
color: #e6a23c; //
cursor: pointer; //
}
}
}
</style>
</style>

@ -1,109 +1,148 @@
<template>
<!-- 菜单管理模块 -->
<div class="mod-menu">
<el-form :inline="true" :model="dataForm">
<el-form-item>
<el-button v-if="isAuth('sys:menu:save')" type="primary" @click="addOrUpdateHandle()"></el-button>
</el-form-item>
</el-form>
<el-table :data="dataList" row-key="menuId" border style="width: 100%; ">
<el-table-column prop="name" header-align="center" min-width="150" label="名称">
</el-table-column>
<el-table-column prop="parentName" header-align="center" align="center" width="120" label="上级菜单">
</el-table-column>
<el-table-column header-align="center" align="center" label="图标">
<template slot-scope="scope">
<i :class="scope.row.icon"></i>
</template>
</el-table-column>
<el-table-column prop="type" header-align="center" align="center" label="类型">
<template slot-scope="scope">
<el-tag v-if="scope.row.type === 0" size="small"></el-tag>
<el-tag v-else-if="scope.row.type === 1" size="small" type="success">菜单</el-tag>
<el-tag v-else-if="scope.row.type === 2" size="small" type="info">按钮</el-tag>
</template>
</el-table-column>
<el-table-column prop="orderNum" header-align="center" align="center" label="排序号">
</el-table-column>
<el-table-column prop="url" header-align="center" align="center" width="150" :show-overflow-tooltip="true" label="菜单URL">
</el-table-column>
<el-table-column prop="perms" header-align="center" align="center" width="150" :show-overflow-tooltip="true" label="授权标识">
</el-table-column>
<el-table-column fixed="right" header-align="center" align="center" width="150" label="操作">
<template slot-scope="scope">
<el-button v-if="isAuth('sys:menu:update')" type="text" size="small" @click="addOrUpdateHandle(scope.row.menuId)"></el-button>
<el-button v-if="isAuth('sys:menu:delete')" type="text" size="small" @click="deleteHandle(scope.row.menuId)"></el-button>
</template>
</el-table-column>
</el-table>
<!-- 弹窗, 新增 / 修改 -->
<add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList"></add-or-update>
<!-- 表单组件内联布局 -->
<el-form :inline="true" :model="dataForm">
<el-form-item>
<!-- 如果有新增权限显示新增按钮 -->
<el-button v-if="isAuth('sys:menu:save')" type="primary" @click="addOrUpdateHandle()"></el-button>
</el-form-item>
</el-form>
<!-- 菜单数据表格 -->
<el-table :data="dataList" row-key="menuId" border style="width: 100%;">
<!-- 菜单名称列 -->
<el-table-column prop="name" header-align="center" min-width="150" label="名称">
</el-table-column>
<!-- 上级菜单列 -->
<el-table-column prop="parentName" header-align="center" align="center" width="120" label="上级菜单">
</el-table-column>
<!-- 菜单图标列 -->
<el-table-column header-align="center" align="center" label="图标">
<template slot-scope="scope">
<!-- 根据图标类渲染图标 -->
<i :class="scope.row.icon"></i>
</template>
</el-table-column>
<!-- 菜单类型列 -->
<el-table-column prop="type" header-align="center" align="center" label="类型">
<template slot-scope="scope">
<!-- 根据菜单类型渲染不同的标签 -->
<el-tag v-if="scope.row.type === 0" size="small"></el-tag>
<el-tag v-else-if="scope.row.type === 1" size="small" type="success">菜单</el-tag>
<el-tag v-else-if="scope.row.type === 2" size="small" type="info">按钮</el-tag>
</template>
</el-table-column>
<!-- 排序号列 -->
<el-table-column prop="orderNum" header-align="center" align="center" label="排序号">
</el-table-column>
<!-- 菜单URL列支持溢出提示 -->
<el-table-column prop="url" header-align="center" align="center" width="150" :show-overflow-tooltip="true" label="菜单URL">
</el-table-column>
<!-- 授权标识列支持溢出提示 -->
<el-table-column prop="perms" header-align="center" align="center" width="150" :show-overflow-tooltip="true" label="授权标识">
</el-table-column>
<!-- 操作列显示修改和删除按钮 -->
<el-table-column fixed="right" header-align="center" align="center" width="150" label="操作">
<template slot-scope="scope">
<!-- 如果有修改权限显示修改按钮 -->
<el-button v-if="isAuth('sys:menu:update')" type="text" size="small" @click="addOrUpdateHandle(scope.row.menuId)"></el-button>
<!-- 如果有删除权限显示删除按钮 -->
<el-button v-if="isAuth('sys:menu:delete')" type="text" size="small" @click="deleteHandle(scope.row.menuId)"></el-button>
</template>
</el-table-column>
</el-table>
<!-- 弹窗组件用于新增或修改菜单 -->
<add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList"></add-or-update>
</div>
</template>
<script>
import AddOrUpdate from './menu-add-or-update'
import { treeDataTranslate } from '@/utils'
export default {
</template>
<script>
// /
import AddOrUpdate from './menu-add-or-update'
import { treeDataTranslate } from '@/utils'
export default {
data() {
return {
dataForm: {},
dataList: [],
dataListLoading: false,
addOrUpdateVisible: false
}
return {
dataForm: {}, //
dataList: [], //
dataListLoading: false, //
addOrUpdateVisible: false //
}
},
components: {
AddOrUpdate
AddOrUpdate // /
},
activated() {
this.getDataList()
//
this.getDataList()
},
methods: {
//
getDataList() {
this.dataListLoading = true
this.$http({
url: this.$http.adornUrl('/sys/menu/list'),
method: 'get',
params: this.$http.adornParams()
}).then(({ data }) => {
this.dataList = treeDataTranslate(data, 'menuId')
this.dataListLoading = false
})
},
// /
addOrUpdateHandle(id) {
this.addOrUpdateVisible = true
this.$nextTick(() => {
this.$refs.addOrUpdate.init(id)
})
},
//
deleteHandle(id) {
this.$confirm(`确定对[id=${id}]进行[删除]操作?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$http({
url: this.$http.adornUrl(`/sys/menu/delete/${id}`),
method: 'post',
data: this.$http.adornData()
}).then(({ data }) => {
if (data && data.code === 200) {
this.$message({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => this.getDataList()
})
} else {
this.$message.error(data.msg)
}
})
}).catch(() => { })
}
//
getDataList() {
this.dataListLoading = true //
// HTTP
this.$http({
url: this.$http.adornUrl('/sys/menu/list'),
method: 'get',
params: this.$http.adornParams()
}).then(({ data }) => {
// 使
this.dataList = treeDataTranslate(data, 'menuId')
this.dataListLoading = false //
})
},
//
addOrUpdateHandle(id) {
this.addOrUpdateVisible = true // /
this.$nextTick(() => {
//
this.$refs.addOrUpdate.init(id)
})
},
//
deleteHandle(id) {
//
this.$confirm(`确定对[id=${id}]进行[删除]操作?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning' //
}).then(() => {
//
this.$http({
url: this.$http.adornUrl(`/sys/menu/delete/${id}`),
method: 'post',
data: this.$http.adornData()
}).then(({ data }) => {
if (data && data.code === 200) {
//
this.$message({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => this.getDataList() //
})
} else {
//
this.$message.error(data.msg)
}
})
}).catch(() => {
//
})
}
}
}
}
</script>
</script>

@ -1,111 +1,148 @@
<template>
<!-- 弹窗组件显示角色的新增或修改表单 -->
<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="80px">
<el-form-item label="角色名称" prop="roleName">
<el-input v-model="dataForm.roleName" placeholder="角色名称"></el-input>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="dataForm.remark" placeholder="备注"></el-input>
</el-form-item>
<el-form-item size="mini" label="授权">
<el-tree :data="menuList" :props="menuListTreeProps" node-key="menuId" ref="menuListTree" :default-expand-all="true" show-checkbox>
</el-tree>
</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>
</span>
<!-- 表单组件绑定数据和验证规则 -->
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="80px">
<!-- 角色名称输入框 -->
<el-form-item label="角色名称" prop="roleName">
<el-input v-model="dataForm.roleName" placeholder="角色名称"></el-input>
</el-form-item>
<!-- 备注输入框 -->
<el-form-item label="备注" prop="remark">
<el-input v-model="dataForm.remark" placeholder="备注"></el-input>
</el-form-item>
<!-- 授权菜单树 -->
<el-form-item size="mini" label="授权">
<el-tree :data="menuList" :props="menuListTreeProps" node-key="menuId" ref="menuListTree" :default-expand-all="true" show-checkbox>
</el-tree>
</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>
</span>
</el-dialog>
</template>
<script>
import { treeDataTranslate } from '@/utils'
export default {
</template>
<script>
//
import { treeDataTranslate } from '@/utils'
export default {
data() {
return {
visible: false,
menuList: [],
menuListTreeProps: {
label: 'name',
children: 'children'
},
dataForm: {
id: 0,
roleName: '',
remark: ''
},
dataRule: {
roleName: [
{ required: true, message: '角色名称不能为空', trigger: 'blur' }
]
}
}
return {
//
visible: false,
//
menuList: [],
// el-tree
menuListTreeProps: {
label: 'name', //
children: 'children' //
},
//
dataForm: {
id: 0, // ID0
roleName: '', //
remark: '' //
},
//
dataRule: {
roleName: [
{ required: true, message: '角色名称不能为空', trigger: 'blur' } //
]
}
}
},
methods: {
init(id) {
this.dataForm.id = id || 0
this.$http({
url: this.$http.adornUrl('/sys/menu/list'),
method: 'get',
params: this.$http.adornParams()
}).then(({ data }) => {
this.menuList = treeDataTranslate(data, 'menuId')
}).then(() => {
this.visible = true
this.$nextTick(() => {
this.$refs['dataForm'].resetFields()
this.$refs.menuListTree.setCheckedKeys([])
})
}).then(() => {
if (this.dataForm.id) {
this.$http({
url: this.$http.adornUrl(`/sys/role/info/${this.dataForm.id}`),
method: 'get',
params: this.$http.adornParams()
}).then(({ data }) => {
if (data && data.code === 200) {
this.dataForm.roleName = data.role.roleName
this.dataForm.remark = data.role.remark
data.role.menuIdList.forEach(item => {
this.$refs.menuListTree.setChecked(item, true);
});
}
})
}
})
},
//
dataFormSubmit() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
this.$http({
url: this.$http.adornUrl(`/sys/role/${!this.dataForm.id ? 'save' : 'update'}`),
method: 'post',
data: this.$http.adornData({
'roleId': this.dataForm.id || undefined,
'roleName': this.dataForm.roleName,
'remark': this.dataForm.remark,
'menuIdList': [].concat(this.$refs.menuListTree.getCheckedKeys(), this.$refs.menuListTree.getHalfCheckedKeys())
})
}).then(({ data }) => {
if (data && data.code === 200) {
this.$message({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
this.visible = false
this.$emit('refreshDataList')
}
})
} else {
this.$message.error(data.msg)
}
})
}
})
}
// ID
init(id) {
this.dataForm.id = id || 0 // ID0
//
this.$http({
url: this.$http.adornUrl('/sys/menu/list'),
method: 'get',
params: this.$http.adornParams()
}).then(({ data }) => {
this.menuList = treeDataTranslate(data, 'menuId') //
}).then(() => {
//
this.visible = true
// DOM
this.$nextTick(() => {
//
this.$refs['dataForm'].resetFields()
//
this.$refs.menuListTree.setCheckedKeys([])
})
}).then(() => {
// ID
if (this.dataForm.id) {
this.$http({
url: this.$http.adornUrl(`/sys/role/info/${this.dataForm.id}`),
method: 'get',
params: this.$http.adornParams()
}).then(({ data }) => {
if (data && data.code === 200) {
//
this.dataForm.roleName = data.role.roleName
this.dataForm.remark = data.role.remark
//
data.role.menuIdList.forEach(item => {
this.$refs.menuListTree.setChecked(item, true)
})
}
})
}
})
},
//
dataFormSubmit() {
//
this.$refs['dataForm'].validate((valid) => {
if (valid) {
//
this.$http({
url: this.$http.adornUrl(`/sys/role/${!this.dataForm.id ? 'save' : 'update'}`), // ID
method: 'post',
data: this.$http.adornData({
'roleId': this.dataForm.id || undefined, // ID
'roleName': this.dataForm.roleName, //
'remark': this.dataForm.remark, //
// ID
'menuIdList': [].concat(this.$refs.menuListTree.getCheckedKeys(), this.$refs.menuListTree.getHalfCheckedKeys())
})
}).then(({ data }) => {
if (data && data.code === 200) {
//
this.$message({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
//
this.visible = false
this.$emit('refreshDataList')
}
})
} else {
//
this.$message.error(data.msg)
}
})
}
})
}
}
}
}
</script>
</script>

@ -1,132 +1,148 @@
<template>
<div class="mod-role">
<el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
<el-form-item>
<el-input v-model="dataForm.roleName" placeholder="角色名称" clearable></el-input>
</el-form-item>
<el-form-item>
<el-button @click="getDataList()"></el-button>
<el-button v-if="isAuth('sys:role:save')" type="primary" @click="addOrUpdateHandle()"></el-button>
<el-button v-if="isAuth('sys:role: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-column type="selection" header-align="center" align="center" width="50">
</el-table-column>
<el-table-column prop="roleId" header-align="center" align="center" width="80" label="ID">
</el-table-column>
<el-table-column prop="roleName" header-align="center" align="center" label="角色名称">
</el-table-column>
<el-table-column prop="remark" header-align="center" align="center" label="备注">
</el-table-column>
<el-table-column fixed="right" header-align="center" align="center" width="150" label="操作">
<template slot-scope="scope">
<el-button v-if="isAuth('sys:role:update')" type="text" size="small" @click="addOrUpdateHandle(scope.row.roleId)"></el-button>
<el-button v-if="isAuth('sys:role:delete')" type="text" size="small" @click="deleteHandle(scope.row.roleId)"></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="totalCount" 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 './role-add-or-update'
export default {
<!-- 弹窗组件显示角色的新增或修改表单 -->
<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="80px">
<!-- 角色名称输入框 -->
<el-form-item label="角色名称" prop="roleName">
<el-input v-model="dataForm.roleName" placeholder="角色名称"></el-input>
</el-form-item>
<!-- 备注输入框 -->
<el-form-item label="备注" prop="remark">
<el-input v-model="dataForm.remark" placeholder="备注"></el-input>
</el-form-item>
<!-- 授权菜单树 -->
<el-form-item size="mini" label="授权">
<el-tree :data="menuList" :props="menuListTreeProps" node-key="menuId" ref="menuListTree" :default-expand-all="true" show-checkbox>
</el-tree>
</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>
</span>
</el-dialog>
</template>
<script>
//
import { treeDataTranslate } from '@/utils'
export default {
data() {
return {
dataForm: {
roleName: ''
},
dataList: [],
pageIndex: 1,
pageSize: 10,
totalCount: 0,
dataListLoading: false,
dataListSelections: [],
addOrUpdateVisible: false
}
return {
//
visible: false,
//
menuList: [],
// el-tree
menuListTreeProps: {
label: 'name', //
children: 'children' //
},
components: {
AddOrUpdate
//
dataForm: {
id: 0, // ID0
roleName: '', //
remark: '' //
},
activated() {
this.getDataList()
//
dataRule: {
roleName: [
{ required: true, message: '角色名称不能为空', trigger: 'blur' } //
]
}
}
},
methods: {
//
getDataList() {
this.dataListLoading = true
this.$http({
url: this.$http.adornUrl('/sys/role/list'),
method: 'get',
params: this.$http.adornParams({
'page': this.pageIndex,
'limit': this.pageSize,
'roleName': this.dataForm.roleName
})
}).then(({ data }) => {
if (data && data.code === 200) {
this.dataList = data.page.list
this.totalCount = data.page.totalCount
} else {
this.dataList = []
this.totalCount = 0
}
this.dataListLoading = false
})
},
//
sizeChangeHandle(val) {
this.pageSize = val
this.pageIndex = 1
this.getDataList()
},
//
currentChangeHandle(val) {
this.pageIndex = val
this.getDataList()
},
//
selectionChangeHandle(val) {
this.dataListSelections = val
},
// /
addOrUpdateHandle(id) {
this.addOrUpdateVisible = true
this.$nextTick(() => {
this.$refs.addOrUpdate.init(id)
})
},
//
deleteHandle(id) {
var ids = id ? [id] : this.dataListSelections.map(item => item.roleId)
this.$confirm(`确定对[id=${ids.join(',')}]进行[${id ? '删除' : '批量删除'}]操作?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$http({
url: this.$http.adornUrl('/sys/role/delete'),
method: 'post',
data: this.$http.adornData(ids, false)
}).then(({ data }) => {
if (data && data.code === 200) {
this.$message({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => this.getDataList()
})
} else {
this.$message.error(data.msg)
}
})
}).catch(() => { })
}
// ID
init(id) {
this.dataForm.id = id || 0 // ID0
//
this.$http({
url: this.$http.adornUrl('/sys/menu/list'),
method: 'get',
params: this.$http.adornParams()
}).then(({ data }) => {
this.menuList = treeDataTranslate(data, 'menuId') //
}).then(() => {
//
this.visible = true
// DOM
this.$nextTick(() => {
//
this.$refs['dataForm'].resetFields()
//
this.$refs.menuListTree.setCheckedKeys([])
})
}).then(() => {
// ID
if (this.dataForm.id) {
this.$http({
url: this.$http.adornUrl(`/sys/role/info/${this.dataForm.id}`),
method: 'get',
params: this.$http.adornParams()
}).then(({ data }) => {
if (data && data.code === 200) {
//
this.dataForm.roleName = data.role.roleName
this.dataForm.remark = data.role.remark
//
data.role.menuIdList.forEach(item => {
this.$refs.menuListTree.setChecked(item, true)
})
}
})
}
})
},
//
dataFormSubmit() {
//
this.$refs['dataForm'].validate((valid) => {
if (valid) {
//
this.$http({
url: this.$http.adornUrl(`/sys/role/${!this.dataForm.id ? 'save' : 'update'}`), // ID
method: 'post',
data: this.$http.adornData({
'roleId': this.dataForm.id || undefined, // ID
'roleName': this.dataForm.roleName, //
'remark': this.dataForm.remark, //
// ID
'menuIdList': [].concat(this.$refs.menuListTree.getCheckedKeys(), this.$refs.menuListTree.getHalfCheckedKeys())
})
}).then(({ data }) => {
if (data && data.code === 200) {
//
this.$message({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
//
this.visible = false
this.$emit('refreshDataList')
}
})
} else {
//
this.$message.error(data.msg)
}
})
}
})
}
}
}
}
</script>
</script>

@ -6,37 +6,54 @@
<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>
</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>
</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">
<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">
<el-button type="success" icon="el-icon-upload">发布</el-button>
<el-button type="warning" icon="el-icon-delete" @click="delMenu"></el-button>
<el-button type="warning" icon="el-icon-delete" @click="delMenuAll"></el-button>
<!-- 注意这里可能是一个小错误因为delMenu方法通常用于删除选中的菜单项而不是清空所有菜单 -->
<!-- 如果要清空所有菜单可能需要一个单独的方法比如delMenuAll -->
</div>
</div>
</template>
@ -56,6 +73,7 @@ export default {
}
},
mounted() {
//
this.getWxMenu();
},
methods: {
@ -88,7 +106,7 @@ export default {
this.selectedSubMenuIndex = i2
this.selectedButton = this.menu.buttons[i].subButtons[i2]
},
//
//
addMenu(level,i) {
if (level == 1 && this.menu.buttons.length < 3) {
this.menu.buttons.push({
@ -108,7 +126,7 @@ export default {
this.selectSubMenu(i,this.menu.buttons[i].subButtons.length - 1)
}
},
//
//
delMenu() {
if (this.selectedMenuLevel == 1 && confirm('删除后菜单下设置的内容将被删除')) {
this.menu.buttons.splice(this.selectedMenuIndex, 1);
@ -118,12 +136,13 @@ export default {
this.unSelectMenu()
}
},
unSelectMenu(){//
unSelectMenu(){//
this.selectedMenuLevel = 0
this.selectedMenuIndex = ''
this.selectedSubMenuIndex = ''
this.selectedButton = ''
},
//
updateWxMenu() {
this.$http({
url: this.$http.adornUrl('/manage/wxMenu/updateMenu'),
@ -138,7 +157,7 @@ export default {
});
},
onDrop(i,i2){//
onDrop(i,i2){//
this.onDragOverMenu='';
if(i==this.selectedMenuIndex && i2==this.selectedSubMenuIndex) //
return

@ -1,14 +1,22 @@
<template>
<!-- 消息回复对话框 -->
<el-dialog title="消息回复" :close-on-click-modal="false" :visible.sync="visible">
<!-- 表单区域 -->
<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>
</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>
<!-- 发送按钮根据uploading状态显示不同文本并控制是否禁用 -->
<el-button type="success" @click="dataFormSubmit()" :disabled="uploading">{{uploading ? '发送中...' : '发送'}}</el-button>
</span>
</el-dialog>
</template>
@ -17,68 +25,83 @@
export default {
data() {
return {
//
visible: false,
//
uploading: false,
//
dataForm: {
openid:'',
replyType:'text',
replyContent:''
//
openid: '',
//
replyType: 'text',
//
replyContent: ''
},
//
dataRule: {
//
replyContent: [
{ required: true, message: "回复内容不能为空", trigger: "blur" }
]
}
}
},
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
this.visible = true
if (!openid) throw '参数异常'; // openid
this.dataForm.openid = openid; // openid
this.visible = true; //
},
//
//
dataFormSubmit() {
if(this.uploading)return
this.uploading=true
this.$refs['dataForm'].validate((valid) => {
if (valid) {
if (this.uploading) return; //
this.uploading = true; // true
this.$refs['dataForm'].validate((valid) => { //
if (valid) { //
// $http
this.$http({
url: this.$http.adornUrl(`/manage/wxMsg/reply`),
method: 'post',
data: this.$http.adornData(this.dataForm)
}).then(({ data }) => {
if (data && data.code === 200) {
url: this.$http.adornUrl(`/manage/wxMsg/reply`), //
method: 'post', //
data: this.$http.adornData(this.dataForm) //
}).then(({ data }) => { //
if (data && data.code === 200) { // 200
//
this.$message({
message: '回复成功',
type: 'success',
duration: 1500,
onClose: () => {
this.visible = false
this.visible = false;
}
})
this.$emit("success",{...this.dataForm});
this.dataForm.replyContent=''
} else {
this.$message.error(data.msg)
});
this.$emit("success", { ...this.dataForm }); // success
this.dataForm.replyContent = ''; //
} else { //
//
this.$message.error(data.msg);
}
this.uploading=false
})
this.uploading = false; //
});
}
})
});
},
//
addLink() {
this.dataForm.replyContent += '<a href="链接地址">链接文字</a>'
this.dataForm.replyContent += '<a href="链接地址">链接文字</a>';
}
}
}
</script>
<style scoped>
.msg-container{
/* 样式部分,但.msg-container类在模板中未使用 */
.msg-container {
background: #eee;
}
</style>

@ -1,12 +1,16 @@
<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-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>
@ -14,63 +18,80 @@
</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>
<!-- 消息列表使用v-loading显示加载状态 -->
<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="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="reply-btn">
<!-- 回复按钮如果消息在24小时内可点击 -->
<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>
<!-- 弹窗, 消息回复 -->
<wx-msg-reply ref="wxMsgReply" @success="onReplyed"></wx-msg-reply>
</div>
</template>
<script>
const TIME_FORMAT = 'YYYY/MM/DD hh:mm:ss'
export default {
data() {
return {
//
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:[],
//
pageIndex: 1,
pageSize: 20,
totalCount: 0,
//
dataListLoading: false,
// 使
dataListSelections: []
}
},
components: {
//
WxMsgReply:()=>import('./wx-msg-reply'),
WxMsgPreview:()=>import('@/components/wx-msg-preview')
},
//
activated() {
this.getDataList()
},
@ -101,6 +122,7 @@ 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
@ -115,25 +137,26 @@ export default {
}
})
},
// openid
getUserInfo(openid){
return this.userDataList.find(u=>u.openid==openid) || {nickname:'--',headimgurl:''}
},
// 24
// 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
},
@ -143,6 +166,7 @@ export default {
this.$refs.wxMsgReply.init(openid)
})
},
//
onReplyed(replyMsg){
this.dataList.unshift({
openid : replyMsg.openid,

@ -1,22 +1,43 @@
<template>
<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">
<!-- Element UI的对话框组件根据dataForm.id是否存在来设置标题为新增修改 -->
<el-dialog
:title="!dataForm.id ? '新增' : '修改'"
:close-on-click-modal="false" <!-- 点击遮罩层不关闭对话框 -->
:visible.sync="visible"> <!-- 双向绑定控制对话框的显示隐藏 -->
<!-- 表单部分绑定dataForm作为数据模型dataRule作为验证规则 -->
<el-form
:model="dataForm"
:rules="dataRule"
ref="dataForm"
@keyup.enter.native="dataFormSubmit()" <!-- 按下Enter键提交表单 -->
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>
</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,54 +49,55 @@
export default {
data() {
return {
visible: false,
visible: false, //
dataForm: {
isTemp: true,
sceneStr: '',
expireSeconds: 2592000
isTemp: true, //
sceneStr: '', //
expireSeconds: 2592000 // 30
},
dataRule: {
isTemp: [
{ required: true, message: '二维码类型不能为空', trigger: 'blur' }
{ required: true, message: '二维码类型不能为空', trigger: 'blur' } //
],
sceneStr: [
{ required: true, message: '场景值ID不能为空', trigger: 'blur' }
{ required: true, message: '场景值ID不能为空', trigger: 'blur' } //
],
expireSeconds: [
{ required: true, message: '该二维码失效时间不能为空', trigger: 'blur' }
{ required: true, message: '该二维码失效时间不能为空', trigger: 'blur' } //
]
}
}
},
methods: {
// id
init(id) {
this.dataForm.id = id || 0
this.visible = true
this.dataForm.id = id || 0 // dataFormid
this.visible = true //
this.$nextTick(() => {
this.$refs['dataForm'].resetFields()
this.$refs['dataForm'].resetFields() //
})
},
//
//
dataFormSubmit() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
this.$http({
url: this.$http.adornUrl(`/manage/wxQrCode/createTicket`),
method: 'post',
data: this.$http.adornData(this.dataForm)
if (valid) { //
this.$http({ //
url: this.$http.adornUrl(`/manage/wxQrCode/createTicket`), // URL
method: 'post', //
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 +105,4 @@ export default {
}
}
}
</script>
</script>

@ -1,67 +1,86 @@
<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-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="id" header-align="center" align="center" label="ID">
</el-table-column>
<el-table-column type="selection" header-align="center" align="center" width="50"></el-table-column>
<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>
</el-table-column>
<el-table-column prop="sceneStr" header-align="center" align="center" label="场景值">
<!-- 根据isTemp的值显示临时永久 -->
<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>
</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>
</el-table-column>
<el-table-column prop="expireTime" header-align="center" align="center" width="100" label="失效时间">
</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>
<!-- 弹窗, 新增 / 修改 -->
<!-- 分页组件 -->
<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'
export default {
data() {
return {
//
dataForm: {
sceneStr: ''
},
//
dataList: [],
//
pageIndex: 1,
//
pageSize: 10,
//
totalPage: 0,
//
dataListLoading: false,
//
dataListSelections: [],
// /
addOrUpdateVisible: false
}
},
components: {
AddOrUpdate
AddOrUpdate // /
},
activated() {
//
this.getDataList()
},
methods: {
@ -69,7 +88,7 @@ export default {
getDataList() {
this.dataListLoading = true
this.$http({
url: this.$http.adornUrl('/manage/wxQrCode/list'),
url: this.$http.adornUrl('/manage/wxQrCode/list'), //
method: 'get',
params: this.$http.adornParams({
'page': this.pageIndex,
@ -80,63 +99,63 @@ export default {
})
}).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.addOrUpdateVisible = true //
this.$nextTick(() => {
this.$refs.addOrUpdate.init(id)
this.$refs.addOrUpdate.init(id) // id
})
},
//
//
deleteHandle(id) {
var ids = id ? [id] : this.dataListSelections.map(item => item.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({
url: this.$http.adornUrl('/manage/wxQrCode/delete'),
url: this.$http.adornUrl('/manage/wxQrCode/delete'), //
method: 'post',
data: this.$http.adornData(ids, false)
data: this.$http.adornData(ids, false) // id
}).then(({ data }) => {
if (data && data.code === 200) {
this.$message({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => this.getDataList()
onClose: () => this.getDataList() //
})
} else {
this.$message.error(data.msg)
this.$message.error(data.msg) //
}
})
})
}
}
}
</script>
</script>

@ -1,101 +1,120 @@
<template>
<el-dialog :title="modeDesc[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>
</el-select>
<!-- 显示已选择用户的数量 -->
<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>
</span>
</el-dialog>
</template>
<script>
import { mapState } from 'vuex'
export default {
name:'wx-user-tagging',
props:{
wxUsers:Array,
name: 'wx-user-tagging',
//
props: {
wxUsers: Array,
},
data(){
return{
mode:'tagging',//tagging | untagging
modeDesc:{
'tagging':'绑定',
'untagging':'解绑'
data() {
return {
//
mode: 'tagging',
//
modeDesc: {
'tagging': '绑定',
'untagging': '解绑'
},
selectedTagid:'',
dialogVisible:false,
submitting:false
// ID
selectedTagid: '',
//
dialogVisible: false,
//
submitting: false
}
},
// Vuexstate
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'){//
//
wxUserTags: state => state.wxUserTags.tags,
//
tagidsInOption() {
// ID
let userTags = this.wxUsers.map(u => u.tagidList || []);
//
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
userTags.forEach(tagsIdArray => {
tagsIdArray.forEach(tagid => unionSet.add(tagid));
});
return Array.from(unionSet);
}
return []
return [];
}
}),
methods:{
init(mode){
if('tagging'==mode || 'untagging'==mode){
this.mode=mode;
this.dialogVisible=true
}else{
throw('mode参数有误')
methods: {
//
init(mode) {
if (mode === 'tagging' || mode === 'untagging') {
this.mode = mode;
this.dialogVisible = true;
} else {
throw ('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){
this.$message.error('未选择标签')
return
//
dataFormSubmit() {
if (this.submitting) return;
if (!this.selectedTagid) {
this.$message.error('未选择标签');
return;
}
this.submitting=true
let openidList=this.wxUsers.map(u=>u.openid)
this.submitting = true;
let openidList = this.wxUsers.map(u => u.openid);
//
this.$http({
url: this.$http.adornUrl(`/manage/wxUserTags/${this.mode=='tagging'?'batchTagging':'batchUnTagging'}`),
url: this.$http.adornUrl(`/manage/wxUserTags/${this.mode === 'tagging' ? 'batchTagging' : 'batchUnTagging'}`),
method: 'post',
data:this.$http.adornData({
tagid : this.selectedTagid,
openidList : openidList
data: this.$http.adornData({
tagid: this.selectedTagid,
openidList: openidList
})
}).then(({ data }) => {
this.submitting=false
this.submitting = 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)
//
this.$message.error(data.msg);
}
})
});
}
}
}

@ -1,110 +1,144 @@
<template>
<div class="mod-config">
<!-- 使用内联表单绑定数据模型并在按下Enter键时调用获取数据列表的方法 -->
<el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
<!-- 用户标签选择器 -->
<el-form-item>
<el-select v-model="dataForm.tagid" filterable clearable placeholder="用户标签">
<!-- 循环遍历wxUserTags数组生成选项 -->
<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>
<!-- 根据权限显示绑定标签按钮如果选中的用户数为0则禁用 -->
<el-button v-if="isAuth('wx:wxuser:save')" type="primary" @click="$refs.wxUserTagging.init('tagging')" :disabled="dataListSelections.length <= 0"></el-button>
<!-- 根据权限显示解绑标签按钮如果选中的用户数为0则禁用 -->
<el-button v-if="isAuth('wx:wxuser:save')" type="primary" @click="$refs.wxUserTagging.init('untagging')" :disabled="dataListSelections.length <= 0"></el-button>
<!-- 根据权限显示批量删除按钮如果选中的用户数为0则禁用 -->
<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-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>
</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="场景值">
</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>
</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>
<!-- 分页组件 -->
<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>
<script>
//
import WxUserTagsManager from '@/components/wx-user-tags-manager'
import WxUserTagging from './wx-user-tagging'
// vuexmapState
import { mapState } from 'vuex'
export default {
data() {
return {
//
dataForm: {
tagid:'',
nickname: '',
city:'',
qrSceneStr:''
},
//
dataList: [],
//
pageIndex: 1,
//
pageSize: 10,
//
totalPage: 0,
//
showWxUserTagsEditor:false,
//
dataListLoading: false,
//
dataListSelections: [],
}
},
//
components: {
WxUserTagsManager,WxUserTagging
WxUserTagsManager, WxUserTagging
},
// keep-alive
activated() {
this.getDataList()
this.getDataList() //
},
// vuexstatewxUserTags
computed: mapState({
wxUserTags:state=>state.wxUserTags.tags
wxUserTags: state => state.wxUserTags.tags
}),
methods: {
//
getDataList() {
this.dataListLoading = true
this.$http({
url: this.$http.adornUrl('/manage/wxUser/list'),
method: 'get',
params: this.$http.adornParams({
this.dataListLoading = true //
this.$http({ // HTTP
url: this.$http.adornUrl('/manage/wxUser/list'), // URL
method: 'get', //
params: this.$http.adornParams({ //
'page': this.pageIndex,
'limit': this.pageSize,
'nickname': this.dataForm.nickname,
@ -114,7 +148,7 @@ export default {
'sidx': 'subscribe_time',
'order': 'desc'
})
}).then(({ data }) => {
}).then(({ data }) => { //
if (data && data.code === 200) {
this.dataList = data.page.list
this.totalPage = data.page.totalCount
@ -122,33 +156,34 @@ export default {
this.dataList = []
this.totalPage = 0
}
this.dataListLoading = false
this.dataListLoading = false //
})
},
//
//
sizeChangeHandle(val) {
this.pageSize = val
this.pageIndex = 1
this.getDataList()
},
//
//
currentChangeHandle(val) {
this.pageIndex = val
this.getDataList()
},
//
//
selectionChangeHandle(val) {
this.dataListSelections = val
},
//
//
deleteHandle(id) {
// ID
var ids = id ? [id] : this.dataListSelections.map(item => item.openid)
this.$confirm(`确定对[id=${ids.join(',')}]进行[${id ? '删除' : '批量删除'}]操作?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$http({
this.$http({ //
url: this.$http.adornUrl('/manage/wxUser/delete'),
method: 'post',
data: this.$http.adornData(ids, false)
@ -159,7 +194,7 @@ export default {
type: 'success',
duration: 1500,
onClose: () => {
this.getDataList()
this.getDataList() //
}
})
} else {
@ -168,8 +203,9 @@ export default {
})
})
},
//
syncWxUsers(){
this.$http({
this.$http({ //
url: this.$http.adornUrl('/manage/wxUser/syncWxUsers'),
method: 'post',
}).then(({ data }) => {
@ -185,6 +221,7 @@ export default {
})
},
//
sexFormat(row, column, cellValue) {
let sexType = {
0: '未知',
@ -193,6 +230,7 @@ export default {
}
return sexType[cellValue];
},
// tagid
getTagName(tagid){
let tag = this.wxUserTags.find(item=>item.id==tagid)
return tag?tag.name : "?"
@ -202,8 +240,11 @@ export default {
</script>
<style scoped>
.headimg{
width: 50px;
height: 50px;
border-radius: 8px;
width: 50px;
/* // 头像宽度 */
height: 50px;
/* // 头像高度 */
border-radius: 8px;
/* // 头像圆角 */
}
</style>
Loading…
Cancel
Save