chore: 重写初始提交(清空历史,整理后全量提交)

This commit is contained in:
ywxapp
2026-08-16 16:54:14 +08:00
commit 6c1a106bc1
1808 changed files with 238144 additions and 0 deletions
+150
View File
@@ -0,0 +1,150 @@
/**
* setter
*/
// 初始化配置
layui.define(['all'], function (exports) {
console.log('wxapp config loaded');
exports('setter', {
paths: {
core: '/assets/ywxapp/', // 核心库所在目录
// core: layui.cache.base + '/adminui/dist/', // 核心库所在目录
views: layui.cache.base + 'views/', // 动态模板所在目录
modules: layui.cache.base + 'modules/', // 业务模块所在目录
base: layui.cache.base // 记录静态资源所在基础目录
},
container: 'LAY_app', // 容器 ID
entry: 'index', // 默认视图文件名
engine: '.html', // 视图文件后缀名
pageTabs: true, // 是否开启页面选项卡功能。 iframe 版推荐开启
refreshCurrPage: true, // 当跳转页面 url 与当前页 url 相同时,是否自动执行刷新 --- 2.0+
name: 'AdminUI', // 系统名称
tableName: 'adminui', // 本地存储表名
MOD_NAME: 'wxapp', // 模块事件名
// 记录
record: {
url: false // 是否开启 iframe 跳转时 url 记忆功能
},
debug: true, // 是否开启调试模式。如开启,接口异常时会抛出异常 URL 等信息
// 自定义请求字段
request: {
tokenName: false // 自动携带 token 的字段名(如:access_token)。可设置 false 不携带。
},
// 自定义响应字段
response: {
statusName: 'code', // 数据状态的字段名称
statusCode: {
ok: 0, // 数据状态一切正常的状态码
logout: 1001 // 登录状态失效的状态码
},
msgName: 'msg', // 状态信息的字段名称
dataName: 'data' // 数据详情的字段名称
},
// 配置业务模块目录中的特殊模块
extend: {
layim: 'layim/layim' // layim
},
// 主题配置
theme: {
// 内置主题配色方案
color: [{
main: '#20222A', // 主题色
selected: '#16baaa', // 选中色
alias: 'default' // 默认别名
}, {
main: '#03152A',
selected: '#3B91FF',
alias: 'dark-blue' // 藏蓝
}, {
main: '#2E241B',
selected: '#A48566',
alias: 'coffee' // 咖啡
}, {
main: '#50314F',
selected: '#7A4D7B',
alias: 'purple-red' // 紫红
}, {
main: '#344058',
logo: '#1E9FFF',
selected: '#1E9FFF',
alias: 'ocean' // 海洋
}, {
main: '#3A3D49',
logo: '#2F9688',
selected: '#16b777',
alias: 'green' // 墨绿
}, {
main: '#20222A',
logo: '#F78400',
selected: '#F78400',
alias: 'red' // 橙色
}, {
main: '#28333E',
logo: '#AA3130',
selected: '#AA3130',
alias: 'fashion-red' // 时尚红
}, {
main: '#24262F',
logo: '#3A3D49',
selected: '#16baaa',
alias: 'classic-black' // 经典黑
}, {
logo: '#226A62',
header: '#2F9688',
alias: 'green-header' // 墨绿头
}, {
main: '#344058',
logo: '#0085E8',
selected: '#1E9FFF',
header: '#1E9FFF',
alias: 'ocean-header' // 海洋头
}, {
header: '#393D49',
alias: 'classic-black-header' // 经典黑头
}, {
main: '#50314F',
logo: '#50314F',
selected: '#7A4D7B',
header: '#50314F',
alias: 'purple-red-header' // 紫红头
}, {
main: '#28333E',
logo: '#28333E',
selected: '#AA3130',
header: '#AA3130',
alias: 'fashion-red-header' // 时尚红头
}, {
main: '#28333E',
logo: '#16baaa',
selected: '#16baaa',
header: '#16baaa',
alias: 'green-header' // 墨绿头
}, {
main: '#393D49',
logo: '#393D49',
selected: '#16baaa',
header: '#23262E',
alias: 'Classic-style1' // 经典风格1
}, {
main: '#001529',
logo: '#001529',
selected: '#1890FF',
header: '#1890FF',
alias: 'Classic-style2' // 经典风格2
}, {
main: '#25282A',
logo: '#25282A',
selected: '#35BDB2',
header: '#35BDB2',
alias: 'Classic-style3' // 经典风格3
}],
// 初始的颜色索引,对应上面的配色方案数组索引
// 如果本地已经有主题色记录,则以本地记录为优先,除非请求本地数据(localStorage
initColorIndex: 0
}
});
});
+781
View File
@@ -0,0 +1,781 @@
/**
* admin.templte.css
*/
/********************************
模板开始
*******************************/
#LAY_app,
body,
html {
height: 100%;
}
.layui-layout-body {
overflow: auto;
}
#LAY-user-login,
.layadmin-user-display-show {
display: block !important;
}
.layadmin-user-login {
position: relative;
left: 0;
top: 0;
padding: 110px 0;
min-height: 100%;
box-sizing: border-box;
}
.layadmin-user-login-main {
width: 375px;
margin: 0 auto;
box-sizing: border-box;
}
.layadmin-user-login-box {
padding: 20px;
}
.layadmin-user-login-header {
text-align: center;
}
.layadmin-user-login-header h2 {
margin-bottom: 10px;
font-weight: 300;
font-size: 30px;
color: #000;
}
.layadmin-user-login-header p {
font-weight: 300;
color: #999;
}
.layadmin-user-login-body .layui-form-item {
position: relative;
}
.layadmin-user-login-icon {
position: absolute;
left: 1px;
top: 1px;
width: 38px;
line-height: 36px;
text-align: center;
color: #d2d2d2;
}
.layadmin-user-login-body .layui-form-item .layui-input {
padding-left: 38px;
}
.layadmin-user-login-codeimg {
max-height: 38px;
width: 100%;
cursor: pointer;
box-sizing: border-box;
}
.layadmin-user-login-other {
position: relative;
font-size: 0;
line-height: 38px;
padding-top: 20px;
}
.layadmin-user-login-other > * {
display: inline-block;
vertical-align: middle;
margin-right: 10px;
font-size: 14px;
}
.layadmin-user-login-other .layui-icon {
position: relative;
top: 2px;
font-size: 26px;
}
.layadmin-user-login-other a:hover {
opacity: 0.8;
}
.layadmin-user-jump-change {
float: right;
}
.layadmin-user-login-footer {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
line-height: 30px;
padding: 20px;
text-align: center;
box-sizing: border-box;
color: rgba(0, 0, 0, 0.5);
}
.layadmin-user-login-footer span {
padding: 0 5px;
}
.layadmin-user-login-footer a {
padding: 0 5px;
color: rgba(0, 0, 0, 0.5);
}
.layadmin-user-login-footer a:hover {
color: #000;
}
.layadmin-user-login-main[bgimg] {
background-color: #fff;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.05);
}
.ladmin-user-login-theme {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
text-align: center;
}
.ladmin-user-login-theme ul {
display: inline-block;
padding: 5px;
background-color: #fff;
}
.ladmin-user-login-theme ul li {
display: inline-block;
vertical-align: top;
width: 64px;
height: 43px;
cursor: pointer;
transition: all 0.3s;
-webkit-transition: all 0.3s;
background-color: #f2f2f2;
}
.ladmin-user-login-theme ul li:hover {
opacity: 0.9;
}
@media screen and (max-width: 768px) {
.layadmin-user-login {
padding-top: 60px;
}
.layadmin-user-login-main {
width: 300px;
}
.layadmin-user-login-box {
padding: 10px;
}
}
/* 产品版本选择面板 */
.layadmin-panel-selection {
width: 768px;
margin: 30px auto;
}
.layadmin-panel-selection .layui-panel-window {
padding: 30px 50px;
border-top-color: #ddd;
text-align: center;
}
.layadmin-panel-selection .layui-panel-window h2 {
padding-bottom: 15px;
font-size: 18px;
}
.layadmin-panel-selection .layui-panel-window ul {
margin-top: 15px;
text-align: left;
}
.layadmin-panel-selection .layui-btn-container {
margin-top: 30px;
}
.layadmin-panel-selection .layui-btn-container .layui-btn {
width: 100%;
}
/* 通讯录面板 */
.layadmin-maillist-fluid .layadmin-contact-box {
padding: 20px;
background-color: #fff;
border: 1px solid #e7eaec;
overflow: hidden;
}
.layadmin-maillist-fluid .layadmin-text-center {
text-align: center;
}
.layadmin-maillist-fluid .layadmin-text-center img {
max-width: 80%;
border-radius: 50%;
margin-top: 5px;
}
.layadmin-maillist-fluid .layadmin-font-blod {
font-weight: 600;
}
.layadmin-maillist-fluid .layadmin-maillist-img {
margin-top: 5px;
}
.layadmin-maillist-fluid .layadmin-title {
margin: 5px 0 10px 0;
}
.layadmin-maillist-fluid .layadmin-textimg {
line-height: 25px;
margin-bottom: 10px;
}
.layadmin-maillist-fluid .layadmin-address {
line-height: 1.5;
margin-bottom: 20px;
}
.layadmin-maillist-fluid .layadmin-padding-left20 {
padding-left: 20px;
}
/* 个人主页 */
.layadmin-homepage-shadow {
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
background-color: #fff;
border-bottom: 1px solid rgba(0, 0, 0, 0.17);
border-radius: 0;
border: 1px solid #e7ecf3;
}
.layadmin-homepage-panel {
margin-bottom: 15px;
}
.layadmin-homepage-panel .text-center {
text-align: center;
}
.layadmin-homepage-information {
padding: 15px;
border-bottom: 1px solid #e9e9e9;
}
.layadmin-homepage-pad-ver {
padding-top: 15px;
padding-bottom: 15px;
}
.layadmin-homepage-pad-img {
box-shadow: 0 0 0 4px rgba(0, 0, 0, 0.1);
border-radius: 50%;
}
.layadmin-homepage-font {
font-weight: 600;
color: #2b425b;
font-size: 1.2em;
}
.layadmin-homepage-min-font {
margin: 5px 0 10px 0;
font-size: 0.9em;
color: #afb9c3;
}
.layadmin-homepage-pad-ver a {
line-height: 1.1em;
font-size: 1.3em;
vertical-align: middle;
cursor: pointer;
background-color: transparent;
color: inherit;
padding: 6px 12px;
}
.layadmin-homepage-pad-ver a:hover {
background: #16b777;
color: #fff;
}
.layadmin-homepage-about {
padding: 15px;
font-weight: 600;
color: #2b425b;
}
.layadmin-homepage-list-group {
font-size: 0.9em;
line-height: 1.25;
margin-bottom: 5px;
}
.layadmin-homepage-list-group li {
padding: 5px 15px;
border: 1px solid #ddd;
margin-bottom: -1px;
border-width: 1px 0;
border-radius: 0;
background-color: transparent;
border-color: transparent;
color: inherit;
}
.layadmin-homepage-list-group li i {
font-size: 1.333em;
line-height: 1.095em;
vertical-align: middle;
margin-right: 4px;
}
.layadmin-homepage-list-group li a.color {
color: #337ab7;
}
.layadmin-homepage-pad-hor {
padding-left: 15px;
padding-right: 15px;
border-bottom: 15px;
color: #758697;
text-indent: 20px;
}
.layadmin-homepage-list-inline {
margin-left: 15px;
margin-right: 15px;
padding-bottom: 20px;
}
.layadmin-homepage-list-inline a {
display: inline-block;
}
.layadmin-homepage-list-inline .layui-btn {
font-size: 0.9em;
line-height: 1.42857;
vertical-align: middle;
height: auto;
padding: 4px 7px;
margin-left: 0px;
margin: 0 10px 10px 0;
}
/* 左侧个人信息区域 */
.layadmin-homepage-text-center {
text-align: center;
margin-bottom: 15px;
}
.layadmin-homepage-padding15 {
padding: 15px;
margin-bottom: 15px;
}
.layadmin-homepage-padding8 {
padding: 0 8px;
}
.layadmin-homepage-paddingmb {
margin-bottom: 15px;
margin: 0 -7.5px;
}
.layadmin-homepage-content {
margin-left: 15px;
padding: 0;
}
.layadmin-homepage-content .new-section-xs {
margin: 12px 0;
color: inherit;
border: 0 !important;
height: 0px;
box-sizing: content-box;
}
.layadmin-homepage-content .h4 {
font-size: 16px;
font-weight: 600;
color: #2b425b;
}
.layadmin-homepage-content small {
color: #afb9c3;
}
.layadmin-homepage-text-center .layui-icon {
color: #ffb800;
padding-right: 5px;
}
.layadmin-homepage-padding15 .layui-col-sm5 a {
font-size: 11px;
height: auto;
line-height: 1.5;
padding: 5px 10px;
float: right;
}
.layadmin-homepage-padding15 .layui-btn-normal {
margin: 0 15px;
}
.layadmin-homepage-list-imgtxt .layui-col-sm10,
.layadmin-homepage-list-imgtxt .layui-col-sm2 {
background: none;
}
.layadmin-homepage-list-imgtxt .panel-body {
padding: 15px 20px 25px;
overflow: hidden;
margin-bottom: 15px;
}
.layadmin-homepage-list-imgtxt .panel-body .media-left {
display: block;
float: left;
vertical-align: top;
padding-right: 10px;
}
.layadmin-homepage-list-imgtxt .panel-body .media-left img {
border-radius: 50%;
}
.layadmin-homepage-list-imgtxt .media-body {
width: auto;
display: block;
overflow: hidden;
}
.layadmin-homepage-list-imgtxt .media-body .pad-btm {
padding-bottom: 15px;
}
.layadmin-homepage-list-imgtxt .media-body .pad-btm p:first-child {
padding-bottom: 5px;
}
.layadmin-homepage-list-imgtxt .media-body .min-font {
margin-bottom: 10px;
}
.layadmin-homepage-list-imgtxt .media-body .min-font .layui-breadcrumb a {
font-size: 11px;
}
.layui-breadcrumb span[lay-separator] {
margin: 0 5px;
}
.layadmin-homepage-list-imgtxt .media-body .pad-btm .fontColor a {
font-weight: 600;
color: #337ab7;
}
.layadmin-homepage-list-imgtxt .media-body .pad-btm .fontColor span {
color: #758697;
font-weight: 600;
}
.layadmin-homepage-list-imgtxt .media-body .h-img {
display: block;
max-width: 100%;
height: auto;
margin-top: 10px;
}
.layadmin-homepage-list-imgtxt .img-xs {
width: 32px;
height: 32px;
border-radius: 50%;
}
.layadmin-homepage-list-imgtxt .media-body .media {
margin-top: 15px;
overflow: hidden;
}
.layadmin-homepage-list-imgtxt .media-body .media .media-right {
float: right;
padding-top: 10px;
}
.layadmin-homepage-list-imgtxt .media-body .media .media-right .list-inline {
margin-bottom: 10px;
}
.layadmin-homepage-list-imgtxt .media-body .media .list-inline li {
padding: 0 5px;
display: inline-block;
}
.layadmin-homepage-list-imgtxt
.media-body
.media
.media-right
.list-inline
li
span {
font-weight: 600;
}
.layadmin-homepage-list-imgtxt .media-body .media .media-left {
display: block;
overflow: hidden;
width: auto;
}
.layadmin-homepage-list-imgtxt .media-body .media .media-left .font-blod {
font-weight: 700;
color: #758697;
}
.layadmin-homepage-list-imgtxt .media-body .media-list {
padding-top: 15px;
margin-top: 15px;
border-top: 1px solid #e9e9e9;
}
.layadmin-homepage-list-imgtxt .media-body .media-list .media-item {
padding-bottom: 15px;
margin-top: 15px;
}
.layadmin-homepage-list-imgtxt .media-body .media-list .media-item-left {
display: inline-block;
padding-right: 10px;
margin-bottom: 5px;
}
.layadmin-homepage-list-imgtxt .media-body .media-list .media-item-left img {
display: block;
float: left;
}
.layadmin-homepage-list-imgtxt .media-body .media-list .media-text {
overflow: hidden;
width: auto;
display: inline-block;
}
.layadmin-homepage-list-imgtxt .media-body .media-list .media-text a {
margin-right: 10px;
font-weight: 600;
display: inline-block;
}
.layadmin-homepage-list-imgtxt
.media-body
.media-list
.media-text
div:nth-child(2) {
margin-top: 5px;
}
.layadmin-homepage-list-imgtxt .media-body .media-list .layui-btn {
height: 24px;
line-height: 24px;
font-size: 12px;
padding: 0 20px;
margin-bottom: 10px;
}
.layadmin-homepage-list-imgtxt .media-body .media-list .layui-btn {
height: 24px;
line-height: 24px;
font-size: 12px;
padding: 0 20px;
margin-bottom: 10px;
}
/* 个人内容区域 */
.homepage-top .layui-card-body img {
width: 100%;
}
.homepage-top .layui-card-body .margin-top10 {
margin-top: 10px;
}
.homepage-top .layui-card-body .layui-btn {
width: 100%;
display: block;
}
.homepage-top {
padding-bottom: 20px;
}
.layadmin-privateletterlist-item {
position: relative;
display: block;
padding: 10px 15px;
}
.homepage-bottom .layui-card-body {
padding: 0;
}
.homepage-bottom .layui-card-body img {
width: 32px;
height: 32px;
border-radius: 50%;
margin-top: 10px;
}
.homepage-bottom .layui-card-body .meida-left {
display: table-cell;
padding-right: 10px;
}
.homepage-bottom .layui-card-body .meida-right {
display: table-cell;
vertical-align: top;
}
.homepage-bottom .layui-card-body a:hover {
background: #f2f2f2;
}
.homepage-bottom .layui-card-body {
background: #fff;
}
.layui-card-header .panel-title .layui-icon {
position: relative;
left: 0;
right: 0px;
}
/* 右侧信息栏 */
.layadmin-cmdlist-fluid {
padding-bottom: 60px;
}
.cmdlist-container {
background: #fff;
border: 1px solid transparent;
}
.cmdlist-container:hover {
border: 1px solid #e8e8e8;
}
.cmdlist-container img {
width: 100%;
}
.cmdlist-text {
padding: 20px;
}
.cmdlist-text .info {
height: 40px;
font-size: 14px;
line-height: 20px;
width: 100%;
overflow: hidden;
color: #5f5f5f;
margin-bottom: 10px;
}
.cmdlist-text .price {
font-size: 14px;
}
.cmdlist-text .price b {
margin-right: 20px;
}
.cmdlist-text .price p {
display: inline-block;
}
.cmdlist-text .flow {
text-align: right;
float: right;
}
#demo0 {
text-align: center;
}
/* 商品列表 */
.layadmin-message-fluid .layui-col-md12 {
background: #fff;
height: auto;
padding-bottom: 50px;
}
.layadmin-message-fluid .layui-input-block {
margin-left: 0;
}
.layadmin-message-fluid .layui-form {
padding: 45px 40px 0 40px;
}
.layadmin-message-fluid .layui-form-label {
text-align: left;
font-size: 18px;
padding-left: 10px;
}
.layadmin-message-fluid .layui-textarea {
min-height: 100px;
font-size: 16px;
}
.layadmin-message-fluid .layui-input-right {
float: right;
}
.layadmin-messag-icon {
overflow: hidden;
float: left;
}
.layadmin-messag-icon .layui-icon {
font-size: 24px;
line-height: 30px;
margin-right: 15px;
color: #c4cbcf;
}
.layadmin-messag-icon {
margin-top: 4px;
}
.message-content {
padding: 0 40px;
}
.message-content .media-body {
margin-bottom: 60px;
}
.message-content .media-body .pad-btm {
padding-bottom: 0;
}
.message-content .media-left {
float: left;
margin-right: 10px;
}
.message-content .media-left img {
border-radius: 50%;
}
.message-text {
padding-top: 10px;
}
.message-content-btn {
text-align: center;
}
.message-content .layui-btn {
height: auto;
line-height: 26px;
padding: 5px 30px;
font-size: 16px;
}
/* 搜索结果页面 */
.layadmin-serach-main .layui-card-header {
height: auto;
line-height: 24px;
padding: 15px;
}
.layadmin-serach-list {
margin-bottom: 10px;
padding: 10px 0;
border-bottom: 1px solid #f6f6f6;
}
.layadmin-serach-list h3 {
padding: 10px 0;
}
.layadmin-serach-list h3 .layui-badge {
top: -2px;
}
.layadmin-serach-list p {
color: #5f5f5f;
}
.layadmin-serach-list li {
margin-bottom: 20px;
padding-bottom: 20px;
clear: both;
}
.layui-serachlist-cover {
float: left;
margin-right: 15px;
}
.layui-serachlist-cover img {
width: 90px;
height: 90px;
}
/* 用户列表开始 */
.layadmin-caller {
background: #fff;
padding: 30px;
}
.layadmin-caller em {
font-style: normal;
}
.layadmin-caller .caller-fl {
float: left;
}
.layadmin-caller .caller-fr {
float: right;
}
.layadmin-caller .caller-seach {
position: relative;
padding-bottom: 40px;
}
.layadmin-caller .caller-seach .caller-icon {
font-size: 18px;
position: absolute;
top: 9px;
}
.layadmin-caller .caller-seach-icon {
left: 6px;
}
.layadmin-caller .caller-dump-icon {
right: 6px;
cursor: pointer;
}
.layadmin-caller .caller-pl32 {
padding: 0 32px;
}
.layadmin-caller .caller-tab {
margin: 0;
}
.layadmin-caller .caller-contar {
padding-bottom: 20px;
}
.layadmin-caller .caller-contar .caller-item {
padding: 25px 0;
overflow: hidden;
border-bottom: 1px solid #e0e0e0;
}
.layadmin-caller .caller-contar .caller-item .caller-main {
margin-left: 20px;
}
.layadmin-caller .caller-contar .caller-item .caller-main p {
line-height: 100%;
padding: 8px 0;
}
.layadmin-caller .caller-contar .caller-item .caller-main p:first-child {
padding-top: 0;
}
.layadmin-caller .caller-contar .caller-item .caller-main em {
margin-left: 5px;
}
.layadmin-caller .caller-contar .caller-item .caller-main .caller-adds i {
padding-right: 5px;
margin: 0;
}
.layadmin-caller .caller-contar .caller-item .caller-main .caller-adds {
padding-bottom: 12px;
}
.layadmin-caller .caller-contar .caller-iconset i {
margin: 0 5px;
}
.layadmin-caller .caller-contar .caller-iconset i:first-child {
margin-left: 0;
}
.layadmin-caller .caller-contar button {
margin-top: 22px;
}
.layadmin-caller .caller-contar .caller-img {
width: 40px;
height: 40px;
border-radius: 100%;
}
/****** 模板结束 ******/
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,90 @@
layui.define(function (exports) {
"use strict";
class WeChatJsonCrypto {
constructor(token, encodingAesKey, appId) {
if (!token || !appId || encodingAesKey.length !== 43 || !/^[A-Za-z0-9+\/]{43}$/.test(encodingAesKey)) {
throw new Error("配置错误:Token/AppID不能为空,EncodingAESKey需为43位标准Base64字符");
}
this.token = token;
this.appId = appId;
this.blockSize = 32;
this.encodingAesKey = CryptoJS.enc.Base64.parse(encodingAesKey + '=');
if (!this.encodingAesKey.words?.length) throw new Error("密钥解析失败");
}
// ✅ 核心修复1:加密时直接传入明文WordArray(非{ ciphertext: ... }
async encrypt(data) {
const jsonBytes = new TextEncoder().encode(JSON.stringify(data, null, 0));
const appIdBytes = new TextEncoder().encode(this.appId);
const random = crypto.getRandomValues(new Uint8Array(16));
const lenBytes = new Uint8Array(new DataView(new ArrayBuffer(4)).buffer);
new DataView(lenBytes.buffer).setUint32(0, jsonBytes.length, false);
// 拼接明文: random(16) + len(4) + json + appId
const plain = new Uint8Array(20 + jsonBytes.length + appIdBytes.length);
plain.set(random); plain.set(lenBytes, 16);
plain.set(jsonBytes, 20); plain.set(appIdBytes, 20 + jsonBytes.length);
// PKCS#7填充
const pad = this.blockSize - (plain.length % this.blockSize);
const padded = new Uint8Array(plain.length + pad).fill(pad, plain.length);
padded.set(plain);
const iv = CryptoJS.lib.WordArray.create(this.encodingAesKey.words.slice(0, 4));
const encrypted = CryptoJS.AES.encrypt(
CryptoJS.lib.WordArray.create(padded), // ← 修复点:直接传明文
this.encodingAesKey,
{ iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.NoPadding }
);
return CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
}
async decrypt(encryptedBase64) {
try {
const cipherWA = CryptoJS.enc.Base64.parse(encryptedBase64);
if (cipherWA.sigBytes < 32) return null;
const iv = CryptoJS.lib.WordArray.create(this.encodingAesKey.words.slice(0, 4));
const decryptedWA = CryptoJS.AES.decrypt(
{ ciphertext: cipherWA },
this.encodingAesKey,
{ iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.NoPadding }
);
// 转Uint8Array
const decrypted = new Uint8Array(decryptedWA.sigBytes);
for (let i = 0; i < decryptedWA.sigBytes; i++) {
decrypted[i] = (decryptedWA.words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
}
// ✅ 核心修复2:严格PKCS#7填充验证与移除
const padLen = decrypted[decrypted.length - 1];
if (padLen < 1 || padLen > this.blockSize || decrypted.length < padLen) return null;
for (let i = decrypted.length - padLen; i < decrypted.length; i++) {
if (decrypted[i] !== padLen) return null;
}
const plain = decrypted.slice(0, -padLen); // 移除填充
if (plain.length < 20) return null;
const jsonLen = new DataView(plain.slice(16, 20).buffer).getUint32(0, false);
if (jsonLen <= 0 || 20 + jsonLen > plain.length) return null;
const jsonStr = new TextDecoder().decode(plain.slice(20, 20 + jsonLen));
// ✅ 核心修复3:AppID直接解码(无字符过滤)
const fromAppId = new TextDecoder().decode(plain.slice(20 + jsonLen));
if (fromAppId !== this.appId) return null;
return JSON.parse(jsonStr);
} catch {
return null;
}
}
generateSignature(timestamp, nonce, encryptMsg) {
return CryptoJS.SHA1([this.token, timestamp, nonce, encryptMsg].sort().join('')).toString(CryptoJS.enc.Hex);
}
}
// 导出模块
exports('JsonCrypto', WeChatJsonCrypto);
});
+751
View File
@@ -0,0 +1,751 @@
/**
* =============================================
* 权限管理模块 - Layui版本
* Auth + RBAC 权限控制完整实现
* =============================================
*
* 使用说明:
* 1. 放置在layui的modules目录下
* 2. 使用:layui.use('permission', function(mod){...})
* 3. 或者直接:var permission = layui.permission;
*
* @version 2.0.0 - Layui Module
* @author Permission Module
* @date 2024-01-01
*/
layui.define(function (exports) {
'use strict';
/**
* =============================================
* 权限配置管理器
* =============================================
*/
const PermissionConfig = (function () {
// 私有变量
const _defaultToolbar = {
create: { title: '创建资源', layEvent: 'dataCreate', icon: 'layui-icon-add-1', permission: 'create', color: 'layui-btn-normal', size: '', show: true },
recycle: { title: '回收站', layEvent: 'dataRecycle', icon: 'layui-icon-refresh', permission: 'recycle', color: 'layui-btn-warm', size: '', show: true },
filter: { title: '筛选', layEvent: 'filter', icon: 'layui-icon-search', permission: 'filter', color: 'layui-btn-primary', size: '', show: true },
export: { title: '导出', layEvent: 'export', icon: 'layui-icon-download-circle', permission: 'export', color: 'layui-btn-primary', size: '', show: true },
print: { title: '打印', layEvent: 'print', icon: 'layui-icon-print', permission: 'print', color: 'layui-btn-primary', size: '', show: true },
refresh: { title: '刷新', layEvent: 'refresh', icon: 'layui-icon-refresh', permission: 'refresh', color: 'layui-btn-primary', size: '', show: true },
batchDelete: { title: '批量删除', layEvent: 'batchDelete', icon: 'layui-icon-delete', permission: 'batch_delete', color: 'layui-btn-danger', size: '', show: true },
import: { title: '导入', layEvent: 'import', icon: 'layui-icon-upload-circle', permission: 'import', color: 'layui-btn-primary', size: '', show: true }
};
const _rowToolbar = {
view: { title: '查看', layEvent: 'dataView', icon: 'layui-icon-eye', permission: 'view', color: 'layui-btn-primary layui-btn-xs', size: 'layui-btn-xs', show: true },
edit: { title: '编辑', layEvent: 'dataEdit', icon: 'layui-icon-edit', permission: 'edit', color: 'layui-btn-normal layui-btn-xs', size: 'layui-btn-xs', show: true },
delete: { title: '删除', layEvent: 'dataDelete', icon: 'layui-icon-delete', permission: 'delete', color: 'layui-btn-danger layui-btn-xs', size: 'layui-btn-xs', show: true },
copy: { title: '复制', layEvent: 'dataCopy', icon: 'layui-icon-file-b', permission: 'copy', color: 'layui-btn-warm layui-btn-xs', size: 'layui-btn-xs', show: true },
enable: { title: '启用', layEvent: 'dataEnable', icon: 'layui-icon-play', permission: 'enable', color: 'layui-btn-normal layui-btn-xs', size: 'layui-btn-xs', show: true },
disable: { title: '禁用', layEvent: 'dataDisable', icon: 'layui-icon-pause', permission: 'disable', color: 'layui-btn-warm layui-btn-xs', size: 'layui-btn-xs', show: true },
audit: { title: '审核', layEvent: 'dataAudit', icon: 'layui-icon-ok-circle', permission: 'audit', color: 'layui-btn-normal layui-btn-xs', size: 'layui-btn-xs', show: true },
unaudit: { title: '取消审核', layEvent: 'dataUnaudit', icon: 'layui-icon-close', permission: 'unaudit', color: 'layui-btn-warm layui-btn-xs', size: 'layui-btn-xs', show: true }
};
const _customButtons = {
toolbar: {},
row: {}
};
// 工具函数
const _isArray = function (obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
};
// 公共方法
return {
/**
* 获取默认工具条配置
*/
getDefaultToolbar: function () {
return Object.assign({}, _defaultToolbar);
},
/**
* 获取行工具条配置
*/
getRowToolbar: function () {
return Object.assign({}, _rowToolbar);
},
/**
* 注册自定义按钮
* @param {String} type - 按钮类型 (toolbar/row)
* @param {String} key - 按钮唯一标识
* @param {Object} config - 按钮配置
*/
registerButton: function (type, key, config) {
if (!['toolbar', 'row'].includes(type)) {
console.error('按钮类型必须是 toolbar 或 row');
return false;
}
if (!_customButtons[type]) {
_customButtons[type] = {};
}
_customButtons[type][key] = Object.assign({
title: key,
layEvent: key,
icon: 'layui-icon-more',
permission: key,
color: 'layui-btn-primary',
size: '',
show: true
}, config);
return true;
},
/**
* 获取工具条配置
* @param {String} type - toolbar/row
* @param {Array} keys - 需要的按钮key列表
* @param {Object} customConfig - 自定义配置(覆盖默认配置)
* @returns {Array} 配置数组
*/
getToolbarConfig: function (type, keys = [], customConfig = {}) {
const baseConfig = type === 'toolbar' ? _defaultToolbar : _rowToolbar;
const customBtns = _customButtons[type] || {};
// 合并基础配置和自定义配置
const allConfig = Object.assign({}, baseConfig, customBtns);
// 应用自定义配置覆盖
Object.keys(customConfig).forEach(key => {
if (allConfig[key]) {
allConfig[key] = Object.assign(allConfig[key], customConfig[key]);
}
});
// 如果指定了keys,只返回指定的按钮
if (keys.length > 0) {
return keys.map(key => allConfig[key]).filter(Boolean);
}
// 返回所有配置
return Object.values(allConfig).filter(btn => btn.show !== false);
},
/**
* 获取单个按钮配置
* @param {String} type - toolbar/row
* @param {String} key - 按钮key
* @returns {Object} 按钮配置
*/
getButtonConfig: function (type, key) {
const config = this.getToolbarConfig(type);
return config.find(btn => btn.layEvent === key || (btn.permission && btn.permission.includes(key)));
},
/**
* 批量注册按钮
* @param {String} type - 按钮类型
* @param {Object} buttons - 按钮配置对象
*/
registerButtons: function (type, buttons) {
Object.keys(buttons).forEach(key => {
this.registerButton(type, key, buttons[key]);
});
},
/**
* 清除自定义按钮
* @param {String} type - 按钮类型
* @param {String} key - 按钮key,不传则清除所有
*/
clearCustomButtons: function (type, key) {
if (key) {
delete _customButtons[type]?.[key];
} else {
_customButtons[type] = {};
}
}
};
})();
/**
* =============================================
* 权限验证工具类
* =============================================
*/
const Auth = (function () {
// 私有变量
let _permissions = [];
let _permissionCache = {};
let _user = null;
let _controller = null; // 当前页面对应的控制器(用于裸动作 token 解析)
// 工具函数
const _isArray = function (obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
};
const _isString = function (obj) {
return typeof obj === 'string';
};
// 前端短词 → 后端动作 同义词(按钮配置常用 create,权限 key 用 add
const _synonyms = {
create: 'add',
recycle: 'recyclebin',
batch_delete: 'delete',
batchDelete: 'delete'
};
const _splitPermissions = function (perm) {
if (_isArray(perm)) return perm;
if (_isString(perm)) return perm.split(',').map(p => p.trim());
return [];
};
// 公共方法
return {
/**
* 初始化权限
* @param {Array} permissions - 用户权限列表
* @param {Object} user - 用户信息
*/
init: function (permissions = [], user = null) {
_permissions = permissions || [];
_user = user;
_controller = null;
this.clearCache();
},
/**
* 获取当前用户
* @returns {Object} 用户信息
*/
getUser: function () {
return _user;
},
/**
* 设置当前用户
* @param {Object} user - 用户信息
*/
setUser: function (user) {
_user = user;
},
/**
* 获取权限列表
* @returns {Array} 权限列表
*/
getPermissions: function () {
return _permissions;
},
/**
* 设置当前页面对应的控制器(用于裸动作 token 解析,如 has('view') → admin:view
*/
setController: function (controller) {
_controller = controller || null;
this.clearCache();
return this;
},
/**
* 检查单个权限
* 支持:
* - 精确 keyadmin:edit
* - 控制器通配:admin:*(覆盖该控制器下所有动作)
* - 超级管理员:* / *:*
* - 裸动作 token(结合 setController):view → admin:viewcreate → admin:add(同义词)
* @param {String} permission - 权限标识
* @returns {Boolean}
*/
has: function (permission) {
if (!permission) return true;
// 使用缓存
if (_permissionCache[permission] !== undefined) {
return _permissionCache[permission];
}
const perms = _splitPermissions(permission);
const result = perms.some(p => this._match(p));
_permissionCache[permission] = result;
return result;
},
/**
* 单个权限标识匹配(内部)
*/
_match: function (p) {
if (!p) return true;
// 精确匹配
if (_permissions.indexOf(p) > -1) return true;
// 超级管理员放行
if (_permissions.indexOf('*') > -1 || _permissions.indexOf('*:*') > -1) return true;
// 完整 keycontroller:action / controller:*
if (p.indexOf(':') > -1) {
const parts = p.split(':');
const ctrl = parts[0];
const act = parts[1];
if (act === '*') {
return _permissions.indexOf(ctrl + ':*') > -1;
}
return _permissions.indexOf(ctrl + ':' + act) > -1
|| _permissions.indexOf(ctrl + ':*') > -1;
}
// 裸动作 token:结合当前控制器解析(含同义词)
const action = _synonyms[p] || p;
if (_controller) {
if (_permissions.indexOf(_controller + ':*') > -1) return true;
if (_permissions.indexOf(_controller + ':' + action) > -1) return true;
}
return false;
},
/**
* 检查多个权限(满足一个即可)
* @param {Array} permissions - 权限列表
* @returns {Boolean}
*/
hasAny: function (permissions) {
if (!_isArray(permissions) || permissions.length === 0) return false;
return permissions.some(p => this.has(p));
},
/**
* 检查所有权限
* @param {Array} permissions - 权限列表
* @returns {Boolean}
*/
hasAll: function (permissions) {
if (!_isArray(permissions) || permissions.length === 0) return false;
return permissions.every(p => this.has(p));
},
/**
* 检查权限并返回权限标识
* @param {String|Array} permissions - 权限标识
* @returns {String|Boolean} 有权限返回权限标识,无权限返回false
*/
check: function (permissions) {
if (_isArray(permissions)) {
for (let p of permissions) {
if (this.has(p)) {
return p;
}
}
return false;
}
return this.has(permissions) ? permissions : false;
},
/**
* 过滤有权限的按钮
* @param {Array} buttons - 按钮配置数组
* @returns {Array} 有权限的按钮数组
*/
filterButtons: function (buttons) {
if (!_isArray(buttons)) return [];
return buttons.filter(btn => {
// 如果没有设置permission,默认显示
if (!btn.permission) return true;
// 支持多个权限,用逗号分隔
const perms = _splitPermissions(btn.permission);
return this.hasAny(perms);
});
},
/**
* 生成工具条HTML
* @param {Array} buttons - 按钮配置数组
* @param {String} containerClass - 容器样式类
* @param {Object} options - 额外选项
* @returns {String} HTML字符串
*/
renderToolbar: function (buttons, containerClass = 'layui-btn-container', options = {}) {
const filteredButtons = this.filterButtons(buttons);
if (filteredButtons.length === 0) return '';
console.log('🔧 渲染工具条 - 需要在项目中实现',filteredButtons);
// const html = filteredButtons.map(btn => {
// const color = btn.color || 'layui-btn-primary';
// const size = btn.size || '';
// const icon = btn.icon ? `<i class="${btn.icon}"></i>` : '';
// const disabled = btn.disabled ? 'disabled' : '';
// const style = btn.style ? `style="${btn.style}"` : '';
// return `<button class="layui-btn ${color} ${size}" lay-event="${btn.layEvent}" title="${btn.title}" ${disabled} ${style}>
// ${icon}${btn.title}
// </button>`;
// }).join('');
// const containerStyle = options.containerStyle ? `style="${options.containerStyle}"` : '';
// return `<div class="${containerClass}" ${containerStyle}>${html}</div>`;
},
/**
* 生成行工具条HTML
* @param {Array} buttons - 按钮配置数组
* @param {Object} options - 额外选项
* @returns {String} HTML字符串
*/
renderRowToolbar: function (buttons, options = {}) {
const filteredButtons = this.filterButtons(buttons);
if (filteredButtons.length === 0) return '';
const html = filteredButtons.map(btn => {
const color = btn.color || 'layui-btn-primary layui-btn-xs';
const icon = btn.icon ? `<i class="${btn.icon}"></i>` : '';
const disabled = btn.disabled ? 'disabled' : '';
const style = btn.style ? `style="${btn.style}"` : '';
return `<a class="layui-btn ${color}" lay-event="${btn.layEvent}" title="${btn.title}" ${disabled} ${style}>
${icon}
</a>`;
}).join('');
return `<div class="layui-btn-group">${html}</div>`;
},
/**
* 清除缓存
*/
clearCache: function () {
_permissionCache = {};
},
/**
* 批量检查权限
* @param {Array} permissions - 权限列表
* @returns {Object} 权限检查结果对象
*/
batchCheck: function (permissions) {
if (!_isArray(permissions)) return {};
return permissions.reduce((result, perm) => {
result[perm] = this.has(perm);
return result;
}, {});
},
/**
* 预加载常用权限
* @param {Array} permissions - 常用权限列表
*/
preload: function (permissions = ['create', 'edit', 'delete', 'view']) {
permissions.forEach(perm => this.has(perm));
}
};
})();
/**
* =============================================
* 动态权限更新管理器
* =============================================
*/
const PermissionManager = (function () {
// 私有变量
const _listeners = [];
let _refreshTimeout = null;
// 公共方法
return {
/**
* 添加权限变更监听器
* @param {Function} callback - 回调函数
* @param {String} id - 监听器ID(用于移除)
*/
onPermissionChange: function (callback, id = null) {
_listeners.push({ callback, id });
},
/**
* 移除权限变更监听器
* @param {String} id - 监听器ID
*/
offPermissionChange: function (id) {
const index = _listeners.findIndex(l => l.id === id);
if (index !== -1) {
_listeners.splice(index, 1);
}
},
/**
* 触发权限变更
* @param {Array} newPermissions - 新的权限列表
* @param {Object} user - 用户信息
*/
triggerChange: function (newPermissions, user = null) {
// 更新权限
Auth.init(newPermissions, user);
// 保存到localStorage
if (typeof localStorage !== 'undefined') {
localStorage.setItem('permissions', JSON.stringify(newPermissions));
if (user) {
localStorage.setItem('user', JSON.stringify(user));
}
}
// 通知所有监听器
_listeners.forEach(({ callback }) => {
try {
callback(newPermissions, user);
} catch (e) {
console.error('权限变更监听器执行出错:', e);
}
});
// 防抖刷新表格
clearTimeout(_refreshTimeout);
_refreshTimeout = setTimeout(() => {
this.refreshTables();
}, 100);
},
/**
* 刷新所有表格
*/
refreshTables: function () {
console.log('🔄 刷新表格 - 需要在项目中实现');
// 触发自定义事件
const event = new CustomEvent('permissionChanged', {
detail: { permissions: Auth.getPermissions() }
});
if (typeof window !== 'undefined') {
window.dispatchEvent(event);
}
},
/**
* 从服务器同步最新权限
* @param {String} url - 权限接口地址
* @returns {Promise} 同步结果
*/
syncPermissions: function (url = '/api/user/permissions') {
// return new Promise((resolve, reject) => {
// if (typeof $ !== 'undefined' && $.ajax) {
// $.get(url, function (res) {
// if (res.code === 200 || res.success) {
// this.triggerChange(res.data.permissions || res.data, res.data.user);
// resolve(true);
// } else {
// reject(new Error(res.msg || '同步权限失败'));
// }
// }.bind(this)).fail(function (xhr) {
// reject(new Error('网络请求失败: ' + xhr.statusText));
// });
// } else if (typeof fetch !== 'undefined') {
// fetch(url)
// .then(res => res.json())
// .then(res => {
// if (res.code === 200 || res.success) {
// this.triggerChange(res.data.permissions || res.data, res.data.user);
// resolve(true);
// } else {
// reject(new Error(res.msg || '同步权限失败'));
// }
// })
// .catch(err => reject(err));
// } else {
// reject(new Error('不支持的HTTP请求方式'));
// }
// }.bind(this));
},
/**
* 清除所有监听器
*/
clearListeners: function () {
_listeners.length = 0;
}
};
})();
/**
* =============================================
* 权限按钮组件
* =============================================
*/
class AuthButton extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this._clickHandler = this._handleClick.bind(this);
}
static get observedAttributes() {
return ['permission', 'text', 'icon', 'type', 'size', 'disabled'];
}
connectedCallback() {
this.render();
}
disconnectedCallback() {
this.shadowRoot.removeEventListener('click', this._clickHandler);
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue !== newValue) {
this.render();
}
}
_handleClick(e) {
if (!this.hasAttribute('disabled')) {
this.dispatchEvent(new CustomEvent('auth-click', {
bubbles: true,
composed: true,
detail: { button: this }
}));
}
}
render() {
const permission = this.getAttribute('permission');
const text = this.getAttribute('text') || '按钮';
const icon = this.getAttribute('icon') || '';
const type = this.getAttribute('type') || 'primary';
const size = this.getAttribute('size') || '';
const disabled = this.hasAttribute('disabled');
const hasPermission = !permission || Auth.has(permission);
// 无权限时隐藏
if (!hasPermission) {
this.shadowRoot.innerHTML = '';
this.style.display = 'none';
return;
}
this.style.display = '';
const iconHtml = icon ? `<i class="${icon}"></i>` : '';
const btnClass = `layui-btn layui-btn-${type} ${size}`;
const disabledAttr = disabled ? 'disabled' : '';
this.shadowRoot.innerHTML = `
<style>
:host {
display: inline-block;
}
.layui-btn {
display: inline-block;
height: 38px;
line-height: 38px;
padding: 0 18px;
background-color: #009688;
color: #fff;
white-space: nowrap;
text-align: center;
font-size: 14px;
border: none;
border-radius: 2px;
cursor: pointer;
box-sizing: border-box;
text-decoration: none;
}
.layui-btn:hover {
opacity: 0.9;
}
.layui-btn[disabled] {
opacity: 0.5;
cursor: not-allowed;
}
.layui-btn-xs {
height: 26px;
line-height: 26px;
padding: 0 10px;
font-size: 12px;
}
.layui-btn-sm {
height: 30px;
line-height: 30px;
padding: 0 10px;
font-size: 12px;
}
.layui-btn-lg {
height: 44px;
line-height: 44px;
padding: 0 25px;
font-size: 16px;
}
.layui-btn-primary {
background-color: #fff;
color: #333;
border: 1px solid #d2d2d2;
}
.layui-btn-normal {
background-color: #1E9FFF;
}
.layui-btn-warm {
background-color: #FFB800;
}
.layui-btn-danger {
background-color: #FF5722;
}
i {
margin-right: 5px;
}
</style>
<button class="${btnClass}" ${disabledAttr}>
${iconHtml}${text}
</button>
`;
// 添加点击事件
const btn = this.shadowRoot.querySelector('button');
btn.removeEventListener('click', this._clickHandler);
btn.addEventListener('click', this._clickHandler);
}
}
// 注册自定义元素
if (typeof customElements !== 'undefined' && !customElements.get('auth-button')) {
customElements.define('auth-button', AuthButton);
}
/**
* =============================================
* 导出模块
* =============================================
*/
exports('auth', {
// 核心对象auth
Auth: Auth,
PermissionConfig: PermissionConfig,
PermissionManager: PermissionManager,
// 便捷方法(直接暴露常用方法)
init: Auth.init.bind(Auth),
setController: Auth.setController.bind(Auth),
has: Auth.has.bind(Auth),
hasAny: Auth.hasAny.bind(Auth),
hasAll: Auth.hasAll.bind(Auth),
getPermissions: Auth.getPermissions.bind(Auth),
renderToolbar: Auth.renderToolbar.bind(Auth),
renderRowToolbar: Auth.renderRowToolbar.bind(Auth),
filterButtons: Auth.filterButtons.bind(Auth),
// 配置方法
getToolbarConfig: PermissionConfig.getToolbarConfig.bind(PermissionConfig),
registerButton: PermissionConfig.registerButton.bind(PermissionConfig),
registerButtons: PermissionConfig.registerButtons.bind(PermissionConfig),
// 管理器方法
onPermissionChange: PermissionManager.onPermissionChange.bind(PermissionManager),
triggerChange: PermissionManager.triggerChange.bind(PermissionManager),
syncPermissions: PermissionManager.syncPermissions.bind(PermissionManager),
// 按钮组件
AuthButton: AuthButton
});
});
+364
View File
@@ -0,0 +1,364 @@
// ==================== ChatClient 模块定义 (ES6 兼容) ====================
layui.define(['jquery', 'layer', 'laytpl', 'util'], function (exports) {
"use strict";
class ChatClient {
constructor(config) {
this.config = Object.assign({
wsUrl: 'wss://echo.websocket.org',
token: '',
uid: '',
reconnectDelay: 3000,
maxReconnect: 5
}, config);
this.ws = null;
this.reconnectCount = 0;
this.messageQueue = [];
this.isConnected = false;
this.currentSession = { id: '', type: '' };
this.requestCallbacks = {};
this.heartbeatTimer = null;
this.eventHandlers = {};
this.init();
}
/**
* 系统事件
* @param {*} event
* @param {*} handler
* @returns
*/
on(event, handler) {
if (!this.eventHandlers[event]) this.eventHandlers[event] = [];
this.eventHandlers[event].push(handler);
return this;
}
/**
* 触发
* @param {*} event
* @param {...any} args
* @returns
*/
emit(event, ...args) {
if (this.eventHandlers[event]) {
this.eventHandlers[event].forEach(handler => handler.apply(this, args));
}
return this;
}
/**
* 初始化
*/
init() {
this.connect();
window.addEventListener('beforeunload', () => this.disconnect());
}
/**
* 链接
*/
connect() {
try {
this.ws = new WebSocket(this.config.wsUrl);
this.ws.onopen = () => {
console.log('✅ WebSocket 连接成功');
this.isConnected = true;
this.reconnectCount = 5;
this.sendAuth();
this.startHeartbeat();
this.emit('connect');
// 重发队列消息
while (this.messageQueue.length > 0) {
const msg = this.messageQueue.shift();
this.ws.send(JSON.stringify(msg));
}
};
this.ws.onmessage = (event) => {
try {
const packet = JSON.parse(event.data);
this.handleMessage(packet);
} catch (e) {
console.error('❌ 消息解析失败:', e);
this.emit('error', e);
}
};
this.ws.onerror = (error) => {
console.error('❌ WebSocket 错误:', error);
this.emit('error', error);
};
this.ws.onclose = () => {
console.log('⚠️ WebSocket 连接关闭');
this.isConnected = false;
this.stopHeartbeat();
this.emit('close');
this.attemptReconnect();
};
} catch (e) {
console.error('❌ WebSocket 初始化失败:', e);
this.attemptReconnect();
}
}
/**
* 发送验证信息
*/
sendAuth() {
this.send({ content: this.config.token }, 'sys', 'auth');
}
/**
* 开始心跳
*/
startHeartbeat() {
this.stopHeartbeat();
this.heartbeatTimer = setInterval(() => {
if (this.isConnected && this.ws.readyState === WebSocket.OPEN) {
this.send({}, 'sys', 'heartbeat');
}
}, 30000);
}
/**
* 停止心跳
*/
stopHeartbeat() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
}
}
/**
* 重连
*/
attemptReconnect() {
if (this.reconnectCount < this.config.maxReconnect && !this.isConnected) {
this.reconnectCount++;
console.log(`🔄 尝试重连 (${this.reconnectCount}/${this.config.maxReconnect})...`);
setTimeout(() => this.connect(), this.config.reconnectDelay * this.reconnectCount);
} else {
this.emit('reconnect-failed');
}
}
/**
* 发送
* @param {*} payload
* @param {*} msgType
* @param {*} options
* @returns
*/
send(payload, _frame, type, options = {}) {
const packet = {
header: {
id: 'pkt_' + Date.now() + '_' + Math.random().toString(36).substr(2, 5),
frame: _frame,
type: type,
from: this.user?.uid,
timestamp: Date.now()
},
payload: payload || {}
};
// 存储回调
if (typeof options.callback === 'function') {
this.requestCallbacks[packet.header.id] = {
callback: options.callback,
timeout: setTimeout(() => {
if (this.requestCallbacks[packet.header.id]) {
this.requestCallbacks[packet.header.id].callback({
header: { type: 'timeout' },
payload: { message: '请求超时' }
});
delete this.requestCallbacks[packet.header.id];
}
}, 10000)
};
}
if (this.isConnected && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(packet));
} else {
this.messageQueue.push(packet);
if (!this.isConnected) this.connect();
}
return packet.header.id;
}
handleMessage(packet) {
const frame = packet.header?.frame;
// if (packet.header?.id) {
// const origId = packet.header.id;
// if (this.requestCallbacks[origId]) {
// clearTimeout(this.requestCallbacks[origId].timeout);
// this.requestCallbacks[origId].callback(packet);
// delete this.requestCallbacks[origId];
// return;
// }
// }
// 业务消息分发
switch (frame) {
case 'sys':
this.handleSystem(packet);
break;
case 'chat':
this.emit('friend-apply', packet.payload);
break;
case 'rel':
this.emit('message-read', packet.payload);
break;
case 'ctl':
this.emit('friend-apply-result', packet.payload);
break;
case 'err':
this.emit('group-invite', packet.payload);
break;
case 'auth':
this.emit('auth-response', packet.payload);
break;
case 'heartbeat':
this.emit('auth-response', packet.payload);
break;
default:
this.emit('message', packet);
}
}
handleSystem(packet) {
const type = packet.header?.type;
switch (type) {
case 'heartbeat':
break;
case 'auth':
this.user = packet.payload.content ;
break;
}
}
// 业务方法(只处理数据,不涉及渲染)
sendMessage(sessionId, sessionType, content, msgType = 'text') {
const message = {
header: {
id: 'msg_' + Date.now(),
type: msgType,
subtype: sessionType,
from: this.user.uid,
to: sessionId,
timestamp: new Date().toISOString()
},
payload: {
content: content,
}
};
this.ws.send(JSON.stringify(packet));
// // 发送消息到服务器
// this.send({
// session_id: sessionId,
// session_type: sessionType,
// content: content,
// type: msgType,
// id: msgId
// }, 'send_message', {
// callback: (res) => {
// if (res.payload?.success) {
// // 更新本地消息状态为已发送
// message.read_status = 'sent';
// this.emit('message-sent', message);
// } else {
// message.read_status = 'failed';
// this.emit('message-failed', message);
// }
// }
// });
// 立即触发本地消息事件(用于前端渲染)
this.emit('local-message', message);
return msgId;
}
/**
* 发送图片
* @param {*} sessionId
* @param {*} sessionType
* @param {*} imgUrl
* @returns
*/
sendImage(sessionId, sessionType, imgUrl) {
return this.sendMessage(sessionId, sessionType, `[图片]${imgUrl}`, 'image');
}
/**发送读取状态 */
sendReadReceipt(msgId, sessionId, sessionType) {
this.send({
id: msgId,
session_id: sessionId,
session_type: sessionType
}, 'read_receipt');
}
/**设置会话 */
setCurrentSession(sessionId, sessionType) {
this.currentSession = { id: sessionId, type: sessionType };
this.emit('session-changed', this.currentSession);
}
/**
* 搜索用户
* @param {*} keyword
* @param {*} callback
*/
searchUsers(keyword, callback) {
// 实际项目中替换为真实API调用
setTimeout(() => {
const mockUsers = [
{ uid: 'u2001', nickname: '设计师小王', avatar: 'W', is_friend: false },
{ uid: 'u2002', nickname: '产品经理李', avatar: 'L', is_friend: true },
{ uid: 'u2003', nickname: '后端工程师赵', avatar: 'Z', is_friend: false }
].filter(u => u.nickname.includes(keyword) || u.uid.includes(keyword));
callback({ code: 0, data: mockUsers });
}, 300);
}
/**
* 添加好友
* @param {*} targetUid
* @param {*} remark
* @param {*} callback
*/
addFriend(targetUid, remark, callback) {
this.send({
target_uid: targetUid,
remark: remark || '你好,加个好友吧~'
}, 'add_friend_request', { callback });
}
/**
* 接受好友
* @param {*} applyId
* @param {*} agree
* @param {*} callback
*/
handleFriendApply(applyId, agree, callback) {
this.send({
apply_id: applyId,
action: agree ? 'agree' : 'reject'
}, 'handle_friend_apply', { callback });
}
disconnect() {
this.stopHeartbeat();
if (this.ws) {
this.ws.close();
this.ws = null;
}
this.isConnected = false;
}
}
// 导出模块
exports('chatClient', ChatClient);
});
+198
View File
@@ -0,0 +1,198 @@
/**
* common
* 通用元素组件
*/
layui.define('component', function (exports) {
var table = layui.table;
// 创建组件
var component = layui.component({
name: 'common', // 组件名称
config: {
urls: {
create: '',
edit: '',
delete: '',
restore: '',
},
cols: []
},
CONST: {},
render: function () { // 组件渲染逻辑
// …
},
beforeInit: function (options) {
console.log(options); // 获得组件初始化前的配置项
},
beforeRender: function (options) {
console.log(options); // 获得组件渲染前的配置项
},
extendsInstance: function (that) {
return {
// 关闭组件
close: function () {
that.remove(); // 调用组件原型中的 remove 方法
}
}
},
events: function () {
// 亦可包含针对组件的 window, document 等全局事件
// …
}
// 其他选项
});
var Class = component.Class;
Class.prototype.tableRender = function () {
var dataTable = table.render({
elem: '#dataTable'
, url: '/admin/Role/index' // 后端数据接口
, page: true
, limit: 15
, toolbar: '#tableBarTpl'
, cols: [[
{ type: 'checkbox', fixed: 'left' }
, { field: 'id', title: 'ID', width: 60, align: 'center', sort: true, fixed: 'left' }
, { field: 'title', title: '名称', width: 150, align: 'center', fixed: 'left' }
, { field: 'name', title: '代码', width: 150, align: 'center', fixed: 'left' }
, { field: 'description', title: '描述', minWidth: 200, align: 'left' }
, { field: 'status', title: '状态', width: 120, align: 'center', templet: '#statusTpl' }
, { field: 'create_time', title: '创建时间', width: 160, align: 'center', sort: true }
, { title: '操作', width: 220, align: 'left', toolbar: '#dataBarTpl', fixed: 'right', unresize: true }
]]
, done: function (res, curr, count) { }
});
table.on('toolbar(dataTable)', function (elem) {
switch (elem.event) {
case 'dataCreate':
active.dataCreate({ pid: 0, type: 1 });
break;
case 'dataDelete':
var checkStatus = table.checkStatus('dataTable'), checkData = checkStatus.data;
let ids = checkData.map((item, index, array) => { return item.id; });
active.dataDelete(ids);
break;
case 'dataRecybin':
active.dataRecybin();
break;
};
});
table.on('tool(dataTable)', function (elem) {
var data = elem.data;
switch (elem.event) {
case 'update':
active.dataEdit(data);
break;
case 'permission':
openPermissionForm(data);
break;
case 'delete':
active.dataDelete([data.id]);
break;
}
});
};
Class.prototype.fromRender = function (data, callback, done) {
var title = data ? '编辑数据[' + data.account + ']' : '添加数据';
var formHtml = laytpl($('#dataFormTpl').html()).render(data || {});
layer.open({
title: data.id ? '编辑菜单' : (data.pid ? '添加子菜单' : '添加根菜单'),
content: formHtml,
anim: "slideLeft",
offset: "r",
btnAlign: "l",
area: ['50% ', '99%'],
shade: 0.1,
shadeClose: true,
btn: ['确定', '取消'],
success: function (layero, index) {
callback(layero, index);
form.render();
},
yes: function (index, layero) {
window.layui.form.on('submit(wxapp-form-submit)', function (elem) {
done(layero, index, elem);
layui.off('submit(wxapp-form)', 'from');
return false;
});
layero.contents().find("#wxapp-form-submit").trigger('click');
}
});
};
Class.prototype.dataCreate = function (data = {}) {
dataFromFun(data,
function (layero, index) { },
function (layero, index, elem) {
var field = elem.field;
http.post('/admin/Role/save', field)
.then((res) => {
if (res.code === 0) {
layer.close(index);
table.reload('dataTable', {}, true);
} else {
layer.msg(res.msg || '操作失败');
}
});
}
);
};
Class.prototype.dataEdit = function (data = {}) {
dataFromFun(data,
function (layero, index) { },
function (layero, index, elem) {
var field = elem.field;
http.put('/admin/Role/update', field)
.then((res) => {
if (res.code === 0) {
layer.close(index);
table.reload('dataTable', {}, true);
} else {
layer.msg(res.msg || '操作失败');
}
});
}
);
};
Class.prototype.dataRecybin = function () {
// …
};
Class.prototype.dataDelete = function (ids, force = 0) {
if (ids.length === 0) {
return layer.msg('请选择数据');
}
layer.prompt({
formType: 1
, title: '敏感操作,请验证口令'
}, function (value, index) {
layer.close(index);
layer.confirm('确定删除吗?', function (index) {
http.delete('/admin/Role/delete', { ids: ids.join(','), force: force })
.then((res) => {
if (res.code === 0) {
layer.close(index);
table.reload('dataTable', {}, true);
} else {
layer.msg(res.msg || '操作失败');
}
});
});
});
};
Class.prototype.dataRestore = function (ids) {
if (ids.length === 0) {
return layer.msg('请选择数据');
}
http.put('/admin/Role/restore', { ids: ids.join(',') })
.then((res) => {
if (res.code === 0) {
layer.msg('恢复成功');
table.reload('dataTable', {}, true);
} else {
layer.msg(res.msg || '恢复失败');
}
});
};
exports(component.CONST.MOD_NAME, component);
});
@@ -0,0 +1,11 @@
body {
}
/*!
* Cropper v0.7.6-beta
* https://github.com/fengyuanchen/cropper
*
* Copyright 2014 Fengyuan Chen
* Released under the MIT license
*/
.cropper-container{position:relative;overflow:hidden;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;-webkit-touch-callout:none}.cropper-container img{width:100%;height:100%;min-width:0!important;min-height:0!important;max-width:none!important;max-height:none!important}.cropper-modal,.cropper-canvas{position:absolute;top:0;right:0;bottom:0;left:0}.cropper-canvas{background-color:#fff;opacity:0;filter:alpha(opacity=0)}.cropper-modal{background-color:#000;opacity:.5;filter:alpha(opacity=50)}.cropper-dragger{position:absolute;top:10%;left:10%;width:80%;height:80%}.cropper-viewer{display:block;width:100%;height:100%;overflow:hidden;outline-width:1px;outline-style:solid;outline-color:#69f;outline-color:rgba(51,102,255,.75)}.cropper-dashed{position:absolute;display:block;border:0 dashed #fff;opacity:.5;filter:alpha(opacity=50)}.cropper-dashed.dashed-h{top:33.3%;left:0;width:100%;height:33.3%;border-top-width:1px;border-bottom-width:1px}.cropper-dashed.dashed-v{top:0;left:33.3%;width:33.3%;height:100%;border-right-width:1px;border-left-width:1px}.cropper-face,.cropper-line,.cropper-point{position:absolute;display:block;width:100%;height:100%;opacity:.1;filter:alpha(opacity=10)}.cropper-face{top:0;left:0;cursor:move;background-color:#fff}.cropper-line{background-color:#69f}.cropper-line.line-e{top:0;right:-3px;width:5px;cursor:e-resize}.cropper-line.line-n{top:-3px;left:0;height:5px;cursor:n-resize}.cropper-line.line-w{top:0;left:-3px;width:5px;cursor:w-resize}.cropper-line.line-s{bottom:-3px;left:0;height:5px;cursor:s-resize}.cropper-point{width:5px;height:5px;background-color:#69f;opacity:.75;filter:alpha(opacity=75)}.cropper-point.point-e{top:50%;right:-3px;margin-top:-3px;cursor:e-resize}.cropper-point.point-n{top:-3px;left:50%;margin-left:-3px;cursor:n-resize}.cropper-point.point-w{top:50%;left:-3px;margin-top:-3px;cursor:w-resize}.cropper-point.point-s{bottom:-3px;left:50%;margin-left:-3px;cursor:s-resize}.cropper-point.point-ne{top:-3px;right:-3px;cursor:ne-resize}.cropper-point.point-nw{top:-3px;left:-3px;cursor:nw-resize}.cropper-point.point-sw{bottom:-3px;left:-3px;cursor:sw-resize}.cropper-point.point-se{right:-3px;bottom:-3px;width:20px;height:20px;cursor:se-resize;opacity:1;filter:alpha(opacity=100)}.cropper-point.point-se:before{position:absolute;right:-50%;bottom:-50%;display:block;width:200%;height:200%;content:" ";background-color:#69f;opacity:0;filter:alpha(opacity=0)}@media (min-width:768px){.cropper-point.point-se{width:15px;height:15px}}@media (min-width:992px){.cropper-point.point-se{width:10px;height:10px}}@media (min-width:1200px){.cropper-point.point-se{width:5px;height:5px;opacity:.75;filter:alpha(opacity=75)}}.cropper-hidden{display:none!important}.cropper-invisible{position:fixed;top:0;left:0;z-index:-1;width:auto!important;max-width:none!important;height:auto!important;max-height:none!important;opacity:0;filter:alpha(opacity=0)}.cropper-move{cursor:move}.cropper-crop{cursor:crosshair}.cropper-disabled .cropper-canvas,.cropper-disabled .cropper-face,.cropper-disabled .cropper-line,.cropper-disabled .cropper-point{cursor:not-allowed}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,133 @@
/*!
* Cropper v3.0.0
*/
layui.define(['jquery','layer','cropper'],function (exports) {
var $ = layui.jquery
,layer = layui.layer;
var html = "<link rel=\"stylesheet\" href=\"/assets/ywxapp/modules/cropper/cropper.css\">\n" +
"<div class=\"layui-fluid showImgEdit\" style=\"display: none\">\n" +
" <div class=\"layui-form-item\">\n" +
" <div class=\"layui-input-inline layui-btn-container\" style=\"width: auto;\">\n" +
" <label for=\"cropper_avatarImgUpload\" class=\"layui-btn layui-btn-primary\">\n" +
" <i class=\"layui-icon\">&#xe67c;</i>选择图片\n" +
" </label>\n" +
" <input class=\"layui-upload-file\" id=\"cropper_avatarImgUpload\" type=\"file\" value=\"选择图片\" name=\"file\">\n" +
" </div>\n" +
" <div class=\"layui-form-mid layui-word-aux\">头像的尺寸限定150x150px,大小在50kb以内</div>\n" +
" </div>\n" +
" <div class=\"layui-row layui-col-space15\">\n" +
" <div class=\"layui-col-xs9\">\n" +
" <div class=\"readyimg\" style=\"height:450px;background-color: rgb(247, 247, 247);\">\n" +
" <img src=\"\" >\n" +
" </div>\n" +
" </div>\n" +
" <div class=\"layui-col-xs3\">\n" +
" <div class=\"img-preview\" style=\"width:200px;height:200px;overflow:hidden\">\n" +
" </div>\n" +
" </div>\n" +
" </div>\n" +
" <div class=\"layui-row layui-col-space15\">\n" +
" <div class=\"layui-col-xs9\">\n" +
" <div class=\"layui-row\">\n" +
" <div class=\"layui-col-xs6\">\n" +
" <button type=\"button\" class=\"layui-btn layui-icon layui-icon-left\" cropper-event=\"rotate\" data-option=\"-15\" title=\"Rotate -90 degrees\"> 向左旋转</button>\n" +
" <button type=\"button\" class=\"layui-btn layui-icon layui-icon-right\" cropper-event=\"rotate\" data-option=\"15\" title=\"Rotate 90 degrees\"> 向右旋转</button>\n" +
" </div>\n" +
" <div class=\"layui-col-xs5\" style=\"text-align: right;\">\n" +
" <button type=\"button\" class=\"layui-btn\" title=\"移动\"></button>\n" +
" <button type=\"button\" class=\"layui-btn\" title=\"放大图片\"></button>\n" +
" <button type=\"button\" class=\"layui-btn\" title=\"缩小图片\"></button>\n" +
" <button type=\"button\" class=\"layui-btn layui-icon layui-icon-refresh\" cropper-event=\"reset\" title=\"重置图片\"></button>\n" +
" </div>\n" +
" </div>\n" +
" </div>\n" +
" <div class=\"layui-col-xs3\">\n" +
" <button class=\"layui-btn layui-btn-fluid\" cropper-event=\"confirmSave\" type=\"button\"> 保存修改</button>\n" +
" </div>\n" +
" </div>\n" +
"\n" +
"</div>";
var obj = {
render: function(e){
$('body').append(html);
var self = this,
elem = e.elem,
saveW = e.saveW,
saveH = e.saveH,
mark = e.mark,
area = e.area,
url = e.url,
done = e.done;
var content = $('.showImgEdit')
,image = $(".showImgEdit .readyimg img")
,preview = '.showImgEdit .img-preview'
,file = $(".showImgEdit input[name='file']")
, options = {aspectRatio: mark,preview: preview,viewMode:1};
$(elem).on('click',function () {
layer.open({
type: 1
, content: content
, area: area
, success: function () {
image.cropper(options);
}
, cancel: function (index) {
layer.close(index);
image.cropper('destroy');
}
});
});
$(".layui-btn").on('click',function () {
var event = $(this).attr("cropper-event");
//监听确认保存图像
if(event === 'confirmSave'){
image.cropper("getCroppedCanvas",{
width: saveW,
height: saveH
}).toBlob(function(blob){
var formData=new FormData();
formData.append('file',blob,'head.jpg');
$.ajax({
method:"post",
url: url, //用于文件上传的服务器端请求地址
data: formData,
processData: false,
contentType: false,
success:function(result){
if(result.code == 0){
layer.msg(result.msg,{icon: 1});
layer.closeAll('page');
return done(result.data.src);
}else if(result.code == -1){
layer.alert(result.msg,{icon: 2});
}
}
});
});
//监听旋转
}else if(event === 'rotate'){
var option = $(this).attr('data-option');
image.cropper('rotate', option);
//重设图片
}else if(event === 'reset'){
image.cropper('reset');
}
//文件选择
file.change(function () {
var r= new FileReader();
var f=this.files[0];
r.readAsDataURL(f);
r.onload=function (e) {
image.cropper('destroy').attr('src', this.result).cropper(options);
};
});
});
}
};
exports('croppers', obj);
});
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,262 @@
/**
* Set echarts theme
*/
layui.define(function(exports) {
exports('echartsTheme', {
// 默认色板
color: [
'#16baaa','#1E9FFF','#16b777','#FFB980','#D87A80',
'#8d98b3','#e5cf0d','#97b552','#95706d','#dc69aa',
'#07a2a4','#9a7fd1','#588dd5','#f5994e','#c05050',
'#59678c','#c9ab00','#7eb00a','#6f5553','#c14089'
],
// 图表标题
title: {
textStyle: {
fontWeight: 'normal',
color: '#666' // 主标题文字颜色
}
},
// 值域
dataRange: {
itemWidth: 15,
color: ['#16baaa','#e0ffff']
},
// 工具箱
toolbox: {
color : ['#1e90ff', '#1e90ff', '#1e90ff', '#1e90ff'],
effectiveColor : '#ff4500'
},
// 提示框
tooltip: {
backgroundColor: 'rgba(50,50,50,0.5)', // 提示背景颜色,默认为透明度为0.7的黑色
axisPointer : { // 坐标轴指示器,坐标轴触发有效
type : 'line', // 默认为直线,可选为:'line' | 'shadow'
lineStyle : { // 直线指示器样式设置
color: '#16baaa'
},
crossStyle: {
color: '#008acd'
},
shadowStyle : { // 阴影指示器样式设置
color: 'rgba(200,200,200,0.2)'
}
}
},
// 区域缩放控制器
dataZoom: {
dataBackgroundColor: '#efefff', // 数据背景颜色
fillerColor: 'rgba(182,162,222,0.2)', // 填充颜色
handleColor: '#008acd' // 手柄颜色
},
// 网格
grid: {
borderColor: '#eee'
},
// 类目轴 - X轴
categoryAxis: {
axisLine: { // 坐标轴线
lineStyle: { // 属性lineStyle控制线条样式
color: '#16baaa'
}
},
axisTick: { //小标记
show: false
},
splitLine: { // 分隔线
lineStyle: { // 属性lineStyle(详见lineStyle)控制线条样式
color: ['#eee']
}
}
},
// 数值型坐标轴默认参数 - Y轴
valueAxis: {
axisLine: { // 坐标轴线
lineStyle: { // 属性lineStyle控制线条样式
color: '#16baaa'
}
},
splitArea : {
show : true,
areaStyle : {
color: ['rgba(250,250,250,0.1)','rgba(200,200,200,0.1)']
}
},
splitLine: { // 分隔线
lineStyle: { // 属性lineStyle(详见lineStyle)控制线条样式
color: ['#eee']
}
}
},
polar : {
axisLine: { // 坐标轴线
lineStyle: { // 属性lineStyle控制线条样式
color: '#ddd'
}
},
splitArea : {
show : true,
areaStyle : {
color: ['rgba(250,250,250,0.2)','rgba(200,200,200,0.2)']
}
},
splitLine : {
lineStyle : {
color : '#ddd'
}
}
},
timeline : {
lineStyle : {
color : '#16baaa'
},
controlStyle : {
normal : { color : '#16baaa'},
emphasis : { color : '#16baaa'}
},
symbol : 'emptyCircle',
symbolSize : 3
},
// 柱形图默认参数
bar: {
itemStyle: {
normal: {
barBorderRadius: 2
},
emphasis: {
barBorderRadius: 2
}
}
},
// 折线图默认参数
line: {
smooth : true,
symbol: 'emptyCircle', // 拐点图形类型
symbolSize: 3 // 拐点图形大小
},
// K线图默认参数
k: {
itemStyle: {
normal: {
color: '#d87a80', // 阳线填充颜色
color0: '#2ec7c9', // 阴线填充颜色
lineStyle: {
color: '#d87a80', // 阳线边框颜色
color0: '#2ec7c9' // 阴线边框颜色
}
}
}
},
// 散点图默认参数
scatter: {
symbol: 'circle', // 图形类型
symbolSize: 4 // 图形大小,半宽(半径)参数,当图形为方向或菱形则总宽度为symbolSize * 2
},
// 雷达图默认参数
radar : {
symbol: 'emptyCircle', // 图形类型
symbolSize:3
//symbol: null, // 拐点图形类型
//symbolRotate : null, // 图形旋转控制
},
map: {
itemStyle: {
normal: {
areaStyle: {
color: '#ddd'
},
label: {
textStyle: {
color: '#d87a80'
}
}
},
emphasis: { // 也是选中样式
areaStyle: {
color: '#fe994e'
}
}
}
},
force : {
itemStyle: {
normal: {
linkStyle : {
color : '#1e90ff'
}
}
}
},
chord : {
itemStyle : {
normal : {
borderWidth: 1,
borderColor: 'rgba(128, 128, 128, 0.5)',
chordStyle : {
lineStyle : {
color : 'rgba(128, 128, 128, 0.5)'
}
}
},
emphasis : {
borderWidth: 1,
borderColor: 'rgba(128, 128, 128, 0.5)',
chordStyle : {
lineStyle : {
color : 'rgba(128, 128, 128, 0.5)'
}
}
}
}
},
gauge : {
axisLine: { // 坐标轴线
lineStyle: { // 属性lineStyle控制线条样式
color: [[0.2, '#2ec7c9'],[0.8, '#5ab1ef'],[1, '#d87a80']],
width: 10
}
},
axisTick: { // 坐标轴小标记
splitNumber: 10, // 每份split细分多少段
length :15, // 属性length控制线长
lineStyle: { // 属性lineStyle控制线条样式
color: 'auto'
}
},
splitLine: { // 分隔线
length :22, // 属性length控制线长
lineStyle: { // 属性lineStyle(详见lineStyle)控制线条样式
color: 'auto'
}
},
pointer : {
width : 5
}
},
textStyle: {
fontFamily: '微软雅黑, Arial, Verdana, sans-serif'
}
});
});
+422
View File
@@ -0,0 +1,422 @@
// Layui 轻量级请求模块(基于 Fetch API + layui layer
// 支持文件上传
//
// 使用示例:
// layui.use(['http'], function(){ var req = layui.request; req.post('/api/upload', formData).then(...).catch(...) });
layui.define(["layer", "setter"], function (exports) {
"use strict";
var setter = layui.setter,
layer = layui.layer;
let isRefreshing = false;
let refreshQueue = [];
let loadIndex;
// 默认配置
const defaultConfig = {
baseURL: '',
timeout: 30000,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
withCredentials: true,
};
/**
* 刷新 Tokens
* @returns {Promise<string>}
*/
async function refreshToken() {
const { refresh_token } = setter.getTokens();
if (!refresh_token) {
throw new Error("No refresh token");
}
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), defaultConfig.timeout);
try {
var Accept = defaultConfig.headers['Accept'] || 'application/json';
const response = await fetch(defaultConfig.baseURL + "/api/refresh", {
method: "POST",
headers: {
'Content-Type': 'application/json',
'Accept': Accept,
...defaultConfig.headers,
},
credentials: defaultConfig.withCredentials ? 'include' : 'same-origin',
signal: controller.signal,
body: JSON.stringify({ refresh_token }),
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const res = await response.json();
if (res.code === 200) {
setter.saveTokens({
access_token: res.access || res.access_token,
refresh_token: res.refresh || res.refresh_token || refresh_token,
});
return res.access || res.access_token;
} else {
throw new Error(res.msg || "Refresh failed");
}
} catch (error) {
if (error.name === 'AbortError') {
throw new Error('Request timeout');
}
throw error;
}
}
/**
* 重试请求
* @param {Object} options - fetch 配置选项
* @returns {Promise}
*/
async function retryRequest(options) {
const { access_token } = setter.getTokens();
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), options.timeout || defaultConfig.timeout);
try {
// 构建请求头
const headers = { ...defaultConfig.headers, ...options.headers };
const { access_token: newToken } = setter.getTokens();
if (newToken) {
headers['Authorization'] = `Bearer ${newToken}`;
}
// 如果是 FormData,删除 Content-Type 让浏览器自动设置
if (options.data instanceof FormData) {
delete headers['Content-Type'];
}
const fetchOptions = {
method: options.method || 'GET',
headers,
credentials: defaultConfig.withCredentials ? 'include' : 'same-origin',
signal: controller.signal,
};
// 添加 body(如果不是 GET/HEAD 请求)
if (options.method !== 'GET' && options.method !== 'HEAD') {
fetchOptions.body = options.data instanceof FormData
? options.data
: JSON.stringify(options.data);
}
const response = await fetch(options.url, fetchOptions);
clearTimeout(timeoutId);
// 处理响应头中的 token
const authHeader = response.headers.get('Authorization');
if (authHeader && authHeader.startsWith('Bearer ')) {
const newToken = authHeader.substring(7);
setter.saveTokens({ access_token: newToken });
}
let data;
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
data = await response.json();
} else if (contentType && contentType.includes('text/')) {
data = await response.text();
} else {
data = await response.blob();
}
if (!response.ok) {
const error = new Error(response.statusText || 'Request failed');
error.status = response.status;
error.data = data;
throw error;
}
return data;
} catch (error) {
if (error.name === 'AbortError') {
error.message = 'Request timeout';
}
throw error;
}
}
/**
* Fetch 请求核心方法
* @param {Object} options - fetch 配置选项
* @returns {Promise}
*/
async function _Request(options) {
const { access_token } = setter.getTokens();
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), options.timeout || defaultConfig.timeout);
try {
// 构建请求头
const headers = { ...defaultConfig.headers, ...options.headers };
if (access_token) {
headers['Authorization'] = `Bearer ${access_token}`;
}
// 如果是 FormData,删除 Content-Type 让浏览器自动设置
if (options.data instanceof FormData) {
delete headers['Content-Type'];
}
const fetchOptions = {
method: options.method || 'GET',
headers,
credentials: defaultConfig.withCredentials ? 'include' : 'same-origin',
signal: controller.signal,
};
// 添加 body(如果不是 GET/HEAD 请求)
if (options.method !== 'GET' && options.method !== 'HEAD') {
fetchOptions.body = options.data instanceof FormData
? options.data
: JSON.stringify(options.data);
}
const response = await fetch(options.url, fetchOptions);
clearTimeout(timeoutId);
// 处理响应头中的 token
const authHeader = response.headers.get('Authorization');
if (authHeader && authHeader.startsWith('Bearer ')) {
const newToken = authHeader.substring(7);
setter.saveTokens({ access_token: newToken });
}
let data;
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
data = await response.json();
} else if (contentType && contentType.includes('text/')) {
data = await response.text();
} else {
data = await response.blob();
}
// 检查响应数据中的 token
if (data && (data.access_token || data.access)) {
setter.saveTokens({
access_token: data.access_token || data.access,
refresh_token: data.refresh_token || data.refresh,
});
}
if (!response.ok) {
if (response.status === 401 && !options._retry) {
// 401 错误,尝试刷新 token
return new Promise((resolve, reject) => {
refreshQueue.push({ resolve, reject, options });
if (!isRefreshing) {
isRefreshing = true;
refreshToken()
.then(() => {
const queue = [...refreshQueue];
refreshQueue = [];
queue.forEach(({ resolve, reject, options }) => {
options._retry = true;
retryRequest(options)
.then(resolve)
.catch(reject);
});
isRefreshing = false;
})
.catch((refreshError) => {
refreshQueue.forEach(({ reject }) => reject(refreshError));
refreshQueue = [];
isRefreshing = false;
// 清除本地存储并跳转登录
localStorage.clear();
layer.msg("登录已过期,请重新登录", { icon: 2, time: 2000 });
setTimeout(() => {
window.top.location.href = "./login.html";
// if (window === window.parent) {
// window.location.href = "./login.html";
// } else {
// window.parent.location.href = "./login.html";
// }
}, 2000);
reject(refreshError);
});
}
});
} else {
const error = new Error(response.statusText || 'Request failed');
error.status = response.status;
error.data = data;
throw error;
}
}
return data;
} catch (error) {
if (error.name === 'AbortError') {
error.message = 'Request timeout';
}
throw error;
}
};
async function setAccept(accept = "json") {
switch (accept) {
case 'xml':
defaultConfig.headers['Accept'] = `application/xml`;
break;
case 'html':
defaultConfig.headers['Accept'] = `text/html`;
break;
default:
defaultConfig.headers['Accept'] = `application/json`;
break;
}
}
// 暴露接口
var http = {
/**
* 配置请求默认参数
* @param {Object} cfg - 配置对象
* @returns {Object} 当前配置
*/
config: function (cfg) {
Object.assign(defaultConfig, cfg || {});
return defaultConfig;
},
/**
* 通用请求方法
* @param {Object} options - fetch 配置选项
* @returns {Promise}
*/
request: _Request,
/**
* GET 请求
* @param {string} url - 请求地址
* @param {Object} params - URL 参数
* @param {Object} options - fetch 配置选项
* @returns {Promise}
*/
get: function (url, params, options = {}) {
// 处理 URL 参数
if (params && Object.keys(params).length > 0) {
const queryString = new URLSearchParams(params).toString();
url += (url.includes('?') ? '&' : '?') + queryString;
}
options.method = "GET";
options.url = defaultConfig.baseURL + url;
return _Request(options);
},
/**
* POST 请求
* @param {string} url - 请求地址
* @param {Object|FormData} data - 请求数据或 FormData 对象
* @param {Object} options - fetch 配置选项
* @returns {Promise}
*/
post: function (url, data, options = {}) {
options.method = "POST";
options.url = defaultConfig.baseURL + url;
options.data = data;
return _Request(options);
},
/**
* PUT 请求
* @param {string} url - 请求地址
* @param {Object|FormData} data - 请求数据或 FormData 对象
* @param {Object} options - fetch 配置选项
* @returns {Promise}
*/
put: function (url, data, options = {}) {
options.method = "PUT";
options.url = defaultConfig.baseURL + url;
options.data = data;
return _Request(options);
},
/**
* DELETE 请求
* @param {string} url - 请求地址
* @param {Object} params - URL 参数
* @param {Object} options - fetch 配置选项
* @returns {Promise}
*/
delete: function (url, params, options = {}) {
// 处理 URL 参数
if (params && Object.keys(params).length > 0) {
const queryString = new URLSearchParams(params).toString();
url += (url.includes('?') ? '&' : '?') + queryString;
}
options.method = "DELETE";
options.url = defaultConfig.baseURL + url;
return _Request(options);
},
/**
* 上传文件(简化版)
* @param {string} url - 上传地址
* @param {File|File[]} files - 文件对象或文件数组
* @param {Object} params - 其他参数
* @param {Object} options - fetch 配置选项
* @returns {Promise}
*/
upload: function (url, files, params = {}, options = {}) {
const formData = new FormData();
// 添加文件
if (Array.isArray(files)) {
files.forEach((file, index) => {
formData.append(`file${index}`, file);
});
} else if (files) {
formData.append('file', files);
}
// 添加其他参数
Object.keys(params).forEach(key => {
formData.append(key, params[key]);
});
options.method = "POST";
options.url = defaultConfig.baseURL + url;
options.data = formData;
return _Request(options);
},
/**
* 刷新 token
* @returns {Promise<string>}
*/
refreshToken: refreshToken,
accept: setAccept,
};
exports("http", http);
});
+400
View File
@@ -0,0 +1,400 @@
/**
* Layui图标选择器
* @author wujiawei0926@yeah.net
* @version 1.1
*/
layui.define(['laypage', 'form'], function (exports) {
"use strict";
var IconPicker =function () {
this.v = '1.1';
}, _MOD = 'iconPicker',
_this = this,
$ = layui.jquery,
laypage = layui.laypage,
form = layui.form,
BODY = 'body',
TIPS = '请选择图标';
/**
* 渲染组件
*/
IconPicker.prototype.render = function(options){
var opts = options,
// DOM选择器
elem = opts.elem,
// 数据类型:fontClass/unicode
type = opts.type == null ? 'fontClass' : opts.type,
// 是否分页:true/false
page = opts.page == null ? true : opts.page,
// 每页显示数量
limit = opts.limit == null ? 12 : opts.limit,
// 是否开启搜索:true/false
search = opts.search == null ? true : opts.search,
// 每个图标格子的宽度:'43px'或'20%'
cellWidth = opts.cellWidth,
// 点击回调
click = opts.click,
// 渲染成功后的回调
success = opts.success,
// json数据
data = {},
// 唯一标识
tmp = new Date().getTime(),
// 是否使用的class数据
isFontClass = opts.type === 'fontClass',
// 初始化时input的值
ORIGINAL_ELEM_VALUE = $(elem).val(),
TITLE = 'layui-select-title',
TITLE_ID = 'layui-select-title-' + tmp,
ICON_BODY = 'layui-iconpicker-' + tmp,
PICKER_BODY = 'layui-iconpicker-body-' + tmp,
PAGE_ID = 'layui-iconpicker-page-' + tmp,
LIST_BOX = 'layui-iconpicker-list-box',
selected = 'layui-form-selected',
unselect = 'layui-unselect';
var a = {
init: function () {
data = common.getData[type]();
a.hideElem().createSelect().createBody().toggleSelect();
a.preventEvent().inputListen();
common.loadCss();
if (success) {
success(this.successHandle());
}
return a;
},
successHandle: function(){
var d = {
options: opts,
data: data,
id: tmp,
elem: $('#' + ICON_BODY)
};
return d;
},
/**
* 隐藏elem
*/
hideElem: function () {
$(elem).hide();
return a;
},
/**
* 绘制select下拉选择框
*/
createSelect: function () {
var oriIcon = '<i class="layui-icon">';
// 默认图标
if(ORIGINAL_ELEM_VALUE === '') {
if(isFontClass) {
ORIGINAL_ELEM_VALUE = 'layui-icon-circle-dot';
} else {
ORIGINAL_ELEM_VALUE = '&#xe617;';
}
}
if (isFontClass) {
oriIcon = '<i class="layui-icon '+ ORIGINAL_ELEM_VALUE +'">';
} else {
oriIcon += ORIGINAL_ELEM_VALUE;
}
oriIcon += '</i>';
var selectHtml = '<div class="layui-iconpicker layui-unselect layui-form-select" id="'+ ICON_BODY +'">' +
'<div class="'+ TITLE +'" id="'+ TITLE_ID +'">' +
'<div class="layui-iconpicker-item">'+
'<span class="layui-iconpicker-icon layui-unselect">' +
oriIcon +
'</span>'+
'<i class="layui-edge"></i>' +
'</div>'+
'</div>' +
'<div class="layui-anim layui-anim-upbit" style="">' +
'123' +
'</div>';
$(elem).after(selectHtml);
return a;
},
/**
* 展开/折叠下拉框
*/
toggleSelect: function () {
var item = '#' + TITLE_ID + ' .layui-iconpicker-item,#' + TITLE_ID + ' .layui-iconpicker-item .layui-edge';
a.event('click', item, function (e) {
var $icon = $('#' + ICON_BODY);
if ($icon.hasClass(selected)) {
$icon.removeClass(selected).addClass(unselect);
} else {
// 隐藏其他picker
$('.layui-form-select').removeClass(selected);
// 显示当前picker
$icon.addClass(selected).removeClass(unselect);
}
e.stopPropagation();
});
return a;
},
/**
* 绘制主体部分
*/
createBody: function () {
// 获取数据
var searchHtml = '';
if (search) {
searchHtml = '<div class="layui-iconpicker-search">' +
'<input class="layui-input">' +
'<i class="layui-icon">&#xe615;</i>' +
'</div>';
}
// 组合dom
var bodyHtml = '<div class="layui-iconpicker-body" id="'+ PICKER_BODY +'">' +
searchHtml +
'<div class="'+ LIST_BOX +'"></div> '+
'</div>';
$('#' + ICON_BODY).find('.layui-anim').eq(0).html(bodyHtml);
a.search().createList().check().page();
return a;
},
/**
* 绘制图标列表
* @param text 模糊查询关键字
* @returns {string}
*/
createList: function (text) {
var d = data,
l = d.length,
pageHtml = '',
listHtml = $('<div class="layui-iconpicker-list">')//'<div class="layui-iconpicker-list">';
// 计算分页数据
var _limit = limit, // 每页显示数量
_pages = l % _limit === 0 ? l / _limit : parseInt(l / _limit + 1), // 总计多少页
_id = PAGE_ID;
// 图标列表
var icons = [];
for (var i = 0; i < l; i++) {
var obj = d[i];
// 判断是否模糊查询
if (text && obj.indexOf(text) === -1) {
continue;
}
// 是否自定义格子宽度
var style = '';
if (cellWidth !== null) {
style += ' style="width:' + cellWidth + '"';
}
// 每个图标dom
var icon = '<div class="layui-iconpicker-icon-item" title="'+ obj +'" '+ style +'>';
if (isFontClass){
icon += '<i class="layui-icon '+ obj +'"></i>';
} else {
icon += '<i class="layui-icon">'+ obj.replace('amp;', '') +'</i>';
}
icon += '</div>';
icons.push(icon);
}
// 查询出图标后再分页
l = icons.length;
_pages = l % _limit === 0 ? l / _limit : parseInt(l / _limit + 1);
for (var i = 0; i < _pages; i++) {
// 按limit分块
var lm = $('<div class="layui-iconpicker-icon-limit" id="layui-iconpicker-icon-limit-' + tmp + (i+1) +'">');
for (var j = i * _limit; j < (i+1) * _limit && j < l; j++) {
lm.append(icons[j]);
}
listHtml.append(lm);
}
// 无数据
if (l === 0) {
listHtml.append('<p class="layui-iconpicker-tips">无数据</p>');
}
// 判断是否分页
if (page){
$('#' + PICKER_BODY).addClass('layui-iconpicker-body-page');
pageHtml = '<div class="layui-iconpicker-page" id="'+ PAGE_ID +'">' +
'<div class="layui-iconpicker-page-count">' +
'<span id="'+ PAGE_ID +'-current">1</span>/' +
'<span id="'+ PAGE_ID +'-pages">'+ _pages +'</span>' +
' (<span id="'+ PAGE_ID +'-length">'+ l +'</span>)' +
'</div>' +
'<div class="layui-iconpicker-page-operate">' +
'<i class="layui-icon" id="'+ PAGE_ID +'-prev" data-index="0" prev>&#xe603;</i> ' +
'<i class="layui-icon" id="'+ PAGE_ID +'-next" data-index="2" next>&#xe602;</i> ' +
'</div>' +
'</div>';
}
$('#' + ICON_BODY).find('.layui-anim').find('.' + LIST_BOX).html('').append(listHtml).append(pageHtml);
return a;
},
// 阻止Layui的一些默认事件
preventEvent: function() {
var item = '#' + ICON_BODY + ' .layui-anim';
a.event('click', item, function (e) {
e.stopPropagation();
});
return a;
},
// 分页
page: function () {
var icon = '#' + PAGE_ID + ' .layui-iconpicker-page-operate .layui-icon';
$(icon).unbind('click');
a.event('click', icon, function (e) {
var elem = e.currentTarget,
total = parseInt($('#' +PAGE_ID + '-pages').html()),
isPrev = $(elem).attr('prev') !== undefined,
// 按钮上标的页码
index = parseInt($(elem).attr('data-index')),
$cur = $('#' +PAGE_ID + '-current'),
// 点击时正在显示的页码
current = parseInt($cur.html());
// 分页数据
if (isPrev && current > 1) {
current=current-1;
$(icon + '[prev]').attr('data-index', current);
} else if (!isPrev && current < total){
current=current+1;
$(icon + '[next]').attr('data-index', current);
}
$cur.html(current);
// 图标数据
$('#'+ ICON_BODY + ' .layui-iconpicker-icon-limit').hide();
$('#layui-iconpicker-icon-limit-' + tmp + current).show();
e.stopPropagation();
});
return a;
},
/**
* 搜索
*/
search: function () {
var item = '#' + PICKER_BODY + ' .layui-iconpicker-search .layui-input';
a.event('input propertychange', item, function (e) {
var elem = e.target,
t = $(elem).val();
a.createList(t);
});
return a;
},
/**
* 点击选中图标
*/
check: function () {
var item = '#' + PICKER_BODY + ' .layui-iconpicker-icon-item';
a.event('click', item, function (e) {
var el = $(e.currentTarget).find('.layui-icon'),
icon = '';
if (isFontClass) {
var clsArr = el.attr('class').split(/[\s\n]/),
cls = clsArr[1],
icon = cls;
$('#' + TITLE_ID).find('.layui-iconpicker-item .layui-icon').html('').attr('class', clsArr.join(' '));
} else {
var cls = el.html(),
icon = cls;
$('#' + TITLE_ID).find('.layui-iconpicker-item .layui-icon').html(icon);
}
$('#' + ICON_BODY).removeClass(selected).addClass(unselect);
$(elem).val(icon).attr('value', icon);
// 回调
if (click) {
click({
icon: icon
});
}
});
return a;
},
// 监听原始input数值改变
inputListen: function(){
var el = $(elem);
a.event('change', elem, function(){
var value = el.val();
})
// el.change(function(){
// });
return a;
},
event: function (evt, el, fn) {
$(BODY).on(evt, el, fn);
}
};
var common = {
/**
* 加载样式表
*/
loadCss: function () {
var css = '.layui-iconpicker {max-width: 280px;}.layui-iconpicker .layui-anim{display:none;position:absolute;left:0;top:42px;padding:5px 0;z-index:899;min-width:100%;border:1px solid #d2d2d2;max-height:300px;overflow-y:auto;background-color:#fff;border-radius:2px;box-shadow:0 2px 4px rgba(0,0,0,.12);box-sizing:border-box;}.layui-iconpicker-item{border:1px solid #e6e6e6;width:90px;height:38px;border-radius:4px;cursor:pointer;position:relative;}.layui-iconpicker-icon{border-right:1px solid #e6e6e6;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:block;width:60px;height:100%;float:left;text-align:center;background:#fff;transition:all .3s;}.layui-iconpicker-icon i{line-height:38px;font-size:18px;}.layui-iconpicker-item > .layui-edge{left:70px;}.layui-iconpicker-item:hover{border-color:#D2D2D2!important;}.layui-iconpicker-item:hover .layui-iconpicker-icon{border-color:#D2D2D2!important;}.layui-iconpicker.layui-form-selected .layui-anim{display:block;}.layui-iconpicker-body{padding:6px;}.layui-iconpicker .layui-iconpicker-list{background-color:#fff;border:1px solid #ccc;border-radius:4px;}.layui-iconpicker .layui-iconpicker-icon-item{display:inline-block;width:21.1%;line-height:36px;text-align:center;cursor:pointer;vertical-align:top;height:36px;margin:4px;border:1px solid #ddd;border-radius:2px;transition:300ms;}.layui-iconpicker .layui-iconpicker-icon-item i.layui-icon{font-size:17px;}.layui-iconpicker .layui-iconpicker-icon-item:hover{background-color:#eee;border-color:#ccc;-webkit-box-shadow:0 0 2px #aaa,0 0 2px #fff inset;-moz-box-shadow:0 0 2px #aaa,0 0 2px #fff inset;box-shadow:0 0 2px #aaa,0 0 2px #fff inset;text-shadow:0 0 1px #fff;}.layui-iconpicker-search{position:relative;margin:0 0 6px 0;border:1px solid #e6e6e6;border-radius:2px;transition:300ms;}.layui-iconpicker-search:hover{border-color:#D2D2D2!important;}.layui-iconpicker-search .layui-input{cursor:text;display:inline-block;width:86%;border:none;padding-right:0;margin-top:1px;}.layui-iconpicker-search .layui-icon{position:absolute;top:11px;right:4%;}.layui-iconpicker-tips{text-align:center;padding:8px 0;cursor:not-allowed;}.layui-iconpicker-page{margin-top:6px;margin-bottom:-6px;font-size:12px;padding:0 2px;}.layui-iconpicker-page-count{display:inline-block;}.layui-iconpicker-page-operate{display:inline-block;float:right;cursor:default;}.layui-iconpicker-page-operate .layui-icon{font-size:12px;cursor:pointer;}.layui-iconpicker-body-page .layui-iconpicker-icon-limit{display:none;}.layui-iconpicker-body-page .layui-iconpicker-icon-limit:first-child{display:block;}';
var $style = $('head').find('style[iconpicker]');
if ($style.length === 0) {
$('head').append('<style rel="stylesheet" iconpicker>'+css+'</style>');
}
},
/**
* 获取数据
*/
getData: {
fontClass: function () {
var arr = ["layui-icon-rate-half","layui-icon-rate","layui-icon-rate-solid","layui-icon-cellphone","layui-icon-vercode","layui-icon-login-wechat","layui-icon-login-qq","layui-icon-login-weibo","layui-icon-password","layui-icon-username","layui-icon-refresh-3","layui-icon-auz","layui-icon-spread-left","layui-icon-shrink-right","layui-icon-snowflake","layui-icon-tips","layui-icon-note","layui-icon-home","layui-icon-senior","layui-icon-refresh","layui-icon-refresh-1","layui-icon-flag","layui-icon-theme","layui-icon-notice","layui-icon-website","layui-icon-console","layui-icon-face-surprised","layui-icon-set","layui-icon-template-1","layui-icon-app","layui-icon-template","layui-icon-praise","layui-icon-tread","layui-icon-male","layui-icon-female","layui-icon-camera","layui-icon-camera-fill","layui-icon-more","layui-icon-more-vertical","layui-icon-rmb","layui-icon-dollar","layui-icon-diamond","layui-icon-fire","layui-icon-return","layui-icon-location","layui-icon-read","layui-icon-survey","layui-icon-face-smile","layui-icon-face-cry","layui-icon-cart-simple","layui-icon-cart","layui-icon-next","layui-icon-prev","layui-icon-upload-drag","layui-icon-upload","layui-icon-download-circle","layui-icon-component","layui-icon-file-b","layui-icon-user","layui-icon-find-fill","layui-icon-loading","layui-icon-loading-1","layui-icon-add-1","layui-icon-play","layui-icon-pause","layui-icon-headset","layui-icon-video","layui-icon-voice","layui-icon-speaker","layui-icon-fonts-del","layui-icon-fonts-code","layui-icon-fonts-html","layui-icon-fonts-strong","layui-icon-unlink","layui-icon-picture","layui-icon-link","layui-icon-face-smile-b","layui-icon-align-left","layui-icon-align-right","layui-icon-align-center","layui-icon-fonts-u","layui-icon-fonts-i","layui-icon-tabs","layui-icon-radio","layui-icon-circle","layui-icon-edit","layui-icon-share","layui-icon-delete","layui-icon-form","layui-icon-cellphone-fine","layui-icon-dialogue","layui-icon-fonts-clear","layui-icon-layer","layui-icon-date","layui-icon-water","layui-icon-code-circle","layui-icon-carousel","layui-icon-prev-circle","layui-icon-layouts","layui-icon-util","layui-icon-templeate-1","layui-icon-upload-circle","layui-icon-tree","layui-icon-table","layui-icon-chart","layui-icon-chart-screen","layui-icon-engine","layui-icon-triangle-d","layui-icon-triangle-r","layui-icon-file","layui-icon-set-sm","layui-icon-add-circle","layui-icon-404","layui-icon-about","layui-icon-up","layui-icon-down","layui-icon-left","layui-icon-right","layui-icon-circle-dot","layui-icon-search","layui-icon-set-fill","layui-icon-group","layui-icon-friends","layui-icon-reply-fill","layui-icon-menu-fill","layui-icon-log","layui-icon-picture-fine","layui-icon-face-smile-fine","layui-icon-list","layui-icon-release","layui-icon-ok","layui-icon-help","layui-icon-chat","layui-icon-top","layui-icon-star","layui-icon-star-fill","layui-icon-close-fill","layui-icon-close","layui-icon-ok-circle","layui-icon-add-circle-fine"];
return arr;
},
unicode: function () {
return ["&amp;#xe6c9;","&amp;#xe67b;","&amp;#xe67a;","&amp;#xe678;","&amp;#xe679;","&amp;#xe677;","&amp;#xe676;","&amp;#xe675;","&amp;#xe673;","&amp;#xe66f;","&amp;#xe9aa;","&amp;#xe672;","&amp;#xe66b;","&amp;#xe668;","&amp;#xe6b1;","&amp;#xe702;","&amp;#xe66e;","&amp;#xe68e;","&amp;#xe674;","&amp;#xe669;","&amp;#xe666;","&amp;#xe66c;","&amp;#xe66a;","&amp;#xe667;","&amp;#xe7ae;","&amp;#xe665;","&amp;#xe664;","&amp;#xe716;","&amp;#xe656;","&amp;#xe653;","&amp;#xe663;","&amp;#xe6c6;","&amp;#xe6c5;","&amp;#xe662;","&amp;#xe661;","&amp;#xe660;","&amp;#xe65d;","&amp;#xe65f;","&amp;#xe671;","&amp;#xe65e;","&amp;#xe659;","&amp;#xe735;","&amp;#xe756;","&amp;#xe65c;","&amp;#xe715;","&amp;#xe705;","&amp;#xe6b2;","&amp;#xe6af;","&amp;#xe69c;","&amp;#xe698;","&amp;#xe657;","&amp;#xe65b;","&amp;#xe65a;","&amp;#xe681;","&amp;#xe67c;","&amp;#xe601;","&amp;#xe857;","&amp;#xe655;","&amp;#xe770;","&amp;#xe670;","&amp;#xe63d;","&amp;#xe63e;","&amp;#xe654;","&amp;#xe652;","&amp;#xe651;","&amp;#xe6fc;","&amp;#xe6ed;","&amp;#xe688;","&amp;#xe645;","&amp;#xe64f;","&amp;#xe64e;","&amp;#xe64b;","&amp;#xe62b;","&amp;#xe64d;","&amp;#xe64a;","&amp;#xe64c;","&amp;#xe650;","&amp;#xe649;","&amp;#xe648;","&amp;#xe647;","&amp;#xe646;","&amp;#xe644;","&amp;#xe62a;","&amp;#xe643;","&amp;#xe63f;","&amp;#xe642;","&amp;#xe641;","&amp;#xe640;","&amp;#xe63c;","&amp;#xe63b;","&amp;#xe63a;","&amp;#xe639;","&amp;#xe638;","&amp;#xe637;","&amp;#xe636;","&amp;#xe635;","&amp;#xe634;","&amp;#xe633;","&amp;#xe632;","&amp;#xe631;","&amp;#xe630;","&amp;#xe62f;","&amp;#xe62e;","&amp;#xe62d;","&amp;#xe62c;","&amp;#xe629;","&amp;#xe628;","&amp;#xe625;","&amp;#xe623;","&amp;#xe621;","&amp;#xe620;","&amp;#xe61f;","&amp;#xe61c;","&amp;#xe60b;","&amp;#xe619;","&amp;#xe61a;","&amp;#xe603;","&amp;#xe602;","&amp;#xe617;","&amp;#xe615;","&amp;#xe614;","&amp;#xe613;","&amp;#xe612;","&amp;#xe611;","&amp;#xe60f;","&amp;#xe60e;","&amp;#xe60d;","&amp;#xe60c;","&amp;#xe60a;","&amp;#xe609;","&amp;#xe605;","&amp;#xe607;","&amp;#xe606;","&amp;#xe604;","&amp;#xe600;","&amp;#xe658;","&amp;#x1007;","&amp;#x1006;","&amp;#x1005;","&amp;#xe608;"];
}
}
};
a.init();
return new IconPicker();
};
/**
* 选中图标
* @param filter lay-filter
* @param iconName 图标名称,自动识别fontClass/unicode
*/
IconPicker.prototype.checkIcon = function (filter, iconName){
var el = $('*[lay-filter='+ filter +']'),
p = el.next().find('.layui-iconpicker-item .layui-icon'),
c = iconName;
if (c.indexOf('#xe') > 0){
p.html(c);
} else {
p.html('').attr('class', 'layui-icon ' + c);
}
el.attr('value', c).val(c);
};
var iconPicker = new IconPicker();
exports(_MOD, iconPicker);
});
+130
View File
@@ -0,0 +1,130 @@
/**
* forum demo
*/
layui.define(['table', 'form'], function(exports){
var $ = layui.$
,table = layui.table
,form = layui.form;
//帖子管理
table.render({
elem: '#LAY-app-forum-list'
,url: layui.setter.paths.base + 'json/forum/list.js' //模拟接口
,cols: [[
{type: 'checkbox', fixed: 'left'}
,{field: 'id', width: 100, title: 'ID', sort: true}
,{field: 'poster', title: '发帖人'}
,{field: 'avatar', title: '头像', width: 100, templet: '#imgTpl'}
,{field: 'content', title: '发帖内容'}
,{field: 'posttime', title: '发帖时间', sort: true}
,{field: 'top', title: '置顶', templet: '#buttonTpl', minWidth: 80, align: 'center'}
,{title: '操作', width: 150, align: 'center', fixed: 'right', toolbar: '#table-forum-list'}
]]
,page: true
,limit: 10
,limits: [10, 15, 20, 25, 30]
,text: '加载出现异常'
});
//事件-工具条
table.on('tool(LAY-app-forum-list)', function(obj){
var data = obj.data;
if(obj.event === 'del'){
layer.confirm('确定删除此条帖子?', function(index){
obj.del();
layer.close(index);
});
} else if(obj.event === 'edit'){
var tr = $(obj.tr);
layer.open({
type: 2
,title: '编辑帖子'
,content: '../../../views/app/forum/listform.html'
,area: ['550px', '400px']
,btn: ['确定', '取消']
,resize: false
,yes: function(index, layero){
var iframeWindow = window['layui-layer-iframe'+ index]
,submitID = 'LAY-app-forum-submit'
,submit = layero.find('iframe').contents().find('#'+ submitID);
//事件-提交
iframeWindow.layui.form.on('submit('+ submitID +')', function(data){
var field = data.field; //获取提交的字段
//提交 Ajax 成功后,静态更新表格中的数据
//$.ajax({});
table.reload('LAY-app-forum-list'); //数据刷新
layer.close(index); //关闭弹层
});
submit.trigger('click');
}
,success: function(layero, index){
}
});
}
});
//回帖管理
table.render({
elem: '#LAY-app-forumreply-list'
,url: layui.setter.paths.base + 'json/forum/replys.js' //模拟接口
,cols: [[
{type: 'checkbox', fixed: 'left'}
,{field: 'id', width: 100, title: 'ID', sort: true}
,{field: 'replyer', title: '回帖人'}
,{field: 'cardid', title: '回帖ID', sort: true}
,{field: 'avatar', title: '头像', width: 100, templet: '#imgTpl'}
,{field: 'content', title: '回帖内容', width: 200}
,{field: 'replytime', title: '回帖时间', sort: true}
,{title: '操作', width: 150, align: 'center', fixed: 'right', toolbar: '#table-forum-replys'}
]]
,page: true
,limit: 10
,limits: [10, 15, 20, 25, 30]
,text: '对不起,加载出现异常!'
});
//事件-工具条
table.on('tool(LAY-app-forumreply-list)', function(obj){
var data = obj.data;
if(obj.event === 'del'){
layer.confirm('确定删除此条评论?', function(index){
obj.del();
layer.close(index);
});
} else if(obj.event === 'edit'){
var tr = $(obj.tr);
layer.open({
type: 2
,title: '编辑评论'
,content: '../../../views/app/forum/replysform.html'
,area: ['550px', '350px']
,btn: ['确定', '取消']
,resize: false
,yes: function(index, layero){
//获取iframe元素的值
var othis = layero.find('iframe').contents().find("#layuiadmin-form-replys");
var content = othis.find('textarea[name="content"]').val();
//数据更新
obj.update({
content: content
});
layer.close(index);
}
,success: function(layero, index){
}
});
}
});
exports('forum', {})
});
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

File diff suppressed because it is too large Load Diff
+161
View File
@@ -0,0 +1,161 @@
/**
* YwxApp 路由模块(ThinkPHP 风格 URL 生成)
* ------------------------------------------------------------------
* 通过后端模板注入的全局 layui.app 获取部署配置(root / module /
* routeBase / controller / action / assetUrl),对外提供 path / asset /
* api / route 四个方法,供任意 layui 模块以模块化方式调用:
*
* layui.use(['route'], function () {
* var route = layui.route;
* route.route('edit', 'user', null, { id: 1 }); // -> /admin/user/edit?id=1
* route.asset('ywxapp/css/style.css');
* });
*
* 同时本模块会把方法挂回 layui.app,保持对旧代码的向后兼容
* (旧的 layui.app.route(...) 仍可正常工作)。
*/
layui.define(function (exports) {
// 基础配置来自后端模板注入的全局 layui.app
// 注意:必须在每次调用时实时读取 layui.app,不能在此处缓存。
// 因为本模块(route.js)作为 layui 模块被异步加载,若此时 layui.app
// 尚未被 layout.html 的同步脚本赋值,缓存到的就是空对象,导致后续
// route() 生成的 URL 缺少应用前缀(如 /index/menu 而非 /admin/index/menu)。
var getConf = function () {
return (typeof layui !== 'undefined' && layui.app) || window.layui.app || {};
};
var trim = function (s) {
return String(s).replace(/(^\/+|\/+$)/g, '');
};
/**
* 页面路径拼接(ThinkPHP 风格:/模块/控制器/方法)
* 控制器、模块参数均可选,模块缺省使用当前模块(即后端注入的虚拟名):
* route.path('index') -> /<当前模块>/index
* route.path('index', 'user') -> /<当前模块>/user/index
* route.path('index', 'user', 'admin') -> /admin/user/index
*/
var path = function (method, controller, module) {
var conf = getConf();
var cfgRoot = conf.root || '/';
var cfgModule = conf.module || '';
var r = String(cfgRoot || '/').replace(/\/+$/, '');
if (module === undefined || module === null || module === '') {
module = cfgModule;
}
var parts = [];
if (module) parts.push(trim(module));
if (controller) parts.push(trim(controller));
if (method) parts.push(trim(method));
if (parts.length === 0) return r + '/';
return r + '/' + parts.join('/');
};
/**
* 静态资源路径拼接
* route.asset('ywxapp/css/style.css') -> /assets/ywxapp/css/style.css
*/
var asset = function (p) {
var conf = getConf();
var base = String(conf.assetUrl || '/assets').replace(/\/+$/, '');
p = String(p || '').replace(/^\/+/, '');
return base + '/' + p;
};
/**
* API 路径拼接,同 path()(控制器、模块均可选,模块缺省为当前模块)
* route.api('save') -> /<当前模块>/save
* route.api('save', 'user') -> /<当前模块>/user/save
* route.api('save', 'user', 'admin')-> /admin/user/save
*/
var api = function (method, controller, module) {
return path(method, controller, module);
};
/**
* 后端 Route::buildUrl() 的 JS 版:与后端路由规则完全一致
* 签名同 path()route(方法, 控制器, 模块),控制器/模块可选(缺省用当前模块)。
* 额外支持参数与锚点:route('edit','user', null, {id:5}, 'top')
* -> /admin/user/edit?id=5#top
*/
var route = function (method, controller, module, vars, anchor) {
// 实时读取后端注入的 layui.app(避免模块异步加载时缓存到空的 layui.app)
var conf = getConf();
var cfgRoot = conf.root || '/';
var cfgModule = conf.module || '';
var cfgRouteBase = conf.routeBase || '';
var cfgController = conf.controller || '';
var cfgAction = conf.action || '';
// 基准 URL:优先用后端注入的 cfgRouteBase(形如 /admin/{__CTRL__}/{__ACT__})。
// fallback(后端未注入时)基于 root+module 拼带占位符的合法基准,
// 绝不再用 path('{__CTRL__}','{__ACT__}') —— 那会产出裸 /{__CTRL__}/{__ACT__}。
var base;
if (cfgRouteBase) {
base = String(cfgRouteBase);
// 防御:若后端注入的 routeBase 不含占位符(异常),补上后缀,避免漏替换
if (base.indexOf('{__CTRL__}') === -1) {
base = base.replace(/\/+$/, '') + '/{__CTRL__}/{__ACT__}';
}
} else {
base = String(cfgRoot || '/').replace(/\/+$/, '')
+ (cfgModule ? '/' + trim(cfgModule) : '')
+ '/{__CTRL__}/{__ACT__}';
}
if (module === undefined || module === null || module === '') {
module = cfgModule;
}
// 未显式传 controller 时,优先用后端注入的 cfgController,再兜底默认 'index'
// (绝不再裸用字面量 '{__CTRL__}',否则会拼出 /{__CTRL__}/xxx 这类非法 URL
var ctrl = controller ? trim(controller) : (cfgController || 'index');
var act = method ? trim(method) : (cfgAction || 'index');
// 若显式传了 module,则替换掉基准里的应用前缀
if (module) {
base = base.replace(/\/[^\/]+\/\{__CTRL__\}/, '/' + trim(module) + '/{__CTRL__}');
}
var url = base
.replace('{__CTRL__}', ctrl)
.replace('{__ACT__}', act);
// 拼装查询参数
if (vars && typeof vars === 'object') {
var q = [];
for (var k in vars) {
if (Object.prototype.hasOwnProperty.call(vars, k) && vars[k] !== '' && vars[k] !== null && vars[k] !== undefined) {
q.push(encodeURIComponent(k) + '=' + encodeURIComponent(vars[k]));
}
}
if (q.length) url += (url.indexOf('?') >= 0 ? '&' : '?') + q.join('&');
}
if (anchor) url += '#' + String(anchor).replace(/^#/, '');
return url;
};
var routeModule = {
// 配置快照(只读,便于调试)—— 实时读取 layui.app
config: function () {
var c = getConf();
return {
root: c.root || '/',
assetUrl: c.assetUrl || '/assets',
module: c.module || '',
routeBase: c.routeBase || '',
controller: c.controller || '',
action: c.action || ''
};
},
path: path,
asset: asset,
api: api,
route: route
};
// 向后兼容:把方法挂回全局 layui.app,旧代码 layui.app.route() 继续可用
if (layui.app) {
layui.app.path = path;
layui.app.asset = asset;
layui.app.api = api;
layui.app.route = route;
layui.app.config = routeModule.config;
}
exports('route', routeModule);
});
+360
View File
@@ -0,0 +1,360 @@
layui.define(["jquery", "component", "http"], function (exports) {
"use strict";
var $ = layui.$;
// 创建UEditorPlus组件
var ueditorPlus = layui.component({
name: "ueditor",
// 组件配置项
config: {
// 默认配置
UEDITOR_HOME_URL: "/assets/library/ueditor/",
serverUrl: "/ueditor", // 上传接口地址
loadConfigFromServer: true,
toolbars: [
[
"fullscreen",
"source",
"|",
"undo",
"redo",
"|",
"bold",
"italic",
"underline",
"fontborder",
"strikethrough",
"superscript",
"subscript",
"removeformat",
"formatmatch",
"autotypeset",
"blockquote",
"pasteplain",
"|",
"forecolor",
"backcolor",
"insertorderedlist",
"insertunorderedlist",
"selectall",
"cleardoc",
"|",
"rowspacingtop",
"rowspacingbottom",
"lineheight",
"letterspacing",
"|",
"customstyle",
"paragraph",
"fontfamily",
"fontsize",
"|",
"directionalityltr",
"directionalityrtl",
"indent",
"|",
"justifyleft",
"justifycenter",
"justifyright",
"justifyjustify",
"|",
"touppercase",
"tolowercase",
"|",
"link",
"unlink",
"anchor",
"|",
"imagenone",
"imageleft",
"imagecenter",
"imageright",
"|",
"simpleupload",
"insertimage",
"emotion",
"scrawl",
"insertvideo",
"insertaudio",
"attachment",
"insertframe",
"insertcode",
"pagebreak",
"template",
"background",
"formula",
"|",
"horizontal",
"date",
"time",
// "spechars", // 特殊字符
"wordimage",
"|",
"inserttable",
"deletetable",
"insertparagraphbeforetable",
"insertrow",
"deleterow",
"insertcol",
"deletecol",
"mergecells",
"mergeright",
"mergedown",
"splittocells",
"splittorows",
"splittocols",
"|",
"print",
"preview",
"searchreplace",
"|",
"contentimport",
"ai",
"help",
],
],
toolbarShow: {
ai: false,
},
toolbarCallback: function (cmd, editor) {
// switch(cmd){
// case 'insertimage':
// editor.execCommand('insertHtml', '<p><img src="xxxxx" /></p>');
// console.log('toolbarCallback',cmd, editor)
// return true;
// case 'insertvideo':
// editor.execCommand('insertHtml', '<p><img src="xxxxx" /></p>');
// console.log('toolbarCallback',cmd, editor)
// return true;
// }
},
imageConfig: {
// 禁止本地上传
disableUpload: false,
// 禁止在线管理
disableOnline: false,
// 自定义选择按钮
selectCallback: null,
// selectCallback: function(editor,cb){
// console.log('selectCallback',cb);
// setTimeout(function(){
// cb({
// path:'https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png',
// name:'测试图片'
// });
// },1000);
// }
},
videoConfig: {
// 禁止本地上传,
disableUpload: false,
// 自定义选择按钮
selectCallback: null,
// selectCallback: function(editor,cb){
// console.log('selectCallback',cb);
// setTimeout(function(){
// cb({
// path:'https://www.bilibili.com/video/BV1y44y1g7NR?spm_id_from=333.1007.tianma.1-1-1.click',
// name:'测试视频'
// });
// },1000);
// }
},
formulaConfig: {
// 公式渲染链接模板
imageUrlTemplate: "https://latex.codecogs.com/svg.image?{}",
// 为了更稳定的公式渲染服务,可以使用
// 魔众官方公式转图片渲染引擎 https://api.tecmz.com
},
autoSaveEnable: true,
autoSaveRestore: true,
autoSaveKey: null,
initialContent: "<p>初始化内容</p>",
uploadServiceEnable: false,
uploadServiceUpload: function (type, file, callback, option) {
var i = 0;
var call = function () {
i++;
if (i > 3) {
callback.success({
state: "SUCCESS",
url: "https://ms-assets.modstart.com/demo/modstart.jpg",
});
return;
}
setTimeout(function () {
callback.progress(0.3 * i);
call();
}, 500);
};
call();
},
focus: true,
initialStyle: "p{line-height:1.5em}",
iframeCssStylesAddition: ["body { background-color: #f0f0f0; }", ".custom-class { font-size: 14px; color: #333; }", "p { line-height: 1.8; }"],
initialFrameWidth: "100%", // 初始宽度
initialFrameHeight: 320, // 初始高度
readonly: false,
enableContextMenu: true,
autoHeightEnabled: true, // 自动高度
autotypeset: {
// 合并空行
mergeEmptyline: true,
// 去掉冗余的class
removeClass: true,
// 去掉空行
removeEmptyline: false,
// 段落的排版方式,可以是 left,right,center,justify 去掉这个属性表示不执行排版
textAlign: "left",
// 图片的浮动方式,独占一行剧中,左右浮动,默认: center,left,right,none 去掉这个属性表示不执行排版
imageBlockLine: "center",
// 根据规则过滤没事粘贴进来的内容
pasteFilter: false,
// 去掉所有的内嵌字号,使用编辑器默认的字号
clearFontSize: false,
// 去掉所有的内嵌字体,使用编辑器默认的字体
clearFontFamily: false,
// 去掉空节点
removeEmptyNode: false,
// 可以去掉的标签
removeTagNames: { div: 1 },
// 行首缩进
indent: false,
// 行首缩进的大小
indentValue: "2em",
// 全角转半角
bdc2sb: false,
// 半角转全角
tobdc: false,
},
// UEditorPlus文件路径配置
},
// 常量定义
CONST: {
MOD_NAME: "ueditor",
ELEM: "layui-ueditor-plus",
CLASS_EDITOR: "layui-ueditor-editor",
},
render: function (options) {
var that = this;
const { access_token } = layui.http.getTokens();
that.config.serverHeaders = {
Authorization: "Bearer " + access_token,
};
var config = $.extend(true, {}, this.config, options);
if (!config.elem) {
layui.hint().error("UEditorPlus组件: elem参数不能为空");
return that;
}
var elem = $(config.elem);
if (!elem.length) {
layui.hint().error("UEditorPlus组件: 元素 " + config.elem + " 不存在");
return that;
}
var id = config.id; //|| "ueditor-plus-" + that.index;
// 缓存配置
that.cache[id] = config;
// 创建编辑器容器
// var editorContainer = $('<div class="' + "layui-ueditor-editor" + '" id="' + id + '-container"></div>');
// elem.html(editorContainer);
that.loadUEditorPlus(config, function () {
that.initEditor(id, config);
});
return ueditorPlus.getInst(id);
},
beforeInit: function (options) {},
beforeRender: function (options) {},
extendsInstance: function (that) {
return {
close: function () {
that.remove(); // 调用组件原型中的 remove 方法
},
setContent: function (id,content) {
var that = ueditorPlus.getInst(id);
return that.ueditor.setContent(content);
},
getContent: function (id) {
var that = ueditorPlus.getInst(id);
return that.ueditor.getContent();
},
destroy: function (id) {
var that = ueditorPlus.getInst(id);
if (!that) return this;
that.ueditor.destroy();
},
getEditor: function (id) {
var that = ueditorPlus.getInst(id);
return that.ueditor || null;
},
reload: function (params) {},
};
},
});
// 获取构造器
var Class = ueditorPlus.Class;
// 初始化编辑器
Class.prototype.initEditor = function (id, config) {
var that = this;
var container = document.getElementById(id);
if (!container || !window.UE) {
layui.hint().error("UEditorPlus组件: 初始化失败,容器或编辑器未就绪");
return;
}
try {
// // 创建编辑器实例
var editor = UE.getEditor(id, config);
// 缓存编辑器实例
that.cache[id].editor = that.ueditor = editor;
} catch (e) {
layui.hint().error("UEditorPlus组件: 初始化异常 - " + e.message);
}
};
Class.prototype.ueditor = null;
Class.prototype.loadUEditorPlus = function (config, callback) {
var that = this;
// 检查是否已加载
if (window.UEDITOR_CONFIG || window.UEditorPlus) {
callback && callback();
return;
}
// 设置UEditorPlus全局配置
window.UEDITOR_HOME_URL = config.UEDITOR_HOME_URL || "./ueditor-plus/";
layui.link(window.UEDITOR_HOME_URL + "themes/default/css/ueditor.css");
// 加载配置文件
that.loadScript(window.UEDITOR_HOME_URL + "ueditor.config.js", function () {
// 加载编辑器主文件
that.loadScript(window.UEDITOR_HOME_URL + "ueditor.all.js", function () {
// 加载完成后执行回调
setTimeout(function () {
callback && callback();
}, 100);
});
});
};
// 动态加载JS文件
Class.prototype.loadScript = function (url, callback) {
var script = document.createElement("script");
script.type = "text/javascript";
script.src = url;
script.onload = script.onreadystatechange = function () {
if (!this.readyState || this.readyState === "loaded" || this.readyState === "complete") {
callback && callback();
script.onload = script.onreadystatechange = null;
}
};
script.onerror = function () {
layui.hint().error("UEditorPlus组件: 加载文件失败: " + url);
};
document.getElementsByTagName("head")[0].appendChild(script);
};
// 输出组件
exports(ueditorPlus.CONST.MOD_NAME, ueditorPlus);
});
+302
View File
@@ -0,0 +1,302 @@
/**
* The Web UI Theme-v2.7.1
*/
layui.define(["laytpl", "layer", "http"], function (exports) {
// 构造函数 u - 主要接口
function View(e) {
return new Template(e);
}
// 构造函数 t - 模板类
function Template(e) {
this.id = e;
this.container = layui.jquery("#" + (e || "LAY_app_body"));
}
// 引入依赖模块
var $ = layui.jquery,
laytpl = layui.laytpl,
layer = layui.layer,
request = layui.http,
setter = layui.setter,
device = layui.device(),
hint = layui.hint(),
DEFAULT_CONTAINER_ID = "LAY_app_body";
// 加载动画
View.loading = function (container) {
container.append(
this.elemLoad = $('<i class="layui-anim layui-anim-rotate layui-anim-loop layui-icon layui-icon-loading layadmin-loading"></i>')
);
};
// 移除加载动画
View.removeLoad = function () {
this.elemLoad && this.elemLoad.remove();
};
// 退出登录
View.exit = function (callback) {
// setter.request.tokenName 当前为 false,不能作为键,否则等于没清理。
// 统一清空本应用命名空间下的登录态与菜单缓存(对齐 layuiadmin 的退出清理)。
var appName = setter.appName;
['access_token', 'refresh_token', 'menus', 'user', 'note'].forEach(function (key) {
layui.data(appName, { key: key, remove: true });
});
callback && callback();
};
// AJAX 请求封装
View.req = function (options) {
function getUrlInfo() {
return setter.debug ? "<br><cite>URL</cite>" + options.url : "";
}
var successCallback = options.success,
errorCallback = options.error,
requestConfig = setter.request,
responseConfig = setter.response;
// 处理请求数据
options.data = options.data || {};
options.headers = options.headers || {};
// 处理 token
if (requestConfig.tokenName) {
var data = typeof options.data === "string" ? JSON.parse(options.data) : options.data;
options.data[requestConfig.tokenName] =
requestConfig.tokenName in data ?
options.data[requestConfig.tokenName] :
layui.data(setter.appName)[requestConfig.tokenName] || "";
options.headers[requestConfig.tokenName] =
requestConfig.tokenName in options.headers ?
options.headers[requestConfig.tokenName] :
layui.data(setter.appName)[requestConfig.tokenName] || "";
}
// 清除原始回调
delete options.success;
delete options.error;
// 执行 AJAX 请求
return $.ajax($.extend({
type: "get",
dataType: "json",
success: function (response) {
var statusCode = responseConfig.statusCode;
if (response[responseConfig.statusName] == statusCode.ok) {
// 成功
typeof options.done === "function" && options.done(response);
} else if (response[responseConfig.statusName] == statusCode.logout) {
// 退出登录
View.exit();
} else {
// 错误处理
var errorMsg = [
"<cite>Error</cite> " + (response[responseConfig.msgName] || "返回状态码异常"),
getUrlInfo()
].join("");
View.error(errorMsg);
}
typeof successCallback === "function" && successCallback(response);
},
error: function (xhr, status) {
var errorMsg = [
"请求异常,请重试<br><cite>错误信息:</cite>" + status,
getUrlInfo()
].join("");
View.error(errorMsg);
typeof errorCallback === "function" && errorCallback.apply(this, arguments);
}
}, options));
};
// 弹窗
View.popup = function (options) {
var successCallback = options.success, skin = options.skin;
delete options.success;
delete options.skin;
return layer.open($.extend({
type: 1,
title: "提示",
content: "",
id: "LAY-system-view-popup",
skin: "layui-layer-admin" + (skin ? " " + skin : ""),
shadeClose: true,
closeBtn: false,
success: function (layero, index) {
var closeBtn = $('<i class="layui-icon" close>&#x1006;</i>');
layero.append(closeBtn);
closeBtn.on("click", function () {
layer.close(index);
});
typeof successCallback === "function" && successCallback.apply(this, arguments);
}
}, options));
};
// 错误提示
View.error = function (content, options) {
return View.popup($.extend({
content: content,
maxWidth: 300,
offset: "t",
anim: 6,
id: "LAY_adminError"
}, options));
};
// Template 原型方法
// 渲染视图
Template.prototype.render = function (templatePath, params) {
var self = this;
layui.hash();
// 构建完整模板路径
templatePath = (setter.paths && setter.paths.views ? setter.paths : setter).views +
templatePath + setter.engine;
// 移除加载动画
$("#" + DEFAULT_CONTAINER_ID).children(".layadmin-loading").remove();
View.loading(self.container);
// AJAX 获取模板
request.get(templatePath, { v: layui.cache.version }).then(function (html) {
// 提取标题
var titleElem = $(html = "<div>" + html + "</div>").find("title");
var viewData = {
title: titleElem.text() || (html.match(/<title>([\s\S]*)<\/title>/) || [])[1],
body: html
};
titleElem.remove();
// 设置参数
self.params = params || {};
// 执行 then 回调
self.then && (self.then(viewData), delete self.then);
// 解析模板
self.parse(html);
View.removeLoad();
// 执行 done 回调
self.done && (self.done(viewData), delete self.done);
}).catch(function (xhr) {
View.removeLoad();
// 防止重复错误处理
if (self.render.isError) {
return View.error("请求视图文件异常,状态:" + xhr.status);
}
// 根据状态码显示不同错误页面
if (xhr.status === 404) {
self.render("template/tips/404");
} else {
self.render("template/tips/error");
}
self.render.isError = true;
});
return self;
};
// 解析模板
Template.prototype.parse = function (html, isRefresh, callback) {
function processTemplate(templateInfo) {
var compiledTemplate = laytpl(templateInfo.dataElem.html());
var renderData = $.extend({ params: self.params }, templateInfo.res);
// 渲染并插入 DOM
templateInfo.dataElem.after(compiledTemplate.render(renderData));
// 执行回调
typeof callback === "function" && callback();
// 执行 done 回调(可能是 JavaScript 代码)
try {
templateInfo.done && new Function("d", templateInfo.done)(renderData);
} catch (error) {
console.error(templateInfo.dataElem[0], "\n存在错误回调脚本\n\n", error);
}
}
var self = this;
var isObject = typeof html === "object";
var $html = isObject ? html : $(html);
var templateElems = isObject ? html : $html.find("*[template]");
var hashData = layui.hash();
// 移除标题
$html.find("title").remove();
// 插入 HTML
self.container[isRefresh ? "after" : "html"]($html.children());
// 设置参数
hashData.params = self.params || {};
// 处理所有模板元素
for (var i = templateElems.length; i > 0; i--) {
(function () {
var templateElem = templateElems.eq(i - 1);
var doneCallback = templateElem.attr("lay-done") || templateElem.attr("lay-then");
// 解析模板属性
var urlTemplate = templateElem.attr("lay-url") || "";
var dataTemplate = templateElem.attr("lay-data") || "";
var headersTemplate = templateElem.attr("lay-headers") || "";
var url = laytpl(urlTemplate).render(hashData);
var data = laytpl(dataTemplate).render(hashData);
var headers = laytpl(headersTemplate).render(hashData);
// 解析数据对象
try {
data = new Function("return " + data + ";")();
} catch (error) {
hint.error("lay-data: " + error.message);
data = {};
}
// 解析头部对象
try {
headers = new Function("return " + headers + ";")();
} catch (error) {
hint.error("lay-headers: " + error.message);
headers = headers || {};
}
// 如果有 URL,则发起请求
if (url) {
View.req({
type: templateElem.attr("lay-type") || "get",
url: url,
data: data,
dataType: "json",
headers: headers,
success: function (response) {
processTemplate({
dataElem: templateElem,
res: response,
done: doneCallback
});
}
});
} else {
// 直接处理模板
processTemplate({
dataElem: templateElem,
done: doneCallback
});
}
})();
}
return self;
};
// 自动渲染
Template.prototype.autoRender = function (selector, callback) {
var self = this;
$(selector || "body").find("*[template]").each(function () {
var $this = $(this);
self.container = $this;
self.parse($this, "refresh");
});
};
// 发送数据到模板
Template.prototype.send = function (template, data) {
template = laytpl(template || this.container.html()).render(data || {});
this.container.html(template);
return this;
};
// 刷新
Template.prototype.refresh = function (callback) {
var self = this;
var nextTemplateId = self.container.next().attr("lay-templateid");
if (self.id == nextTemplateId) {
self.parse(self.container, "refresh", function () {
self.container.siblings('[lay-templateid="' + self.id + '"]:last').remove();
typeof callback === "function" && callback();
});
}
return self;
};
// 设置 then 回调
Template.prototype.then = function (callback) {
this.then = callback;
return this;
};
// 设置 done 回调
Template.prototype.done = function (callback) {
this.done = callback;
return this;
};
// 导出模块
exports("view", View);
});
File diff suppressed because it is too large Load Diff
+835
View File
@@ -0,0 +1,835 @@
/**
* Layui Admin Module - Theme v2.7.1
* 功能后台管理系统的核心模块负责布局导航标签页主题事件管理等
*/
layui.define("view", function (exports) {
// 依赖模块
var $ = layui.jquery,
laytpl = layui.laytpl,
table = layui.table,
element = layui.element,
util = layui.util,
upload = layui.upload,
setter = layui.setter,
view = layui.view,
device = layui.device();
// DOM 元素缓存
var win = $(window),
doc = $(document),
body = $("body"),
container = $("#" + setter.container),
MAIN_BODY_ID = "#LAY_app_body",
FLEXIBLE_BTN_ID = "LAY_app_flexible",
TABS_CONTAINER_ID = "layadmin-layout-tabs",
SIDE_SPREAD_CLASS = "layadmin-side-spread-sm",
TABS_BODY_ITEM_CLASS = "layadmin-tabsbody-item",
SHRINK_ICON_CLASS = "layui-icon-shrink-right",
SPREAD_ICON_CLASS = "layui-icon-spread-left",
SIDE_SHRINK_CLASS = "layadmin-side-shrink";
// 核心 Admin 对象
var admin = {
version: "2.7.1",
mode: "iframe", // 默认使用 iframe 模式
req: view.req, // 复用 view 模块的请求方法
exit: view.exit, // 复用 view 模块的退出方法
escape: util.escape, // 复用工具方法
// 事件监听快捷方式
on: function (eventName, callback) {
return layui.onevent.call(this, setter.appName, eventName, callback);
},
/**
* 根据当前路由高亮侧边栏菜单
* @param {Object} hashObj - 路由哈希对象可选默认从当前URL解析
*/
setSideMenuStatus: function (hashObj) {
hashObj = hashObj || layui.hash();
var $sideMenu = $("#LAY-system-side-menu"),
activeClass = "layui-nav-itemed";
// 移除所有激活状态
$sideMenu.find(".layui-this").removeClass("layui-this");
// 如果是小屏幕,自动处理侧边栏伸缩
admin.screen() < 2 && admin.sideFlexible();
var currentPath = hashObj.path.join("/");
// 遍历所有菜单项,匹配当前路由
$sideMenu.find("li, dd").each(function () {
var $this = $(this),
$link = $this.children("a"),
href = admin.correctRouter($link.attr("lay-href"));
// 匹配逻辑:完全匹配或路径包含
if (href === hashObj.href || href === currentPath || (href !== "/" && hashObj.href.indexOf(href) !== -1)) {
// 激活当前项
$sideMenu.find(".layui-this").removeClass("layui-this");
$this.addClass("layui-this");
// 展开父级菜单
var $parentItem = $this.closest(".layui-nav-item"),
$parentDd = $this.parent().closest("dd");
function expandItem($elem) {
$elem.addClass(activeClass).siblings().removeClass(activeClass);
}
expandItem($parentItem);
expandItem($parentDd);
// 滚动到可视区域
util.toVisibleArea({
scrollElem: container.find(".layui-side-scroll"),
thisElem: $this
});
}
});
},
/**
* 获取屏幕尺寸等级
* @returns {Number} 3: >1200px, 2: 992-1200px, 1: 768-992px, 0: <768px
*/
screen: function () {
var width = win.width();
if (width > 1200) return 3;
if (width > 992) return 2;
if (width > 768) return 1;
return 0;
},
/**
* 侧边栏伸缩切换
* @param {String} status - 'spread' 展开其他或空为收缩
*/
sideFlexible: function (status) {
var $flexBtn = $("#" + FLEXIBLE_BTN_ID),
screenSize = admin.screen();
if (status === "spread") {
$flexBtn.removeClass(SPREAD_ICON_CLASS).addClass(SHRINK_ICON_CLASS);
if (screenSize < 2) {
container.addClass(SIDE_SPREAD_CLASS);
} else {
container.removeClass(SIDE_SPREAD_CLASS);
}
container.removeClass(SIDE_SHRINK_CLASS);
} else {
$flexBtn.removeClass(SHRINK_ICON_CLASS).addClass(SPREAD_ICON_CLASS);
if (screenSize < 2) {
container.removeClass(SIDE_SHRINK_CLASS);
} else {
container.addClass(SIDE_SHRINK_CLASS);
}
container.removeClass(SIDE_SPREAD_CLASS);
}
// 触发自定义事件
layui.event.call(this, setter.appName, "side({*})", { status: status });
},
// 弹窗相关方法(复用 view 模块)
popup: view.popup,
popupRight: function (options) {
return admin.popup.index = layer.open($.extend({
type: 1,
id: "LAY_adminPopupR",
anim: -1,
title: false,
closeBtn: false,
offset: "r",
shade: 0.1,
shadeClose: true,
skin: "layui-anim layui-anim-rl layui-layer-adminRight",
area: "300px"
}, options));
},
/**
* 动态切换主题
* @param {Object} themeConfig - 主题配置对象
*/
theme: function (themeConfig) {
var localTheme = layui.data(setter.appName).theme;
var styleId = "LAY_layadmin_theme";
var oldStyle = document.getElementById(styleId);
var newStyle = document.createElement("style");
// 清除主题
if (themeConfig.CLEAR) {
$(oldStyle).remove();
layui.data(setter.appName, { key: "theme", remove: true });
return;
}
// 合并配置并生成 CSS
themeConfig = $.extend({}, localTheme, themeConfig);
var cssTemplate = [
".layui-side-menu,",
".layui-layer-admin .layui-layer-title,",
".layadmin-side-shrink .layui-side-menu .layui-nav>.layui-nav-item>.layui-nav-child",
"{background-color:{{d.color.main}} !important;}",
".layadmin-pagetabs .layui-tab-title li:after,",
".layadmin-pagetabs .layui-tab-title li.layui-this:after,",
".layui-nav-tree .layui-this,",
".layui-nav-tree .layui-this>a,",
".layui-nav-tree .layui-nav-child dd.layui-this,",
".layui-nav-tree .layui-nav-child dd.layui-this a,",
".layui-nav-tree .layui-nav-bar",
"{background-color:{{d.color.selected}} !important;}",
".layadmin-pagetabs .layui-tab-title li:hover,",
".layadmin-pagetabs .layui-tab-title li.layui-this",
"{color: {{d.color.selected}} !important;}",
".layui-layout-admin .layui-logo{background-color:{{d.color.logo || d.color.main}} !important;}",
"{{# if(d.color.header){ }}",
".layui-layout-admin .layui-header{background-color:{{ d.color.header }};}",
".layui-layout-admin .layui-header a,",
".layui-layout-admin .layui-header a cite{color: #f8f8f8;}",
".layui-layout-admin .layui-header a:hover{color: #fff;}",
".layui-layout-admin .layui-header .layui-nav .layui-nav-more{border-top-color: #fbfbfb;}",
".layui-layout-admin .layui-header .layui-nav .layui-nav-mored{border-color: transparent; border-bottom-color: #fbfbfb;}",
".layui-layout-admin .layui-header .layui-this:after, .layui-layout-admin .layui-header .layui-nav-bar{background-color: #fff; background-color: rgba(255,255,255,.5);}",
".layadmin-pagetabs .layui-tab-title li:after{display: none;}",
"{{# } }}"
].join("");
var cssContent = laytpl(cssTemplate).render(themeConfig);
// 插入样式
if ("styleSheet" in newStyle) {
newStyle.setAttribute("type", "text/css");
newStyle.styleSheet.cssText = cssContent;
} else {
newStyle.innerHTML = cssContent;
}
newStyle.id = styleId;
if (oldStyle && body.removeChild) {
body[0].removeChild(oldStyle);
}
console.log(body);
body[0].appendChild(newStyle);
// 更新主题别名和本地存储
if (themeConfig.color) {
body.attr("layadmin-themealias", themeConfig.color.alias);
}
localTheme = localTheme || {};
$.each(themeConfig, function (key, value) {
localTheme[key] = value;
});
layui.data(setter.appName, { key: "theme", value: localTheme });
},
/**
* 初始化主题从配置中读取
* @param {Number} index - 主题颜色索引
*/
initTheme: function (index) {
var themeConfig = setter.theme;
if (themeConfig.color && themeConfig.color[index]) {
themeConfig.color[index].index = index;
admin.theme({ color: themeConfig.color[index] });
}
},
// 标签页状态
tabsPage: {},
/**
* 获取标签页内容区域
* @param {Number} index - 标签页索引
*/
tabsBody: function (index) {
return $(MAIN_BODY_ID).find("." + TABS_BODY_ITEM_CLASS).eq(index || 0);
},
/**
* 切换标签页内容
* @param {Number} index - 要切换到的标签页索引
* @param {Object} data - 标签页数据 {url, title}
*/
tabsBodyChange: function (index, data) {
data = data || {};
admin.tabsBody(index).addClass("layui-show").siblings().removeClass("layui-show");
admin.events.rollPage("auto", index);
admin.recordURL(data);
layui.event.call(this, setter.appName, "tabsPage({*})", data);
},
/**
* 记录当前URL到本地存储用于刷新恢复
* @param {Object} data - {url, title}
*/
recordURL: function (data) {
var recordData;
if ((setter.record || {}).url && data.url) {
// 过滤外部链接
if (/^(\w*:)*\/\/.+/.test(data.url)) {
data.url = "";
}
// 更新 hash
location.hash = admin.correctRouter(data.url);
// 存储标题
if (data.url && data.title) {
(recordData = {})[data.url] = data.title;
layui.data(setter.appName, { key: "record", value: recordData });
}
}
},
/**
* 窗口大小变化监听管理
* @param {Function} fn - 回调函数传入 'off' 则移除监听
*/
resize: function (fn) {
var routeKey = layui.hash().path.join("-");
if (admin.resizeFn[routeKey]) {
win.off("resize", admin.resizeFn[routeKey]);
delete admin.resizeFn[routeKey];
}
if (fn !== "off") {
fn();
admin.resizeFn[routeKey] = fn;
win.on("resize", admin.resizeFn[routeKey]);
}
},
// 存储 resize 回调函数
resizeFn: {},
/**
* 手动触发当前路由对应的 resize 回调
*/
runResize: function () {
var routeKey = layui.hash().path.join("-");
admin.resizeFn[routeKey] && admin.resizeFn[routeKey];
},
/**
* 移除当前路由的 resize 监听
*/
delResize: function () {
this.resize("off");
},
/**
* 关闭当前标签页
*/
closeThisTabs: function () {
admin.tabsPage.index && $(".layui-tab-title li").eq(admin.tabsPage.index).find(".layui-tab-close").trigger("click");
},
/**
* 全屏
*/
fullScreen: function () {
var docEl = document.documentElement;
var requestFullscreen = docEl.requestFullscreen || docEl.webkitRequestFullScreen || docEl.mozRequestFullScreen || docEl.msRequestFullscreen;
if (typeof requestFullscreen !== "undefined" && requestFullscreen) {
requestFullscreen.call(docEl);
}
},
/**
* 退出全屏
*/
exitScreen: function () {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitCancelFullScreen) {
document.webkitCancelFullScreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
},
/**
* 修正路由路径
* @param {String} path - 原始路径
*/
correctRouter: function (path) {
path = /^\//.test(path) ? path : "/" + path;
path = path.replace(/^(\/+)/, "/").replace(new RegExp("/" + setter.entry + "$"), "/");
return path;
}
};
// 事件处理对象
var events = admin.events = {
/**
* 侧边栏伸缩按钮事件
*/
flexible: function ($elem) {
var isSpread = $elem.find("#" + FLEXIBLE_BTN_ID).hasClass(SPREAD_ICON_CLASS);
admin.sideFlexible(isSpread ? "spread" : null);
},
/**
* 刷新当前页
*/
refresh: function () {
var tabCount = $("." + TABS_BODY_ITEM_CLASS).length;
// 防止索引越界
if (admin.tabsPage.index >= tabCount) {
admin.tabsPage.index = tabCount - 1;
}
admin.tabsBody(admin.tabsPage.index).find(".layadmin-iframe")[0].contentWindow.location.reload(true);
},
/**
* 顶部搜索框事件
*/
serach: function ($searchInput) {
$searchInput.off("keypress").on("keypress", function (e) {
var actionUrl, title;
if (this.value.replace(/\s/g, "") && e.keyCode === 13) { // 回车键
actionUrl = $searchInput.attr("lay-action");
title = $searchInput.attr("lay-title") || $searchInput.attr("lay-text") || "搜索";
actionUrl += this.value;
title = title + ": " + admin.escape(this.value);
admin.openTabsPage({
url: actionUrl,
title: title,
highlight: "color: #FF5722;"
});
// 记录搜索关键词
events.serach.keys || (events.serach.keys = {});
events.serach.keys[admin.tabsPage.index] = this.value;
// 如果与上次搜索相同,则刷新页面
if (this.value === events.serach.keys[admin.tabsPage.index]) {
events.refresh($searchInput);
}
this.value = "";
}
});
},
/**
* 清除消息红点
*/
message: function ($elem) {
$elem.find(".layui-badge-dot").remove();
},
/**
* 打开主题设置面板
*/
theme: function () {
admin.popupRight({
id: "LAY_adminPopupTheme",
success: function () {
view(this.id).render("theme");
}
});
},
/**
* 打开便签面板
*/
note: function ($btn) {
var isSmallScreen = admin.screen() < 2;
var savedNote = layui.data(setter.appName).note;
events.note.index = admin.popup({
title: "本地便签",
shade: 0,
offset: ["41px", isSmallScreen ? null : $btn.offset().left - 250 + "px"],
anim: -1,
id: "LAY_adminNote",
skin: "layadmin-note layui-anim layui-anim-upbit",
content: '<textarea placeholder="内容"></textarea>',
resize: false,
success: function (elem, index) {
elem.find("textarea")
.val(typeof savedNote === "undefined" ? "便签中的内容会存储在本地,这样即便你关掉了浏览器,在下次打开时,依然会读取到上一次的记录。是个非常小巧实用的本地备忘录。" : savedNote)
.focus()
.on("keyup", function () {
layui.data(setter.appName, { key: "note", value: this.value });
});
}
});
},
/**
* 全屏/退出全屏切换
*/
fullscreen: function ($btn, data) {
function toggleIcon(isFullscreen) {
isFullscreen ? $btn.addClass("layui-icon-screen-restore").removeClass("layui-icon-screen-full") :
$btn.addClass("layui-icon-screen-full").removeClass("layui-icon-screen-restore");
}
var $icon = $btn.children("i");
var isFullscreen = $icon.hasClass("layui-icon-screen-full");
if (data) {
return toggleIcon(data.status);
}
toggleIcon(isFullscreen);
isFullscreen ? admin.fullScreen() : admin.exitScreen();
},
/**
* 打开关于面板
*/
about: function () {
admin.popupRight({
id: "LAY_adminPopupAbout",
success: function () {
view(this.id).render("about");
}
});
},
/**
* 退出登录对齐 layuiadmin 标准
* 之前的 events 中没有 logout导致后台/会员中心点击"退出"无反应
* 这里统一清本地登录态与菜单缓存 -> 调后端登出 -> 跳登录页
*/
logout: function () {
var appName = setter.appName;
var segs = location.pathname.split('/').filter(Boolean);
var prefix = segs.length ? '/' + segs[0] : '';
// 1. 清空本应用命名空间下的登录态与菜单缓存,避免同浏览器串号、残留旧菜单
['access_token', 'refresh_token', 'menus', 'user', 'note', 'theme', 'record']
.forEach(function (key) {
layui.data(appName, { key: key, remove: true });
});
// 2. 调后端登出接口(best-effort,失败也不影响前端清理)
try {
layui.http.post(prefix + '/login/logout');
} catch (e) {}
// 3. 跳登录页(优先顶级窗口,兼容 iframe 版)
var loginUrl = prefix + '/login/index';
if (window.self !== window.top) {
window.top.location.href = loginUrl;
} else {
location.href = loginUrl;
}
},
/**
* 打开更多面板
*/
more: function () {
admin.popupRight({
id: "LAY_adminPopupMore",
success: function () {
view(this.id).render("system/more");
}
});
},
/**
* 浏览器后退
*/
back: function () {
history.back();
},
/**
* 设置主题颜色
*/
setTheme: function ($colorBtn) {
var colorIndex = $colorBtn.data("index");
var prevIndex = $colorBtn.siblings(".layui-this").data("index");
if (!$colorBtn.hasClass("layui-this")) {
$colorBtn.addClass("layui-this").siblings(".layui-this").removeClass("layui-this");
admin.initTheme(colorIndex);
view("LAY_adminPopupTheme").render("theme");
}
},
/**
* 标签页滚动
* @param {String} type - 'left' 向左'auto' 自动其他向右
* @param {Number} index - 目标标签页索引用于 auto 模式
*/
rollPage: function (type, index) {
var $tabsHeader = $("#LAY_app_tabsheader");
var $tabItems = $tabsHeader.children("li");
var headerWidth = $tabsHeader.outerWidth();
var currentLeft = parseFloat($tabsHeader.css("left"));
if (type === "left") {
if (currentLeft && currentLeft <= 0) {
var targetPos = -currentLeft - headerWidth;
$tabItems.each(function (i, elem) {
var pos = $(elem).position().left;
if (targetPos <= pos) {
$tabsHeader.css("left", -pos);
return false; // 退出循环
}
});
}
} else if (type === "auto") {
var $targetItem = $tabItems.eq(index);
if ($targetItem) {
var itemLeft = $targetItem.position().left;
// 如果目标项在可视区域左侧之外,滚动到它
if (itemLeft < -currentLeft) {
$tabsHeader.css("left", -itemLeft);
return;
}
// 如果目标项在可视区域右侧之外,滚动到它
var itemRight = itemLeft + $targetItem.outerWidth();
if (itemRight >= headerWidth - currentLeft) {
var overflow = itemRight - (headerWidth - currentLeft);
$tabItems.each(function (i, elem) {
var pos = $(elem).position().left;
if (0 < pos + currentLeft && overflow < pos - currentLeft) {
$tabsHeader.css("left", -pos);
return false;
}
});
}
}
} else {
// 向右滚动到第一个在可视区域右侧的项
$tabItems.each(function (i, elem) {
var $elem = $(elem);
var pos = $elem.position().left;
if (pos + $elem.outerWidth() >= headerWidth - currentLeft) {
$tabsHeader.css("left", -pos);
return false;
}
});
}
},
leftPage: function () {
events.rollPage("left");
},
rightPage: function () {
events.rollPage();
},
closeThisTabs: function () {
(parent === self ? admin : parent.layui.admin).closeThisTabs();
},
closeOtherTabs: function (type) {
var removeClass = "LAY-system-pagetabs-remove";
var currentIndex = admin.tabsPage.index;
var $allTabs = $(".layui-tab-title li");
var $allBodies = $(MAIN_BODY_ID).find("." + TABS_BODY_ITEM_CLASS);
if (type === "all") {
// 关闭所有(保留第一个)
$allTabs.filter(":gt(0)").remove();
$allBodies.filter(":gt(0)").remove();
$allTabs.eq(0).trigger("click");
} else if (type === "right") {
// 关闭右侧所有
$allTabs.filter(":gt(" + currentIndex + ")").remove();
$allBodies.filter(":gt(" + currentIndex + ")").remove();
} else {
// 关闭其他(保留当前)
$allTabs.each(function (i) {
if (i && i != currentIndex) {
$(this).addClass(removeClass);
admin.tabsBody(i).addClass(removeClass);
}
});
$("." + removeClass).remove();
}
},
closeRightTabs: function () {
events.closeOtherTabs("right");
},
closeAllTabs: function () {
events.closeOtherTabs("all");
},
shade: function () {
admin.sideFlexible();
},
im: function () {
admin.popup({
id: "LAY-popup-layim-demo",
shade: 0,
area: ["800px", "300px"],
title: "面板外的操作示例",
offset: "lb",
success: function () {
layui.view(this.id).render("layim/demo").then(function () {
layui.use("im");
});
}
});
}
};
// 初始化逻辑
(function init() {
var $tabHeaders = $("#LAY_app_tabsheader > li");
var $win = $(window);
var $doc = $(document);
// 标签页配置检查
if (!("pageTabs" in layui.setter)) {
layui.setter.pageTabs = true;
}
if (!setter.pageTabs) {
$("#LAY_app_tabs").addClass("layui-hide");
container.addClass("layadmin-tabspage-none");
}
// IE 版本提示
if (device.ie && device.ie < 10) {
view.error("IE" + device.ie + "下访问可能不佳,推荐使用:Chrome / Firefox / Edge 等高级浏览器", {
offset: "auto",
id: "LAY_errorIE"
});
}
// 主题初始化
var localData = layui.data(setter.appName);
if (localData.theme) {
admin.theme(localData.theme);
} else if (setter.theme) {
admin.initTheme(setter.theme.initColorIndex);
}
// 标签页切换事件
element.on("tab(" + TABS_CONTAINER_ID + ")", function (data) {
admin.tabsPage.index = data.index;
});
// 监听标签页切换,更新侧边栏菜单状态
admin.on("tabsPage(setMenustatus)", function (data) {
var hashObj = layui.hash("#" + admin.correctRouter(data.url));
admin.setSideMenuStatus(hashObj);
});
// 侧边栏菜单点击事件(小屏自动展开)
element.on("nav(layadmin-system-side-menu)", function (data) {
if (data.siblings(".layui-nav-child") && container.hasClass(SIDE_SHRINK_CLASS)) {
admin.sideFlexible("spread");
layer.close(data.data("index"));
}
admin.tabsPage.type = "nav";
});
// 标签页导航点击事件
element.on("nav(layadmin-pagetabs-nav)", function (data) {
var $parent = $(data.elem).parent();
$parent.removeClass("layui-this");
$parent.parent().removeClass("layui-show");
});
// 标签页头点击事件
body.on("click", "#LAY_app_tabsheader > li", function () {
var $this = $(this);
var index = $this.index();
admin.tabsPage.type = "tab";
admin.tabsPage.index = index;
// 切换标签页内容
var layId = $this.attr("lay-id");
$this.attr("lay-attr"); // 未使用
admin.tabsBodyChange(index, {
url: layId,
title: $this.children("span").text()
});
});
// 标签页关闭事件
element.on("tabDelete(" + TABS_CONTAINER_ID + ")", function (data) {
var $activeTab = $(".layui-tab-title li.layui-this");
if (data.index) {
admin.tabsBody(data.index).remove();
}
// 切换到上一个标签
var layId = $activeTab.attr("lay-id");
$activeTab.attr("lay-attr"); // 未使用
admin.tabsBodyChange($activeTab.index(), {
url: layId,
title: $activeTab.children("span").text()
});
admin.delResize();
});
// 带 lay-href 属性的链接点击事件(打开新标签页)
body.on("click", "*[lay-href]", function () {
var $this = $(this);
var href = $this.attr("lay-href");
var title = $this.attr("lay-title") || $this.attr("lay-text") || $this.text();
admin.tabsPage.elem = $this;
(parent === self ? layui : setter.parentLayui || top.layui).ywxapp.openTabsPage({
url: href,
title: title
});
// 如果开启标签页且点击的是当前页链接,则刷新
if (setter.pageTabs && setter.refreshCurrPage && href === admin.tabsBody(admin.tabsPage.index).find("iframe").attr("src")) {
events.refresh();
}
});
// 自定义事件委托(layadmin-event 属性)
body.on("click", "*[layadmin-event]", function () {
var $this = $(this);
var eventName = $this.attr("layadmin-event");
if (events[eventName]) {
events[eventName].call(this, $this);
}
});
// 提示框(lay-tips 属性)
body.on("mouseenter", "*[lay-tips]", function () {
var $this = $(this);
// 侧边栏收缩状态下或非导航项才显示提示
if (!$this.parent().hasClass("layui-nav-item") || container.hasClass(SIDE_SHRINK_CLASS)) {
var tips = $this.attr("lay-tips");
var offset = $this.attr("lay-offset");
var direction = $this.attr("lay-direction");
var tipsIndex = layer.tips(tips, this, {
tips: direction || 1,
time: -1,
success: function (elem, index) {
if (offset) {
elem.css("margin-left", offset + "px");
}
}
});
$this.data("index", tipsIndex);
}
}).on("mouseleave", "*[lay-tips]", function () {
layer.close($(this).data("index"));
});
// 窗口大小变化处理(防抖)
layui.data.resizeSystem = function () {
layer.closeAll("tips");
if (!layui.data.resizeSystem.lock) {
setTimeout(function () {
admin.sideFlexible(admin.screen() < 2 ? "" : "spread");
delete layui.data.resizeSystem.lock;
}, 100);
layui.data.resizeSystem.lock = true;
}
};
$win.on("resize", layui.data.resizeSystem);
// 全屏变化事件
$doc.on("fullscreenchange", function () {
events.fullscreen($('[layadmin-event="fullscreen"]'), {
status: document.fullscreenElement
});
});
// 全局请求 Token 注入
var reqConfig = setter.request;
if (reqConfig.tokenName) {
var headers = {};
headers[reqConfig.tokenName] = layui.data(setter.appName)[reqConfig.tokenName] || "";
table.set({ headers: headers, where: headers });
upload.set({ headers: headers, data: headers });
}
})();
// 导出模块
exports("ywxapp", admin);
});
+245
View File
@@ -0,0 +1,245 @@
// 在脚本【同步执行阶段】捕获当前 <script> 标签(异步回调中 document.currentScript 会失效)。
// 注意:appName 真正取自 ThinkPHP 注入的 layui.app(真实应用目录名),
// 这里仅作为兜底缓存 <script module> 标签上的属性值。
var _scriptEl = document.currentScript || document.scripts[document.scripts.length - 1];
var _appNameFromTag = _scriptEl ? _scriptEl.getAttribute('module') : null;
// 【关键】在脚本顶层同步配置 layui 模块 base 目录。
// 原因:普通业务模块(如 login,未在下方 extend 列表中)由 layui 用 base + 名称 + '.js' 解析路径。
// 若 base 未提前配置,页面里直接 layui.use('login') 时 base 为 undefined
// 会请求 /当前路径/undefinedlogin.js 而 404。
// 之前只在 define 回调里配置 base,而该回调是异步触发的(需有人 use('ywxapp') 才执行),
// 页面 use 业务模块时它往往还没跑,导致 base 缺失。此处同步先行配置即可规避执行顺序问题。
var _appName = (layui.app && layui.app.app) || _appNameFromTag || null;
if (layui && _appName) {
layui.config({ base: '/static/' + _appName + '/modules/' });
}
layui.extend({
ywxapp: "{/}/assets/ywxapp/modules/ywxapp",
route: "{/}/assets/ywxapp/modules/route",
view: "{/}/assets/ywxapp/modules/view",
wxapp: "{/}/assets/ywxapp/modules/wxapp",
auth: "{/}/assets/ywxapp/modules/auth",
iconPicker: "{/}/assets/ywxapp/modules/iconPicker",
ueditor: "{/}/assets/ywxapp/modules/ueditor",
http: "{/}/assets/ywxapp/modules/http",
wxedit: "{/}/assets/ywxapp/modules/wxedit",
chatClient: "{/}/assets/ywxapp/modules/chat-client",
JsonCrypto: "{/}/assets/ywxapp/modules/JsonCrypto",
cropper: "{/}/assets/ywxapp/modules/cropper/cropper",
croppers: "{/}/assets/ywxapp/modules/cropper/croppers",
echartsTheme: "{/}/assets/ywxapp/modules/echartsTheme",
echarts: "{/}/assets/ywxapp/modules/echarts",
}).define(["all", "route"], function (exports) {
var $ = layui.$,
table = layui.table, http = layui.http;
var loadIndex;
/** 设置Ajax */
var Setter = {
layerArea: function () {
const width = window.innerWidth;
// if (width >= 1400) return ["1330px", "99%"];
// if (width >= 1200) return ["1170px", "99%"];
if (width >= 992) return ["970px", "99%"];
if (width >= 768) return ["750px", "99%"];
return ["100%", "99%"];
},
paths: {
// core: layui.cache.base + 'adminui/dist/', // 核心库所在目录
// views: '/static/backend/views/', // 动态模板所在目录
// modules: layui.cache.base + 'modules/', // 业务模块所在目录
// base: layui.cache.base // 记录静态资源所在基础目录
},
container: 'LAY_app', // 容器 ID
entry: 'index', // 默认视图文件名
engine: '.html', // 视图文件后缀名
pageTabs: true, // 是否开启页面选项卡功能。 iframe 版推荐开启
refreshCurrPage: true, // 当跳转页面 url 与当前页 url 相同时,是否自动执行刷新 --- 2.0+
name: 'YwxApp', // 系统名称 --- 2.0+
appName: 'admin',
// 记录
record: {
url: true // 是否开启 iframe 跳转时 url 记忆功能
},
debug: true, // 是否开启调试模式。如开启,接口异常时会抛出异常 URL 等信息
// 自定义请求字段
request: {
tokenName: false // 自动携带 token 的字段名(如:access_token)。可设置 false 不携带。
},
// 配置业务模块目录中的特殊模块
extend: {
layim: 'layim/layim' // layim
},
// 主题配置
theme: {
// 内置主题配色方案
color: [{
main: '#20222A', // 主题色
selected: '#16baaa', // 选中色
alias: 'default' // 默认别名
}, {
main: '#03152A',
selected: '#3B91FF',
alias: 'dark-blue' // 藏蓝
}, {
main: '#2E241B',
selected: '#A48566',
alias: 'coffee' // 咖啡
}, {
main: '#50314F',
selected: '#7A4D7B',
alias: 'purple-red' // 紫红
}, {
main: '#344058',
logo: '#1E9FFF',
selected: '#1E9FFF',
alias: 'ocean' // 海洋
}, {
main: '#3A3D49',
logo: '#2F9688',
selected: '#16b777',
alias: 'green' // 墨绿
}, {
main: '#20222A',
logo: '#F78400',
selected: '#F78400',
alias: 'red' // 橙色
}, {
main: '#28333E',
logo: '#AA3130',
selected: '#AA3130',
alias: 'fashion-red' // 时尚红
}, {
main: '#24262F',
logo: '#3A3D49',
selected: '#16baaa',
alias: 'classic-black' // 经典黑
}, {
logo: '#226A62',
header: '#2F9688',
alias: 'green-header' // 墨绿头
}, {
main: '#344058',
logo: '#0085E8',
selected: '#1E9FFF',
header: '#1E9FFF',
alias: 'ocean-header' // 海洋头
}, {
header: '#393D49',
alias: 'classic-black-header' // 经典黑头
}, {
main: '#50314F',
logo: '#50314F',
selected: '#7A4D7B',
header: '#50314F',
alias: 'purple-red-header' // 紫红头
}, {
main: '#28333E',
logo: '#28333E',
selected: '#AA3130',
header: '#AA3130',
alias: 'fashion-red-header' // 时尚红头
}, {
main: '#28333E',
logo: '#16baaa',
selected: '#16baaa',
header: '#16baaa',
alias: 'green-header' // 墨绿头
}, {
main: '#393D49',
logo: '#393D49',
selected: '#16baaa',
header: '#23262E',
alias: 'Classic-style1' // 经典风格1
}, {
main: '#001529',
logo: '#001529',
selected: '#1890FF',
header: '#1890FF',
alias: 'Classic-style2' // 经典风格2
}, {
main: '#25282A',
logo: '#25282A',
selected: '#35BDB2',
header: '#35BDB2',
alias: 'Classic-style3' // 经典风格3
}],
// 初始的颜色索引,对应上面的配色方案数组索引
// 如果本地已经有主题色记录,则以本地记录为优先,除非请求本地数据(localStorage
initColorIndex: 0
}
};
Setter.paths = {
base: '/static/' + _appName + '/', // 静态资源根目录(供业务模块拼接 json 等相对资源)
modules: '/static/' + _appName + '/modules/', // 业务模块所在目录
views: '/static/' + _appName + '/views/', // 动态模板所在目录
}
Setter.common = {
layerArea: function (url) {
return [window.innerWidth > 1200 ? '30%' : (window.innerWidth > 768 ? '50%' : '99%'), '99%'];
}
};
let localData = layui.data(Setter.appName);
localData.setter && (Setter = $.extend(Setter, localData.setter));
/**
* 获取Tokens
* @returns
*/
Setter.getTokens = function () {
let localData = layui.data(Setter.appName);
return {
access_token: localData.access_token,
refresh_token: localData.refresh_token,
};
}
/**
* 保存Tokens
* @param {*} tokens
*/
Setter.saveTokens = function (tokens) {
if (tokens.access_token)
layui.data(Setter.appName, {
key: "access_token",
value: tokens.access_token,
});
if (tokens.refresh_token)
layui.data(Setter.appName, {
key: "refresh_token",
value: tokens.refresh_token,
});
}
Setter.setMenus = function (menus) {
layui.data(Setter.appName, {
key: "menus",
value: menus,
});
}
Setter.permissions = function (permission) {
layui.data(Setter.appName, {
key: "permission",
value: permission,
});
}
// table.set({
// ajax: function (Options, type) {
// layui.http
// .get(Options.url, Options.data, Options)
// .then((res) => {
// Options.success(res);
// })
// .catch((res) => {
// console.log(res);
// //Options.error(xhr, status, error);
// });
// },
// });
exports("setter", Setter);
});