初始化提交

This commit is contained in:
maticarmy
2025-02-10 10:39:00 +08:00
commit 59cd2c19d1
491 changed files with 54545 additions and 0 deletions

568
app/manager/view/error.php Normal file
View File

@@ -0,0 +1,568 @@
<?php
if (!function_exists('parse_padding')) {
function parse_padding($source)
{
$length = strlen(strval(count($source['source']) + $source['first']));
return 40 + ($length - 1) * 8;
}
}
if (!function_exists('parse_class')) {
function parse_class($name): string
{
$names = explode('\\', $name);
return '<abbr title="' . $name . '">' . end($names) . '</abbr>';
}
}
if (!function_exists('parse_file')) {
function parse_file($file, $line): string
{
return '<a class="toggle" title="' . "{$file} line {$line}" . '">' . basename($file) . " line {$line}" . '</a>';
}
}
if (!function_exists('parse_args')) {
function parse_args($args): string
{
$result = [];
foreach ($args as $key => $item) {
switch (true) {
case is_object($item):
$value = sprintf('<em>object</em>(%s)', parse_class(get_class($item)));
break;
case is_array($item):
if (count($item) > 3) {
$value = sprintf('[%s, ...]', parse_args(array_slice($item, 0, 3)));
} else {
$value = sprintf('[%s]', parse_args($item));
}
break;
case is_string($item):
if (strlen($item) > 20) {
$value = sprintf(
'\'<a class="toggle" title="%s">%s...</a>\'',
htmlentities($item),
htmlentities(substr($item, 0, 20))
);
} else {
$value = sprintf("'%s'", htmlentities($item));
}
break;
case is_int($item):
case is_float($item):
$value = $item;
break;
case is_null($item):
$value = '<em>null</em>';
break;
case is_bool($item):
$value = '<em>' . ($item ? 'true' : 'false') . '</em>';
break;
case is_resource($item):
$value = '<em>resource</em>';
break;
default:
$value = htmlentities(str_replace("\n", '', var_export(strval($item), true)));
break;
}
$result[] = is_int($key) ? $value : "'{$key}' => {$value}";
}
return implode(', ', $result);
}
}
if (!function_exists('echo_value')) {
function echo_value($val)
{
if (is_array($val) || is_object($val)) {
echo htmlentities(json_encode($val, JSON_PRETTY_PRINT));
} elseif (is_bool($val)) {
echo $val ? 'true' : 'false';
} elseif (is_scalar($val)) {
echo htmlentities($val);
} else {
echo 'Resource';
}
}
}
?>
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>系统发生错误</title>
<meta name="robots" content="noindex,nofollow"/>
<style>
/* Base */
body {
color: #333;
font: 16px Verdana, "Helvetica Neue", helvetica, Arial, 'Microsoft YaHei', sans-serif;
margin: 0;
padding: 0 20px 20px;
}
h1 {
margin: 10px 0 0;
font-size: 28px;
font-weight: 500;
line-height: 32px;
}
h2 {
color: #4288ce;
font-weight: 400;
padding: 6px 0;
margin: 6px 0 0;
font-size: 18px;
border-bottom: 1px solid #eee;
}
h3 {
margin: 12px;
font-size: 16px;
font-weight: bold;
}
abbr {
cursor: help;
text-decoration: underline;
text-decoration-style: dotted;
}
a {
color: #868686;
cursor: pointer;
}
a:hover {
text-decoration: underline;
}
.line-error {
background: #f8cbcb;
}
.echo table {
width: 100%;
}
.echo pre {
padding: 16px;
overflow: auto;
font-size: 85%;
line-height: 1.45;
background-color: #f7f7f7;
border: 0;
border-radius: 3px;
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
}
.echo pre > pre {
padding: 0;
margin: 0;
}
/* Exception Info */
.exception {
margin-top: 20px;
}
.exception .message {
padding: 12px;
border: 1px solid #ddd;
border-bottom: 0 none;
line-height: 18px;
font-size: 16px;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
font-family: Consolas, "Liberation Mono", Courier, Verdana, "微软雅黑", serif;
}
.exception .code {
float: left;
text-align: center;
color: #fff;
margin-right: 12px;
padding: 16px;
border-radius: 4px;
background: #999;
}
.exception .source-code {
padding: 6px;
border: 1px solid #ddd;
background: #f9f9f9;
overflow-x: auto;
}
.exception .source-code pre {
margin: 0;
}
.exception .source-code pre ol {
margin: 0;
color: #4288ce;
display: inline-block;
min-width: 100%;
box-sizing: border-box;
font-size: 14px;
font-family: "Century Gothic", Consolas, "Liberation Mono", Courier, Verdana, serif;
padding-left: <?php echo (isset($source) && ! empty($source)) ? parse_padding($source): 40;?> px;
}
.exception .source-code pre li {
border-left: 1px solid #ddd;
height: 18px;
line-height: 18px;
}
.exception .source-code pre code {
color: #333;
height: 100%;
display: inline-block;
border-left: 1px solid #fff;
font-size: 14px;
font-family: Consolas, "Liberation Mono", Courier, Verdana, "微软雅黑", serif;
}
.exception .trace {
padding: 6px;
border: 1px solid #ddd;
border-top: 0 none;
line-height: 16px;
font-size: 14px;
font-family: Consolas, "Liberation Mono", Courier, Verdana, "微软雅黑", serif;
}
.exception .trace h2:hover {
text-decoration: underline;
cursor: pointer;
}
.exception .trace ol {
margin: 12px;
}
.exception .trace ol li {
padding: 2px 4px;
}
.exception div:last-child {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
}
/* Exception Variables */
.exception-var table {
width: 100%;
margin: 12px 0;
box-sizing: border-box;
table-layout: fixed;
word-wrap: break-word;
}
.exception-var table caption {
text-align: left;
font-size: 16px;
font-weight: bold;
padding: 6px 0;
}
.exception-var table caption small {
font-weight: 300;
display: inline-block;
margin-left: 10px;
color: #ccc;
}
.exception-var table tbody {
font-size: 13px;
font-family: Consolas, "Liberation Mono", Courier, "微软雅黑", serif;
}
.exception-var table td {
padding: 0 6px;
vertical-align: top;
word-break: break-all;
}
.exception-var table td:first-child {
width: 28%;
font-weight: bold;
white-space: nowrap;
}
.exception-var table td pre {
margin: 0;
}
/* Copyright Info */
.copyright {
margin-top: 24px;
padding: 12px 0;
border-top: 1px solid #eee;
}
/* SPAN elements with the classes below are added by prettyprint. */
pre.prettyprint .pln {
color: #000
}
/* plain text */
pre.prettyprint .str {
color: #080
}
/* string content */
pre.prettyprint .kwd {
color: #008
}
/* a keyword */
pre.prettyprint .com {
color: #800
}
/* a comment */
pre.prettyprint .typ {
color: #606
}
/* a type name */
pre.prettyprint .lit {
color: #066
}
/* a literal value */
/* punctuation, lisp open bracket, lisp close bracket */
pre.prettyprint .pun, pre.prettyprint .opn, pre.prettyprint .clo {
color: #660
}
pre.prettyprint .tag {
color: #008
}
/* a markup tag name */
pre.prettyprint .atn {
color: #606
}
/* a markup attribute name */
pre.prettyprint .atv {
color: #080
}
/* a markup attribute value */
pre.prettyprint .dec, pre.prettyprint .var {
color: #606
}
/* a declaration; a variable name */
pre.prettyprint .fun {
color: red
}
/* a function name */
</style>
</head>
<body>
<?php if (\think\facade\App::isDebug()) { ?>
<?php foreach ($traces as $index => $trace) { ?>
<div class="exception">
<div class="message">
<div class="info">
<div>
<h2><?php echo "#{$index} [{$trace['code']}]" . sprintf('%s in %s', parse_class($trace['name']), parse_file($trace['file'], $trace['line'])); ?></h2>
</div>
<div><h1><?php echo nl2br(htmlentities($trace['message'])); ?></h1></div>
</div>
</div>
<?php if (!empty($trace['source'])) { ?>
<div class="source-code">
<pre class="prettyprint lang-php">
<ol start="<?php echo $trace['source']['first']; ?>"><!--<?php foreach ((array)$trace['source']['source'] as $key => $value) { ?>--><li class="line-<?php echo " {$index}-" . ($key + $trace['source']['first']) . ($trace['line'] === $key + $trace['source']['first'] ? ' line-error' : ''); ?>"><code><?php echo htmlentities($value); ?></code></li><!--<?php } ?>--></ol>
</pre>
</div>
<?php } ?>
<div class="trace">
<h2 data-expand="<?php echo 0 === $index ? '1' : '0'; ?>">Call Stack</h2>
<ol>
<li><?php echo sprintf('in %s', parse_file($trace['file'], $trace['line'])); ?></li>
<?php foreach ((array)$trace['trace'] as $value) { ?>
<li>
<?php
// Show Function
if ($value['function']) {
echo sprintf('at %s%s%s(%s)', isset($value['class']) ? parse_class($value['class']) : '', $value['type'] ?? '', $value['function'], isset($value['args']) ? parse_args($value['args']) : '');
}
// Show line
if (isset($value['file']) && isset($value['line'])) {
echo sprintf(' in %s', parse_file($value['file'], $value['line']));
}
?>
</li>
<?php } ?>
</ol>
</div>
</div>
<?php } ?>
<?php } else { ?>
<div class="exception">
<div class="info"><h1><?php echo htmlentities(isset($message) ? $message : ''); ?></h1></div>
</div>
<?php } ?>
<?php if (!empty($datas)) { ?>
<div class="exception-var">
<h2>Exception Datas</h2>
<?php foreach ((array)$datas as $label => $value) { ?>
<table>
<?php if (empty($value)) { ?>
<caption><?php echo $label; ?><small>empty</small></caption>
<?php } else { ?>
<caption><?php echo $label; ?></caption>
<tbody>
<?php foreach ((array)$value as $key => $val) { ?>
<tr>
<td><?php echo htmlentities($key); ?></td>
<td><?php echo_value($val); ?></td>
</tr>
<?php } ?>
</tbody>
<?php } ?>
</table>
<?php } ?>
</div>
<?php } ?>
<?php if (!empty($tables)) { ?>
<div class="exception-var">
<h2>Environment Variables</h2>
<?php foreach ((array)$tables as $label => $value) { ?>
<table>
<?php if (empty($value)) { ?>
<caption><?php echo $label; ?><small>empty</small></caption>
<?php } else { ?>
<caption><?php echo $label; ?></caption>
<tbody>
<?php foreach ((array)$value as $key => $val) { ?>
<tr>
<td><?php echo htmlentities($key); ?></td>
<td><?php echo_value($val); ?></td>
</tr>
<?php } ?>
</tbody>
<?php } ?>
</table>
<?php } ?>
</div>
<?php } ?>
<?php if (\think\facade\App::isDebug()) { ?>
<script>
function $(selector, node) {
var elements;
node = node || document;
if (document.querySelectorAll) {
elements = node.querySelectorAll(selector);
} else {
switch (selector.substr(0, 1)) {
case '#':
elements = [node.getElementById(selector.substr(1))];
break;
case '.':
if (document.getElementsByClassName) {
elements = node.getElementsByClassName(selector.substr(1));
} else {
elements = get_elements_by_class(selector.substr(1), node);
}
break;
default:
elements = node.getElementsByTagName();
}
}
return elements;
function get_elements_by_class(search_class, node, tag) {
var elements = [], eles,
pattern = new RegExp('(^|\\s)' + search_class + '(\\s|$)');
node = node || document;
tag = tag || '*';
eles = node.getElementsByTagName(tag);
for (var i = 0; i < eles.length; i++) {
if (pattern.test(eles[i].className)) {
elements.push(eles[i])
}
}
return elements;
}
}
$.getScript = function (src, func) {
var script = document.createElement('script');
script.async = 'async';
script.src = src;
script.onload = func || function () {
};
$('head')[0].appendChild(script);
}
;(function () {
var files = $('.toggle');
var ol = $('ol', $('.prettyprint')[0]);
var li = $('li', ol[0]);
// 短路径和长路径变换
for (var i = 0; i < files.length; i++) {
files[i].ondblclick = function () {
var title = this.title;
this.title = this.innerHTML;
this.innerHTML = title;
}
}
(function () {
var expand = function (dom, expand) {
var ol = $('ol', dom.parentNode)[0];
expand = undefined === expand ? dom.attributes['data-expand'].value === '0' : undefined;
if (expand) {
dom.attributes['data-expand'].value = '1';
ol.style.display = 'none';
dom.innerText = 'Call Stack (展开)';
} else {
dom.attributes['data-expand'].value = '0';
ol.style.display = 'block';
dom.innerText = 'Call Stack (折叠)';
}
};
var traces = $('.trace');
for (var i = 0; i < traces.length; i++) {
var h2 = $('h2', traces[i])[0];
expand(h2);
h2.onclick = function () {
expand(this);
};
}
})();
$.getScript('//cdn.bootcss.com/prettify/r298/prettify.min.js', function () {
prettyPrint();
});
})();
</script>
<?php } ?>
</body>
</html>

View File

@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<title>{block name="title"}{$title|default=''}{if !empty($title)} · {/if}{:sysconf('site_name')}{/block}</title>
<meta charset="utf-8">
<meta name="renderer" content="webkit">
<meta name="format-detection" content="telephone=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=0.4">
<link rel="stylesheet" href="__ROOT__/static/plugs/layui/css/layui.css?at={:date('md')}">
<link rel="stylesheet" href="__ROOT__/static/theme/css/iconfont.css?at={:date('md')}">
<link rel="stylesheet" href="__ROOT__/static/theme/css/console.css?at={:date('md')}">
<link rel="stylesheet" href="__ROOT__/static/extra/style.css?at={:date('md')}">
{block name="style"}{/block}
<script src="__ROOT__/static/plugs/jquery/pace.min.js"></script>
<script src="{:url('admin/api.plugs/script',[],false,false)}"></script>
</head>
<body class="layui-layout-body">
{block name='body'}
<div class="layui-layout layui-layout-admin layui-layout-left-hide">
<div class="layui-body think-bg-white margin-0 padding-0" style="top:0">{block name='content'}{/block}</div>
</div>
{/block}
<script src="__ROOT__/static/plugs/layui/layui.js"></script>
<script src="__ROOT__/static/plugs/require/require.js"></script>
<script src="__ROOT__/static/admin.js"></script>
<script src="__ROOT__/static/extra/script.js"></script>
{block name='script'}{/block}
</body>
</html>

View File

@@ -0,0 +1,16 @@
{extend name="main"}
{block name="content"}
<div class="layui-fluid">
<div class="layui-row layui-col-space15">
<div class="layui-col-md12">
<div class="layui-card">
<div class="layui-card-header">欢迎页面</div>
<div class="layui-card-body">
欢迎使用管理系统{$userid}
</div>
</div>
</div>
</div>
</div>
{/block}

View File

@@ -0,0 +1,23 @@
<div class="layui-card">
{block name='style'}{/block}
{block name='header'}
{notempty name='title'}
<div class="layui-card-header">
<span class="layui-icon font-s10 color-desc margin-right-5">&#xe65b;</span>{$title|lang}
<div class="pull-right">{block name='button'}{/block}</div>
</div>
{/notempty}
{/block}
<div class="layui-card-line"></div>
<div class="layui-card-body">
<div class="layui-card-html">
{notempty name='showErrorMessage'}
<div class="think-box-notify" type="error">
<b>{:lang('系统提示:')}</b><span>{$showErrorMessage|raw}</span>
</div>
{/notempty}
{block name='content'}{/block}
</div>
</div>
{block name='script'}{/block}
</div>

View File

@@ -0,0 +1,74 @@
{extend name='../../admin/view/main'}
{block name='content'}
<form class="layui-form" action="{:sysuri()}" data-auto="true" method="post" autocomplete="off" onsubmit="return false" lay-filter="FormData">
<div class="layui-card">
<div class="layui-card-body">
<div class="layui-form-item">
<label class="layui-form-label">邮箱</label>
<div class="layui-input-block">
<input type="text" name="email" value='{$member.email|default=""}' required lay-verify="required|email" placeholder="请输入邮箱" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">闲鱼订单号</label>
<div class="layui-input-block">
<input type="text" name="order_id" value='{$member.order_id|default=""}' required lay-verify="required" placeholder="请输入闲鱼订单号" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">有效期</label>
<div class="layui-input-block">
<input type="text" name="expire_time" value='{$member.expire_time|default=""}' required lay-verify="required" placeholder="请选择有效期" class="layui-input" id="expire_time">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">可用次数</label>
<div class="layui-input-block">
<input type="number" name="usage_limit" value='{$member.usage_limit|default="0"}' required lay-verify="required|number" placeholder="请输入可用次数" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">已用次数</label>
<div class="layui-input-block">
<input type="number" name="used_count" value='{$member.used_count|default="0"}' lay-verify="number" placeholder="请输入已用次数" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<input type="radio" name="status" value="1" title="正常" {if !isset($member.status) or $member.status eq 1}checked{/if}>
<input type="radio" name="status" value="0" title="禁用" {if isset($member.status) and $member.status eq 0}checked{/if}>
</div>
</div>
</div>
<div class="hr-line-dashed"></div>
<div class="layui-form-item text-center">
{notempty name='member.id'}<input type='hidden' value='{$member.id}' name='id'>{/notempty}
<button class="layui-btn" type="submit" lay-submit lay-filter="*">保存数据</button>
<button type="reset" class="layui-btn layui-btn-primary">重置数据</button>
</div>
</div>
</form>
{/block}
{block name='script'}
<script>
layui.use(['form', 'laydate'], function(){
var form = layui.form;
// 渲染日期时间选择器
layui.laydate.render({
elem: '#expire_time',
type: 'datetime'
});
});
</script>
{/block}

View File

@@ -0,0 +1,92 @@
{extend name='../../admin/view/main'}
{block name='button'}
<!-- 表格工具栏 -->
<div class="layui-btn-container">
<button class='layui-btn layui-btn-sm layui-btn-primary' data-modal='{:url("add")}' data-width="800px" data-height="600px">添加会员</button>
</div>
{/block}
{block name='content'}
<div class="think-box-shadow">
<form class="layui-form layui-form-pane form-search" action="">
<div class="layui-form-item layui-inline">
<label class="layui-form-label">搜索</label>
<div class="layui-input-inline">
<input name="keyword" value="{$get.keyword|default=''}" placeholder="请输入邮箱/订单号" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">状态</label>
<div class="layui-input-inline">
<select name="status">
<option value=''>-- 全部 --</option>
<option value='1'>正常</option>
<option value='0'>禁用</option>
</select>
</div>
</div>
<div class="layui-form-item layui-inline">
<button class="layui-btn layui-btn-primary" lay-submit lay-filter="search"><i class="layui-icon">&#xe615;</i> 搜 索</button>
</div>
</form>
<table id="TableList" lay-filter="TableList"></table>
</div>
{/block}
{block name='script'}
<script type="text/html" id="toolbar">
<a class="layui-btn layui-btn-sm" data-modal='{:url("edit")}?id={{d.id}}' data-width="800px" data-height="600px">编辑</a>
<a class="layui-btn layui-btn-sm layui-btn-danger" data-confirm="确定要删除此会员吗?" data-action='{:url("remove")}' data-value="id#{{d.id}}">删除</a>
</script>
<script>
layui.use(['table','form'], function(){
var table = layui.table;
var form = layui.form;
// 表格渲染
table.render({
elem: '#TableList'
,url: '{:sysuri()}?output=layui.table'
,page: true
,cellMinWidth: 200
,cols: [[
{field: 'id', title: 'ID', width: 70, align: 'center'},
{field: 'email', title: '邮箱', minWidth: 80},
{field: 'order_id', title: '闲鱼订单号', minWidth: 100},
{field: 'expire_time', title: '有效期', minWidth: 100},
{field: 'usage_limit', title: '可用次数', width: 70, align: 'center'},
{field: 'used_count', title: '已用次数', width: 70, align: 'center'},
{field: 'last_login_time', title: '最后登录时间', minWidth: 100},
{field: 'status', title: '状态', width: 100, align: 'center', templet: function(d){
return '<input type="checkbox" name="status" value="'+d.id+'" lay-skin="switch" lay-text="正常|禁用" lay-filter="statusSwitch" '+(d.status == 1 ? 'checked' : '')+'>';
}},
{field: 'create_time', title: '创建时间', minWidth: 180},
{title: '操作', toolbar: '#toolbar', width: 250, align: 'center', fixed: 'right'}
]]
});
// 搜索提交
form.on('submit(search)', function(data){
table.reload('TableList', {where: data.field});
return false;
});
// 监听状态切换
form.on('switch(statusSwitch)', function(obj){
let id = this.value;
let status = obj.elem.checked ? 1 : 0;
$.post('{:url("state")}', {id: id, status: status}, function(res){
if (res.code != 1) {
obj.elem.checked = !obj.elem.checked;
form.render('checkbox');
layer.msg(res.info, {icon: 2});
}
}, 'json');
});
});
</script>
{/block}

View File

@@ -0,0 +1,32 @@
{if isset($list)}
<fieldset>
<legend>条件搜索</legend>
<form class="layui-form layui-form-pane form-search" action="{:request()->url()}" onsubmit="return false" method="get" autocomplete="off">
<div class="layui-form-item layui-inline">
<label class="layui-form-label">搜索关键词</label>
<div class="layui-input-inline">
<input name="keyword" value="{$get.keyword|default=''}" placeholder="请输入用户名/昵称/手机/邮箱" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">会员状态</label>
<div class="layui-input-inline">
<select name="status">
<option value=''>-- 全部状态 --</option>
<option value='1' {if isset($get.status) and $get.status eq 1}selected{/if}>正常</option>
<option value='0' {if isset($get.status) and $get.status eq 0}selected{/if}>禁用</option>
</select>
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">注册时间</label>
<div class="layui-input-inline">
<input data-date-range name="create_time" value="{$get.create_time|default=''}" placeholder="请选择注册时间" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<button class="layui-btn layui-btn-primary"><i class="layui-icon">&#xe615;</i> 搜 索</button>
</div>
</form>
</fieldset>
{/if}

View File

@@ -0,0 +1,294 @@
{extend name="../../admin/view/main"}
{block name='content'}
<div class="think-box-shadow">
<!-- 表单搜索 -->
<form class="layui-form layui-form-pane form-search" action="">
<div class="layui-form-item layui-inline">
<label class="layui-form-label">包名</label>
<div class="layui-input-inline">
<input name="package_name" placeholder="请输入包名" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">应用名称</label>
<div class="layui-input-inline">
<input name="name" placeholder="请输入应用名称" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">状态</label>
<div class="layui-input-inline">
<select name="status">
<option value="">所有状态</option>
<option value="1">启用</option>
<option value="0">禁用</option>
</select>
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">创建人</label>
<div class="layui-input-inline">
<input name="username" placeholder="请输入创建人" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">添加时间</label>
<div class="layui-input-inline">
<input name="add_time" id="add_time" placeholder="请选择添加时间" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<button class="layui-btn layui-btn-primary" type="button" lay-submit lay-filter="search-btn">
<i class="layui-icon">&#xe615;</i> 搜 索
</button>
<button class="layui-btn layui-btn-primary" type="reset" id="resetSearch">重置</button>
</div>
</form>
<!-- 数据表格 -->
<table id="currentTable" lay-filter="currentTable"></table>
</div>
<!-- 表单模板 -->
<script type="text/html" id="editForm">
<form class="layui-form" lay-filter="editForm" style="padding: 30px;">
<div class="layui-form-item">
<label class="layui-form-label" style="width: 100px;">应用名称</label>
<div class="layui-input-block" style="margin-left: 130px;">
<input type="text" name="name" lay-verify="required" placeholder="请输入应用名称" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" style="width: 100px;">包名</label>
<div class="layui-input-block" style="margin-left: 130px;">
<input type="text" name="package_name" lay-verify="required" placeholder="请输入包名" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" style="width: 100px;">状态</label>
<div class="layui-input-block" style="margin-left: 130px;">
<input type="radio" name="status" value="1" title="启用" checked>
<input type="radio" name="status" value="0" title="禁用">
</div>
</div>
<div class="layui-form-item text-center" style="margin-top: 40px;">
<input type="hidden" name="id">
<button class="layui-btn layui-btn-lg" lay-submit lay-filter="formSubmit">保存</button>
<button type="button" class="layui-btn layui-btn-lg layui-btn-danger" onclick="layer.closeAll()">取消</button>
</div>
</form>
</script>
{/block}
{block name='script'}
<!-- 表格操作列 -->
<script type="text/html" id="toolbar">
<div class="layui-btn-container">
{if auth("edit")}
<a class="layui-btn layui-btn-sm" lay-event="edit">编辑</a>
{/if}
{if auth("remove")}
<a class="layui-btn layui-btn-sm layui-btn-danger" lay-event="delete">删除</a>
{/if}
</div>
</script>
<script>
layui.use(['table', 'form', 'laydate'], function () {
var table = layui.table;
var form = layui.form;
var laydate = layui.laydate;
// 日期范围
laydate.render({
elem: '#add_time',
type: 'date',
range: true,
trigger: 'click'
});
// 数据表格初始化
table.render({
elem: '#currentTable',
url: '{:request()->url()}',
method: 'post',
defaultToolbar: ['filter', 'print', 'exports'],
toolbar: ['<div class="layui-btn-container">',
'{if auth("add")}',
'<button class="layui-btn layui-btn-sm layui-btn-normal" lay-event="add"><i class="layui-icon">&#xe654;</i> 添加包名</button>',
'{/if}',
'</div>'
].join(''),
cols: [[
{type: "checkbox"},
{field: 'id', width: 80, title: 'ID', sort: true},
{field: 'name', minWidth: 150, title: '应用名称'},
{field: 'package_name', minWidth: 200, title: '包名'},
{field: 'username', width: 100, title: '创建人'},
{field: 'status', width: 100, title: '状态', templet: function(d){
return '<input type="checkbox" name="status" value="' + d.id + '" lay-skin="switch" lay-text="启用|禁用" lay-filter="status" ' + (d.status == 1 ? 'checked' : '') + '>';
}},
{field: 'add_time', minWidth: 170, title: '添加时间', templet: function(d){
return d.add_time ? layui.util.toDateString(d.add_time * 1000, 'yyyy-MM-dd HH:mm:ss') : '';
}},
{field: 'update_time', minWidth: 170, title: '更新时间', templet: function(d){
return d.update_time ? layui.util.toDateString(d.update_time * 1000, 'yyyy-MM-dd HH:mm:ss') : '';
}},
{title: '操作', minWidth: 120, toolbar: '#toolbar', align: "center", fixed: 'right'}
]],
page: true,
limit: 15,
limits: [10, 15, 20, 25, 50, 100]
});
// 搜索提交
form.on('submit(search-btn)', function (data) {
table.reload('currentTable', {
page: {curr: 1},
where: data.field,
method: 'post'
});
return false;
});
// 重置搜索
$('#resetSearch').click(function() {
// 重置表单
$('.form-search')[0].reset();
// 重载表格
table.reload('currentTable', {
page: {curr: 1},
where: {},
method: 'post'
});
});
// 监听表单提交
form.on('submit(formSubmit)', function (data) {
var url = data.field.id ? '{:url("edit")}' : '{:url("add")}';
$.ajax({
url: url,
type: 'post',
data: data.field,
success: function(res) {
if (res.code === 1) {
layer.closeAll();
layer.msg(res.info, {icon: 1, time: 1000});
table.reload('currentTable');
} else {
layer.msg(res.info, {icon: 2, time: 2000});
}
},
error: function() {
layer.msg('网络错误,请稍后重试!', {icon: 2, time: 2000});
}
});
return false;
});
// 监听工具条
table.on('tool(currentTable)', function (obj) {
var data = obj.data;
if (obj.event === 'edit') {
showForm('编辑包名', data);
} else if (obj.event === 'delete') {
layer.confirm('确定要删除该包名吗?', {
title: '删除确认',
btn: ['确定', '取消']
}, function (index) {
$.ajax({
url: '{:url("remove")}',
type: 'post',
data: {id: data.id},
success: function(res) {
if (res.code === 1) {
layer.msg(res.info, {icon: 1, time: 1000});
table.reload('currentTable');
} else {
layer.msg(res.info, {icon: 2, time: 2000});
}
layer.close(index);
},
error: function() {
layer.msg('网络错误,请稍后重试!', {icon: 2, time: 2000});
layer.close(index);
}
});
});
}
});
// 监听头工具栏事件
table.on('toolbar(currentTable)', function(obj){
if(obj.event === 'add'){
showForm('添加包名');
}
});
// 显示表单
function showForm(title, data) {
layer.open({
type: 1,
title: title,
area: ['800px', '500px'],
offset: '50px',
shadeClose: true,
content: $('#editForm').html(),
success: function(layero, index) {
$(layero).find('.layui-form').css('margin', '15px');
$(layero).find('.layui-input-block').css('max-width', '500px');
form.render();
if(data) {
form.val('editForm', data);
}
}
});
}
// 监听状态切换
form.on('switch(status)', function(obj){
var id = this.value;
var status = obj.elem.checked ? 1 : 0;
layer.confirm('确定要' + (status == 1 ? '启用' : '禁用') + '该包名吗?', {
title: '操作确认',
btn: ['确定', '取消']
}, function(index){
$.ajax({
url: '{:url("state")}',
type: 'post',
data: {
id: id,
status: status
},
success: function(res) {
if (res.code === 1) {
layer.msg(res.info, {icon: 1, time: 1000});
table.reload('currentTable');
} else {
layer.msg(res.info, {icon: 2, time: 2000});
// 状态切换失败,恢复开关状态
obj.elem.checked = !obj.elem.checked;
form.render('checkbox');
}
layer.close(index);
},
error: function() {
layer.msg('网络错误请稍后重试2', {icon: 2, time: 2000});
// 发生错误,恢复开关状态
obj.elem.checked = !obj.elem.checked;
form.render('checkbox');
layer.close(index);
}
});
}, function(){
// 取消操作,恢复开关状态
obj.elem.checked = !obj.elem.checked;
form.render('checkbox');
});
});
});
</script>
{/block}

View File

@@ -0,0 +1,451 @@
<!-- 权限详情弹窗模板 -->
<div class="detail-container" style="padding: 20px;" data-user-id="{{d.userId}}">
<div class="layui-row layui-col-space15">
<!-- 左侧已授权包名列表 -->
<div class="layui-col-md6">
<div class="layui-card">
<div class="layui-card-header">
授权的包名列表
<div class="layui-inline" style="width: 200px; margin-left: 10px;">
<input type="text" id="searchAuthorized" placeholder="输入包名或应用名称搜索" class="layui-input">
</div>
</div>
<div class="layui-card-body auth-table-container">
<table id="authorizedTable" lay-filter="authorizedTable"></table>
</div>
</div>
</div>
<!-- 右侧可授权包名列表 -->
<div class="layui-col-md6">
<div class="layui-card">
<div class="layui-card-header">
可授权的包名列表
<span class="layui-badge layui-bg-blue">仅显示启用状态的包名</span>
<div class="layui-inline" style="width: 200px; margin-left: 10px;">
<input type="text" id="searchPackage" placeholder="输入包名或应用名称搜索" class="layui-input">
</div>
</div>
<div class="layui-card-body auth-table-container">
<table id="unauthorizedTable" lay-filter="unauthorizedTable"></table>
</div>
</div>
</div>
</div>
<!-- 底部按钮 -->
<div class="layui-form-item text-center" style="margin-top:15px;">
<button class="layui-btn layui-btn-primary" onclick="closeAndRefresh()">关闭窗口</button>
</div>
</div>
<!-- 已授权表格工具栏 -->
<script type="text/html" id="authDetailAuthorizedToolbar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-event="batchRemove" lay-tips="移除选中的包名权限">
<i class="layui-icon">&#xe640;</i> 批量移除
</button>
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-event="removeAll" lay-tips="移除该管理员的所有包名权限">
<i class="layui-icon">&#xe640;</i> 一键全部移除
</button>
</div>
</script>
<!-- 未授权表格工具栏 -->
<script type="text/html" id="authDetailUnauthorizedToolbar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-event="batchAuth" lay-tips="授权选中的包名">
<i class="layui-icon">&#xe654;</i> 批量授权
</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-event="authAll" lay-tips="授权所有未授权的包名">
<i class="layui-icon">&#xe674;</i> 一键全部授权
</button>
</div>
</script>
<!-- 表格行工具条 -->
<script type="text/html" id="authDetailTableBar">
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="remove">移除</a>
</script>
<script type="text/html" id="authDetailUnAuthTableBar">
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="auth">授权</a>
</script>
<!-- 表格初始化脚本 -->
<script>
window.initAuthDetailTables = function(userId) {
layui.use(['table', 'form'], function() {
let table = layui.table;
// 初始化已授权表格
table.render({
elem: '#authorizedTable',
url: '{:url("getAuthDetailData")}',
where: {
user_id: userId,
type: 'authorized'
},
toolbar: '#authDetailAuthorizedToolbar',
defaultToolbar: ['filter'],
page: true,
limit: 10,
limits: [10, 20, 50, 100],
cols: [[
{type: 'checkbox', width: 50},
{field: 'package_name', title: '包名', minWidth: 250},
{field: 'name', title: '名称', minWidth: 200},
{field: 'create_at', title: '授权时间', width: 160},
{title: '操作', toolbar: '#authDetailTableBar', width: 80, align: 'center', fixed: 'right'}
]]
});
// 初始化未授权表格
table.render({
elem: '#unauthorizedTable',
url: '{:url("getAuthDetailData")}',
where: {
user_id: userId,
type: 'unauthorized'
},
toolbar: '#authDetailUnauthorizedToolbar',
defaultToolbar: ['filter'],
page: true,
limit: 10,
limits: [10, 20, 50, 100],
cols: [[
{type: 'checkbox', width: 50},
{field: 'package_name', title: '包名', minWidth: 250},
{field: 'name', title: '名称', minWidth: 200},
{title: '操作', toolbar: '#authDetailUnAuthTableBar', width: 80, align: 'center', fixed: 'right'}
]]
});
// 绑定搜索事件
bindSearchEvents(table, userId);
// 绑定工具条事件
bindTableEvents(table, userId);
});
};
// 绑定搜索事件
function bindSearchEvents(table, userId) {
let searchTimeout;
$('#searchAuthorized').on('input', function() {
clearTimeout(searchTimeout);
let value = this.value;
searchTimeout = setTimeout(function() {
table.reload('authorizedTable', {
where: {
user_id: userId,
type: 'authorized',
keyword: value
},
page: {curr: 1}
});
}, 300);
});
$('#searchPackage').on('input', function() {
clearTimeout(searchTimeout);
let value = this.value;
searchTimeout = setTimeout(function() {
table.reload('unauthorizedTable', {
where: {
user_id: userId,
type: 'unauthorized',
keyword: value
},
page: {curr: 1}
});
}, 300);
});
}
// 绑定工具条事件
function bindTableEvents(table, userId) {
// 已授权表格工具条事件
table.on('toolbar(authorizedTable)', function(obj) {
if (obj.event === 'batchRemove') {
let checkStatus = table.checkStatus('authorizedTable');
if (checkStatus.data.length === 0) {
layer.msg('请选择要移除的包名', {icon: 2});
return;
}
layer.confirm('确定要移除选中的 ' + checkStatus.data.length + ' 个包名权限吗?', {
title: '批量移除确认',
btn: ['确定移除', '取消操作'],
skin: 'layer-danger'
}, function(index) {
let loadIndex = layer.load(2);
let packageIds = checkStatus.data.map(item => item.id);
$.post('{:url("batchRemoveAuth")}', {
user_id: userId,
package_ids: packageIds
}, function(res) {
layer.close(loadIndex);
layer.msg(res.info);
if (res.code === 1) {
// 刷新两个表格
table.reload('authorizedTable');
table.reload('unauthorizedTable');
}
});
layer.close(index);
});
} else if (obj.event === 'removeAll') {
layer.confirm('确定要移除该管理员的所有包名权限吗?<br>此操作不可恢复!', {
title: '危险操作',
btn: ['确定移除', '取消操作'],
icon: 2,
skin: 'layer-danger'
}, function(index) {
let loadIndex = layer.load(2);
$.post('{:url("clearAuth")}', {
user_ids: [userId]
}, function(res) {
layer.close(loadIndex);
layer.msg(res.info);
if (res.code === 1) {
// 刷新两个表格
table.reload('authorizedTable');
table.reload('unauthorizedTable');
}
});
layer.close(index);
});
}
});
// 未授权表格工具条事件
table.on('toolbar(unauthorizedTable)', function(obj) {
if (obj.event === 'batchAuth') {
let checkStatus = table.checkStatus('unauthorizedTable');
if (checkStatus.data.length === 0) {
layer.msg('请选择要授权的包名', {icon: 2});
return;
}
layer.confirm('确定要授权选中的 ' + checkStatus.data.length + ' 个包名吗?', {
title: '批量授权确认',
btn: ['确定授权', '取消操作']
}, function(index) {
let loadIndex = layer.load(2);
let packageIds = checkStatus.data.map(item => item.id);
$.post('{:url("addAuth")}', {
user_id: userId,
package_ids: packageIds
}, function(res) {
layer.close(loadIndex);
layer.msg(res.info);
if (res.code === 1) {
// 刷新两个表格
table.reload('authorizedTable');
table.reload('unauthorizedTable');
}
});
layer.close(index);
});
} else if (obj.event === 'authAll') {
layer.confirm('确定要授权所有可用的包名吗?', {
title: '一键全部授权',
btn: ['确定授权', '取消操作'],
icon: 3
}, function(index) {
let loadIndex = layer.load(2);
$.post('{:url("addAuth")}', {
user_id: userId,
is_all: 1
}, function(res) {
layer.close(loadIndex);
layer.msg(res.info);
if (res.code === 1) {
// 刷新两个表格
table.reload('authorizedTable');
table.reload('unauthorizedTable');
}
});
layer.close(index);
});
}
});
// 单个移除事件
table.on('tool(authorizedTable)', function(obj) {
if (obj.event === 'remove') {
layer.confirm('确定要移除此包名权限吗?', function(index) {
let loadIndex = layer.load(2);
$.post('{:url("removeAuth")}', {
user_id: userId,
package_id: obj.data.id
}, function(res) {
layer.close(loadIndex);
layer.msg(res.info);
if (res.code === 1) {
obj.del();
// 刷新未授权表格
table.reload('unauthorizedTable');
}
});
layer.close(index);
});
}
});
// 单个授权事件
table.on('tool(unauthorizedTable)', function(obj) {
if (obj.event === 'auth') {
let loadIndex = layer.load(2);
$.post('{:url("addAuth")}', {
user_id: userId,
package_ids: [obj.data.id]
}, function(res) {
layer.close(loadIndex);
layer.msg(res.info);
if (res.code === 1) {
// 刷新两个表格
table.reload('authorizedTable');
table.reload('unauthorizedTable');
}
});
}
});
}
// 添加关闭并刷新的函数
function closeAndRefresh() {
// 关闭当前弹窗
layer.closeAll();
// 刷新父页面
parent.location.reload();
}
</script>
<!-- 弹窗专用样式 -->
<style>
.detail-container {
min-height: 600px;
display: flex;
flex-direction: column;
position: relative;
z-index: 1;
}
.detail-container .layui-row {
flex: 1;
}
.detail-container .layui-card {
margin-bottom: 0;
height: 100%;
display: flex;
flex-direction: column;
}
.detail-container .layui-card-body {
flex: 1;
padding: 15px;
display: flex;
flex-direction: column;
}
.auth-table-container {
min-height: 550px;
position: relative;
}
/* 搜索框样式 */
.detail-container .layui-inline {
margin: 0;
}
.detail-container .layui-input {
height: 30px;
line-height: 30px;
}
/* 底部按钮样式 */
.detail-container .layui-form-item.text-center {
padding: 10px 0 0;
margin: 0;
border-top: 1px solid #f0f0f0;
}
/* 表格美化样式 */
.auth-table-container .layui-table-tool {
background-color: #f8f8f8;
}
.auth-table-container .layui-table-page {
background-color: #f8f8f8;
}
/* 表格头部样式 */
.auth-table-container .layui-table-header {
background-color: #fafafa;
}
/* 表格内容区域样式 */
.auth-table-container .layui-table-body {
background: #fff;
}
/* 工具栏按钮样式优化 */
.auth-table-container .layui-table-tool {
padding: 10px 15px;
}
.auth-table-container .layui-table-tool .layui-btn {
height: 30px;
line-height: 30px;
padding: 0 15px;
font-size: 12px;
margin-right: 8px;
}
.auth-table-container .layui-table-tool .layui-btn:last-child {
margin-right: 0;
}
.auth-table-container .layui-table-tool .layui-btn .layui-icon {
font-size: 14px;
margin-right: 3px;
}
/* 危险操作按钮样式 */
.auth-table-container .layui-table-tool .layui-btn-danger {
background-color: #FF5722;
}
.auth-table-container .layui-table-tool .layui-btn-danger:hover {
background-color: #ff4208;
}
/* 一键全部授权按钮样式 */
.auth-table-container .layui-table-tool .layui-btn-warm {
background-color: #FFB800;
}
.auth-table-container .layui-table-tool .layui-btn-warm:hover {
background-color: #ff9900;
}
/* 弹窗样式 */
.layer-danger .layui-layer-title {
background-color: #FF5722 !important;
color: #fff !important;
}
.layer-danger .layui-layer-btn0 {
background-color: #FF5722 !important;
border-color: #FF5722 !important;
}
/* 确保弹窗控制按钮在正确位置 */
.layui-layer-page .layui-layer-setwin {
position: absolute !important;
right: 15px !important;
top: 16px !important;
}
</style>

View File

@@ -0,0 +1,786 @@
{extend name="../../admin/view/main"}
{block name='content'}
<style>
/* 基础样式 */
.layui-card-body {
padding: 15px;
}
.layui-table-tool {
background-color: #f8f8f8;
}
.layui-table-page {
background-color: #f8f8f8;
}
.layui-table-box {
background: #fff;
border-radius: 2px;
box-shadow: 0 1px 2px rgba(0,0,0,.1);
}
/* 时间选择器样式 */
#dateRange {
width: 280px;
}
.layui-inline .layui-input {
height: 32px;
line-height: 32px;
}
.layui-inline .layui-btn {
height: 32px;
line-height: 32px;
}
</style>
<!-- 在主容器前添加操作指南 -->
<div class="think-box-shadow" style="margin-bottom: 15px; padding: 15px;">
<h3 class="layui-inline" style="margin-right: 15px;">操作指南</h3>
<button class="layui-btn layui-btn-sm" id="showGuide">展开/收起</button>
<div class="guide-content layui-hide" style="margin-top: 10px;">
<div class="layui-card">
<div class="layui-card-body">
<div class="layui-elem-quote" style="border-left: 5px solid #009688;">
<h4>基本说明:</h4>
<p>本页面用于管理不同管理员的包名权限,可以进行查看、添加、移除等操作。</p>
</div>
<div class="layui-elem-quote" style="border-left: 5px solid #FFB800;">
<h4>操作步骤:</h4>
<ol>
<li>左侧列表显示所有可配置权限的管理员</li>
<li>绿色标签表示已授权包名数量,灰色表示未授权</li>
<li>点击【查看权限】可以查看和修改该管理员的包名权限</li>
<li>击【批量授权】可以同时给多个管理员授权相同的包名</li>
<li>点击【清空权限】可以清除选中管理员的所有包名权限</li>
</ol>
</div>
<div class="layui-elem-quote" style="border-left: 5px solid #FF5722;">
<h4>权限配置窗口说明:</h4>
<ol>
<li>左侧显示已授权的包名列表,可以搜索和移除</li>
<li>右侧显示可授权的包名列表,仅显示启用状态的包名</li>
<li>在右侧勾选包名后点击【保存配置】即可添加授权</li>
<li>两侧都支持搜索功能,可以快速查找包名</li>
<li>所有操作都会实时更新显示结果</li>
</ol>
</div>
<div class="layui-elem-quote" style="border-left: 5px solid #01AAED;">
<h4>注意事项:</h4>
<ol>
<li>批量授权会覆盖已有的权限配置,请谨慎操作</li>
<li>清空权限操作不可恢复,请确认后再操作</li>
<li>建议先使用搜索功能查找包名,再进行授权作</li>
<li>可以使用全选功能快速选择多个管理员或包名</li>
<li>如有疑问请联系技术支持</li>
</ol>
</div>
</div>
</div>
</div>
</div>
<div class="think-box-shadow main-container">
<div class="layui-row layui-col-space15">
<!-- 左侧管理员列表 -->
<div class="layui-col-md3">
<div class="layui-card">
<div class="layui-card-header">
管理员列表
<div class="layui-layout-right" style="margin:5px;">
<span class="layui-badge layui-bg-blue" style="padding: 5px 10px;"> {$users|count} </span>
</div>
</div>
<div class="layui-card-body user-list-container">
<form class="layui-form" lay-filter="userForm">
<!-- 添加搜索框 -->
<div class="search-box">
<div class="layui-form-item" style="margin-bottom: 10px;">
<div class="layui-input-block" style="margin-left: 0;">
<input type="text" id="searchUsers" placeholder="输入管理员名称搜索" class="layui-input">
</div>
</div>
</div>
<!-- 全选复选框 -->
<div class="layui-form-item" style="padding: 10px;">
<input type="checkbox" lay-filter="checkAllUsers" title="全选" lay-skin="primary" lay-tips="选中/取消选中所有管理员">
</div>
<!-- 管理员列表 -->
<div class="user-list">
{foreach $users as $user}
<div class="layui-form-item">
<input type="checkbox" name="user_ids[]" value="{$user.id}" title="{$user.username}" lay-skin="primary">
{if isset($authMap[$user.id])}
<span class="layui-badge layui-bg-green" style="margin-left:10px; padding: 4px 8px;">已授权 {$authMap[$user.id]|count} 个包名</span>
<a class="layui-btn layui-btn-normal layui-btn-sm" data-user="{$user.id}" lay-event="showAuth" style="margin-left:5px;" lay-tips="点击查看和修改该管理员的包名权限配置">
<i class="layui-icon layui-icon-edit"></i> 查看权限
</a>
{else}
<span class="layui-badge layui-bg-gray" style="margin-left:10px; padding: 4px 8px;">未授权</span>
<a class="layui-btn layui-btn-warm layui-btn-sm" data-user="{$user.id}" lay-event="showAuth" style="margin-left:5px;" lay-tips="点击为该管理员添加包名权限">
<i class="layui-icon layui-icon-add-1"></i> 添加权限
</a>
{/if}
</div>
{/foreach}
</div>
</form>
</div>
</div>
</div>
<!-- 右侧包名列表 -->
<div class="layui-col-md9">
<div class="layui-card">
<div class="layui-card-header">
包名权限配置
<div class="layui-layout-right header-btns">
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="batchAuth" lay-tips="选中管理员和包名后,点击此处进行批量授权操作">
<i class="layui-icon layui-icon-add-circle"></i> 批量授权
</button>
<button class="layui-btn layui-btn-danger" id="clearAuth" lay-tips="清空选中管理员的所有包名权限,请谨慎操作">
<i class="layui-icon layui-icon-delete"></i> 清空权限
</button>
</div>
</div>
<div class="layui-card-body">
<!-- 搜索框 -->
<div class="layui-form-item" style="margin-bottom: 10px;">
<div class="layui-inline" style="width: 250px;">
<input type="text" id="searchPackages" placeholder="输入包名或应用名称搜索" class="layui-input">
</div>
<div class="layui-inline">
<input type="text" id="dateRange" placeholder="选择时间范围" class="layui-input" readonly lay-tips="选择时间范围进行筛选">
</div>
<div class="layui-inline">
<button class="layui-btn layui-btn-primary" id="resetFilter" lay-tips="清空所有筛选条件">重置</button>
</div>
</div>
<!-- 表格 -->
<table id="packageTable" lay-filter="packageTable"></table>
</div>
</div>
</div>
</div>
</div>
<!-- 修改权限详情模板引用 -->
<script type="text/html" id="authDetailTpl">
{include file="package_auth/auth_detail"}
</script>
<!-- JavaScript 部分 -->
<script>
// 全局配置
var CONFIG = {
pageSize: 10,
loadingTime: 1000,
debounceDelay: 300
};
// 消息提示
var MSG = {
networkError: '网络错误,请稍后重试!',
selectAdmin: '请选择管理员',
selectPackage: '请选择包名',
confirmClear: '确定要清空权限吗?此操作不可恢复!',
confirmBatchAuth: '确定要批量授权吗?<br>此操作将覆盖已有权限!',
getUserFailed: '获取用户信息失败',
authSuccess: '授权成功!',
clearSuccess: '清空成功!'
};
// 防抖函数
function debounce(fn, delay) {
let timer = null;
return function() {
let context = this, args = arguments;
clearTimeout(timer);
timer = setTimeout(function() {
fn.apply(context, args);
}, delay);
};
}
// 成功提示
function showSuccess(msg, callback) {
layer.msg(msg, {
icon: 1,
time: CONFIG.loadingTime,
shade: 0.3
}, callback);
}
// 错误提示
function showError(msg) {
layer.msg(msg, {
icon: 2,
time: 2000,
shade: 0.3
});
}
// 表格事件处理对象
var TableEventHandler = {
// 工具条事件
tool: function(obj, callback) {
var event = obj.event;
var handlers = {
remove: function() {
layer.confirm('确定要移除此包名权限吗?', {
title: '操作确认',
btn: ['确定', '取消']
}, function(index) {
callback && callback(obj, index);
});
}
};
handlers[event] && handlers[event]();
},
// 表头工具栏事件
toolbar: function(obj, callback) {
var event = obj.event;
var handlers = {
batchRemove: function() {
var checkStatus = table.checkStatus(obj.config.id);
if (!checkStatus.data.length) {
layer.msg('请选择要移除的包名', {icon: 2});
return;
}
layer.confirm('确定要移除选中的 ' + checkStatus.data.length + ' 个包名权限吗?', {
title: '批量移除确认',
btn: ['确定', '取消']
}, function(index) {
callback && callback(checkStatus.data, index);
});
}
};
handlers[event] && handlers[event]();
}
};
// 表格缓存对象
var TableCache = {
data: {},
set: function(key, value) {
this.data[key] = value;
// 最多缓存100条数据
var keys = Object.keys(this.data);
if (keys.length > 100) {
delete this.data[keys[0]];
}
},
get: function(key) {
return this.data[key];
},
clear: function() {
this.data = {};
}
};
// 基础表格配置
var baseTableConfig = {
page: true,
limit: 15,
loading: true,
text: {
none: '暂无数据'
},
size: 'sm'
};
layui.use(['table', 'form', 'laydate'], function() {
let table = layui.table,
form = layui.form,
laydate = layui.laydate;
// 将 table 绑定到全局,以便其他函数使用
window.packageAuthTable = table;
// 初始化时间选择器
laydate.render({
elem: '#dateRange',
type: 'datetime',
range: true,
trigger: 'click',
done: function(value, date){
if(value) {
var dates = value.split(' - ');
table.reload('packageTable', {
where: {
start_time: dates[0],
end_time: dates[1]
},
page: {curr: 1}
});
}
}
});
// 重置筛选
$('#resetFilter').on('click', function(){
$('#searchPackages').val('');
$('#dateRange').val('');
table.reload('packageTable', {
where: {
keyword: '',
start_time: '',
end_time: ''
},
page: {curr: 1}
});
});
// 优化搜索功能
$('#searchPackages').on('input', debounce(function(){
var value = this.value;
var dateRange = $('#dateRange').val();
var dates = dateRange ? dateRange.split(' - ') : ['', ''];
table.reload('packageTable', {
where: {
keyword: value,
start_time: dates[0],
end_time: dates[1]
},
page: {curr: 1}
});
}, 300));
// 初始化包名权限配置表格
table.render({
elem: '#packageTable',
url: '{:url("getPackageList")}',
method: 'post',
toolbar: true,
defaultToolbar: ['filter', 'exports'],
cols: [[
{type: 'checkbox', width: 50},
{field: 'package_name', title: '包名', sort: true},
{field: 'name', title: '应用名称'}
]],
page: true,
limit: 20,
limits: [10, 20, 50, 100],
height: 'full-120',
text: {
none: '暂无包名数据'
},
size: 'sm'
});
// 管理员全选
form.on('checkbox(checkAllUsers)', function(data){
var checked = data.elem.checked;
$('input[name="user_ids[]"]').prop('checked', checked);
form.render('checkbox');
});
// 启用包名全选
form.on('checkbox(checkAllEnabled)', function(data){
var checked = data.elem.checked;
$(data.elem).closest('.layui-form-item').nextUntil('.layui-form-item:has(label:contains("禁用包名"))')
.find('input[type="checkbox"]').prop('checked', checked);
form.render('checkbox');
});
// 禁用包全选
form.on('checkbox(checkAllDisabled)', function(data){
var checked = data.elem.checked;
$(data.elem).closest('.layui-form-item').nextAll()
.find('input[type="checkbox"]').prop('checked', checked);
form.render('checkbox');
});
// 批量授权按钮事件
form.on('submit(batchAuth)', function(data) {
let userIds = [];
$('input[name="user_ids[]"]:checked').each(function() {
userIds.push($(this).val());
});
if(userIds.length === 0) {
layer.msg('请选择要授权的管理员', {icon: 2});
return false;
}
// 使用全局的 table 变量
let checkStatus = window.packageAuthTable.checkStatus('packageTable');
let packageIds = checkStatus.data.map(item => item.id);
if(packageIds.length === 0) {
layer.msg('请选择要授权的包名', {icon: 2});
return false;
}
layer.confirm('确定要批量授权吗?<br>此操作将覆盖已有权限!', {
title: '操作确认',
btn: ['确定', '取消'],
icon: 3
}, function(index) {
let loadIndex = layer.load(2);
$.ajax({
url: '{:url("batchAuth")}',
type: 'post',
data: {
user_ids: userIds,
package_ids: packageIds
},
success: function(res) {
layer.close(loadIndex);
if(res.code === 1) {
layer.msg(res.info, {icon: 1, time: 1000}, function() {
location.reload();
});
} else {
layer.msg(res.info, {icon: 2, time: 2000});
}
layer.close(index);
}
});
});
return false;
});
// 清空权限按钮事件
$('#clearAuth').click(function() {
let userIds = [];
$('input[name="user_ids[]"]:checked').each(function() {
userIds.push($(this).val());
});
if(userIds.length === 0) {
layer.msg('请选择要清空权限的管理员', {icon: 2});
return false;
}
layer.confirm('确定要清空所选管理员的权限吗?<br>此操作不可恢复!', {
title: '危险操作',
btn: ['确定清空', '取消'],
icon: 2
}, function(index) {
let loadIndex = layer.load(2);
$.ajax({
url: '{:url("clearAuth")}',
type: 'post',
data: {
user_ids: userIds
},
success: function(res) {
layer.close(loadIndex);
if(res.code === 1) {
layer.msg(res.info, {icon: 1, time: 1000}, function() {
location.reload();
});
} else {
layer.msg(res.info, {icon: 2, time: 2000});
}
layer.close(index);
}
});
});
});
// 操作指南展开/收起
$('#showGuide').click(function(){
var $content = $('.guide-content');
if ($content.hasClass('layui-hide')) {
$content.removeClass('layui-hide').hide().slideDown();
// 存储状态到 localStorage
localStorage.setItem('packageAuthGuideShow', '1');
} else {
$content.slideUp(function(){
$(this).addClass('layui-hide');
});
// 存储状态到 localStorage
localStorage.setItem('packageAuthGuideShow', '0');
}
});
// 页面加载时检查是否需要显示操作指南
if (localStorage.getItem('packageAuthGuideShow') === '1') {
$('.guide-content').removeClass('layui-hide');
}
// 初始化按钮提示
$('[lay-tips]').each(function(){
var tips = $(this).attr('lay-tips');
if(tips) {
layui.use('layer', function(){
var layer = layui.layer;
$(this).on('mouseenter', function(){
this.index = layer.tips(tips, this, {
tips: [1, '#3595CC'],
time: 0
});
}).on('mouseleave', function(){
layer.close(this.index);
});
}.bind(this));
}
});
});
// 辅助函数
function viewAuth(userId, username){
$('[data-user="' + userId + '"][lay-event="showAuth"]').click();
}
function clearSingleAuth(userId){
layer.confirm('确定要清空此管理员的权限吗?', {
title: '操作确认',
btn: ['确定', '取消'],
icon: 3,
skin: 'layui-layer-molv'
}, function(index){
var loadIndex = layer.load(2);
$.ajax({
url: '{:url("clearAuth")}',
type: 'post',
data: {
user_ids: [userId]
},
success: function(res){
layer.close(loadIndex);
if(res.code === 1){
layer.msg(res.info, {icon: 1, time: 1000}, function(){
location.reload();
});
} else {
layer.msg(res.info, {icon: 2, time: 2000});
}
layer.close(index);
}
});
});
}
// 将权限详情相关的函数抽取出来
function showAuthDetail(userId, username) {
layer.open({
type: 1,
title: username + ' 的权限配置',
area: ['1200px', '80%'],
offset: '50px',
moveType: 1,
content: $('#authDetailTpl').html(),
success: function(layero) {
window.initAuthDetailTables(userId);
// 隐藏默认的关闭按钮
$(layero).find('.layui-layer-setwin').css({
'display': 'none'
});
}
});
}
// 绑定查看权限按钮事件
$(document).on('click', '[lay-event="showAuth"]', function() {
let userId = $(this).data('user');
let username = $(this).closest('.layui-form-item').find('input[type="checkbox"]').attr('title');
showAuthDetail(userId, username);
});
</script>
<!-- 添加一些样式 -->
<style>
.guide-content ol {
padding-left: 20px;
margin: 10px 0;
}
.guide-content ol li {
line-height: 28px;
color: #666;
}
.guide-content h4 {
margin: 5px 0;
color: #333;
}
.layui-elem-quote {
margin-bottom: 10px;
}
.context-menu-layer {
box-shadow: 0 2px 8px rgba(0,0,0,.1);
background: none;
}
.context-menu {
background: #fff;
border-radius: 2px;
padding: 5px 0;
}
.context-menu .menu-item {
padding: 8px 15px;
cursor: pointer;
font-size: 14px;
color: #666;
transition: all .2s;
}
.context-menu .menu-item:hover {
background: #f2f2f2;
color: #009688;
}
.context-menu .menu-item .layui-icon {
margin-right: 5px;
font-size: 14px;
}
.context-menu .menu-item[onclick*="clearSingleAuth"] {
color: #FF5722;
}
.context-menu .menu-item[onclick*="clearSingleAuth"]:hover {
background: #fff1f0;
}
/* 管理员列表样式优化 */
.user-list .layui-form-item {
padding: 8px 0;
margin-bottom: 5px;
border-bottom: 1px solid #f0f0f0;
}
.user-list .layui-form-item:last-child {
border-bottom: none;
}
.user-list .layui-btn-sm {
height: 28px;
line-height: 28px;
padding: 0 12px;
}
.user-list .layui-badge {
font-size: 12px;
font-weight: normal;
}
.user-list .layui-btn-normal {
background-color: #1E9FFF;
}
.user-list .layui-btn-warm {
background-color: #FFB800;
}
.user-list .layui-icon {
font-size: 14px;
margin-right: 3px;
}
/* 美化搜索框 */
.search-box .layui-input {
height: 36px;
line-height: 36px;
border-radius: 2px;
}
/* 美化全选框 */
.layui-form-item .layui-form-checkbox[lay-skin=primary] {
margin-top: 0;
}
/* 右侧顶部按钮样式优化 */
.header-btns {
margin: 3px 0;
padding-right: 5px;
}
.header-btns .layui-btn {
height: 34px;
line-height: 34px;
padding: 0 15px;
font-size: 13px;
margin-left: 8px;
}
.header-btns .layui-btn .layui-icon {
font-size: 14px;
margin-right: 3px;
}
.header-btns .layui-btn-normal {
background-color: #1E9FFF;
}
.header-btns .layui-btn-normal:hover {
background-color: #0d8aff;
}
.header-btns .layui-btn-danger {
background-color: #FF5722;
}
.header-btns .layui-btn-danger:hover {
background-color: #ff4208;
}
/* 提示框样式优化 */
.layui-layer-tips {
font-size: 13px !important;
}
.layui-layer-tips .layui-layer-content {
padding: 8px 12px !important;
line-height: 1.5 !important;
border-radius: 2px !important;
}
.layui-layer-tips i.layui-layer-TipsT {
border-right-color: #3595CC !important;
}
/* 危险操作弹窗样式 */
.layer-danger .layui-layer-title {
background-color: #FF5722 !important;
color: #fff !important;
}
.layer-danger .layui-layer-btn0 {
background-color: #FF5722 !important;
border-color: #FF5722 !important;
}
/* 工具栏按钮样式 */
.auth-table-container .layui-table-tool .layui-btn {
margin-right: 8px;
}
.auth-table-container .layui-table-tool .layui-btn:last-child {
margin-right: 0;
}
.auth-table-container .layui-table-tool .layui-btn-danger:hover {
background-color: #ff4208;
}
/* 正常操作弹窗样式 */
.layer-normal .layui-layer-title {
background-color: #1E9FFF !important;
color: #fff !important;
}
.layer-normal .layui-layer-btn0 {
background-color: #1E9FFF !important;
border-color: #1E9FFF !important;
}
/* 工具栏按钮样式补充 */
.auth-table-container .layui-table-tool .layui-btn-normal {
background-color: #1E9FFF;
}
.auth-table-container .layui-table-tool .layui-btn-normal:hover {
background-color: #0d8aff;
}
</style>
{/block}

View File

@@ -0,0 +1,258 @@
{extend name="../../admin/view/main"}
{block name="content"}
<form class="layui-form layui-card" action="{:request()->url()}" data-auto="true" method="post">
<div class="layui-card-body">
<div class="layui-form-item">
<label class="layui-form-label">选择包名</label>
<div class="layui-input-block">
<div id="packageSelect"></div>
<input type="hidden" name="package_id" id="selectedPackageId">
<input type="hidden" name="package_name" id="selectedPackageName">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">事件名称</label>
<div class="layui-input-block">
<input type="text" name="event_name" required lay-verify="required" placeholder="请输入事件名称" class="layui-input">
<tip>例如install, register, purchase </tip>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">回传地址</label>
<div class="layui-input-block">
<input type="text" name="callback_url" required lay-verify="required|callback_url" placeholder="请输入回传地址" class="layui-input">
<tip>
完整的回传接口地址例如http://api.example.com/callback<br>
支持变量替换,如 {package_name}, {event_name} <br>
详细说明请查看完整回传指南
</tip>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<input type="radio" name="status" value="1" title="启用" checked>
<input type="radio" name="status" value="0" title="停用">
</div>
</div>
</div>
<div class="hr-line-dashed"></div>
<div class="layui-form-item text-center">
<button class="layui-btn" lay-submit lay-filter="formSubmit">保存数据</button>
<button class="layui-btn layui-btn-danger" type="button" data-close>取消添加</button>
</div>
</form>
{/block}
{block name="script"}
<style>
.layui-form tip {
display: block;
padding: 10px;
margin-top: 10px;
color: #666;
background-color: #f8f8f8;
border-radius: 2px;
line-height: 1.8;
}
.layui-badge {
margin: 2px;
font-family: Consolas, monospace;
}
</style>
<script>
$(function () {
layui.use(['form', 'xmSelect', 'layer'], function () {
let form = layui.form;
let layer = layui.layer;
let existingConfig = null; // 用于存储已存在的配置信息
// 定义包名选择器
const packageSelect = xmSelect.render({
el: '#packageSelect',
name: 'package_id',
radio: true,
clickClose: true,
filterable: true,
tips: '请选择包名',
data: [],
direction: 'auto',
model: { label: { type: 'text' } },
on: function(data) {
if(data.arr.length > 0) {
let selected = data.arr[0];
$('#selectedPackageId').val(selected.value);
$('#selectedPackageName').val(selected.package_name);
// 选择包名后,如果已有事件名称,则检查是否存在
checkEventExists();
} else {
$('#selectedPackageId').val('');
$('#selectedPackageName').val('');
}
form.render();
}
});
let checkTimer = null; // 添加防抖定时器
// 监听事件名称输入框
$('input[name="event_name"]').on('input propertychange', function() {
let currentValue = $(this).val();
console.log('事件名称输入:', currentValue);
// 使用防抖,避免频繁请求
if (checkTimer) clearTimeout(checkTimer);
checkTimer = setTimeout(function() {
checkEventExists(currentValue);
}, 300);
});
// 检查事件是否存在
function checkEventExists(eventValue) {
// 获取当前弹窗内的元素
let $dialog = $('.layui-layer-page').last();
let packageId = $('#selectedPackageId').val();
let $eventInput = $dialog.find('input[name="event_name"]');
// 清除现有提示和验证状态
$eventInput.removeClass('layui-form-danger');
$eventInput.nextAll('.layui-form-mid,.layui-word-aux').remove();
$dialog.find('tip').show();
// 重置existingConfig
existingConfig = null;
if (packageId && eventValue) {
// 直接获取配置详情
$.ajax({
url: '{:url("getExistingConfig")}',
type: 'POST',
data: {
package_id: packageId,
event_name: eventValue
},
dataType: 'json',
success: function(configRes) {
if (configRes.code === 1 && configRes.data) {
// 保存已存在的配置信息
existingConfig = configRes.data; // 保存完整的配置信息
// 添加验证失败样式
$eventInput.addClass('layui-form-danger');
// 隐藏当前弹窗内的tip
$dialog.find('.layui-input-block tip').hide();
// 添加提示信息
$eventInput.after(`
<div class="layui-form-mid" style="margin-top: 5px;">
<div style="color: #FF5722; font-weight: bold; margin-bottom: 5px;">
<i class="layui-icon layui-icon-warning" style="font-size: 16px; margin-right: 5px;"></i>
该包名下已存在此事件名称的配置,提交时将覆盖现有配置
</div>
<div style="color: #666; background: #f8f8f8; padding: 8px; border-radius: 2px; margin-top: 5px;">
<i class="layui-icon layui-icon-link" style="margin-right: 5px;"></i>
已配置的回调地址:${configRes.data.callback_url}
</div>
</div>
`);
}
}
});
}
}
// 加载包名列表
function loadPackageList() {
$.get('{:url("searchPackages")}', {
init: 1
}, function(res) {
if (res.code === 1) {
let data = res.data.map(function(item) {
return {
name: item.package_name + ' (' + item.name + ')',
value: item.id,
package_name: item.package_name
};
});
packageSelect.update({
data: data,
autoRow: true
});
}
}, 'json');
}
// 初始加载包名列表
loadPackageList();
// 表单验证
form.verify({
callback_url: function(value) {
if (!/^https?:\/\/.+/.test(value)) {
return '请输入正确的URL地址';
}
}
});
// 修改表单提交处理
form.on('submit(formSubmit)', function(data) {
// 如果存在配置,显示确认框
if (existingConfig) {
layer.confirm('该包名下已存在此事件配置,是否确认覆盖?', {
title: '覆盖确认',
btn: ['确认覆盖', '取消'],
icon: 3
}, function(index) {
// 确认覆盖,将请求转为更新操作
let formData = data.field;
formData.id = existingConfig.id; // 添加已存在配置的ID
$.ajax({
url: '{:url("edit")}',
type: 'POST',
data: formData,
dataType: 'json',
success: function(res) {
if (res.code === 1) {
layer.msg(res.info, {icon: 1, time: 1500}, function() {
parent.layer.close(parent.layer.getFrameIndex(window.name));
parent.location.reload();
});
} else {
layer.msg(res.info, {icon: 2, time: 1500});
}
}
});
layer.close(index);
});
} else {
// 如果不存在配置,使用默认表单提交
let formData = data.field;
$.ajax({
url: '{:url("add")}',
type: 'POST',
data: formData,
dataType: 'json',
success: function(res) {
if (res.code === 1) {
layer.msg(res.info, {icon: 1, time: 1500}, function() {
parent.layer.close(parent.layer.getFrameIndex(window.name));
parent.location.reload();
});
} else {
layer.msg(res.info, {icon: 2, time: 1500});
}
}
});
}
return false; // 阻止表单默认提交
});
});
});
</script>
{/block}

View File

@@ -0,0 +1,160 @@
{extend name="../../admin/view/main"}
{block name="content"}
<form class="layui-form layui-card" action="{:request()->url()}" data-auto="true" method="post">
<div class="layui-card-body">
<input type="hidden" name="id" value="{$vo.id|default=''}">
<div class="layui-form-item">
<label class="layui-form-label">选择包名</label>
<div class="layui-input-block">
<div id="packageSelect"></div>
<input type="hidden" name="package_id" id="selectedPackageId" value="{$vo.package_id|default=''}">
<input type="hidden" name="package_name" id="selectedPackageName" value="{$vo.package_name|default=''}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">事件名称</label>
<div class="layui-input-block">
<input type="text" name="event_name" required lay-verify="required" placeholder="请输入事件名称" class="layui-input" value="{$vo.event_name|default=''}">
<tip>例如install, register, purchase </tip>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">回传地址</label>
<div class="layui-input-block">
<input type="text" name="callback_url" required lay-verify="required|callback_url" placeholder="请输入回传地址" class="layui-input" value="{$vo.callback_url|default=''}">
<tip>
完整的回传接口地址例如http://api.example.com/callback<br>
支持变量替换,如 {package_name}, {event_name} <br>
详细说明请查看完整回传指南
</tip>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<input type="radio" name="status" value="1" title="启用" {if $vo.status eq 1}checked{/if}>
<input type="radio" name="status" value="0" title="停用" {if $vo.status eq 0}checked{/if}>
</div>
</div>
</div>
<div class="hr-line-dashed"></div>
<div class="layui-form-item text-center">
<button class="layui-btn" lay-submit lay-filter="formSubmit">保存数据</button>
<button class="layui-btn layui-btn-danger" type="button" data-close>取消编辑</button>
</div>
</form>
{/block}
{block name="script"}
<style>
.layui-form tip {
display: block;
padding: 10px;
margin-top: 10px;
color: #666;
background-color: #f8f8f8;
border-radius: 2px;
line-height: 1.8;
}
.layui-badge {
margin: 2px;
font-family: Consolas, monospace;
}
</style>
<script>
$(function () {
layui.use(['form', 'xmSelect'], function () {
let form = layui.form;
// 定义包名选择器
const packageSelect = xmSelect.render({
el: '#packageSelect',
name: 'package_id',
radio: true,
clickClose: true,
filterable: true,
tips: '请选择包名',
data: [],
direction: 'auto',
model: { label: { type: 'text' } },
initValue: [{$vo.package_id|default=0}], // 设置初始选中值
on: function(data) {
if(data.arr.length > 0) {
let selected = data.arr[0];
$('#selectedPackageId').val(selected.value);
$('#selectedPackageName').val(selected.package_name);
} else {
$('#selectedPackageId').val('');
$('#selectedPackageName').val('');
}
form.render();
}
});
// 加载包名列表并选中当前包名
function loadPackageList() {
$.get('{:url("searchPackages")}', {
init: 1,
edit_id: '{$vo.id|default=0}' // 传递当前编辑的ID
}, function(res) {
if (res.code === 1) {
let data = res.data.map(function(item) {
return {
name: item.package_name + ' (' + item.name + ')',
value: item.id,
package_name: item.package_name,
selected: item.id == '{$vo.package_id|default=0}' // 标记当前选中项
};
});
packageSelect.update({
data: data,
autoRow: true
});
}
}, 'json');
}
// 初始加载包名列表
loadPackageList();
// 表单验证
form.verify({
callback_url: function(value) {
if (!/^https?:\/\/.+/.test(value)) {
return '请输入正确的URL地址';
}
}
});
// 表单提交
form.on('submit(formSubmit)', function(data) {
let formData = data.field;
$.ajax({
url: '{:url("edit")}',
type: 'POST',
data: formData,
dataType: 'json',
success: function(res) {
if (res.code === 1) {
layer.msg(res.info, {icon: 1, time: 1500}, function() {
parent.layer.close(parent.layer.getFrameIndex(window.name));
parent.location.reload();
});
} else {
layer.msg(res.info, {icon: 2, time: 1500});
}
}
});
return false;
});
});
});
</script>
{/block}

View File

@@ -0,0 +1,232 @@
{extend name="../../admin/view/main"}
{block name="content"}
<!-- 操作指南开始 -->
<div class="think-box-shadow" style="margin-bottom: 15px; padding: 15px;">
<h3 class="layui-inline" style="margin-right: 15px;">回传配置指南</h3>
<button class="layui-btn layui-btn-sm" id="showGuide">展开/收起</button>
<div class="guide-content layui-hide" style="margin-top: 10px;">
<div class="layui-card">
<div class="layui-card-body">
<div class="layui-elem-quote" style="border-left: 5px solid #009688;">
<h4>基本说明:</h4>
<p>本页面用于管理事件回传配置,支持变量替换功能,可以灵活配置回传地址。</p>
</div>
<div class="layui-elem-quote" style="border-left: 5px solid #FFB800;">
<h4>支持的变量:</h4>
<ol>
<li><code>{package_name}</code> - 应用包名com.example.app</li>
<li><code>{event_name}</code> - 事件名称install, register</li>
<li><code>{event_time}</code> - 事件发生时间格式YYYY-MM-DD HH:mm:ss</li>
<li><code>{gaid}</code> - 广告ID</li>
<li><code>{network_name}</code> - 网络名称AppsFlyer, Kochava</li>
</ol>
</div>
<div class="layui-elem-quote" style="border-left: 5px solid #FF5722;">
<h4>配置示例:</h4>
<ol>
<li>基础URLhttp://api.example.com/callback</li>
<li>
完整示例http://api.example.com/callback?package={package_name}&event={event_name}&time={event_time}&gaid={gaid}&network={network_name}
</li>
<li>实际回传时,花括号中的变量会被替换为实际值</li>
<li>所有特殊字符会自动进行URL编码</li>
</ol>
</div>
<div class="layui-elem-quote" style="border-left: 5px solid #01AAED;">
<h4>注意事项:</h4>
<ol>
<li>变量名称必须使用大括号{}包裹,且区分大小写</li>
<li>同一个包名下不能配置重复的事件名称</li>
<li>回传地址必须以http://或https://开头</li>
<li>建议在测试环境验证回传地址的正确性</li>
<li>可以通过状态开关临时停用某个回传配置</li>
</ol>
</div>
</div>
</div>
</div>
</div>
<!-- 操作指南结束 -->
<!-- 表单搜索区域 -->
<div class="think-box-shadow">
<div class="layui-row">
<div class="layui-col-md11">
<form class="layui-form layui-form-pane form-search" action="">
<div class="layui-form-item layui-inline">
<label class="layui-form-label">包名</label>
<div class="layui-input-inline">
<input name="package_name" placeholder="请输入包名" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">事件名称</label>
<div class="layui-input-inline">
<input name="event_name" placeholder="请输入事件名称" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">状态</label>
<div class="layui-input-inline">
<select name="status" lay-search>
<option value="">所有状态</option>
<option value="1">启用</option>
<option value="0">停用</option>
</select>
</div>
</div>
<div class="layui-form-item layui-inline">
<button class="layui-btn layui-btn-primary" lay-submit lay-filter="search_form">
<i class="layui-icon">&#xe615;</i> 搜 索
</button>
</div>
</form>
</div>
<div class="layui-col-md1 text-right">
<!--{if auth("add")}-->
<button class='layui-btn layui-btn-normal' data-modal='{:url("add")}'
data-title="添加回传配置">
<i class="layui-icon layui-icon-add-circle"></i> 添加事件
</button>
<!--{/if}-->
</div>
</div>
</div>
<!-- 数据表格区域 -->
<div class="think-box-shadow">
<table class="layui-table" id="CallbackTable" data-url="{:url('get_list')}"
data-target-search="form.form-search"></table>
</div>
<!-- 数据操作工具条模板 -->
<script type="text/html" id="toolbar">
<!--{if auth("edit")}-->
<a class="layui-btn layui-btn-sm" data-modal="{:url('edit')}?id={{d.id}}" data-title="编辑回传配置">
<i class="layui-icon layui-icon-edit"></i> 编辑
</a>
<!--{/if}-->
<!--{if auth("remove")}-->
<a class="layui-btn layui-btn-sm layui-btn-danger" data-action="{:url('remove')}" data-value="id#{{d.id}}"
data-confirm="确定要删除此配置吗?">
<i class="layui-icon layui-icon-delete"></i> 删除
</a>
<!--{/if}-->
</script>
<!-- 数据状态切换模板 -->
<script type="text/html" id="statusTpl">
<!--{if auth("state")}-->
<input type="checkbox" value="{{d.id}}" lay-skin="switch" lay-text="启用|停用" lay-filter="status"
{{d.status>0?'checked':''}}>
<!--{else}-->
{{d.status ? '<b class="color-green">启用</b>' : '<b class="color-red">停用</b>'}}
<!--{/if}-->
</script>
{/block}
{block name="script"}
<script>
$(function () {
let table; // 定义表格变量
// 初始化表格组件
table = $('#CallbackTable').layTable({
url: '{:url("get_list")}',
method: 'get',
even: true,
page: true,
limit: 15,
limits: [10, 15, 20, 25, 50, 100],
cols: [[
{field: 'id', title: 'ID', width: 80, align: 'center'},
{field: 'package_name', title: '包名', minWidth: 200},
{field: 'event_name', title: '事件名称', minWidth: 150},
{field: 'callback_url', title: '回传地址', minWidth: 300},
{field: 'status', title: '状态', width: 100, align: 'center', templet: '#statusTpl'},
{field: 'create_at', title: '创建时间', width: 180, align: 'center'},
{title: '操作', toolbar: '#toolbar', width: 180, align: 'center', fixed: 'right'}
]],
text: {
none: '暂无回传配置数据'
}
});
// 数据状态切换操作
form.on('switch(status)', function (obj) {
$.ajax({
url: '{:url("state")}',
type: 'POST',
data: {
id: obj.value,
status: obj.elem.checked ? 1 : 0
},
dataType: 'json',
success: function (res) {
if (res.code === 1) {
layer.msg(res.info, {icon: 1});
// 仅重载表格数据
table.reload('CallbackTable', {
page: {
curr: $(".layui-laypage-em").next().html() // 保持在当前页
}
});
} else {
layer.msg(res.info, {icon: 2});
// 状态切换失败回滚switch状态
obj.elem.checked = !obj.elem.checked;
form.render('checkbox');
}
},
error: function () {
layer.msg('操作失败,请重试', {icon: 2});
// 发生错误时也回滚switch状态
obj.elem.checked = !obj.elem.checked;
form.render('checkbox');
}
});
});
// 操作指南展开/收起
$('#showGuide').on('click', function () {
$('.guide-content').toggleClass('layui-hide');
});
});
</script>
<style>
/* 操作指南样式 */
.guide-content .layui-elem-quote {
margin: 10px 0;
padding: 15px;
background-color: #f8f8f8;
}
.guide-content h4 {
margin-bottom: 10px;
font-weight: bold;
}
.guide-content code {
padding: 2px 4px;
background-color: #f1f1f1;
border-radius: 3px;
color: #333;
font-family: Consolas, monospace;
}
.guide-content ol li {
margin: 5px 0;
color: #666;
}
</style>
{/block}

View File

@@ -0,0 +1,58 @@
{extend name="../../admin/view/main"}
{block name="content"}
<form class="layui-form layui-card" action="javascript:void(0)" autocomplete="off">
<div class="layui-card-body padding-left-40">
<div class="layui-form-item layui-row layui-col-space15">
<label class="layui-col-xs2 text-right">包名</label>
<div class="layui-col-xs10">
<div class="layui-input-line">{$vo.package_name|default=''}</div>
</div>
</div>
<div class="layui-form-item layui-row layui-col-space15">
<label class="layui-col-xs2 text-right">事件名称</label>
<div class="layui-col-xs10">
<div class="layui-input-line">{$vo.event_name|default=''}</div>
</div>
</div>
<div class="layui-form-item layui-row layui-col-space15">
<label class="layui-col-xs2 text-right">回传URL模板</label>
<div class="layui-col-xs10">
<div class="layui-input-line">{$vo.callback_url|default=''}</div>
</div>
</div>
<div class="layui-form-item layui-row layui-col-space15">
<label class="layui-col-xs2 text-right">实际回传URL</label>
<div class="layui-col-xs10">
<div class="layui-input-line">{$vo.final_url|default=''}</div>
</div>
</div>
<div class="layui-form-item layui-row layui-col-space15">
<label class="layui-col-xs2 text-right">响应状态码</label>
<div class="layui-col-xs10">
<div class="layui-input-line">{$vo.response_code|default=''}</div>
</div>
</div>
<div class="layui-form-item layui-row layui-col-space15">
<label class="layui-col-xs2 text-right">响应内容</label>
<div class="layui-col-xs10">
<pre class="layui-code">{$vo.response_body|default=''}</pre>
</div>
</div>
<div class="layui-form-item layui-row layui-col-space15">
<label class="layui-col-xs2 text-right">状态</label>
<div class="layui-col-xs10">
<div class="layui-input-line">
{if $vo.status==1}<span class="layui-badge layui-bg-green">成功</span>{else}<span class="layui-badge layui-bg-red">失败</span>{/if}
</div>
</div>
</div>
<div class="layui-form-item layui-row layui-col-space15">
<label class="layui-col-xs2 text-right">回传时间</label>
<div class="layui-col-xs10">
<div class="layui-input-line">{$vo.callback_time|default=''}</div>
</div>
</div>
</div>
</form>
{/block}

View File

@@ -0,0 +1,87 @@
{extend name="../../admin/view/main"}
{block name="button"}
{/block}
{block name="content"}
<div class="think-box-shadow">
<fieldset>
<legend>条件搜索</legend>
<form class="layui-form layui-form-pane form-search" action="javascript:void(0)" method="get" autocomplete="off">
<div class="layui-form-item layui-inline">
<label class="layui-form-label">包名</label>
<div class="layui-input-inline">
<input name="package_name" value="{$get.package_name|default=''}" placeholder="请输入包名" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">事件名称</label>
<div class="layui-input-inline">
<input name="event_name" value="{$get.event_name|default=''}" placeholder="请输入事件名称" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">推送ID</label>
<div class="layui-input-inline">
<input name="push_record_id" value="{$get.push_record_id|default=''}" placeholder="请输入推送ID" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">状态</label>
<div class="layui-input-inline">
<select name="status" class="layui-select">
<option value="">所有状态</option>
<option value="1">成功</option>
<option value="0">失败</option>
</select>
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">回传时间</label>
<div class="layui-input-inline">
<input data-date-range name="callback_time" value="{$get.callback_time|default=''}" placeholder="请选择回传时间" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<button class="layui-btn layui-btn-primary"><i class="layui-icon">&#xe615;</i> 搜 索</button>
</div>
</form>
</fieldset>
<table id="RecordTable" data-url="{:sysuri('get_list')}" data-target-search="form.form-search"></table>
</div>
{/block}
{block name="script"}
<script>
$(function () {
$('#RecordTable').layTable({
even: true, height: 'full',
sort: {field: 'id', type: 'desc'},
cols: [[
{field: 'id', title: 'ID', width: 80, sort: true},
{field: 'push_record_id', title: '推送ID', width: 150, templet: function(d){
var source = d.source_type || '-';
return '<a class="layui-link" data-title="查看推送详情" data-open="{:url("package_push_records/detail")}?id='+ d.push_record_id +'">'+ d.push_record_id +'('+ source +')</a>';
}},
{field: 'package_name', title: '包名', minWidth: 100},
{field: 'event_name', title: '事件名称', minWidth: 100},
{field: 'callback_url', title: '回传URL模板', minWidth: 200, templet: function(d){
return '<div class="layui-elip" title="'+ d.callback_url +'">'+ d.callback_url +'</div>';
}},
{field: 'final_url', title: '实际回传URL', minWidth: 200, templet: function(d){
return '<div class="layui-elip" title="'+ d.final_url +'">'+ d.final_url +'</div>';
}},
{field: 'response_code', title: '响应码', width: 100},
{field: 'status', title: '状态', width: 100, templet: function(d){
return d.status == 1 ? '<span class="layui-badge layui-bg-green">成功</span>' : '<span class="layui-badge layui-bg-red">失败</span>';
}},
{field: 'callback_time', title: '回传时间', width: 180, sort: true},
{title: '操作', toolbar: '#toolbar', width: 100, align: 'center', fixed: 'right'}
]]
});
});
</script>
<script type="text/html" id="toolbar">
<a class="layui-btn layui-btn-xs" data-title="查看详情" data-open="{:url('detail')}?id={{d.id}}">详情</a>
</script>
{/block}

View File

@@ -0,0 +1,44 @@
{extend name="../../admin/view/main"}
{block name="content"}
<form class="layui-form layui-card" action="javascript:void(0)" autocomplete="off">
<div class="layui-card-body padding-left-40">
<div class="layui-form-item layui-row layui-col-space15">
<label class="layui-col-xs2 text-right">来源</label>
<div class="layui-col-xs10">
<div class="layui-input-line">{$vo.source_type|default=''}</div>
</div>
</div>
<div class="layui-form-item layui-row layui-col-space15">
<label class="layui-col-xs2 text-right">包名</label>
<div class="layui-col-xs10">
<div class="layui-input-line">{$vo.package_name|default=''}</div>
</div>
</div>
<div class="layui-form-item layui-row layui-col-space15">
<label class="layui-col-xs2 text-right">事件名称</label>
<div class="layui-col-xs10">
<div class="layui-input-line">{$vo.event_name|default=''}</div>
</div>
</div>
<div class="layui-form-item layui-row layui-col-space15">
<label class="layui-col-xs2 text-right">原始请求URL</label>
<div class="layui-col-xs10">
<div class="layui-input-line">{$vo.original_url|default=''}</div>
</div>
</div>
<div class="layui-form-item layui-row layui-col-space15">
<label class="layui-col-xs2 text-right">接收时间</label>
<div class="layui-col-xs10">
<div class="layui-input-line">{$vo.receive_time|default=''}</div>
</div>
</div>
<div class="layui-form-item layui-row layui-col-space15">
<label class="layui-col-xs2 text-right">创建时间</label>
<div class="layui-col-xs10">
<div class="layui-input-line">{$vo.created_at|default=''}</div>
</div>
</div>
</div>
</form>
{/block}

View File

@@ -0,0 +1,72 @@
{extend name="../../admin/view/main"}
{block name="button"}
{/block}
{block name="content"}
<div class="think-box-shadow">
<fieldset>
<legend>条件搜索</legend>
<form class="layui-form layui-form-pane form-search" action="javascript:void(0)" method="get" autocomplete="off">
<div class="layui-form-item layui-inline">
<label class="layui-form-label">包名</label>
<div class="layui-input-inline">
<input name="package_name" value="{$get.package_name|default=''}" placeholder="请输入包名" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">事件名称</label>
<div class="layui-input-inline">
<input name="event_name" value="{$get.event_name|default=''}" placeholder="请输入事件名称" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">来源</label>
<div class="layui-input-inline">
<select name="source_type" class="layui-select">
<option value="">所有来源</option>
<option value="AppsFlyer">AppsFlyer</option>
<option value="Kochava">Kochava</option>
</select>
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">接收时间</label>
<div class="layui-input-inline">
<input data-date-range name="receive_time" value="{$get.receive_time|default=''}" placeholder="请选择接收时间" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<button class="layui-btn layui-btn-primary"><i class="layui-icon">&#xe615;</i> 搜 索</button>
</div>
</form>
</fieldset>
<table id="RecordTable" data-url="{:sysuri('get_list')}" data-target-search="form.form-search"></table>
</div>
{/block}
{block name="script"}
<script>
$(function () {
$('#RecordTable').layTable({
even: true, height: 'full',
sort: {field: 'id', type: 'desc'},
cols: [[
{field: 'id', title: 'ID', width: 80, sort: true},
{field: 'source_type', title: '来源', width: 100},
{field: 'package_name', title: '包名', minWidth: 100},
{field: 'event_name', title: '事件名称', minWidth: 100},
{field: 'original_url', title: '原始请求URL', minWidth: 200, templet: function(d){
return '<div class="layui-elip" title="'+ d.original_url +'">'+ d.original_url +'</div>';
}},
{field: 'receive_time', title: '接收时间', width: 180, sort: true},
{field: 'created_at', title: '创建时间', width: 180},
{title: '操作', toolbar: '#toolbar', width: 100, align: 'center', fixed: 'right'}
]]
});
});
</script>
<script type="text/html" id="toolbar">
<a class="layui-btn layui-btn-xs" data-title="查看详情" data-open="{:url('detail')}?id={{d.id}}">详情</a>
</script>
{/block}

View File

@@ -0,0 +1,23 @@
<div class="layui-card">
{block name='style'}{/block}
{block name='header'}
{notempty name='title'}
<div class="layui-card-header">
<span class="layui-icon font-s10 color-desc margin-right-5">&#xe65b;</span>{$title|lang}
<div class="pull-right">{block name='button'}{/block}</div>
</div>
{/notempty}
{/block}
<div class="layui-card-line"></div>
<div class="layui-card-body">
<div class="layui-card-table">
{notempty name='showErrorMessage'}
<div class="think-box-notify" type="error">
<b>{:lang('系统提示:')}</b><span>{$showErrorMessage|raw}</span>
</div>
{/notempty}
{block name='content'}{/block}
</div>
</div>
{block name='script'}{/block}
</div>