Compare commits

..

3 Commits

Author SHA1 Message Date
peyqfw698 c086ce336f branch_hxd
8 months ago
1234567 327565db82
8 months ago
yue 5e703caeae
8 months ago

File diff suppressed because it is too large Load Diff

@ -1,167 +1,166 @@
@charset "utf-8";
* {
box-sizing: border-box;
box-sizing: border-box; /* 设置所有元素的盒子模型为border-box */
}
#app-menu ul {
padding: 0;
padding: 0; /* 移除列表的默认内边距 */
}
#app-menu li {
list-style: none;
list-style: none; /* 移除列表项的默认样式 */
}
#app-menu {
overflow: hidden;
width: 100%;
overflow: hidden; /* 隐藏溢出的内容 */
width: 100%; /* 设置宽度为100% */
}
.weixin-preview {
position: relative;
width: 320px;
height: 540px;
float: left;
margin-right: 10px;
border: 1px solid #e7e7eb;
position: relative; /* 相对定位 */
width: 320px; /* 设置宽度 */
height: 540px; /* 设置高度 */
float: left; /* 左浮动 */
margin-right: 10px; /* 右边距 */
border: 1px solid #e7e7eb; /* 边框颜色和宽度 */
}
.weixin-preview a {
text-decoration: none;
color: #616161;
text-decoration: none; /* 移除链接下划线 */
color: #616161; /* 文字颜色 */
}
.weixin-preview .weixin-hd .weixin-title {
color: #fff;
font-size: 15px;
width: 100%;
text-align: center;
position: absolute;
top: 33px;
left: 0px;
color: #fff; /* 文字颜色 */
font-size: 15px; /* 字体大小 */
width: 100%; /* 宽度 */
text-align: center; /* 文字居中对齐 */
position: absolute; /* 绝对定位 */
top: 33px; /* 顶部位置 */
left: 0px; /* 左侧位置 */
}
.weixin-preview .weixin-header{
text-align: center;
padding: 10px 0;
background-color: #616161;
color: #ffffff;
text-align: center; /* 文字居中对齐 */
padding: 10px 0; /* 上下内边距 */
background-color: #616161; /* 背景颜色 */
color: #ffffff; /* 文字颜色 */
}
.weixin-preview .weixin-menu {
position: absolute;
bottom: 0;
left: 0;
right: 0;
border-top: 1px solid #e7e7e7;
background-position: 0 0;
background-repeat: no-repeat;
margin-bottom: 0px;
position: absolute; /* 绝对定位 */
bottom: 0; /* 底部位置 */
left: 0; /* 左侧位置 */
right: 0; /* 右侧位置 */
border-top: 1px solid #e7e7e7; /* 上边框颜色和宽度 */
background-position: 0 0; /* 背景图像位置 */
background-repeat: no-repeat; /* 背景图像不重复 */
margin-bottom: 0px; /* 底部外边距 */
}
/*一级*/
.weixin-preview .weixin-menu .menu-item {
position: relative;
float: left;
line-height: 50px;
height: 50px;
text-align: center;
width: 33.33%;
border-left: 1px solid #e7e7e7;
cursor: pointer;
color: #616161;
position: relative; /* 相对定位 */
float: left; /* 左浮动 */
line-height: 50px; /* 行高 */
height: 50px; /* 高度 */
text-align: center; /* 文字居中对齐 */
width: 33.33%; /* 宽度 */
border-left: 1px solid #e7e7e7; /* 左边框颜色和宽度 */
cursor: pointer; /* 鼠标指针变为手型 */
color: #616161; /* 文字颜色 */
}
/*二级*/
.weixin-preview .weixin-sub-menu {
position: absolute;
bottom: 60px;
left: 0;
right: 0;
border-top: 1px solid #d0d0d0;
margin-bottom: 0px;
background: #fafafa;
display: block;
padding: 0;
position: absolute; /* 绝对定位 */
bottom: 60px; /* 底部位置 */
left: 0; /* 左侧位置 */
right: 0; /* 右侧位置 */
border-top: 1px solid #d0d0d0; /* 上边框颜色和宽度 */
margin-bottom: 0px; /* 底部外边距 */
background: #fafafa; /* 背景颜色 */
display: block; /* 块级显示 */
padding: 0; /* 内边距 */
}
.weixin-preview .weixin-sub-menu .menu-sub-item {
line-height: 50px;
height: 50px;
text-align: center;
width: 100%;
border: 1px solid #d0d0d0;
border-top-width: 0px;
cursor: pointer;
position: relative;
color: #616161;
line-height: 50px; /* 行高 */
height: 50px; /* 高度 */
text-align: center; /* 文字居中对齐 */
width: 100%; /* 宽度 */
border: 1px solid #d0d0d0; /* 边框颜色和宽度 */
border-top-width: 0px; /* 顶部边框宽度 */
cursor: pointer; /* 鼠标指针变为手型 */
position: relative; /* 相对定位 */
color: #616161; /* 文字颜色 */
}
.weixin-preview .weixin-sub-menu .menu-sub-item.on-drag-over{
border-top: 2px solid #44b549;
border-top: 2px solid #44b549; /* 顶部边框颜色和宽度 */
}
.weixin-preview .menu-arrow {
position: absolute;
left: 50%;
margin-left: -6px;
position: absolute; /* 绝对定位 */
left: 50%; /* 左侧位置 */
margin-left: -6px; /* 左边距调整 */
}
.weixin-preview .arrow_in {
bottom: -4px;
display: inline-block;
width: 0px;
height: 0px;
border-width: 6px 6px 0px;
border-style: solid dashed dashed;
border-color: #fafafa transparent transparent;
bottom: -4px; /* 底部位置 */
display: inline-block; /* 行内块级显示 */
width: 0px; /* 宽度 */
height: 0px; /* 高度 */
border-width: 6px 6px 0px; /* 边框宽度 */
border-style: solid dashed dashed; /* 边框样式 */
border-color: #fafafa transparent transparent; /* 边框颜色 */
}
.weixin-preview .arrow_out {
bottom: -5px;
display: inline-block;
width: 0px;
height: 0px;
border-width: 6px 6px 0px;
border-style: solid dashed dashed;
border-color: #d0d0d0 transparent transparent;
bottom: -5px; /* 底部位置 */
display: inline-block; /* 行内块级显示 */
width: 0px; /* 宽度 */
height: 0px; /* 高度 */
border-width: 6px 6px 0px; /* 边框宽度 */
border-style: solid dashed dashed; /* 边框样式 */
border-color: #d0d0d0 transparent transparent; /* 边框颜色 */
}
.weixin-preview .menu-item .menu-item-title, .weixin-preview .menu-sub-item .menu-item-title {
width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
box-sizing: border-box;
width: 100%; /* 宽度 */
overflow: hidden; /* 内容溢出时隐藏 */
white-space: nowrap; /* 不换行 */
text-overflow: ellipsis; /* 文本溢出时显示省略号 */
box-sizing: border-box; /* 盒子模型为border-box */
}
.weixin-preview .menu-item.current, .weixin-preview .menu-sub-item.current {
border: 1px solid #44b549;
background: #fff;
color: #44b549;
border: 1px solid #44b549; /* 边框颜色和宽度 */
background: #fff; /* 背景颜色 */
color: #44b549; /* 文字颜色 */
}
.weixin-preview .weixin-sub-menu.show {
display: block;
display: block; /* 块级显示 */
}
.weixin-preview .icon_menu_dot {
/* background: url(../images/index_z354723.png) 0px -36px no-repeat; */
width: 7px;
height: 7px;
vertical-align: middle;
display: inline-block;
margin-right: 2px;
margin-top: -2px;
width: 7px; /* 宽度 */
height: 7px; /* 高度 */
vertical-align: middle; /* 垂直居中对齐 */
display: inline-block; /* 行内块级显示 */
margin-right: 2px; /* 右边距 */
margin-top: -2px; /* 顶部外边距调整 */
}
.weixin-preview .icon14_menu_add {
/* background: url(../images/index_z354723.png) 0px 0px no-repeat; */
width: 14px;
height: 14px;
vertical-align: middle;
display: inline-block;
margin-top: -2px;
width: 14px; /* 宽度 */
height: 14px; /* 高度 */
vertical-align: middle; /* 垂直居中对齐 */
display: inline-block; /* 行内块级显示 */
margin-top: -2px; /* 顶部外边距调整 */
}
.weixin-preview li:hover .icon14_menu_add {
@ -169,197 +168,197 @@
}
.weixin-preview .menu-item:hover {
color: #000;
color: #000; /* 文字颜色 */
}
.weixin-preview .menu-sub-item:hover {
background: #eee;
background: #eee; /* 背景颜色 */
}
.weixin-preview li.current:hover {
background: #fff;
color: #44b549;
background: #fff; /* 背景颜色 */
color: #44b549; /* 文字颜色 */
}
/*菜单内容*/
.weixin-menu-detail {
width: 600px;
padding: 0px 20px 5px;
background-color: #f4f5f9;
border: 1px solid #e7e7eb;
float: left;
min-height: 540px;
width: 600px; /* 宽度 */
padding: 0px 20px 5px; /* 内边距 */
background-color: #f4f5f9; /* 背景颜色 */
border: 1px solid #e7e7eb; /* 边框颜色和宽度 */
float: left; /* 左浮动 */
min-height: 540px; /* 最小高度 */
}
.weixin-menu-detail .menu-name {
float: left;
height: 40px;
line-height: 40px;
font-size: 18px;
float: left; /* 左浮动 */
height: 40px; /* 高度 */
line-height: 40px; /* 行高 */
font-size: 18px; /* 字体大小 */
}
.weixin-menu-detail .menu-del {
float: right;
height: 40px;
line-height: 40px;
color: #459ae9;
cursor: pointer;
float: right; /* 右浮动 */
height: 40px; /* 高度 */
line-height: 40px; /* 行高 */
color: #459ae9; /* 文字颜色 */
cursor: pointer; /* 鼠标指针变为手型 */
}
.weixin-menu-detail .menu-input-group {
width: 540px;
margin: 10px 0 30px 0;
overflow: hidden;
width: 540px; /* 宽度 */
margin: 10px 0 30px 0; /* 外边距 */
overflow: hidden; /* 隐藏溢出的内容 */
}
.weixin-menu-detail .menu-label {
float: left;
height: 30px;
line-height: 30px;
width: 80px;
text-align: right;
float: left; /* 左浮动 */
height: 30px; /* 高度 */
line-height: 30px; /* 行高 */
width: 80px; /* 宽度 */
text-align: right; /* 文字右对齐 */
}
.weixin-menu-detail .menu-input {
float: left;
width: 380px
float: left; /* 左浮动 */
width: 380px; /* 宽度 */
}
.weixin-menu-detail .menu-input-text {
border: 0px;
outline: 0px;
background: #fff;
width: 300px;
padding: 5px 0px 5px 0px;
margin-left: 10px;
text-indent: 10px;
height: 35px;
border: 0px; /* 边框宽度 */
outline: 0px; /* focus时的轮廓宽度 */
background: #fff; /* 背景颜色 */
width: 300px; /* 宽度 */
padding: 5px 0px 5px 0px; /* 内边距 */
margin-left: 10px; /* 左边距 */
text-indent: 10px; /* text缩进 */
height: 35px; /* 高度 */
}
.weixin-menu-detail .menu-tips {
color: #8d8d8d;
padding-top: 4px;
margin: 0;
color: #8d8d8d; /* 文字颜色 */
padding-top: 4px; /* top方向的内边距 */
margin: 0; /* margin属性值设置为0 */
}
.weixin-menu-detail .menu-tips.cursor {
color: #459ae9;
cursor: pointer;
color: #459ae9; /* 文字颜色 */
cursor: pointer; /* mouse指针变为手型图标 */
}
.weixin-menu-detail .menu-input .menu-tips {
margin: 0 0 0 10px;
margin: 0 0 0 10px; /* margin属性值设置为0最后一个参数为10px */
}
.weixin-menu-detail .menu-content {
padding: 16px 20px;
border: 1px solid #e7e7eb;
background-color: #fff;
padding: 16px 20px; /* Padding属性值设置为16px和20px */
border: 1px solid #e7e7eb; /* Border属性值设置为1px和#e7e7eb颜色值 */
background-color: #fff; /* background-color属性值设置为白色 */
}
.weixin-menu-detail .menu-content .menu-input-group {
margin: 0px 0 10px 0;
margin: 0px 0 10px 0; /* margin属性值设置为0px,10px和0px */
}
.weixin-menu-detail .menu-content .menu-label {
text-align: left;
width: 100px;
text-align: left; /* text对齐方式设置为左对齐 */
width: 100px; /* width属性值设置为100px */
}
.weixin-menu-detail .menu-content .menu-input-text {
border: 1px solid #e7e7eb;
border: 1px solid #e7e7eb; /* border属性值设置为1px和#e7e7eb颜色值 */
}
.weixin-menu-detail .menu-content .menu-tips {
padding-bottom: 10px;
padding-bottom: 10px; /* bottom方向的内边距设置为10px */
}
.weixin-menu-detail .menu-msg-content {
padding: 0;
border: 1px solid #e7e7eb;
background-color: #fff;
padding: 0; /* Padding属性值设置为0 */
border: 1px solid #e7e7eb; /* Border属性值设置为1px和#e7e7eb颜色值 */
background-color: #fff; /* background-color属性值设置为白色 */
}
.weixin-menu-detail .menu-msg-content .menu-msg-head {
overflow: hidden;
border-bottom: 1px solid #e7e7eb;
line-height: 38px;
height: 38px;
padding: 0 20px;
overflow: hidden; /* Overflow属性值设置为hidden */
border-bottom: 1px solid #e7e7eb; /* Border属性值设置为1px和#e7e7eb颜色值 */
line-height: 38px; /* Line height属性值设置为38px */
height: 38px; /* height属性值设置为38px */
padding: 0 20px; /* Padding属性值设置为0和20px */
}
.weixin-menu-detail .menu-msg-content .menu-msg-panel {
padding: 30px 50px;
padding: 30px 50px; /* Padding属性值设置为30px和50px */
}
.weixin-menu-detail .menu-msg-content .menu-msg-select {
padding: 40px 20px;
border: 2px dotted #d9dadc;
text-align: center;
padding: 40px 20px; /* Padding属性值设置为40px和20px */
border: 2px dotted #d9dadc; /* Border属性值设置为2px虚线和#d9dadc颜色值 */
text-align: center; /* text对齐方式设置为居中对齐 */
}
.weixin-menu-detail .menu-msg-content .menu-msg-select:hover {
border-color: #b3b3b3;
border-color: #b3b3b3; /* border颜色值设置为#b3b3b3 */
}
.weixin-menu-detail .menu-msg-content strong {
display: block;
padding-top: 3px;
font-weight: 400;
font-style: normal;
display: block; /* Display属性值设置为block */
padding-top: 3px; /* Top方向的内边距设置为3px */
font-weight: 400; /* font weight属性值设置为400 */
font-style: normal; /* font style属性值设置为normal */
}
.weixin-menu-detail .menu-msg-content .menu-msg-title {
float: left;
width: 310px;
height: 30px;
line-height: 30px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
float: left; /* float属性值设置为left */
width: 310px; /* width属性值设置为310px */
height: 30px; /* height属性值设置为30px */
line-height: 30px; /* line height属性值设置为30px */
overflow: hidden; /* Overflow属性值设置为hidden */
text-overflow: ellipsis; /* TextOverflow属性值设置为ellipsis */
white-space: nowrap; /* WhiteSpace属性值设置为nowrap */
}
.icon36_common {
width: 36px;
height: 36px;
vertical-align: middle;
display: inline-block;
width: 36px; /* width属性值设置为36px */
height: 36px; /* height属性值设置为36px */
vertical-align: middle; /* vertical align属性值设置为middle */
display: inline-block; /* Display属性值设置为inline block */
}
.icon_msg_sender {
margin-right: 3px;
margin-top: -2px;
width: 20px;
height: 20px;
vertical-align: middle;
display: inline-block;
margin-right: 3px; /* Right方向的外边距设置为3px */
margin-top: -2px; /* Top方向的外边距设置为负2px */
width: 20px; /* width属性值设置为20px */
height: 20px; /* height属性值设置为20px */
vertical-align: middle; /* vertical align属性值设置为middle */
display: inline-block; /* Display属性值设置为inline block */
/* background: url(../images/msg_tab_z25df2d.png) 0 -270px no-repeat; */
}
.weixin-btn-group {
text-align: center;
width: 100%;
margin: 30px 0px;
overflow: hidden;
text-align: center; /* text对齐方式设置为居中对齐 */
width: 100%; /* width属性值设置为100% */
margin: 30px 0px; /* margin属性值设置为30px和0px */
overflow: hidden; /* Overflow属性值设置为hidden */
}
.weixin-btn-group .btn {
width: 100px;
border-radius: 0px;
width: 100px; /* width属性值设置为100px */
border-radius: 0px; /* border radius属性值设置为0px */
}
#material-list {
padding: 20px;
overflow-y: scroll;
height: 558px;
padding: 20px; /* Padding属性值设置为20px */
overflow-y: scroll; /* Overflow y属性值设置为scroll使内容可滚动 */
height: 558px; /* height属性值设置为558px */
}
#news-list {
padding: 20px;
overflow-y: scroll;
height: 558px;
padding: 20px; /* Padding属性值设置为20px */
overflow-y: scroll; /* Overflow y属性值设置为scroll使内容可滚动 */
height: 558px; /* height属性值设置为558px */
}
#material-list table {
width: 100%;
width: 100%; /* width属性值设置为100% */
}

@ -1,9 +1,11 @@
/* 设置所有元素和伪元素的 box-sizing 为 border-box */
*,
*:before,
*:after {
box-sizing: border-box;
}
/* 设置 body 的字体、大小、行高和颜色 */
body {
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
font-size: 14px;
@ -12,10 +14,12 @@ body {
background-color: #fff;
}
/* 设置链接的颜色和文本装饰 */
a {
color: mix(#fff, $--color-primary, 20%);
text-decoration: none;
color: mix(#fff, $--color-primary, 20%); /* 使用混合色函数设置颜色 */
text-decoration: none; /* 去掉下划线 */
/* 当链接获得焦点或悬停时,改变颜色并添加下划线 */
&:focus,
&:hover {
color: $--color-primary;
@ -23,97 +27,104 @@ a {
}
}
/* 设置图片垂直对齐方式为中间 */
img {
vertical-align: middle;
}
/* Utils
------------------------------ */
/* 清除浮动工具类 */
.clearfix:before,
.clearfix:after {
content: " ";
display: table;
content: " "; /* 生成内容以触发 clearfix */
display: table; /* 设置为表格布局 */
}
.clearfix:after {
clear: both;
clear: both; /* 清除浮动 */
}
/* Animation
------------------------------ */
/* 淡入淡出动画过渡效果 */
.fade-enter-active,
.fade-leave-active {
transition: opacity .5s;
transition: opacity .5s; /* 设置透明度变化的过渡时间 */
}
.fade-enter,
.fade-leave-to {
opacity: 0;
opacity: 0; /* 初始透明度为 0 */
}
/* Reset element-ui
------------------------------ */
/* 重置 element-ui 样式 */
.site-wrapper {
.el-pagination {
margin-top: 15px;
text-align: right;
margin-top: 15px; /* 设置分页组件的上边距 */
text-align: right; /* 设置分页组件的文本对齐方式 */
}
}
/* Layout
------------------------------ */
/* 设置布局相关样式 */
.site-wrapper {
position: relative;
min-width: 1180px;
position: relative; /* 相对定位 */
min-width: 1180px; /* 最小宽度 */
}
/* Sidebar fold
------------------------------ */
/* 侧边栏折叠样式 */
.site-sidebar--fold {
/* 设置导航栏头部、品牌、侧边栏及其内部元素的宽度 */
.site-navbar__header,
.site-navbar__brand,
.site-sidebar,
.site-sidebar__inner,
.el-menu.site-sidebar__menu {
width: 64px;
width: 64px; /* 宽度为 64px */
}
/* 设置导航栏主体和内容包装器的左边距 */
.site-navbar__body,
.site-content__wrapper {
margin-left: 64px;
margin-left: 64px; /* 左边距为 64px */
}
/* 设置品牌图标在不同状态下的显示方式 */
.site-navbar__brand {
&-lg {
display: none;
display: none; /* 大尺寸时隐藏 */
}
&-mini {
display: inline-block;
display: inline-block; /* 小尺寸时内联显示 */
}
}
/* 设置侧边栏和其内部元素的溢出处理 */
.site-sidebar,
.site-sidebar__inner {
overflow: initial;
overflow: initial; /* 初始溢出处理 */
}
/* 设置侧边栏菜单图标的右边距和字体大小 */
.site-sidebar__menu-icon {
margin-right: 0;
font-size: 20px;
margin-right: 0; /* 右边距为 0 */
font-size: 20px; /* 字体大小为 20px */
}
/* 设置标签页内容的左边距 */
.site-content--tabs > .el-tabs > .el-tabs__header {
left: 64px;
left: 64px; /* 左边距为 64px */
}
}
// animation
//* animation */
.site-navbar__header,
.site-navbar__brand,
.site-navbar__body,
@ -123,154 +134,154 @@ img {
.site-sidebar__menu-icon,
.site-content__wrapper,
.site-content--tabs > .el-tabs .el-tabs__header {
//
transition: inline-block .3s, left .3s, width .3s, margin-left .3s, font-size .3s;
}
/* Navbar
------------------------------ */
/* Navbar */
.site-navbar {
//
position: fixed;
top: 0;
right: 0;
left: 0;
z-index: 1030;
height: 50px;
box-shadow: 0 2px 4px rgba(0, 0, 0, .08);
background-color: $navbar--background-color;
z-index: 1030; //
height: 50px; // 50px
box-shadow: 0 2px 4px rgba(0, 0, 0, .08); //
background-color: $navbar--background-color; //
&--inverse {
.site-navbar__body {
background-color: transparent;
background-color: transparent; //
}
.el-menu {
> .el-menu-item,
> .el-submenu > .el-submenu__title {
color: #fff;
color: #fff; //
&:focus,
&:hover {
color: #fff;
background-color: mix(#000, $navbar--background-color, 15%);
color: #fff; //
background-color: mix(#000, $navbar--background-color, 15%); //
}
}
> .el-menu-item.is-active,
> .el-submenu.is-active > .el-submenu__title {
border-bottom-color: mix(#fff, $navbar--background-color, 85%);
border-bottom-color: mix(#fff, $navbar--background-color, 85%); //
}
.el-menu-item i,
.el-submenu__title i,
.el-dropdown {
color: #fff;
color: #fff; //
}
}
.el-menu--popup-bottom-start {
background-color: $navbar--background-color;
background-color: $navbar--background-color; //
}
}
&__header {
position: relative;
float: left;
width: 230px;
height: 50px;
overflow: hidden;
position: relative; //
float: left; //
width: 230px; // 230px
height: 50px; // 50px
overflow: hidden; //
}
&__brand {
display: table-cell;
vertical-align: middle;
width: 230px;
height: 50px;
margin: 0;
line-height: 50px;
font-size: 20px;
text-align: center;
text-transform: uppercase;
white-space: nowrap;
color: #fff;
display: table-cell; //
vertical-align: middle; //
width: 230px; // 230px
height: 50px; // 50px
margin: 0; //
line-height: 50px; // 50px
font-size: 20px; // 20px
text-align: center; //
text-transform: uppercase; //
white-space: nowrap; //
color: #fff; //
&-lg,
&-mini {
margin: 0 5px;
color: #fff;
margin: 0 5px; // 5px
color: #fff; //
&:focus,
&:hover {
color: #fff;
text-decoration: none;
color: #fff; //
text-decoration: none; // 线
}
}
&-mini {
display: none;
display: none; //
}
}
&__switch {
font-size: 18px;
border-bottom: none !important;
font-size: 18px; // 18px
border-bottom: none !important; //
}
&__avatar {
border-bottom: none !important;
border-bottom: none !important; //
* {
vertical-align: inherit;
vertical-align: inherit; //
}
.el-dropdown-link {
> img {
width: 36px;
height: auto;
margin-right: 5px;
border-radius: 100%;
vertical-align: middle;
width: 36px; // 36px
height: auto; //
margin-right: 5px; // 5px
border-radius: 100%; //
vertical-align: middle; //
}
}
}
&__body {
position: relative;
margin-left: 230px;
padding-right: 15px;
background-color: #fff;
position: relative; //
margin-left: 230px; // 230px
padding-right: 15px; // 15px
background-color: #fff; //
}
&__menu {
float: left;
background-color: transparent;
border-bottom: 0;
float: left; //
background-color: transparent; //
border-bottom: 0; //
&--right {
float: right;
float: right; //
}
a:focus,
a:hover {
text-decoration: none;
text-decoration: none; // 线
}
.el-menu-item,
.el-submenu > .el-submenu__title {
height: 50px;
line-height: 50px;
height: 50px; // 50px
line-height: 50px; // 50px
}
.el-submenu > .el-menu {
top: 55px;
top: 55px; // 55px
}
.el-badge {
display: inline;
z-index: 2;
display: inline; //
z-index: 2; // z2
&__content {
line-height: 16px;
line-height: 16px; // 16px
}
}
}
@ -280,124 +291,123 @@ img {
/* Sidebar
------------------------------ */
.site-sidebar {
position: fixed;
top: 50px;
left: 0;
bottom: 0;
z-index: 1020;
width: 230px;
overflow: hidden;
position: fixed; //
top: 50px; // 50px
left: 0; // 0
bottom: 0; //
z-index: 1020; // z
width: 230px; // 230px
overflow: hidden; //
&--dark,
&--dark-popper {
background-color: $sidebar--background-color-dark;
background-color: $sidebar--background-color-dark; //
.site-sidebar__menu.el-menu,
> .el-menu--popup {
background-color: $sidebar--background-color-dark;
background-color: $sidebar--background-color-dark; //
.el-menu-item,
.el-submenu > .el-submenu__title {
color: $sidebar--color-text-dark;
color: $sidebar--color-text-dark; //
&:focus,
&:hover {
color: mix(#fff, $sidebar--color-text-dark, 50%);
background-color: mix(#fff, $sidebar--background-color-dark, 2.5%);
color: mix(#fff, $sidebar--color-text-dark, 50%); //
background-color: mix(#fff, $sidebar--background-color-dark, 2.5%); //
}
}
.el-menu,
.el-submenu.is-opened {
background-color: mix(#000, $sidebar--background-color-dark, 15%);
background-color: mix(#000, $sidebar--background-color-dark, 15%); //
}
.el-menu-item.is-active,
.el-submenu.is-active > .el-submenu__title {
color: mix(#fff, $sidebar--color-text-dark, 80%);
color: mix(#fff, $sidebar--color-text-dark, 80%); //
}
}
}
&__inner {
position: relative;
z-index: 1;
width: 250px;
height: 100%;
padding-bottom: 15px;
overflow-y: scroll;
position: relative; //
z-index: 1; // z
width: 250px; // 250px
height: 100%; // 100%
padding-bottom: 15px; // 15px
overflow-y: scroll; // y
}
&__menu.el-menu {
width: 230px;
border-right: 0;
width: 230px; // 230px
border-right: 0; //
}
&__menu-icon {
width: 24px;
margin-right: 5px;
text-align: center;
font-size: 16px;
color: inherit !important;
width: 24px; // 24px
margin-right: 5px; // 5px
text-align: center; //
font-size: 16px; // 16px
color: inherit !important; //
}
}
/* Content
------------------------------ */
.site-content {
position: relative;
padding: 15px;
position: relative; //
padding: 15px; // 15px
&__wrapper {
position: relative;
padding-top: 50px;
margin-left: 230px;
min-height: 100%;
background: $content--background-color;
position: relative; //
padding-top: 50px; // 50px
margin-left: 230px; // 230px
min-height: 100%; // 100%
background: $content--background-color; //
}
&--tabs {
padding: 55px 0 0;
padding: 55px 0 0; // 55px00
}
> .el-tabs {
> .el-tabs__header {
position: fixed;
top: 50px;
left: 230px;
right: 0;
z-index: 930;
padding: 0 55px 0 15px;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .12), 0 0 6px 0 rgba(0, 0, 0, .04);
background-color: #fff;
position: fixed; //
top: 50px; // 50px
left: 230px; // 230px
right: 0; // 0
z-index: 930; // z
padding: 0 55px 0 15px; //
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .12), 0 0 6px 0 rgba(0, 0, 0, .04); //
background-color: #fff; //
> .el-tabs__nav-wrap {
margin-bottom: 0;
margin-bottom: 0; // 0
&:after {
display: none;
display: none; //
}
}
}
> .el-tabs__content {
padding: 0 15px 15px;
padding: 0 15px 15px; //
> .site-tabs__tools {
position: fixed;
top: 50px;
right: 0;
z-index: 931;
height: 40px;
padding: 0 12px;
font-size: 16px;
line-height: 40px;
background-color: $content--background-color;
cursor: pointer;
position: fixed; //
top: 50px; // 50px
right: 0; // 0
z-index: 931; // z
height: 40px; // 40px
padding: 0 12px; //
font-size: 16px; // 16px
line-height: 40px; // 40px
background-color: $content--background-color; //
cursor: pointer; //
.el-icon--right {
margin-left: 0;
margin-left: 0; // 0
}
}
}
@ -405,8 +415,8 @@ img {
}
.el-table__expand-icon {
display: inline-block;
width: 14px;
vertical-align: middle;
margin-right: 5px;
display: inline-block; //
width: 14px; // 14px
vertical-align: middle; //
margin-right: 5px; // 5px
}

@ -1,12 +1,11 @@
/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */
/* Document
========================================================================== */
//* Document
========================================================================== */
/**
* 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in
* IE on Windows Phone and in iOS.
* 1.
* 2. Windows Phone iOS IE
*/
html {
@ -19,7 +18,7 @@ html {
========================================================================== */
/**
* Remove the margin in all browsers (opinionated).
*
*/
body {
@ -27,7 +26,7 @@ body {
}
/**
* Add the correct display in IE 9-.
* IE 9-
*/
article,
@ -40,8 +39,7 @@ section {
}
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
* ChromeFirefox Safari `h1` `section` `article`
*/
h1 {
@ -53,8 +51,8 @@ h1 {
========================================================================== */
/**
* Add the correct display in IE 9-.
* 1. Add the correct display in IE.
* IE 9-
* 1. IE
*/
figcaption,
@ -64,7 +62,7 @@ main { /* 1 */
}
/**
* Add the correct margin in IE 8.
* IE 8
*/
figure {
@ -72,8 +70,8 @@ figure {
}
/**
* 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE.
* 1. Firefox
* 2. Edge IE
*/
hr {
@ -83,8 +81,8 @@ hr {
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
* 1.
* 2. `em`
*/
pre {
@ -96,8 +94,8 @@ pre {
========================================================================== */
/**
* 1. Remove the gray background on active links in IE 10.
* 2. Remove gaps in links underline in iOS 8+ and Safari 8+.
* 1. IE 10
* 2. iOS 8+ Safari 8+ 线
*/
a {
@ -106,8 +104,8 @@ a {
}
/**
* 1. Remove the bottom border in Chrome 57- and Firefox 39-.
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
* 1. Chrome 57- Firefox 39-
* 2. ChromeEdgeIEOpera Safari
*/
abbr[title] {
@ -117,7 +115,7 @@ abbr[title] {
}
/**
* Prevent the duplicate application of `bolder` by the next rule in Safari 6.
* Safari 6 `bolder`
*/
b,
@ -126,7 +124,7 @@ strong {
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
* ChromeEdge Safari
*/
b,
@ -135,8 +133,8 @@ strong {
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
* 1.
* 2. `em`
*/
code,
@ -147,7 +145,7 @@ samp {
}
/**
* Add the correct font style in Android 4.3-.
* Android 4.3-
*/
dfn {
@ -155,7 +153,7 @@ dfn {
}
/**
* Add the correct background and color in IE 9-.
* IE 9-
*/
mark {
@ -164,7 +162,7 @@ mark {
}
/**
* Add the correct font size in all browsers.
*
*/
small {
@ -172,8 +170,7 @@ small {
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in
* all browsers.
* `sub` `sup`
*/
sub,
@ -196,7 +193,7 @@ sup {
========================================================================== */
/**
* Add the correct display in IE 9-.
* IE 9-
*/
audio,
@ -205,7 +202,7 @@ video {
}
/**
* Add the correct display in iOS 4-7.
* iOS 4-7
*/
audio:not([controls]) {
@ -214,7 +211,7 @@ audio:not([controls]) {
}
/**
* Remove the border on images inside links in IE 10-.
* IE 10-
*/
img {
@ -222,19 +219,20 @@ img {
}
/**
* Hide the overflow in IE.
* IE
*/
svg:not(:root) {
overflow: hidden;
}
/* Forms
========================================================================== */
/**
* 1. Change the font styles in all browsers (opinionated).
* 2. Remove the margin in Firefox and Safari.
* 1.
* 2. Firefox Safari
*/
button,
@ -249,8 +247,8 @@ textarea {
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
* IE
* 1. Edge
*/
button,
@ -259,8 +257,8 @@ input { /* 1 */
}
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
* EdgeFirefox IE
* 1. Firefox
*/
button,
@ -269,9 +267,8 @@ select { /* 1 */
}
/**
* 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`
* controls in Android 4.
* 2. Correct the inability to style clickable types in iOS and Safari.
* 1. WebKit bug bug Android 4 `audio` `video`
* 2. iOS Safari
*/
button,
@ -282,7 +279,7 @@ html [type="button"], /* 1 */
}
/**
* Remove the inner border and padding in Firefox.
* Firefox
*/
button::-moz-focus-inner,
@ -294,7 +291,7 @@ button::-moz-focus-inner,
}
/**
* Restore the focus styles unset by the previous rule.
*
*/
button:-moz-focusring,
@ -305,7 +302,7 @@ button:-moz-focusring,
}
/**
* Correct the padding in Firefox.
* Firefox
*/
fieldset {
@ -313,10 +310,9 @@ fieldset {
}
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
* 1. Edge IE
* 2. IE `fieldset`
* 3. `fieldset`
*/
legend {
@ -329,8 +325,8 @@ legend {
}
/**
* 1. Add the correct display in IE 9-.
* 2. Add the correct vertical alignment in Chrome, Firefox, and Opera.
* 1. IE 9+
* 2. ChromeFirefox Opera
*/
progress {
@ -339,7 +335,7 @@ progress {
}
/**
* Remove the default vertical scrollbar in IE.
* IE
*/
textarea {
@ -347,8 +343,8 @@ textarea {
}
/**
* 1. Add the correct box sizing in IE 10-.
* 2. Remove the padding in IE 10-.
* 1. IE 10+
* 2. IE 10+
*/
[type="checkbox"],
@ -358,7 +354,7 @@ textarea {
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
* Chrome
*/
[type="number"]::-webkit-inner-spin-button,
@ -367,8 +363,8 @@ textarea {
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
* 1. Chrome Safari
* 2. Safari
*/
[type="search"] {
@ -377,7 +373,7 @@ textarea {
}
/**
* Remove the inner padding and cancel buttons in Chrome and Safari on macOS.
* Chrome Safari on macOS
*/
[type="search"]::-webkit-search-cancel-button,
@ -386,8 +382,8 @@ textarea {
}
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
* 1. iOS Safari
* 2. Safari `inherit`
*/
::-webkit-file-upload-button {
@ -399,8 +395,8 @@ textarea {
========================================================================== */
/*
* Add the correct display in IE 9-.
* 1. Add the correct display in Edge, IE, and Firefox.
* IE 9+
* 1. EdgeIE Firefox
*/
details, /* 1 */
@ -409,7 +405,7 @@ menu {
}
/*
* Add the correct display in all browsers.
*
*/
summary {
@ -420,7 +416,7 @@ summary {
========================================================================== */
/**
* Add the correct display in IE 9-.
* IE 9+
*/
canvas {
@ -428,7 +424,7 @@ canvas {
}
/**
* Add the correct display in IE.
* IE
*/
template {
@ -439,9 +435,9 @@ template {
========================================================================== */
/**
* Add the correct display in IE 10-.
* IE 10+
*/
[hidden] {
display: none;
}
}

@ -3,11 +3,15 @@
$--color-primary: #409EFF;
// Navbar
//
$navbar--background-color: $--color-primary;
// Sidebar
//
$sidebar--background-color-dark: #263238;
//
$sidebar--color-text-dark: #8a979e;
// Content
//
$content--background-color: #f1f4f5;

@ -1,5 +1,7 @@
@import "normalize";
// normalize.css
// api: https://github.com/necolas/normalize.css/
@import "variables";
//
// 使
@import "base";
//

@ -1,36 +1,40 @@
<template>
<!-- SVG元素使用动态绑定的类名宽度和高度属性 -->
<svg :class="getClassName" :width="width" :height="height" aria-hidden="true">
<!-- 使用xlink:href属性引用外部定义的SVG符号 -->
<use :xlink:href="getName"></use>
</svg>
</template>
<script>
export default {
name: 'icon-svg',
name: 'icon-svg', // 'icon-svg'
props: {
name: {
type: String,
required: true
type: String, // 'name'
required: true // 'name'
},
className: {
type: String
type: String // 'className'
},
width: {
type: String
type: String // 'width'
},
height: {
type: String
type: String // 'height'
}
},
computed: {
// 'getName'ID
getName() {
return `#icon-${this.name}`
},
// 'getClassName'
getClassName() {
return [
'icon-svg',
`icon-svg__${this.name}`,
this.className && /\S/.test(this.className) ? `${this.className}` : ''
'icon-svg', //
`icon-svg__${this.name}`, // 'name'
this.className && /\S/.test(this.className) ? `${this.className}` : '' // 'className'
]
}
}
@ -38,10 +42,11 @@ export default {
</script>
<style>
/* 样式定义 */
.icon-svg {
width: 1em;
height: 1em;
fill: currentColor;
overflow: hidden;
width: 1em; // 1em
height: 1em; // 1em
fill: currentColor; //
overflow: hidden; //
}
</style>

@ -1,7 +1,11 @@
<template>
<!-- 定义一个表格列绑定属性和插槽 -->
<el-table-column :prop="prop" v-bind="$attrs">
<!-- 自定义单元格内容 -->
<template slot-scope="scope">
<!-- 点击时触发toggleHandle方法并应用样式 -->
<span @click.prevent="toggleHandle(scope.$index, scope.row)" :style="childStyles(scope.row)">
<!-- 根据行数据动态设置图标类和样式 -->
<i :class="iconClasses(scope.row)" :style="iconStyles(scope.row)"></i>
{{ scope.row[prop] }}
</span>
@ -10,74 +14,81 @@
</template>
<script>
// lodashisArray
import isArray from 'lodash/isArray'
export default {
name: 'table-tree-column',
name: 'table-tree-column', //
props: {
prop: {
type: String
type: String //
},
treeKey: {
type: String,
default: 'id'
default: 'id' // 'id'
},
parentKey: {
type: String,
default: 'parentId'
default: 'parentId' // 'parentId'
},
levelKey: {
type: String,
default: '_level'
default: '_level' // '_level'
},
childKey: {
type: String,
default: 'children'
default: 'children' // 'children'
}
},
methods: {
//
childStyles(row) {
return { 'padding-left': (row[this.levelKey] > 1 ? row[this.levelKey] * 7 : 0) + 'px' }
},
//
iconClasses(row) {
return [!row._expanded ? 'el-icon-caret-right' : 'el-icon-caret-bottom']
},
//
iconStyles(row) {
return { 'visibility': this.hasChild(row) ? 'visible' : 'hidden' }
},
//
hasChild(row) {
return (isArray(row[this.childKey]) && row[this.childKey].length >= 1) || false
},
//
//
toggleHandle(index, row) {
if (this.hasChild(row)) {
var data = this.$parent.store.states.data.slice(0)
data[index]._expanded = !data[index]._expanded
var data = this.$parent.store.states.data.slice(0) //
data[index]._expanded = !data[index]._expanded //
if (data[index]._expanded) {
//
data = data.splice(0, index + 1).concat(row[this.childKey]).concat(data)
} else {
//
data = this.removeChildNode(data, row[this.treeKey])
}
this.$parent.store.commit('setData', data)
this.$parent.store.commit('setData', data) //
this.$nextTick(() => {
this.$parent.doLayout()
this.$parent.doLayout() //
})
}
},
//
removeChildNode(data, parentId) {
var parentIds = isArray(parentId) ? parentId : [parentId]
var parentIds = isArray(parentId) ? parentId : [parentId] // parentId
if (parentId.length <= 0) {
return data
return data // parentId
}
var ids = []
var ids = [] // ID
for (var i = 0; i < data.length; i++) {
if (parentIds.indexOf(data[i][this.parentKey]) !== -1 && parentIds.indexOf(data[i][this.treeKey]) === -1) {
data[i]._expanded = false
ids.push(data.splice(i, 1)[0][this.treeKey])
i--
data[i]._expanded = false //
ids.push(data.splice(i, 1)[0][this.treeKey]) // ID
i-- //
}
}
return this.removeChildNode(data, ids)
return this.removeChildNode(data, ids) //
}
}
}

@ -1,77 +1,83 @@
<template>
<div class="panel flex flex-wrap">
<!-- 面板容器使用flex布局 -->
<div class="panel flex flex-wrap">
<!-- 动态生成的标签列表 -->
<el-tag v-for="tag in dynamicTags" closable @close="handleClose(tag)" :disable-transitions="false" :key="tag">
{{tag}}
</el-tag>
<!-- 输入框用于添加新标签 -->
<el-input class="input-new-tag" v-if="inputVisible" v-model="inputValue" ref="saveTagInput" size="small" @keyup.enter.native="handleInputConfirm" @blur="handleInputConfirm">
</el-input>
<!-- 按钮点击后显示输入框 -->
<el-button v-else class="button-new-tag" size="small" @click="showInput">+ </el-button>
</div>
</template>
<script>
/**
* 标签编辑器
* 标签编辑器组件
*/
let touchMoved = false;
let touchMoved = false; //
export default {
name: 'tags-editor',
name: 'tags-editor', //
props: {
value: {
value: { //
type: String,
required: true,
default: ""
},
size: {//[small:,large:]
size: { // [small:,large:]
type: String,
default: 'small'
}
},
data() {
return {
inputVisible: false,
inputValue: ''
inputVisible: false, //
inputValue: '' //
}
},
computed: {
dynamicTags() {
dynamicTags() { // value
if (this.value != "") return this.value.split(',')
return []
}
},
methods: {
handleClose(tag) {
let newTags = this.dynamicTags;
newTags.splice(newTags.indexOf(tag), 1);
this.$emit('input', newTags.join(","));
handleClose(tag) { //
let newTags = this.dynamicTags; //
newTags.splice(newTags.indexOf(tag), 1); //
this.$emit('input', newTags.join(",")); //
},
showInput() {
this.inputVisible = true;
this.$nextTick(_ => {
this.$refs.saveTagInput.$refs.input.focus();
showInput() { //
this.inputVisible = true; //
this.$nextTick(_ => { // DOM
this.$refs.saveTagInput.$refs.input.focus(); //
});
},
handleInputConfirm() {
let inputValue = this.inputValue;
let newTags = this.dynamicTags;
if (inputValue && newTags.indexOf(inputValue) < 0) {
newTags.push(inputValue);
handleInputConfirm() { //
let inputValue = this.inputValue; //
let newTags = this.dynamicTags; //
if (inputValue && newTags.indexOf(inputValue) < 0) { //
newTags.push(inputValue); //
}
this.inputVisible = false;
this.inputValue = '';
this.$emit('input', newTags.join(","));
this.inputVisible = false; //
this.inputValue = ''; //
this.$emit('input', newTags.join(",")); //
}
}
}
</script>
<style scoped>
/* 面板样式 */
.panel {
flex: 1;
flex: 1; /* 弹性布局,占满剩余空间 */
}
/* 标签和按钮样式 */
.el-tag,.button-new-tag{
margin: 5px;
margin: 5px; /* 外边距 */
}
/* 输入框样式 */
.input-new-tag {
width: inherit;
width: inherit; /* 继承父元素的宽度 */
}
</style>

@ -1,185 +1,207 @@
<template>
<!-- 模态对话框用于筛选模板消息目标用户 -->
<el-dialog title="筛选模板消息目标用户" :close-on-click-modal="false" :visible.sync="visible">
<!-- 表单布局为行内绑定数据模型dataForm清除按钮可清空输入框 -->
<el-form :inline="true" :model="dataForm" ref="dataForm" clearable @keyup.enter.native="getWxUsers()">
<!-- 用户标签选择框 -->
<el-form-item>
<el-select v-model="dataForm.tagid" filterable placeholder="用户标签" @change="getWxUsers()">
<!-- 遍历用户标签列表生成选项 -->
<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="昵称" @change="getWxUsers()" clearable></el-input>
</el-form-item>
<!-- 省份输入框 -->
<el-form-item>
<el-input v-model="dataForm.province" placeholder="省份" @change="getWxUsers()" clearable></el-input>
</el-form-item>
<!-- 城市输入框 -->
<el-form-item>
<el-input v-model="dataForm.city" placeholder="城市" @change="getWxUsers()" clearable></el-input>
</el-form-item>
<!-- 备注输入框 -->
<el-form-item>
<el-input v-model="dataForm.remark" placeholder="备注" @change="getWxUsers()" clearable></el-input>
</el-form-item>
<!-- 扫码场景值输入框 -->
<el-form-item>
<el-input v-model="dataForm.qrScene" placeholder="扫码场景值" @change="getWxUsers()" clearable></el-input>
</el-form-item>
</el-form>
<!-- 显示将发送的消息预览 -->
<div class="text-bold">本消息将发送给</div>
<!-- 用户列表区域加载状态时显示加载动画 -->
<div class="user-list" v-loading="wxUsersLoading">
<!-- 遍历用户列表生成用户卡片 -->
<div class="user-card" v-for="item in wxUserList" :key="item.openid">
<!-- 用户头像 -->
<el-avatar :src="item.headimgurl"></el-avatar>
<!-- 用户昵称 -->
<div class="nickname">{{item.nickname}}</div>
</div>
<!-- 当用户数量超过10个时显示省略号和总用户数 -->
<div class="text-bold">
<span v-show="totalCount>10">...</span>
等共<span class="text-success">{{totalCount}}</span>个用户
</div>
</div>
<!-- 消息预览标题 -->
<div class="margin-top text-bold">消息预览</div>
<!-- 消息预览内容禁用编辑且自动调整大小 -->
<div class="margin-top-xs">
<el-input type="textarea" disabled autosize v-model="msgReview" placeholder="模版"></el-input>
</div>
<!-- 底部操作栏 -->
<span slot="footer" class="dialog-footer">
<!-- 发送按钮根据发送状态显示不同文本 -->
<el-button @click="send" type="success" :disabled="totalCount<=0 || sending">{{sending?'发送中...':'发送'}}</el-button>
<!-- 关闭按钮 -->
<el-button @click="visible=false"></el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name:'template-msg-task',
props:{
wxUserTagName:{
type:String,
required:false
}
name: 'template-msg', //
props: {
//
wxUserTagName: String,
},
data(){
return{
visible:false,
wxUsersLoading:false,
sending:false,
msgTemplate:{},
dataForm: {
page:1,
sidx: 'subscribe_time',
order: 'desc',
tagid:'',
nickname: '',
city:'',
province:'',
remark:'',
qrScene:''
data() {
return {
visible: false, //
wxUsersLoading: false, //
dataForm: { //
page: 1, //
sidx: 'subscribe_time', //
order: 'desc', //
tagid: '', // ID
nickname: '', //
province: '', //
city: '', //
remark: '', //
qrScene: '', //
},
wxUserList:[],
totalCount:0
msgTemplate: {}, //
wxUserList: [], //
totalCount: 0, //
}
},
computed: mapState({
wxUserTags:state=>state.wxUserTags.tags,
msgReview(){
if(!this.msgTemplate.data) return ""
let content = this.msgTemplate.content
this.msgTemplate.data.forEach(item=>{
content = content.replace("{{"+item.name+".DATA}}",item.value)
wxUserTags: state => state.wxUserTags.tags, // Vuex
msgReview(): string { //
if (!this.msgTemplate.data) return "" //
let content = this.msgTemplate.content //
this.msgTemplate.data.forEach(item => { //
content = content.replace("{{" + item.name + ".DATA}}", item.value) //
})
return content
return content //
}
}),
mounted() {
this.getWxUserTags().then((taglist)=>{
if(this.wxUserTagName){
let tagItem = taglist.find(tag=>tag.name==this.wxUserTagName)
console.log(tagItem)
if(tagItem) {
this.dataForm.tagid=tagItem.id+''
this.getWxUserTags().then((taglist) => { //
if (this.wxUserTagName) { //
let tagItem = taglist.find(tag => tag.name === this.wxUserTagName) //
if (tagItem) { //
this.dataForm.tagid = tagItem.id + '' // IDID
}
}
this.getWxUsers()
this.getWxUsers(); //
});
},
methods:{
init(msgTemplate){
if(!msgTemplate || !msgTemplate.templateId){
this.$message.error('消息模板无效')
return
methods: {
init(msgTemplate) { //
if (!msgTemplate || !msgTemplate.templateId) { // ID
this.$message.error('消息模板无效') //
return //
}
if(!msgTemplate.data || !(msgTemplate.data instanceof Array)){
this.$message.error('请现配置此模板填充数据')
return
if (!msgTemplate.data || !(msgTemplate.data instanceof Array)) { //
this.$message.error('请现配置此模板填充数据') //
return //
}
this.msgTemplate=msgTemplate
this.visible=true;
this.msgTemplate = msgTemplate //
this.visible = true; //
},
getWxUserTags() {
return new Promise((resolve,reject)=>{
this.$http({
url: this.$http.adornUrl('/manage/wxUserTags/list'),
method: 'get',
}).then(({ data }) => {
if (data && data.code === 200) {
this.$store.commit('wxUserTags/updateTags', data.list)
resolve(data.list)
getWxUserTags() { //
return new Promise((resolve, reject) => { // Promise
this.$http({ // HTTP
url: this.$adorn.url('/manage/wxUserTags/list'), // URL
method: 'get', // HTTPGET
params: this.$adorn.params({}) //
}).then((response) => { //
if (response.data.code == 200) { // 200
this.$store.dispatch('wxUserTags/updateTags', response.data.list) // Vuex
resolve(response.data.list) // Promise
} else {
this.$message.error(data.msg)
reject(data.msg)
this.$message.error(response.data.msg) //
reject(response.data.msg) // Promise
}
}).catch(err=>reject(err))
})
}).catch(err=>reject(err)) // Promise
});
},
getWxUsers() {
this.wxUsersLoading = true
this.$http({
url: this.$http.adornUrl('/manage/wxUser/list'),
method: 'get',
params: this.$http.adornParams(this.dataForm)
}).then(({ data }) => {
if (data && data.code === 200) {
this.wxUserList = data.page.list
this.totalCount = data.page.totalCount
getWxUsers() { //
this.wxUsersLoading = true // true
this.$http({ // HTTP
url: this.$adorn.url('/manage/wxUser/list'), // URL
method: 'get', // HTTPGET
params: this.$adorn.params(this.dataForm) //
}).then((response) => { //
if (response.data.code == 200) { // 200
this.wxUserList = response.data.page.list //
this.totalCount = response.data.page.totalCount //
} else {
this.$message.error(data.msg)
this.$message.error(response.data.msg) //
}
this.wxUsersLoading = false
})
this.wxUsersLoading = false // false
}).catch(err=>reject(err)) // Promise
},
send(){
if(this.sending)return
this.sending=true
this.$http({
url: this.$http.adornUrl('/manage/msgTemplate/sendMsgBatch'),
method: 'post',
data:this.$http.adornData({
wxUserFilterParams : this.dataForm,
templateId : this.msgTemplate.templateId,
url : this.msgTemplate.url,
miniprogram : this.msgTemplate.miniprogram,
data : this.msgTemplate.data,
send() { //
if (this.sending) return //
this.sending = true // true
this.$http({ // HTTP
url: this.$adorn.url('/manage/msgTemplate/sendMsgBatch'), // URL
method: 'post', // HTTPPOST
data: this.$adorn.data({ //
wxUserFilterParams: this.dataForm, //
templateId: this.msgTemplate.templateId, // IDID
url: this.msgTemplate.url, // URLURL
miniprogram: this.msgTemplate.miniprogram, // miniprogram
data: this.msgTemplate.data, // data
})
}).then(({ data }) => {
this.sending = false
if (data && data.code === 200) {
this.$message.success("消息将在后台发送")
this.visible=false
}).then((response) => { //
this.sending = false // false
if (response.data.code == 200) { // 200
this.$message.success("消息将在后台发送") //
this.visible = false; //
} else {
this.$message.error(data.msg)
this.$message.error(response.data.msg) //
}
})
}).catch(err=>reject(err)) // Promise
}
}
}
</script>
<style scoped>
/* 用户列表样式 */
.user-list{
display: flex;
flex-wrap: wrap;
align-items: center;
}
/* 用户卡片样式 */
.user-card{
overflow: hidden;
max-width: 60px;
margin: 5px;
text-align: center;
}
/* 昵称样式 */
.nickname{
color: #999999;
overflow: hidden;

@ -1,99 +1,102 @@
<template>
<!-- 定义一个包含TinyMCE编辑器的div容器 -->
<div class="tinymce-editor">
<!-- 使用TinyMCE组件绑定v-model为myValue初始化配置为init监听onExecCommand事件 -->
<editor v-model="myValue" :init="init" @onExecCommand="onExecCommand"></editor>
</div>
</template>
<script>
import Editor from "@tinymce/tinymce-vue";
var cos;
import Editor from "@tinymce/tinymce-vue"; // TinyMCE Vue
var cos; // cos
export default {
name: "tinymce-editor",
name: "tinymce-editor", //
components: {
Editor
Editor // TinyMCE
},
props: {
value: {
type: String,
default: ""
type: String, // value
default: "" //
}
},
data() {
return {
// TinyMCE
init: {
language_url: "./tinymce/zh_CN.js", //public
language: "zh_CN",
height: 500,
plugins: "lists image media table paste link searchreplace anchor code preview pagebreak importcss",
toolbar: "undo redo searchreplace | formatselect pagebreak | bold italic forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | lists link anchor image media table | removeformat code preview", //
toolbar_drawer: false,
image_advtab: true,
object_resizing: false,
paste_data_images: true,
content_css: "./tinymce/article.css",
language_url: "./tinymce/zh_CN.js", //
language: "zh_CN", //
height: 500, //
plugins: "lists image media table paste link searchreplace anchor code preview pagebreak importcss", //
toolbar: "undo redo searchreplace | formatselect pagebreak | bold italic forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | lists link anchor image media table | removeformat code preview", //
toolbar_drawer: false, //
image_advtab: true, //
object_resizing: false, //
paste_data_images: true, //
content_css: "./tinymce/article.css", //
images_upload_handler: (blobInfo, success, failure) => {
//
this.uploadFile(blobInfo.blob()).then(fileUrl => success(fileUrl)).catch(err => failure(err))
}
},
myValue: this.value,
uploading: false,
cosConfig: []
myValue: this.value, // valuemyValue
uploading: false, //
cosConfig: [] //
};
},
mounted() {
// console.log('tinymce-editor mounted:',this.value)
tinymce.init({});
this.cosInit();
// console.log('tinymce-editor mounted:',this.value) // value
tinymce.init({}); // TinyMCE
this.cosInit(); // cosInit
},
methods: {
cosInit() {
// HTTP
this.$http({
url: this.$http.adornUrl("/sys/oss/config"),
method: "get",
params: this.$http.adornParams()
url: this.$http.adornUrl("/sys/oss/config"), // URL
method: "get", // HTTPGET
params: this.$http.adornParams() //
}).then(({ data }) => {
if (data && data.code === 200) {
this.cosConfig = data.config;
this.cosConfig = data.config; // cosConfig
} else {
this.$message.error("请先配置云存储相关信息!");
this.$message.error("请先配置云存储相关信息!"); //
}
});
},
onExecCommand(e) {
//console.log(e)
//console.log(e) //
},
uploadFile(file) {
this.uploading = true;
this.uploading = true; // true
return new Promise((resolve, reject) => {
let formData = new FormData();
formData.append("file", file);
let formData = new FormData(); // FormData
formData.append("file", file); // FormData
// HTTP
this.$http({
url: this.$http.adornUrl('/sys/oss/upload'),
method: 'post',
data: formData
url: this.$http.adornUrl('/sys/oss/upload'), // URL
method: 'post', // HTTPPOST
data: formData // FormData
}).then(({ data }) => {
console.log(data)
console.log(data) //
if (data && data.code === 200) {
this.$emit('uploaded', data.url)
resolve(data.url)
this.$emit('uploaded', data.url) // uploadedURL
resolve(data.url) // PromiseURL
} else {
this.$message.error("文件上传失败:" + data.msg)
reject(data.msg)
this.$message.error("文件上传失败:" + data.msg) //
reject(data.msg) // Promise
}
this.uploading = false;
}).catch(err=>reject(err))
this.uploading = false; // false
}).catch(err=>reject(err)) // Promise
});
}
},
watch: {
value(newValue) {
this.myValue = newValue;
this.myValue = newValue; // valuemyValue
},
myValue(newValue) {
this.$emit("input", newValue);
this.$emit("input", newValue); // myValueinput
}
}
};
</script>

@ -1,45 +1,46 @@
<template>
<el-select v-model="selectedAppid" size="small" v-loading="dataListLoading" @change="selectAccount" filterable>
<!-- 使用Element UI的el-select组件绑定v-model为selectedAppid设置size为small并添加v-loading指令来控制加载状态 -->
<el-select v-model="selectedAppid" size="small" v-loading="dataListLoading" @change="selectAccount" filterable>
<!-- 遍历accountList数组生成el-option选项 -->
<el-option v-for="item in accountList" :key="item.appid" :label="item.name+''+ACCOUNT_TYPES[item.type]+''" :value="item.appid"></el-option>
</el-select>
</template>
<script>
import { mapState } from 'vuex'
export default {
data() {
return {
dataListLoading: false
dataListLoading: false // dataListLoading
}
},
computed: mapState({
accountList: state=>state.wxAccount.accountList,
ACCOUNT_TYPES: state=>state.wxAccount.ACCOUNT_TYPES,
selectedAppid:state=>state.wxAccount.selectedAppid
accountList: state=>state.wxAccount.accountList, // VuexaccountList
ACCOUNT_TYPES: state=>state.wxAccount.ACCOUNT_TYPES, // VuexACCOUNT_TYPES
selectedAppid:state=>state.wxAccount.selectedAppid // VuexselectedAppid
}),
mounted(){
this.getDataList()
this.getDataList() // getDataList
},
methods:{
getDataList() {
this.dataListLoading = true
this.dataListLoading = true // dataListLoadingtrue
this.$http({
url: this.$http.adornUrl('/manage/wxAccount/list'),
method: 'get'
url: this.$http.adornUrl('/manage/wxAccount/list'), // URL
method: 'get' // HTTPGET
}).then(({ data }) => {
if (data && data.code === 200) {
this.$store.commit('wxAccount/updateAccountList', data.list)
if(!data.list.length){
this.$message.info("公众号列表为空,请先添加")
if (data && data.code === 200) { // 200
this.$store.commit('wxAccount/updateAccountList', data.list) // mutationaccountList
if(!data.list.length){ //
this.$message.info("公众号列表为空,请先添加") //
}
}
this.dataListLoading = false
this.dataListLoading = false // dataListLoadingfalse
})
},
selectAccount(appid){
if(this.selectedAppid!=appid){
this.$store.commit('wxAccount/selectAccount', appid)
if(this.selectedAppid!=appid){ // appidappid
this.$store.commit('wxAccount/selectAccount', appid) // mutationselectedAppid
}
}
}
}
</script>

@ -1,20 +1,28 @@
<template>
<!-- 消息预览面板 -->
<div class="panel">
<!-- 工具提示显示消息方向 -->
<el-tooltip class="item" effect="dark" :content="msg.inOut?'公众号发出的消息':'来自用户的消息'" placement="right">
<el-tag size="mini" v-if="msg.inOut" class="margin-right el-icon-upload2" type="info"></el-tag>
<!-- 根据消息方向显示不同的图标和标签 -->
<el-tag size="mini" v-if="msg.inOut" class="margin-right el-icon-upload2" type="info"></el-tag>
<el-tag size="mini" v-else class="margin-right el-icon-download"></el-tag>
</el-tooltip>
<!-- 消息内容展示区域 -->
<span class="panel-content">
<!-- 文本消息类型 -->
<span v-if="msg.msgType=='text'" v-html="msg.detail.content"></span>
<span v-else-if="msg.msgType=='event'" >
<!-- 事件消息类型 -->
<span v-else-if="msg.msgType=='event'">
<el-tag size="mini" type="warning" effect="plain">事件</el-tag>
<el-tag size="mini" type="info" effect="plain">{{msg.detail.event}}</el-tag>
{{msg.detail.eventKey}}
</span>
<!-- 转客服事件 -->
<span v-else-if="msg.msgType=='transfer_customer_service'">
<el-tag size="mini" type="warning" effect="plain">事件</el-tag>
<el-tag size="mini" type="info" effect="plain">消息转客服</el-tag>
</span>
<!-- 其他消息类型 -->
<span v-else>
<el-tag size="mini" effect="plain">{{XmlMsgType[msg.msgType]}}</el-tag>
后台不支持预览
@ -22,21 +30,25 @@
</span>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name:'wx-msg-preview',
name:'wx-msg-preview', //
props:{
msg:Object
msg:Object //
},
computed:mapState({
// VuexXmlMsgType
XmlMsgType:state=>state.message.XmlMsgType,
})
}
</script>
<style scoped>
/* 面板样式 */
.panel,.panel a{
color: #999;
word-break: break-all;
color: #999; //
word-break: break-all; //
}
</style>
</style>

@ -1,139 +1,157 @@
<template>
<!-- 模态框用于显示公众号用户标签管理界面 -->
<el-dialog title="公众号用户标签管理" :close-on-click-modal="false" :visible.sync="dialogVisible">
<div class="panel flex flex-wrap" v-loading="submitting">
<!-- 面板容器包含标签和输入框 -->
<div class="panel flex flex-wrap" v-loading="submitting">
<!-- 遍历并显示所有标签 -->
<el-tag v-for="tag in wxUserTags" closable @click="editTag(tag.id,tag.name)" @close="deleteTag(tag.id)" :disable-transitions="false" :key="tag.id">
{{tag.id}} {{tag.name}}
</el-tag>
<!-- 当inputVisible为true时显示输入框否则显示添加按钮 -->
<el-input class="input-new-tag" v-if="inputVisible" placeholder="回车确认" v-model="inputValue" ref="saveTagInput" size="small" @keyup.enter.native="addTag">
</el-input>
<el-button v-else class="button-new-tag" size="small" @click="showInput">+ </el-button>
</div>
<!-- 对话框底部的关闭按钮 -->
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible=false"></el-button>
</span>
</el-dialog>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'wx-user-tags-manager',
name: 'wx-user-tags-manager', //
props: {
visible: {
type: Boolean,
default: true
type: Boolean, //
default: true // true
}
},
data() {
return {
dialogVisible:false,
inputVisible: false,
inputValue: '',
submitting:false,
dialogVisible: false, //
inputVisible: false, //
inputValue: '', //
submitting: false, //
}
},
computed: mapState({
wxUserTags:state=>state.wxUserTags.tags
// VuexwxUserTags
wxUserTags: state => state.wxUserTags.tags
}),
mounted() {
//
this.getWxUserTags();
},
methods: {
//
show(){
this.dialogVisible=true;
this.dialogVisible = true;
},
//
getWxUserTags() {
this.$http({
url: this.$http.adornUrl('/manage/wxUserTags/list'),
method: 'get',
url: this.$http.adornUrl('/manage/wxUserTags/list'), // URL
method: 'get', // HTTPGET
}).then(({ data }) => {
if (data && data.code === 200) {
// Vuex
this.$store.commit('wxUserTags/updateTags', data.list)
} else {
//
this.$message.error(data.msg)
}
})
},
//
deleteTag(tagid) {
if(this.submitting){
return
return //
}
this.$confirm(`确定删除标签?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
confirmButtonText: '确定', //
cancelButtonText: '取消', //
type: 'warning' //
}).then(() => {
this.submitting=true
this.submitting = true // true
this.$http({
url: this.$http.adornUrl('/manage/wxUserTags/delete/'+tagid),
method: 'post',
url: this.$http.adornUrl('/manage/wxUserTags/delete/' + tagid), // URL
method: 'post', // HTTPPOST
}).then(({ data }) => {
if (data && data.code === 200) {
this.getWxUserTags();
this.$emit('change');
this.getWxUserTags(); //
this.$emit('change'); // change
} else {
this.$message.error(data.msg)
this.$message.error(data.msg) //
}
this.submitting=false;
this.submitting = false; //
})
})
},
//
showInput() {
this.inputVisible = true;
this.inputVisible = true; //
this.$nextTick(_ => {
this.$refs.saveTagInput.$refs.input.focus();
this.$refs.saveTagInput.$refs.input.focus(); //
});
},
//
addTag() {
let newTagName = this.inputValue;
this.saveTag(newTagName)
this.inputVisible = false;
this.inputValue = '';
let newTagName = this.inputValue; //
this.saveTag(newTagName) //
this.inputVisible = false; //
this.inputValue = ''; //
},
editTag(tagid,orignName=''){
//
editTag(tagid, orignName=''){
this.$prompt('请输入新标签名称', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputValue:orignName,
inputPattern: /^.{1,30}$/,
inputErrorMessage: '名称1-30字符'
confirmButtonText: '确定', //
cancelButtonText: '取消', //
inputValue: orignName, //
inputPattern: /^.{1,30}$/, // 1-30
inputErrorMessage: '名称1-30字符' //
}).then(({ value }) => {
console.log(value)
this.saveTag(value,tagid)
this.saveTag(value, tagid) //
})
},
saveTag(name,tagid){
//
saveTag(name, tagid){
if(this.submitting){
return
return //
}
this.submitting=true
this.submitting = true // true
this.$http({
url: this.$http.adornUrl('/manage/wxUserTags/save'),
method: 'post',
data:this.$http.adornData({
id : tagid?tagid:undefined,
name : name
url: this.$http.adornUrl('/manage/wxUserTags/save'), // URL
method: 'post', // HTTPPOST
data: this.$http.adornData({
id: tagid ? tagid : undefined, // tagid
name: name //
})
}).then(({ data }) => {
if (data && data.code === 200) {
this.getWxUserTags();
this.$emit('change');
this.getWxUserTags(); //
this.$emit('change'); // change
} else {
this.$message.error(data.msg)
this.$message.error(data.msg) //
}
this.submitting=false;
this.submitting = false; //
})
}
}
}
</script>
<style scoped>
/* 面板样式 */
.panel {
flex: 1;
flex: 1; /* 弹性布局,占满剩余空间 */
}
.el-tag,.button-new-tag {
margin: 5px;
/* 标签和按钮样式 */
.el-tag, .button-new-tag {
margin: 5px; /* 外边距 */
}
/* 输入框样式 */
.input-new-tag {
width: inherit;
width: inherit; /* 宽度继承父元素 */
}
</style>
</style>

@ -1 +1,3 @@
// 导出一个函数,该函数接收一个文件名作为参数
// 返回一个动态导入模块的函数
module.exports = file => () => import('@/views/' + file + '.vue')

@ -10,75 +10,76 @@ import http from '@/utils/httpRequest'
import { isURL } from '@/utils/validate'
import { clearLoginInfo } from '@/utils'
// 使用VueRouter插件
Vue.use(VueRouter)
// 动态导入视图组件的函数
const _import = require('./import-views')
// 全局路由(无需嵌套上左右整体布局)
const globalRoutes = [
{ path: '/404', component: () => import('@/views/common/404'), name: '404', meta: { title: '404未找到' } },
{ path: '/login', component: () => import('@/views/common/login'), name: 'login', meta: { title: '登录' } }
{ path: '/404', component: () => import('@/views/common/404'), name: '404', meta: { title: '404未找到' } }, // 404页面路由
{ path: '/login', component: () => import('@/views/common/login'), name: 'login', meta: { title: '登录' } } // 登录页面路由
]
// 主入口路由(需嵌套上左右整体布局)
const mainRoutes = {
path: '/',
component: () => import('@/views/main'),
name: 'main',
redirect: { name: 'home' },
meta: { title: '主入口整体布局' },
children: [
// 通过meta对象设置路由展示方式
// 1. isTab: 是否通过tab展示内容, true: 是, false: 否
// 2. iframeUrl: 是否通过iframe嵌套展示内容, '以http[s]://开头': 是, '': 否
// 提示: 如需要通过iframe嵌套展示内容, 但不通过tab打开, 请自行创建组件使用iframe处理!
{ path: '/home', component: () => import('@/views/common/home'), name: 'home', meta: { title: '首页' } },
{ path: '/theme', component: () => import('@/views/common/theme'), name: 'theme', meta: { title: '主题' } },
],
beforeEnter(to, from, next) {
let token = Vue.cookie.get('token')
if (!token || !/\S/.test(token)) {
clearLoginInfo()
next({ name: 'login' })
path: '/',
component: () => import('@/views/main'),
name: 'main',
redirect: { name: 'home' },
meta: { title: '主入口整体布局' },
children: [
// 通过meta对象设置路由展示方式
// 1. isTab: 是否通过tab展示内容, true: 是, false: 否
// 2. iframeUrl: 是否通过iframe嵌套展示内容, '以http[s]://开头': 是, '': 否
{ path: '/home', component: () => import('@/views/common/home'), name: 'home', meta: { title: '首页' } }, // 首页路由
{ path: '/theme', component: () => import('@/views/common/theme'), name: 'theme', meta: { title: '主题' } } // 主题页面路由
],
beforeEnter(to, from, next) {
// 检查用户是否已登录,如果未登录则重定向到登录页
let token = Vue.cookie.get('token')
if (!token || !/\S/.test(token)) {
clearLoginInfo()
next({ name: 'login' })
}
next()
}
next()
}
}
// 创建VueRouter实例
const router = new VueRouter({
mode: 'hash',
scrollBehavior: () => ({ y: 0 }),
isAddDynamicMenuRoutes: false, // 是否已经添加动态(菜单)路由
routes: globalRoutes.concat(mainRoutes)
mode: 'hash', // 使用hash模式
scrollBehavior: () => ({ y: 0 }), // 滚动行为:切换路由时滚动到顶部
isAddDynamicMenuRoutes: false, // 是否已经添加动态(菜单)路由
routes: globalRoutes.concat(mainRoutes) // 合并全局路由和主入口路由
})
// 全局前置守卫,用于处理动态菜单路由的添加
router.beforeEach((to, from, next) => {
// 添加动态(菜单)路由
// 1. 已经添加 or 全局路由, 直接访问
// 2. 获取菜单列表, 添加并保存本地存储
if (router.options.isAddDynamicMenuRoutes || fnCurrentRouteType(to, globalRoutes) === 'global') {
next()
} else {
http({
url: http.adornUrl('/sys/menu/nav'),
method: 'get',
params: http.adornParams()
}).then(({ data }) => {
if (data && data.code === 200) {
fnAddDynamicMenuRoutes(data.menuList)
router.options.isAddDynamicMenuRoutes = true
sessionStorage.setItem('menuList', JSON.stringify(data.menuList || '[]'))
sessionStorage.setItem('permissions', JSON.stringify(data.permissions || '[]'))
next({ ...to, replace: true })
} else {
sessionStorage.setItem('menuList', '[]')
sessionStorage.setItem('permissions', '[]')
// 如果已经添加了动态(菜单)路由或当前路由是全局路由,直接放行
if (router.options.isAddDynamicMenuRoutes || fnCurrentRouteType(to, globalRoutes) === 'global') {
next()
}
}).catch((e) => {
console.log(`%c${e} 请求菜单列表和权限失败,跳转至登录页!!`, 'color:blue')
router.push({ name: 'login' })
})
}
} else {
// 否则,请求菜单列表并添加动态(菜单)路由
http({
url: http.adornUrl('/sys/menu/nav'),
method: 'get',
params: http.adornParams()
}).then(({ data }) => {
if (data && data.code === 200) {
fnAddDynamicMenuRoutes(data.menuList) // 添加动态(菜单)路由
router.options.isAddDynamicMenuRoutes = true // 标记为已添加动态(菜单)路由
next({ ...to, replace: true }) // 重新导航到当前路由,确保添加完动态路由后能正确显示页面
} else {
console.log(`%c${e} 请求菜单列表和权限失败,跳转至登录页!!`, 'color:blue') // 打印错误信息
router.push({ name: 'login' }) // 跳转到登录页
}
}).catch((e) => {
console.log(`%c${e} 请求菜单列表和权限失败,跳转至登录页!!`, 'color:blue') // 打印错误信息
router.push({ name: 'login' }) // 跳转到登录页
})
}
})
/**
@ -86,15 +87,15 @@ router.beforeEach((to, from, next) => {
* @param {*} route 当前路由
*/
function fnCurrentRouteType(route, globalRoutes = []) {
var temp = []
for (var i = 0; i < globalRoutes.length; i++) {
if (route.path === globalRoutes[i].path) {
return 'global'
} else if (globalRoutes[i].children && globalRoutes[i].children.length >= 1) {
temp = temp.concat(globalRoutes[i].children)
var temp = []
for (var i = 0; i < globalRoutes.length; i++) {
if (route.path === globalRoutes[i].path) {
return 'global' // 如果是全局路由,返回'global'
} else if (globalRoutes[i].children && globalRoutes[i].children.length >= 1) {
temp = temp.concat(globalRoutes[i].children) // 如果有子路由,添加到临时数组中
}
}
}
return temp.length >= 1 ? fnCurrentRouteType(route, temp) : 'main'
return temp.length >= 1 ? fnCurrentRouteType(route, temp) : 'main' // 递归判断,直到找到匹配的路由类型
}
/**
@ -103,52 +104,49 @@ function fnCurrentRouteType(route, globalRoutes = []) {
* @param {*} routes 递归创建的动态(菜单)路由
*/
function fnAddDynamicMenuRoutes(menuList = [], routes = []) {
var temp = []
for (var i = 0; i < menuList.length; i++) {
if (menuList[i].list && menuList[i].list.length >= 1) {
temp = temp.concat(menuList[i].list)
} else if (menuList[i].url && /\S/.test(menuList[i].url)) {
menuList[i].url = menuList[i].url.replace(/^\//, '')
var route = {
path: menuList[i].url.replace('/', '-'),
component: null,
name: menuList[i].url.replace('/', '-'),
meta: {
menuId: menuList[i].menuId,
title: menuList[i].name,
isDynamic: true,
isTab: true,
iframeUrl: ''
var temp = []
for (var i = 0; i < menuList.length; i++) {
if (menuList[i].list && menuList[i].list.length >= 1) {
temp = temp.concat(menuList[i].list) // 如果菜单项有子菜单,添加到临时数组中
} else if (menuList[i].url && /\S/.test(menuList[i].url)) {
menuList[i].url = menuList[i].url.replace(/^\//, '') // 去掉URL前的斜杠
var route = {
path: menuList[i].url.replace('/', '-'), // 将URL中的斜杠替换为短横线作为路径的一部分
component: null, // 初始化组件为空
name: menuList[i].url.replace('/', '-'), // 将URL中的斜杠替换为短横线作为路由名称的一部分
meta: {
menuId: menuList[i].menuId, // 菜单ID
title: menuList[i].name, // 菜单名称
isDynamic: true, // 标记为动态路由
isTab: true, // 标记为标签页
iframeUrl: '' // 初始化iframeUrl为空
}
}
// 如果URL以http[s]://开头通过iframe展示
if (isURL(menuList[i].url)) {
route['path'] = `i-${menuList[i].menuId}` // 修改路径为i-加上菜单ID
route['name'] = `i-${menuList[i].menuId}` // 修改名称为i-加上菜单ID
route['meta']['iframeUrl'] = menuList[i].url // 设置iframeUrl为菜单URL
} else {
try {
route['component'] = _import(`modules/${menuList[i].url}`) || null // 动态导入组件
} catch (e) { } // 如果导入失败,捕获异常但不做处理
}
routes.push(route) // 将路由添加到routes数组中
}
}
// url以http[s]://开头, 通过iframe展示
if (isURL(menuList[i].url)) {
route['path'] = `i-${menuList[i].menuId}`
route['name'] = `i-${menuList[i].menuId}`
route['meta']['iframeUrl'] = menuList[i].url
} else {
try {
route['component'] = _import(`modules/${menuList[i].url}`) || null
// route['component'] = ()=>import(`@/views/modules/${menuList[i].url}.vue`) || null
} catch (e) { }
}
routes.push(route)
}
}
if (temp.length >= 1) {
fnAddDynamicMenuRoutes(temp, routes)
} else {
mainRoutes.name = 'main-dynamic'
mainRoutes.children = routes
router.addRoutes([
mainRoutes,
{ path: '*', redirect: { name: '404' } }
])
sessionStorage.setItem('dynamicMenuRoutes', JSON.stringify(mainRoutes.children || '[]'))
console.log('\n')
console.log('%c!<-------------------- 动态(菜单)路由 s -------------------->', 'color:blue')
console.log(mainRoutes.children)
console.log('%c!<-------------------- 动态(菜单)路由 e -------------------->', 'color:blue')
}
if (temp.length >= 1) {
fnAddDynamicMenuRoutes(temp, routes) // 如果临时数组中有数据,递归调用自身继续处理
} else {
mainRoutes.name = 'main-dynamic' // 修改主入口路由的名称为main-dynamic
mainRoutes.children = routes // 将动态生成的路由设置为主入口路由的子路由
router.addRoutes([mainRoutes, { path: '*', redirect: { name: '404' } }]) // 添加动态路由和404重定向规则到路由器中
sessionStorage.setItem('dynamicMenuRoutes', JSON.stringify(mainRoutes.children || '')) // 将动态路由存储到sessionStorage中以便后续使用
console.log('\n')
console.log('%c!<-------------------- 动态(菜单)路由 s -------------------->', 'color:blue') // 打印动态路由开始标志
console.log(mainRoutes.children) // 打印动态生成的路由信息
console.log('%c!<-------------------- 动态(菜单)路由 e -------------------->', 'color:blue') // 打印动态路由结束标志
console.log('\n')
}
}
export default router
export default router // 导出路由器实例

@ -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,12 +1,14 @@
export default {
namespaced: true,
state: {
ARTICLE_TYPES: {
1: '普通文章',
5: '帮助中心',
// 启用命名空间使得该模块的状态和getters、actions、mutations是注册在全局命名空间下的子模块
namespaced: true,
state: {
// 定义文章类型常量对象
ARTICLE_TYPES: {
1: '普通文章', // 普通文章类型
5: '帮助中心', // 帮助中心类型
}
},
mutations: {
// 目前没有定义任何mutation
}
},
mutations: {
}
}

@ -1,6 +1,7 @@
import router from '@/router'
export default {
// 启用命名空间使得该模块的状态和getters、actions、mutations是注册在全局命名空间下的子模块
namespaced: true,
state: {
// 页面文档可视高度(随窗口改变大小)
@ -11,47 +12,60 @@ export default {
sidebarLayoutSkin: 'dark',
// 侧边栏, 折叠状态
sidebarFold: false,
// 侧边栏, 菜单
// 侧边栏, 菜单列表
menuList: [],
// 当前激活的菜单项名称
menuActiveName: '',
// 内容, 是否需要刷新
// 内容区域是否需要刷新
contentIsNeedRefresh: false,
// 主入口标签页
// 主入口标签页数组
mainTabs: [],
// 当前激活的标签页名称
mainTabsActiveName: ''
},
mutations: {
// 更新文档可视高度
updateDocumentClientHeight(state, height) {
state.documentClientHeight = height
},
// 更新导航条布局类型
updateNavbarLayoutType(state, type) {
state.navbarLayoutType = type
},
// 更新侧边栏布局皮肤
updateSidebarLayoutSkin(state, skin) {
state.sidebarLayoutSkin = skin
},
// 更新侧边栏折叠状态
updateSidebarFold(state, fold) {
state.sidebarFold = fold
},
// 更新菜单列表
updateMenuList(state, list) {
state.menuList = list
},
// 更新当前激活的菜单项名称
updateMenuActiveName(state, name) {
state.menuActiveName = name
},
// 更新内容区域是否需要刷新
updateContentIsNeedRefresh(state, status) {
state.contentIsNeedRefresh = status
},
// 更新主入口标签页数组
updateMainTabs(state, tabs) {
state.mainTabs = tabs
},
// 更新当前激活的标签页名称
updateMainTabsActiveName(state, name) {
state.mainTabsActiveName = name
},
// 移除指定的标签页
removeTab(state, tabName) {
// 过滤掉要删除的标签页
state.mainTabs = state.mainTabs.filter(item => item.name !== tabName)
if (state.mainTabs.length >= 1) {
// 当前选中tab被删除
// 如果当前选中的标签页被删除,则跳转到最后一个标签页
if (tabName === state.mainTabsActiveName) {
var tab = state.mainTabs[state.mainTabs.length - 1]
router.push({ name: tab.name, query: tab.query, params: tab.params }, () => {
@ -59,12 +73,14 @@ export default {
})
}
} else {
// 如果没有剩余的标签页,重置菜单并跳转到首页
state.menuActiveName = ''
router.push({ name: 'home' })
}
},
// 关闭当前激活的标签页
closeCurrentTab(state) {
this.commit('common/removeTab', state.mainTabsActiveName)
}
}
}
}

@ -1,32 +1,39 @@
// 导出一个默认的模块对象
export default {
// 启用命名空间使得该模块的状态和getters、actions、mutations是注册在全局命名空间下的子模块
namespaced: true,
// 定义模块的初始状态
state: {
XmlMsgType:{
"text":"文字",
"image":"图片",
"voice":"语音",
"shortvideo":"短视频",
"video":"视频",
"news":"图文",
"music":"音乐",
"location":"位置",
"link":"链接",
"event":"事件",
"transfer_customer_service":"转客服"
// 定义微信消息类型的映射关系
XmlMsgType: {
"text": "文字", // 文本消息类型
"image": "图片", // 图片消息类型
"voice": "语音", // 语音消息类型
"shortvideo": "短视频", // 短视频消息类型
"video": "视频", // 视频消息类型
"news": "图文", // 图文消息类型
"music": "音乐", // 音乐消息类型
"location": "位置", // 位置消息类型
"link": "链接", // 链接消息类型
"event": "事件", // 事件消息类型
"transfer_customer_service": "转客服" // 转接客服消息类型
},
// 定义客服消息类型的映射关系
KefuMsgType: {
"text": "文本消息",
"image": "图片消息",
"voice": "语音消息",
"video": "视频消息",
"music": "音乐消息",
"news": "文章链接",
"mpnews": "公众号图文消息",
"wxcard": "卡券消息",
"miniprogrampage": "小程序消息",
"msgmenu": "菜单消息"
"text": "文本消息", // 文本消息类型
"image": "图片消息", // 图片消息类型
"voice": "语音消息", // 语音消息类型
"video": "视频消息", // 视频消息类型
"music": "音乐消息", // 音乐消息类型
"news": "文章链接", // 文章链接消息类型
"mpnews": "公众号图文消息", // 公众号图文消息类型
"wxcard": "卡券消息", // 卡券消息类型
"miniprogrampage": "小程序消息", // 小程序消息类型
"msgmenu": "菜单消息" // 菜单消息类型
}
},
// 定义用于修改状态的 mutations目前为空
mutations: {
}

@ -1,15 +1,23 @@
// 导出一个默认的模块对象
export default {
namespaced: true,
state: {
id: 0,
name: ''
},
mutations: {
updateId(state, id) {
state.id = id
// 启用命名空间使得该模块的状态和getters、actions、mutations是注册在全局命名空间下的子模块
namespaced: true,
// 定义模块的初始状态
state: {
id: 0, // 初始化 id 为 0
name: '' // 初始化 name 为空字符串
},
updateName(state, name) {
state.name = name
// 定义用于修改状态的 mutations
mutations: {
// 更新 id 的 mutation
updateId(state, id) {
state.id = id; // 将传入的 id 赋值给 state 中的 id
},
// 更新 name 的 mutation
updateName(state, name) {
state.name = name; // 将传入的 name 赋值给 state 中的 name
}
}
}
}

@ -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,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>
Loading…
Cancel
Save