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
+114
View File
@@ -0,0 +1,114 @@
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">站点广告管理</div>
<div class="layui-card-body">
<form class="layui-form" lay-filter="data-search-form">
<div class="layui-form-item">
<div class="layui-inline">
<input type="text" name="title" placeholder="标题" autocomplete="off" class="layui-input">
</div>
<div class="layui-inline">
<select name="position" lay-search="">
<option value="">全部广告位</option>
{volist name="positionList" id="p"}
<option value="{$key}">{$p}</option>
{/volist}
</select>
</div>
<div class="layui-inline">
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="data-search-btn"><i class="layui-icon">&#xe615;</i> 搜索</button>
<button type="button" class="layui-btn layui-btn-primary" id="btn-reset">重置</button>
</div>
</div>
</form>
<table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
<script type="text/html" id="tableBar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="dataCreate" data-perm="ad:add"><i class="layui-icon">&#xe654;</i> </button>
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-event="dataDelete" data-perm="ad:del"><i class="layui-icon">&#xe640;</i> </button>
<button class="layui-btn layui-btn-sm" lay-event="dataRecybin" data-perm="ad:restore"><i class="layui-icon">&#xe64b;</i> </button>
</div>
</script>
<script type="text/html" id="dataBar">
<a class="layui-btn layui-btn-xs" lay-event="update" data-perm="ad:edit">编辑</a>
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="delete" data-perm="ad:del">删除</a>
</script>
<script type="text/html" id="statusTpl">
<input type="checkbox" name="status" value="{{ d.id }}" lay-skin="switch" lay-text="开|关" lay-filter="statusSwitch" {{ d.status == 1 ? 'checked' : '' }}>
</script>
</div>
</div>
</div>
<script type="text/html" id="dataFormTpl">
<form class="layui-form" id="wxapp-form" lay-filter="wxapp-form" style="padding:15px;">
<input type="hidden" name="id" value="{{ d.id || '' }}">
<div class="layui-form-item">
<label class="layui-form-label">标题</label>
<div class="layui-input-block">
<input type="text" name="title" placeholder="请输入标题" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">广告位</label>
<div class="layui-input-block">
<select name="position" lay-search="">
{volist name="positionList" id="p"}
<option value="{$key}">{$p}</option>
{/volist}
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">类型</label>
<div class="layui-input-block">
<select name="type">
{volist name="typeList" id="t"}
<option value="{$key}">{$t}</option>
{/volist}
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">内容</label>
<div class="layui-input-block">
<textarea name="content" placeholder="请输入内容" class="layui-textarea"></textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">链接</label>
<div class="layui-input-block">
<input type="text" name="url" 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="image" 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="sort" placeholder="请输入排序" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<input type="checkbox" name="status" lay-skin="switch" lay-text="启用|禁用" value="1" checked>
</div>
</div>
</form>
</script>
<script type="text/html" id="dataRecybinTpl">
<table class="layui-hide" id="dataRecybinTable" lay-filter="dataRecybinTable"></table>
<script type="text/html" id="dataRecybinBarTpl">
<a class="layui-btn layui-btn-xs" lay-event="restore" data-perm="ad:restore">恢复</a>
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="forcedelete" data-perm="ad:destroy">彻底删除</a>
</script>
</script>
<script>
layui.use('ad', layui.factory('ad'));
</script>
+374
View File
@@ -0,0 +1,374 @@
<div class="layui-card" style="margin:15px;">
<div class="layui-card-header">
插件设计器 <span id="addonName" class="layui-badge layui-bg-blue"></span>
<a href="{:url('addon/index')}" class="layui-btn layui-btn-sm layui-btn-primary" style="float:right;"><i class="layui-icon layui-icon-return"></i> 返回插件列表</a>
</div>
<div class="layui-card-body">
<!-- 新建模式:填写插件标识与基础信息 -->
<div id="createBox">
<fieldset class="layui-elem-field layui-field-title"><legend>新建插件</legend></fieldset>
<form class="layui-form" lay-filter="createForm" style="max-width:640px;">
<div class="layui-form-item">
<label class="layui-form-label">插件标识</label>
<div class="layui-input-inline" style="width:300px;">
<input type="text" name="name" id="createName" class="layui-input" placeholder="如 myaddon(字母开头)">
</div>
<div class="layui-form-mid layui-word-aux">目录名 / 命名空间标识,创建后不可改</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">名称</label>
<div class="layui-input-inline" style="width:300px;"><input type="text" name="title" class="layui-input" placeholder="插件显示名称"></div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">简介</label>
<div class="layui-input-inline" style="width:300px;"><input type="text" name="intro" class="layui-input"></div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">作者</label>
<div class="layui-input-inline" style="width:300px;"><input type="text" name="author" class="layui-input"></div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">官网</label>
<div class="layui-input-inline" style="width:300px;"><input type="text" name="website" class="layui-input"></div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">版本</label>
<div class="layui-input-inline" style="width:300px;"><input type="text" name="version" value="1.0.0" class="layui-input"></div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">前台入口</label>
<div class="layui-input-inline" style="width:300px;"><input type="text" name="url" class="layui-input" placeholder="如 /myaddon"></div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="createBtn"><i class="layui-icon layui-icon-add-1"></i> 创建骨架</button>
</div>
</div>
</form>
</div>
<!-- 设计模式 -->
<div id="designBox" style="display:none;">
<div class="layui-tab" lay-filter="designTab">
<ul class="layui-tab-title">
<li class="layui-this">基础信息</li>
<li>配置项</li>
<li>菜单</li>
<li>事件/中间件</li>
<li>路由</li>
<li>页面模块</li>
</ul>
<div class="layui-tab-content">
<!-- 基础信息 -->
<div class="layui-tab-item layui-show">
<form class="layui-form" lay-filter="basicForm" style="max-width:640px;">
<div class="layui-form-item"><label class="layui-form-label">标识</label><div class="layui-input-inline" style="width:300px;"><input type="text" name="name" id="basicName" class="layui-input" readonly></div></div>
<div class="layui-form-item"><label class="layui-form-label">名称</label><div class="layui-input-inline" style="width:300px;"><input type="text" name="title" class="layui-input"></div></div>
<div class="layui-form-item"><label class="layui-form-label">简介</label><div class="layui-input-inline" style="width:300px;"><input type="text" name="intro" class="layui-input"></div></div>
<div class="layui-form-item"><label class="layui-form-label">作者</label><div class="layui-input-inline" style="width:300px;"><input type="text" name="author" class="layui-input"></div></div>
<div class="layui-form-item"><label class="layui-form-label">官网</label><div class="layui-input-inline" style="width:300px;"><input type="text" name="website" class="layui-input"></div></div>
<div class="layui-form-item"><label class="layui-form-label">版本</label><div class="layui-input-inline" style="width:300px;"><input type="text" name="version" class="layui-input"></div></div>
<div class="layui-form-item"><label class="layui-form-label">前台入口</label><div class="layui-input-inline" style="width:300px;"><input type="text" name="url" class="layui-input"></div></div>
<div class="layui-form-item"><label class="layui-form-label">授权</label><div class="layui-input-inline" style="width:300px;"><input type="text" name="license" class="layui-input"></div></div>
<div class="layui-form-item"><div class="layui-input-block"><button class="layui-btn" lay-submit lay-filter="basicBtn">保存基础信息</button></div></div>
</form>
</div>
<!-- 配置项 -->
<div class="layui-tab-item">
<button class="layui-btn layui-btn-sm" id="cfgAdd"><i class="layui-icon layui-icon-add-1"></i> 新增配置项</button>
<table class="layui-table" style="margin-top:10px;">
<thead><tr><th>名称(name)</th><th>标题(title)</th><th>类型(type)</th><th>默认值(value)</th><th>提示(tip)</th><th>操作</th></tr></thead>
<tbody id="cfgRows"></tbody>
</table>
<button class="layui-btn" id="cfgSave">保存配置项</button>
</div>
<!-- 菜单 -->
<div class="layui-tab-item">
<div class="layui-form-item"><label class="layui-form-label">后台菜单</label><div class="layui-input-block"><textarea id="menuBackend" class="layui-textarea" style="min-height:120px;"></textarea></div></div>
<div class="layui-form-item"><label class="layui-form-label">会员菜单</label><div class="layui-input-block"><textarea id="menuMember" class="layui-textarea" style="min-height:80px;"></textarea></div></div>
<div class="layui-form-item"><label class="layui-form-label">前台菜单</label><div class="layui-input-block"><textarea id="menuFrontend" class="layui-textarea" style="min-height:80px;"></textarea></div></div>
<button class="layui-btn" id="menuSave">保存菜单(JSON</button>
<div class="layui-form-mid layui-word-aux">格式参考现有插件 menu.json;保存时会校验 JSON。</div>
</div>
<!-- 事件/中间件 -->
<div class="layui-tab-item">
<div class="layui-form-item"><label class="layui-form-label">events</label><div class="layui-input-block"><textarea id="evEvents" class="layui-textarea" style="min-height:100px;"></textarea></div></div>
<div class="layui-form-item"><label class="layui-form-label">middleware</label><div class="layui-input-block"><textarea id="evMiddleware" class="layui-textarea" style="min-height:100px;"></textarea></div></div>
<div class="layui-form-item"><label class="layui-form-label">services</label><div class="layui-input-block"><textarea id="evServices" class="layui-textarea" style="min-height:80px;"></textarea></div></div>
<button class="layui-btn" id="hooksSave">保存事件/中间件/服务</button>
<div class="layui-form-mid layui-word-aux">填写合法 JSONevents 含 bind/listen/subscribemiddleware 含 alias/priority。</div>
</div>
<!-- 路由 -->
<div class="layui-tab-item">
<button class="layui-btn layui-btn-sm" id="routeAdd"><i class="layui-icon layui-icon-add-1"></i> 新增路由</button>
<table class="layui-table" style="margin-top:10px;">
<thead><tr><th>方法</th><th>路径(path)</th><th>控制器(controller)</th><th>方法(action)</th><th>操作</th></tr></thead>
<tbody id="routeRows"></tbody>
</table>
<button class="layui-btn" id="routeSave">保存路由</button>
</div>
<!-- 页面模块 -->
<div class="layui-tab-item">
<form class="layui-form" lay-filter="genForm" style="max-width:560px;">
<div class="layui-form-item">
<label class="layui-form-label">类型</label>
<div class="layui-input-inline" style="width:200px;">
<select name="gtype" id="genType">
<option value="controller">控制器 Controller</option>
<option value="model">模型 Model</option>
<option value="event">事件 Event</option>
<option value="listener">监听器 Listener</option>
<option value="middleware">中间件 Middleware</option>
<option value="service">服务 Service</option>
<option value="subscribe">订阅者 Subscribe</option>
<option value="validate">验证器 Validate</option>
<option value="command">命令行 Command</option>
</select>
</div>
</div>
<div class="layui-form-item" id="genLayerBox">
<label class="layui-form-label">层级</label>
<div class="layui-input-inline" style="width:200px;">
<select name="layer" id="genLayer">
<option value="frontend">前台 frontend</option>
<option value="backend">后台 backend</option>
<option value="member">会员 member</option>
</select>
</div>
</div>
<div class="layui-form-item" id="genKindBox">
<label class="layui-form-label">风格</label>
<div class="layui-input-inline" style="width:200px;">
<select name="kind" id="genKind">
<option value="default">资源(默认)</option>
<option value="api">API</option>
<option value="plain">空白</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">类名</label>
<div class="layui-input-inline" style="width:200px;"><input type="text" name="name" id="genName" class="layui-input" placeholder="如 Category"></div>
<div class="layui-form-mid layui-word-aux">不含命名空间</div>
</div>
<div class="layui-form-item" id="genCmdBox" style="display:none;">
<label class="layui-form-label">命令名</label>
<div class="layui-input-inline" style="width:200px;"><input type="text" name="command" id="genCmd" class="layui-input" placeholder="如 myaddon:demo"></div>
</div>
<div class="layui-form-item"><div class="layui-input-block"><button class="layui-btn layui-btn-normal" id="genBtn">生成文件</button></div></div>
</form>
</div>
</div>
</div>
<hr>
<button class="layui-btn layui-btn-normal" id="btnInstall"><i class="layui-icon layui-icon-ok"></i> 开发安装(建表/注入菜单/启用)</button>
<button class="layui-btn layui-btn-danger" id="btnRemove"><i class="layui-icon layui-icon-delete"></i> 删除插件</button>
<span class="layui-word-aux">开发安装后改动实时生效,无需重新打包。</span>
</div>
</div>
</div>
<script src="/assets/layui/layui.js"></script>
<script>
layui.use(['form', 'layer', 'element', 'jquery', 'http'], function () {
var form = layui.form, layer = layui.layer, element = layui.element, $ = layui.jquery, http = layui.http;
var addon = '{$addon|default=""}';
function showCreate(defName) {
$('#createBox').show();
$('#designBox').hide();
if (defName) $('#createName').val(defName);
}
function showDesign() {
$('#createBox').hide();
$('#designBox').show();
}
// 初始化:有 addon 则尝试读取,否则新建
if (addon) {
http.get('designRead?addon=' + encodeURIComponent(addon)).then(function (res) {
if (res.code === 0) {
fillForm(res.data);
showDesign();
} else {
showCreate(addon);
}
}).catch(function () { showCreate(addon); });
} else {
showCreate('');
}
function fillForm(d) {
$('#addonName').text(d.name || '');
addon = d.name || addon;
var info = d.info || {};
form.val('basicForm', {
name: info.name || '', title: info.title || '', intro: info.intro || '',
author: info.author || '', website: info.website || '', version: info.version || '',
url: info.url || '', license: info.license || ''
});
// 配置项
$('#cfgRows').empty();
(d.config || []).forEach(function (f) { addCfgRow(f); });
// 菜单
var m = d.menu || {};
$('#menuBackend').val(JSON.stringify(m.backend || [], null, 2));
$('#menuMember').val(JSON.stringify(m.member || [], null, 2));
$('#menuFrontend').val(JSON.stringify(m.frontend || [], null, 2));
// 事件/中间件
var ev = info.events || {};
$('#evEvents').val(JSON.stringify(ev, null, 2));
$('#evMiddleware').val(JSON.stringify(info.middleware || {}, null, 2));
$('#evServices').val(JSON.stringify(info.services || [], null, 2));
// 路由(从 route_raw 解析较复杂,这里留空由用户手动加;如需可解析 TODO)
}
// ===== 新建骨架 =====
form.on('submit(createBtn)', function (data) {
http.post('designCreate', data.field).then(function (res) {
if (res.code === 0) {
layer.msg('创建成功', { icon: 1 });
addon = data.field.name;
location.href = 'design?addon=' + encodeURIComponent(addon);
} else {
layer.msg(res.message || '创建失败', { icon: 2 });
}
}).catch(function () { layer.msg('请求失败', { icon: 2 }); });
return false;
});
// ===== 基础信息保存 =====
form.on('submit(basicBtn)', function (data) {
var fd = data.field; fd.addon = addon; fd.type = 'basic';
http.post('designSave', fd).then(function (res) {
layer.msg(res.code === 0 ? '已保存' : (res.message || '失败'), { icon: res.code === 0 ? 1 : 2 });
}).catch(function () { layer.msg('请求失败', { icon: 2 }); });
return false;
});
// ===== 配置项行 =====
function addCfgRow(f) {
f = f || {};
var tr = $('<tr>').append(
$('<td>').append($('<input class="layui-input cfg-name" value="' + (f.name||'') + '">')),
$('<td>').append($('<input class="layui-input cfg-title" value="' + (f.title||'') + '">')),
$('<td>').append($('<input class="layui-input cfg-type" value="' + (f.type||'string') + '">')),
$('<td>').append($('<input class="layui-input cfg-value" value="' + (f.value||'') + '">')),
$('<td>').append($('<input class="layui-input cfg-tip" value="' + (f.tip||'') + '">')),
$('<td>').append($('<button class="layui-btn layui-btn-xs layui-btn-danger cfg-del">删除</button>'))
);
$('#cfgRows').append(tr);
}
$('#cfgAdd').on('click', function () { addCfgRow(); });
$('#cfgRows').on('click', '.cfg-del', function () { $(this).closest('tr').remove(); });
$('#cfgSave').on('click', function () {
var fields = [];
$('#cfgRows tr').each(function () {
var t = $(this);
fields.push({ name: t.find('.cfg-name').val(), title: t.find('.cfg-title').val(),
type: t.find('.cfg-type').val(), value: t.find('.cfg-value').val(), tip: t.find('.cfg-tip').val() });
});
http.post('designSave', { addon: addon, type: 'config', fields: JSON.stringify(fields) })
.then(function (res) { layer.msg(res.code === 0 ? '配置已保存' : (res.message || '失败'), { icon: res.code === 0 ? 1 : 2 }); })
.catch(function () { layer.msg('请求失败', { icon: 2 }); });
});
// ===== 菜单保存 =====
$('#menuSave').on('click', function () {
try {
var menu = {
backend: JSON.parse($('#menuBackend').val() || '[]'),
member: JSON.parse($('#menuMember').val() || '[]'),
frontend: JSON.parse($('#menuFrontend').val() || '[]')
};
http.post('designSave', { addon: addon, type: 'menu', menu: JSON.stringify(menu) })
.then(function (res) { layer.msg(res.code === 0 ? '菜单已保存' : (res.message || '失败'), { icon: res.code === 0 ? 1 : 2 }); })
.catch(function () { layer.msg('请求失败', { icon: 2 }); });
} catch (e) { layer.msg('菜单 JSON 格式错误:' + e.message, { icon: 2 }); }
});
// ===== 事件/中间件保存 =====
$('#hooksSave').on('click', function () {
try {
var events = JSON.parse($('#evEvents').val() || '{}');
var middleware = JSON.parse($('#evMiddleware').val() || '{}');
var services = JSON.parse($('#evServices').val() || '[]');
http.post('designSave', { addon: addon, type: 'hooks',
events: JSON.stringify(events), middleware: JSON.stringify(middleware), services: JSON.stringify(services) })
.then(function (res) { layer.msg(res.code === 0 ? '已保存' : (res.message || '失败'), { icon: res.code === 0 ? 1 : 2 }); })
.catch(function () { layer.msg('请求失败', { icon: 2 }); });
} catch (e) { layer.msg('JSON 格式错误:' + e.message, { icon: 2 }); }
});
// ===== 路由行 =====
function addRouteRow(r) {
r = r || {};
var tr = $('<tr>').append(
$('<td>').append($('<select class="layui-input rt-method"><option>get</option><option>post</option><option>put</option><option>delete</option><option>any</option></select>').val(r.method||'get')),
$('<td>').append($('<input class="layui-input rt-path" value="' + (r.path||'') + '">')),
$('<td>').append($('<input class="layui-input rt-ctrl" value="' + (r.controller||'') + '">')),
$('<td>').append($('<input class="layui-input rt-action" value="' + (r.action||'index') + '">')),
$('<td>').append($('<button class="layui-btn layui-btn-xs layui-btn-danger rt-del">删除</button>'))
);
$('#routeRows').append(tr);
}
$('#routeAdd').on('click', function () { addRouteRow(); });
$('#routeRows').on('click', '.rt-del', function () { $(this).closest('tr').remove(); });
$('#routeSave').on('click', function () {
var routes = [];
$('#routeRows tr').each(function () {
var t = $(this);
routes.push({ method: t.find('.rt-method').val(), path: t.find('.rt-path').val(),
controller: t.find('.rt-ctrl').val(), action: t.find('.rt-action').val() });
});
http.post('designSave', { addon: addon, type: 'route', routes: JSON.stringify(routes) })
.then(function (res) { layer.msg(res.code === 0 ? '路由已保存' : (res.message || '失败'), { icon: res.code === 0 ? 1 : 2 }); })
.catch(function () { layer.msg('请求失败', { icon: 2 }); });
});
// ===== 页面模块生成 =====
$('#genType').on('change', function () {
var t = $(this).val();
var isCtrl = (t === 'controller');
$('#genLayerBox').toggle(isCtrl);
$('#genKindBox').toggle(isCtrl);
$('#genCmdBox').toggle(t === 'command');
});
$('#genBtn').on('click', function () {
var gtype = $('#genType').val();
var opts = { name: $('#genName').val() };
if (gtype === 'controller') { opts.layer = $('#genLayer').val(); opts.kind = $('#genKind').val(); }
if (gtype === 'command') { opts.command = $('#genCmd').val(); }
if (!opts.name) { layer.msg('请填写类名', { icon: 2 }); return; }
http.post('designGenerate', { addon: addon, gtype: gtype, opts: JSON.stringify(opts) })
.then(function (res) { layer.msg(res.code === 0 ? ('已生成:' + (res.data.file||'')) : (res.message || '失败'), { icon: res.code === 0 ? 1 : 2 }); })
.catch(function () { layer.msg('请求失败', { icon: 2 }); });
});
// ===== 开发安装 / 删除 =====
$('#btnInstall').on('click', function () {
http.post('designInstall', { addon: addon }).then(function (res) {
if (res.code === 0) { layer.msg('开发安装成功,菜单已注入', { icon: 1 }); }
else { layer.msg(res.message || '失败', { icon: 2 }); }
}).catch(function () { layer.msg('请求失败', { icon: 2 }); });
});
$('#btnRemove').on('click', function () {
layer.confirm('确定删除该插件目录?(开发调试用,不可恢复)', function (index) {
layer.close(index);
http.post('designRemove', { addon: addon }).then(function (res) {
if (res.code === 0) { layer.msg('已删除', { icon: 1 }); location.href = 'index'; }
else { layer.msg(res.message || '失败', { icon: 2 }); }
}).catch(function () { layer.msg('请求失败', { icon: 2 }); });
});
});
});
</script>
+85
View File
@@ -0,0 +1,85 @@
<!--
* @Author: YwxApp <ywx@ywxapp.cn>
* @Date: 2026-04-29 02:32:33
* @LastEditors: YwxApp <ywx@ywxapp.cn>
* @LastEditTime: 2026-07-31 14:57:37
* @Description:
* @FilePath: \ywxapp_dev\app\backend\view\addon\index.html
* @CustomString: Copyright (c) 2026 YwxApp
-->
<div class="plugin-container layui-card" style="margin-bottom: 5px;">
<!-- 1. 顶部搜索与操作栏 -->
<div class="layui-card-header layui-row layui-col-space15">
<div class="layui-col-md12">
<form class="layui-form" lay-filter="searchForm">
<div class="layui-form-item" style="margin-bottom: 0;">
<div class="layui-inline">
<label class="layui-form-label" style="width: auto;">插件搜索</label>
<div class="layui-input-inline">
<input type="text" name="keyword" placeholder="输入插件名称..." class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label" style="width: auto;">运行状态</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-inline">
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="searchBtn">
<i class="layui-icon layui-icon-search"></i> 搜索
</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</div>
</form>
</div>
</div>
<hr class="layui-border-green">
<!-- 2. 功能性操作按钮组 -->
<div class="layui-card-header" style="margin: 15px;">
<button class="layui-btn" data-type="install" id="buttonInstall" data-perm="addon:*"><i class="layui-icon layui-icon-add-1"></i> 安装新插件</button>
<a class="layui-btn layui-btn-normal" href="{:url('addon/market')}" data-perm="addon:*"><i class="layui-icon layui-icon-app"></i> 应用市场</a>
<a class="layui-btn layui-btn-normal" href="{:url('addon/my')}" data-perm="addon:*"><i class="layui-icon layui-icon-username"></i> 我的插件</a>
<button class="layui-btn layui-btn-danger" data-type="batchDel" data-perm="addon:delete"><i class="layui-icon layui-icon-delete"></i> 批量卸载</button>
<button class="layui-btn layui-btn-warm" data-type="refresh"><i class="layui-icon layui-icon-refresh"></i> 刷新列表</button>
{if config('ywxapp.addon_developer')}
<a class="layui-btn layui-btn-warm" href="{:url('addon/design')}"><i class="layui-icon layui-icon-set"></i> 设计插件</a>
{/if}
</div>
<diiv class="layui-card-body">
<!-- 3. 核心数据表格 -->
<table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
</div>
</div>
<!-- 状态开关模板 (控制启用/禁用) -->
<script type="text/html" id="switchTpl">
<input type="checkbox" name="status" value="{{= d.id }}" lay-skin="switch" lay-text="启用|停用"
lay-filter="statusSwitch" {{= d.status == 1 ? 'checked' : '' }}>
</script>
<!-- 右侧操作列模板 (配置/卸载) -->
<script type="text/html" id="operateTpl">
<div class="layui-clear-space">
{{# if(d.hasConfig){ }}
<a class="layui-btn layui-btn-xs" lay-event="config" data-perm="addon:config"><i class="layui-icon layui-icon-set"></i> </a>
{{# } }}
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="uninstall" data-perm="addon:delete"><i class="layui-icon layui-icon-close"></i> </a>
{{# if(layui.app.devToken){ }}
<a class="layui-btn layui-btn-xs" lay-event="more"> 更多 <i class="layui-icon layui-icon-down"></i></a>
{{# } }}
</div>
</script>
<script>
console.log(window)
layui.use(['addon', 'table']);
</script>
+354
View File
@@ -0,0 +1,354 @@
<div class="layui-card" style="margin-bottom:5px;">
<div class="layui-card-header" style="display:flex;align-items:center;justify-content:space-between;">
<div>
<span class="title" style="font-size:18px;font-weight:700;">应用市场</span>
{if $is_client}
<span class="mode-badge client">客户端模式 · 连接:{$api_url}</span>
{else /}
<span class="mode-badge center">服务中心模式(本地市场)</span>
{/if}
</div>
<span>
<a href="{:url('addon/my')}" class="layui-btn layui-btn-normal layui-btn-sm">我的插件</a>
<a href="{:url('addon/index')}" class="layui-btn layui-btn-primary layui-btn-sm"><i class="layui-icon layui-icon-return"></i> 返回已安装插件</a>
</span>
</div>
{if $error}
<div class="store-banner err">{$error}</div>
{/if}
<div class="layui-card-body">
<form class="store-search" method="get" action="{:url('addon/market')}" style="margin-bottom:14px;display:flex;gap:10px;flex-wrap:wrap;align-items:center;">
<input type="text" name="keyword" value="{$keyword|default=''}" placeholder="搜索插件名称/作者/描述" class="layui-input" style="width:240px;">
<select name="category" class="layui-input" style="width:140px;">
<option value="">全部分类</option>
{volist name="categories" id="c"}
<option value="{$c}" {eq name="category" value="$c"}selected{/eq}>{$c}</option>
{/volist}
</select>
<select name="type" class="layui-input" style="width:120px;">
<option value="">全部类型</option>
<option value="addon" {eq name="type" value="addon"}selected{/eq}>插件</option>
<option value="template" {eq name="type" value="template"}selected{/eq}>模板</option>
</select>
<select name="order" class="layui-input" style="width:130px;">
<option value="new" {eq name="order" value="new"}selected{/eq}>最新上架</option>
<option value="hot" {eq name="order" value="hot"}selected{/eq}>最热门</option>
<option value="price_asc" {eq name="order" value="price_asc"}selected{/eq}>价格升序</option>
<option value="price_desc" {eq name="order" value="price_desc"}selected{/eq}>价格降序</option>
</select>
<button type="submit" class="layui-btn layui-btn-sm">筛选</button>
{if $keyword || $category || $type}<a href="{:url('addon/market')}" class="layui-btn layui-btn-sm layui-btn-primary">清空</a>{/if}
</form>
<div class="store-wrap">
{if empty($list)}
<div class="empty">暂无可用插件</div>
{else /}
{volist name="list" id="it"}
<div class="layui-card plugin-card" data-logo="{$it.logo|htmlspecialchars}" data-title="{$it.title|htmlspecialchars}" data-version="{$it.version|htmlspecialchars}" data-author="{$it.author|htmlspecialchars}" data-price="{$it.price}" data-rating="{$it.rating|default=0}" data-intro="{$it.intro|htmlspecialchars}" data-screenshots="{$it.screenshots|default=''|htmlspecialchars}" data-tags="{$it.tags|default=''|htmlspecialchars}" data-name="{$it.name|htmlspecialchars}" data-installed="{$it.installed}" data-upgradable="{$it.upgradable}" data-localversion="{$it.local_version|default=''|htmlspecialchars}" data-category="{$it.category|default=''|htmlspecialchars}" data-download_count="{$it.download_count|default=0}" data-update_at="{$it.update_at|default=0}" data-changelog="{$it.changelog|default=''|htmlspecialchars}" data-require_framework="{$it.require_framework|default=''|htmlspecialchars}" data-type="{$it.type|default='addon'}" data-versions="{:htmlspecialchars(json_encode($it['versions'] ?? []))}">
<div class="layui-card-body">
{if $it.logo}
<img class="plugin-logo" src="{$it.logo}" alt="">
{/if}
<div class="plugin-title">{$it.title}<span class="ver">v{$it.version}</span>{eq name="it.type" value="template"}<span class="type-badge tpl">模板</span>{/eq}</div>
{if $it.category}<span class="plugin-cat">{$it.category}</span>{/if}
<div class="plugin-author">作者:{$it.author}</div>
{if $it.rating > 0}<div class="plugin-rate"><span>{$it.rating|default='0.0'}</span></div>{/if}
{if !empty($it.tag_list)}<div class="plugin-tags">{volist name="it.tag_list" id="t"}<span class="plugin-cat">{$t}</span> {/volist}</div>{/if}
<div class="plugin-intro">{$it.intro}</div>
<div class="plugin-foot">
{if $it.price > 0}
<span class="price">¥{$it.price}</span>
{else /}
<span class="price free">免费</span>
{/if}
{if $it.installed}
{if $it.upgradable}
<button class="layui-btn layui-btn-xs layui-btn-warm" data-act="install" data-name="{$it.name}" data-version="{$it.version}" data-type="{$it.type|default='addon'}">升级</button>
{else /}
<span class="layui-badge layui-bg-green">已安装 v{$it.local_version}</span>
{/if}
{else /}
<button class="layui-btn layui-btn-xs layui-btn-normal" data-act="install" data-name="{$it.name}" data-version="{$it.version}" data-type="{$it.type|default='addon'}">安装</button>
{/if}
</div>
</div>
</div>
{/volist}
{/if}
</div>
</div>
</div>
<style>
.store-wrap{max-width:1100px;margin:20px auto;padding:0 14px;display:flex;flex-wrap:wrap;gap:14px;}
.plugin-card{width:calc(33.333% - 10px);min-width:260px;flex:1 1 260px;cursor:pointer;transition:box-shadow .2s,transform .2s;}
.plugin-card:hover{box-shadow:0 6px 20px rgba(0,0,0,.12);transform:translateY(-2px);}
.plugin-card .layui-card-body{padding:18px;}
.plugin-logo{width:48px;height:48px;border-radius:8px;object-fit:cover;float:left;margin-right:12px;background:#eee;}
.plugin-title{font-size:16px;font-weight:700;line-height:24px;}
.plugin-title .ver{color:#999;font-size:12px;font-weight:400;margin-left:6px;}
.plugin-author{color:#999;font-size:12px;line-height:20px;}
.plugin-cat{display:inline-block;font-size:11px;color:#1e80ff;background:#e8f3ff;border-radius:10px;padding:1px 8px;margin:2px 4px 2px 0;vertical-align:middle;}
.type-badge{display:inline-block;font-size:11px;font-weight:400;border-radius:10px;padding:1px 8px;margin-left:6px;vertical-align:middle;}
.type-badge.tpl{color:#722ed1;background:#f4eefe;}
.plugin-rate{color:#ff9f00;font-size:12px;line-height:20px;}
.plugin-rate span{color:#ff9f00;font-weight:700;}
.plugin-tags{margin:4px 0;}
.store-search .layui-input{display:inline-block;}
.plugin-intro{color:#666;font-size:13px;line-height:22px;margin:12px 0;min-height:44px;}
.plugin-foot{display:flex;align-items:center;justify-content:space-between;}
.price{color:#e4393c;font-weight:700;font-size:16px;}
.price.free{color:#18a058;}
.empty{text-align:center;color:#999;padding:80px 0;width:100%;}
.mode-badge{display:inline-block;font-size:12px;font-weight:400;padding:2px 10px;border-radius:12px;margin-left:10px;}
.mode-badge.client{background:#e8f3ff;color:#1e80ff;}
.mode-badge.center{background:#e8f7ef;color:#18a058;}
.store-banner{padding:10px 20px;font-size:13px;}
.store-banner.err{background:#fde2e2;color:#d03050;}
/* 详情弹窗 */
.detail-pop{padding:20px;color:#333;}
.dp-head{display:flex;align-items:flex-start;gap:14px;}
.dp-logo{width:64px;height:64px;border-radius:10px;object-fit:cover;background:#eee;flex:0 0 auto;}
.dp-meta{flex:1 1 auto;min-width:0;}
.dp-title{font-size:18px;font-weight:700;}
.dp-title .ver{color:#999;font-size:12px;font-weight:400;margin-left:6px;}
.dp-author{color:#999;font-size:12px;line-height:22px;}
.dp-rate{color:#ff9f00;font-size:13px;line-height:22px;}
.dp-rate .rate-num{color:#999;margin-left:4px;}
.dp-tags{margin-top:4px;}
.dp-price{text-align:right;flex:0 0 auto;}
.dp-gal{margin:16px 0;}
.dp-main{width:100%;max-height:320px;object-fit:contain;background:#f5f5f5;border-radius:8px;}
.dp-thumbs{display:flex;gap:8px;margin-top:8px;flex-wrap:wrap;}
.shot-thumb{width:64px;height:64px;object-fit:cover;border-radius:6px;border:2px solid transparent;cursor:pointer;background:#eee;}
.shot-thumb.active{border-color:#1e80ff;}
.dp-intro{color:#555;font-size:14px;line-height:24px;margin:12px 0;}
.dp-changelog{color:#555;font-size:13px;line-height:1.8;margin:4px 0;white-space:normal;word-break:break-word;}
.dp-foot{text-align:right;margin-top:10px;}
.dp-ver-sel{font-size:13px;font-weight:400;color:#333;border:1px solid #d9d9d9;border-radius:4px;padding:2px 6px;margin-left:6px;vertical-align:middle;background:#fff;cursor:pointer;}
.dp-meta-extra{display:flex;flex-wrap:wrap;gap:16px;margin-top:10px;padding-top:10px;border-top:1px dashed #eee;color:#777;font-size:13px;}
.dp-meta-extra .mi b{color:#333;font-weight:600;}
.dp-section{margin-top:18px;}
.dp-section h4{font-size:14px;font-weight:700;color:#333;margin:0 0 10px;padding-left:8px;border-left:3px solid #1e80ff;}
.dp-versions{display:flex;flex-direction:column;gap:8px;}
.ver-item{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border:1px solid #eee;border-radius:8px;cursor:pointer;transition:border-color .15s,background .15s;}
.ver-item:hover{border-color:#1e80ff;}
.ver-item.active{background:#e8f3ff;border-color:#1e80ff;}
.ver-item .ver-name{font-weight:600;color:#333;}
.ver-item .ver-date{color:#999;font-size:12px;}
.ver-item .ver-price{color:#e4393c;font-weight:600;font-size:13px;}
.ver-item .ver-price.free{color:#18a058;}
@media (max-width:768px){.plugin-card{width:100%;}}
</style>
<script>
layui.use(['layer', 'http'], function () {
var layer = layui.layer, http = layui.http, $ = layui.$;
// 解析逗号分隔或 JSON 数组文本为列表
function parseList(s) {
s = (s || '').trim();
if (!s) return [];
if (s.charAt(0) === '[') {
try { return JSON.parse(s); } catch (e) {}
}
return s.split(',').map(function (x) { return x.trim(); }).filter(Boolean);
}
// 评分星级
function starsHtml(r) {
r = parseFloat(r) || 0;
var html = '';
for (var i = 1; i <= 5; i++) {
if (r >= i) html += '<i class="star on">★</i>';
else if (r >= i - 0.5) html += '<i class="star half">★</i>';
else html += '<i class="star">★</i>';
}
return html + ' <span class="rate-num">' + r.toFixed(1) + '</span>';
}
// 简单版本号比较:a>b 返回 1a<b 返回 -1,相等 0
function cmpVer(a, b) {
var pa = String(a || '0').split('.'), pb = String(b || '0').split('.');
for (var i = 0; i < Math.max(pa.length, pb.length); i++) {
var x = parseInt(pa[i] || 0, 10), y = parseInt(pb[i] || 0, 10);
if (x !== y) return x > y ? 1 : -1;
}
return 0;
}
function priceHtmlOf(p) {
return (parseFloat(p) > 0)
? '<span class="price">¥' + p + '</span>'
: '<span class="price free">免费</span>';
}
// 按所选版本生成操作按钮(已装同版=徽章;已装低版=升级;未装=安装)
function actBtnOf(d, ver) {
var tAttr = ' data-type="' + (d.type || 'addon') + '"';
if (d.installed) {
if (cmpVer(ver, d.localVersion) > 0) {
return '<button class="layui-btn layui-btn-warm layui-btn-sm" data-act="install" data-name="' + d.name + '" data-version="' + ver + '"' + tAttr + '>升级到 v' + ver + '</button>';
}
if (cmpVer(ver, d.localVersion) === 0) {
return '<span class="layui-badge layui-bg-green">已安装 v' + d.localVersion + '</span>';
}
return '<button class="layui-btn layui-btn-primary layui-btn-sm" data-act="install" data-name="' + d.name + '" data-version="' + ver + '"' + tAttr + '>安装 v' + ver + '(低于本地 v' + d.localVersion + '</button>';
}
return '<button class="layui-btn layui-btn-normal layui-btn-sm" data-act="install" data-name="' + d.name + '" data-version="' + ver + '"' + tAttr + '>安装 v' + ver + '</button>';
}
// 时间戳格式化为 YYYY-MM-DD
function fmtDate(ts) {
ts = parseInt(ts || 0, 10);
if (!ts) return '';
var d = new Date(ts * 1000);
var p = function (n) { return (n < 10 ? '0' : '') + n; };
return d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate());
}
// HTML 转义(防 XSS
function escapeHtml(s) {
return String(s == null ? '' : s)
.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
.replace(/"/g, '&quot;').replace(/'/g, '&#39;');
}
// 换行转 <br>
function nl2br(s) {
return String(s == null ? '' : s).replace(/\r\n|\r|\n/g, '<br>');
}
function openDetail(d) {
var shots = parseList(d.screenshots);
var mainShot = shots.length ? shots[0] : (d.logo || '');
var thumbs = shots.map(function (u, i) {
return '<img class="shot-thumb' + (i === 0 ? ' active' : '') + '" src="' + u + '" data-src="' + u + '">';
}).join('');
var tags = parseList(d.tags).map(function (t) {
return '<span class="plugin-cat">' + t + '</span>';
}).join('');
// 历史版本(服务端已按版本号降序返回);无 versions 时退化为当前版本单项
var versions = [];
try { versions = JSON.parse(d.versions || '[]'); } catch (e) {}
if (!versions || !versions.length) versions = [{ version: d.version, price: d.price, update_at: d.updateAt || 0 }];
// 版本选择:多版本时渲染下拉,否则只显示版本号
var verSelHtml;
if (versions.length > 1) {
var opts = versions.map(function (v, i) {
var tag = (parseFloat(v.price) > 0) ? '¥' + v.price : '免费';
return '<option value="' + i + '"' + (i === 0 ? ' selected' : '') + '>v' + v.version + '' + tag + '' + (i === 0 ? ' · 最新' : '') + '</option>';
}).join('');
verSelHtml = '<select class="dp-ver-sel" id="dpVerSel">' + opts + '</select>';
} else {
verSelHtml = '<span class="ver">v' + versions[0].version + '</span>';
}
// 版本历史区块(DZ 风格:列出全部版本,点击切换)
var verHistory = versions.map(function (v, i) {
var pt = (parseFloat(v.price) > 0) ? '¥' + v.price : '免费';
return '<div class="ver-item' + (i === 0 ? ' active' : '') + '" data-idx="' + i + '">'
+ '<span class="ver-name">v' + v.version + (i === 0 ? ' · 最新' : '') + '</span>'
+ '<span class="ver-date">' + (v.update_at ? fmtDate(v.update_at) : '—') + '</span>'
+ '<span class="ver-price' + (parseFloat(v.price) > 0 ? '' : ' free') + '">' + pt + '</span>'
+ '</div>';
}).join('');
var cur = versions[0];
// 信息栏:评分 / 下载量 / 更新时间 / 分类
var extra = ''
+ (d.rating > 0 ? '<span class="mi">评分:<b>' + (parseFloat(d.rating) || 0).toFixed(1) + '</b></span>' : '')
+ (d.downloadCount ? '<span class="mi">下载:<b>' + d.downloadCount + '</b></span>' : '')
+ (d.updateAt && fmtDate(d.updateAt) ? '<span class="mi">更新:<b>' + fmtDate(d.updateAt) + '</b></span>' : '')
+ (d.category ? '<span class="mi">分类:<b>' + d.category + '</b></span>' : '')
+ (d.requireFramework ? '<span class="mi">最低框架:<b>v' + d.requireFramework + '</b></span>' : '');
var html = ''
+ '<div class="detail-pop">'
+ '<div class="dp-head">'
+ (d.logo ? '<img class="dp-logo" src="' + d.logo + '">' : '')
+ '<div class="dp-meta">'
+ '<div class="dp-title">' + d.title + ' ' + verSelHtml + '</div>'
+ '<div class="dp-author">作者:' + d.author + '</div>'
+ '<div class="dp-rate">' + starsHtml(d.rating) + '</div>'
+ (tags ? '<div class="dp-tags">' + tags + '</div>' : '')
+ '</div>'
+ '<div class="dp-price" id="dpPrice">' + priceHtmlOf(cur.price) + '</div>'
+ '</div>'
+ (extra ? '<div class="dp-meta-extra">' + extra + '</div>' : '')
+ (shots.length ? '<div class="dp-gal"><img class="dp-main" src="' + mainShot + '"><div class="dp-thumbs">' + thumbs + '</div></div>' : '')
+ '<div class="dp-intro">' + (d.intro || '暂无简介') + '</div>'
+ (d.changelog ? '<div class="dp-section"><h4>更新日志</h4><div class="dp-changelog">' + nl2br(escapeHtml(d.changelog)) + '</div></div>' : '')
+ (versions.length > 1 ? '<div class="dp-section"><h4>历史版本</h4><div class="dp-versions" id="dpVers">' + verHistory + '</div></div>' : '')
+ '<div class="dp-foot" id="dpFoot">' + actBtnOf(d, cur.version) + '</div>'
+ '</div>';
layer.open({ type: 1, title: false, area: ['680px', 'auto'], shadeClose: true, content: html, success: function () {
bindDetailEvents(versions, d);
}});
}
// 绑定详情弹窗内交互:截略图切换、版本切换(下拉/历史列表联动价格与安装按钮)
function bindDetailEvents(versions, d) {
function apply(idx) {
var v = versions[idx] || versions[0];
$('#dpPrice').html(priceHtmlOf(v.price));
$('#dpFoot').html(actBtnOf(d, v.version));
$('#dpVerSel').val(String(idx));
$('#dpVers .ver-item').removeClass('active').filter('[data-idx="' + idx + '"]').addClass('active');
}
$('.dp-thumbs .shot-thumb').off('click').on('click', function () {
$('.dp-main').attr('src', $(this).data('src'));
$('.dp-thumbs .shot-thumb').removeClass('active');
$(this).addClass('active');
});
$('#dpVerSel').off('change').on('change', function () { apply(parseInt(this.value, 10)); });
$('#dpVers .ver-item').off('click').on('click', function () { apply(parseInt($(this).data('idx'), 10)); });
}
// 卡片点击(安装按钮除外)打开详情
$(document).on('click', '.plugin-card', function (e) {
if ($(e.target).closest('[data-act="install"]').length) return;
var $c = $(this);
openDetail({
logo: $c.data('logo'), title: $c.data('title'), version: $c.data('version'),
author: $c.data('author'), price: $c.data('price'), rating: $c.data('rating'),
intro: $c.data('intro'), screenshots: $c.data('screenshots'), tags: $c.data('tags'),
name: $c.data('name'), installed: $c.data('installed') === '1',
upgradable: $c.data('upgradable') === '1', localVersion: $c.data('localversion'),
category: $c.data('category'), downloadCount: $c.data('download_count'), updateAt: $c.data('update_at'),
changelog: $c.data('changelog'), requireFramework: $c.data('require_framework'),
type: $c.data('type') || 'addon',
versions: $c.attr('data-versions') || '[]'
});
});
// 安装 / 升级(卡片与弹窗内按钮共用)
$(document).on('click', '[data-act="install"]', function () {
var $btn = $(this);
var name = $btn.data('name');
var version = $btn.data('version');
var type = $btn.data('type') || 'addon';
var label = type === 'template' ? '模板' : '';
layer.confirm('确认安装' + label + '「' + name + '」' + (version ? ' v' + version : '') + '', function (idx) {
layer.close(idx);
var loading = layer.load();
http.post('{:url("addon/downloadInstall")}', { name: name, version: version, type: type })
.then(function (res) {
layer.close(loading);
if (res.code === 0) {
layer.msg(res.message || '安装成功', { icon: 1 });
setTimeout(function () { location.reload(); }, 800);
} else {
layer.msg(res.message || '安装失败', { icon: 2 });
}
})
.catch(function () {
layer.close(loading);
layer.msg('请求失败', { icon: 2 });
});
});
});
});
</script>
+60
View File
@@ -0,0 +1,60 @@
<div class="layui-card" style="margin-bottom:5px;">
<div class="layui-card-header" style="display:flex;align-items:center;justify-content:space-between;">
<div>
<span class="title" style="font-size:18px;font-weight:700;">我的插件</span>
{if $is_client}
<span class="mode-badge client">客户端模式 · 连接:{$api_url|default=''}</span>
{else /}
<span class="mode-badge center">服务中心模式(本地市场)</span>
{/if}
</div>
<span>
<a href="{:url('addon/market')}" class="layui-btn layui-btn-normal layui-btn-sm">应用市场</a>
<a href="{:url('addon/index')}" class="layui-btn layui-btn-primary layui-btn-sm">完整管理</a>
</span>
</div>
<div class="layui-card-body">
<div class="store-wrap">
{if empty($list)}
<div class="empty">本机尚未安装任何插件,去 <a href="{:url('addon/market')}">应用市场</a> 看看吧</div>
{else /}
{volist name="list" id="it"}
<div class="layui-card plugin-card">
<div class="layui-card-body">
<div class="plugin-title">{$it.title}<span class="ver">v{$it.version}</span></div>
<div class="plugin-author">标识:{$it.name} · 作者:{$it.author|default='官方'}</div>
<div class="plugin-intro">{$it.description|default=''}</div>
<div class="plugin-foot">
{if $it.status == 1}
<span class="layui-badge layui-bg-green">已启用</span>
{else /}
<span class="layui-badge">已停用</span>
{/if}
<span>
<a href="{:url('addon/setting')}?addon={$it.name}" class="layui-btn layui-btn-xs layui-btn-primary">配置</a>
<a href="{:url('addon/index')}" class="layui-btn layui-btn-xs layui-btn-normal">升级/卸载</a>
</span>
</div>
</div>
</div>
{/volist}
{/if}
</div>
</div>
</div>
<style>
.store-wrap{max-width:1100px;margin:20px auto;padding:0 14px;display:flex;flex-wrap:wrap;gap:14px;}
.plugin-card{width:calc(33.333% - 10px);min-width:260px;flex:1 1 260px;}
.plugin-card .layui-card-body{padding:18px;}
.plugin-title{font-size:16px;font-weight:700;line-height:24px;}
.plugin-title .ver{color:#999;font-size:12px;font-weight:400;margin-left:6px;}
.plugin-author{color:#999;font-size:12px;line-height:20px;}
.plugin-intro{color:#666;font-size:13px;line-height:22px;margin:12px 0;min-height:44px;}
.plugin-foot{display:flex;align-items:center;justify-content:space-between;}
.empty{text-align:center;color:#999;padding:80px 0;width:100%;}
.mode-badge{display:inline-block;font-size:12px;font-weight:400;padding:2px 10px;border-radius:12px;margin-left:10px;}
.mode-badge.client{background:#e8f3ff;color:#1e80ff;}
.mode-badge.center{background:#e8f7ef;color:#18a058;}
@media (max-width:768px){.plugin-card{width:100%;}}
</style>
+134
View File
@@ -0,0 +1,134 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link href="/assets/layui/css/layui.css" rel="stylesheet">
<link href="/assets/ywxapp/css/ywxapp.css" rel="stylesheet">
<style>
html,
body {
height: 100%;
display: flex;
flex-direction: column;
}
body {
padding-bottom: 64px;
}
.layui-card {
flex: 1;
display: flex;
flex-direction: column;
}
.layui-card-body {
display: flex;
flex-direction: column;
flex: 1;
}
.layui-table-view {
display: flex;
flex-direction: column;
flex: 1;
}
.layui-table-box {
display: flex;
flex-direction: column;
flex: 1;
}
.layui-table-body {
display: flex;
flex-direction: column;
flex: 1;
}
</style>
<script src="/assets/layui/layui.js"></script>
<script type="text/javascript">
// 全局路径配置:仅注入部署参数,路由方法来自 layui 的 route 模块。
// app 为 ThinkPHP 真实应用目录名(backend/member/frontend/home),对应 public/static/<app> 目录。
layui.app = {
root: "{$site.root|default=''}",
assetUrl: "/assets",
module: "{$site.module}",
realModule: "{$site.app}",
routeBase: "{$route_base}",
controller: "{$site.controller}",
action: "{$site.action}"
};
</script>
<script src="/assets/ywxapp/ywxapp.js" module="{$site.app}"></script>
</head>
<body>
<div class="layui-card" style="margin:15px;">
<div class="layui-card-header">插件配置:{$addon}</div>
<div class="layui-card-body">
{if empty($fields)}
<div class="layui-form-mid layui-word-aux">该插件未定义 config.php,暂无可配置项。</div>
{else /}
<form class="layui-form" lay-filter="cfgForm">
{volist name="fields" id="f"}
<div class="layui-form-item">
<label class="layui-form-label">{$f.title}</label>
<div class="layui-input-block">
{if $f.type == 'textarea'}
<textarea name="{$f.name}" class="layui-textarea">{$f.value}</textarea>
{elseif $f.type == 'select' /}
<select name="{$f.name}">
{volist name="$f.options" id="opt"}
<option value="{$key}" {if $key==$f.value}selected{/if}>{$opt}</option>
{/volist}
</select>
{elseif $f.type == 'radio' /}
{volist name="$f.options" id="opt"}
<input type="radio" name="{$f.name}" value="{$key}" title="{$opt}" {if $key==$f.value}checked{/if}>
{/volist}
{elseif $f.type == 'checkbox' /}
{volist name="$f.options" id="opt"}
<input type="checkbox" name="{$f.name}[]" value="{$key}" title="{$opt}" {if in_array($key,(array)$f.value)}checked{/if}>
{/volist}
{elseif $f.type == 'number' /}
<input type="number" name="{$f.name}" value="{$f.value}" class="layui-input">
{else /}
<input type="text" name="{$f.name}" value="{$f.value}" class="layui-input">
{/if}
{if $f.tip}<div class="layui-form-mid layui-word-aux">{$f.tip}</div>{/if}
</div>
</div>
{/volist}
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="cfgForm">保存</button>
</div>
</div>
</form>
{/if}
</div>
</div>
<script src="/assets/layui/layui.js"></script>
<script>
layui.use(['form', 'layer'], function () {
var form = layui.form, $ = layui.jquery, layer = layui.layer;
form.on('submit(cfgForm)', function (data) {
$.post(window.location.href, data.field, function (r) {
if (r.code === 0) {
layer.msg(r.message || '保存成功', { icon: 1 });
} else {
layer.msg(r.message || '保存失败', { icon: 2 });
}
}, 'json');
return false;
});
});
</script>
</body>
</html>
+394
View File
@@ -0,0 +1,394 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>插件性能监控</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/assets/layui/css/layui.css">
<style>
body { background: #f2f3f5; padding: 15px; }
.stat-card {
background: #fff;
border-radius: 4px;
padding: 20px;
margin-bottom: 15px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.stat-card .value {
font-size: 28px;
font-weight: bold;
color: #1e9fff;
margin: 10px 0;
}
.stat-card .label {
color: #666;
font-size: 14px;
}
.stat-card.warning .value { color: #ffb800; }
.stat-card.danger .value { color: #ff5722; }
.stat-card.success .value { color: #5fb878; }
.chart-container {
background: #fff;
border-radius: 4px;
padding: 20px;
margin-bottom: 15px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
min-height: 400px;
}
.addon-list-item {
background: #fff;
border-radius: 4px;
padding: 15px;
margin-bottom: 10px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.addon-status {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 8px;
}
.addon-status.healthy { background: #5fb878; }
.addon-status.warning { background: #ffb800; }
.addon-status.danger { background: #ff5722; }
.performance-bar {
height: 8px;
background: #f0f0f0;
border-radius: 4px;
overflow: hidden;
margin-top: 5px;
}
.performance-bar-fill {
height: 100%;
background: #1e9fff;
transition: width 0.3s;
}
.performance-bar-fill.warning { background: #ffb800; }
.performance-bar-fill.danger { background: #ff5722; }
</style>
</head>
<body>
<div class="layui-fluid">
<!-- 统计卡片 -->
<div class="layui-row layui-col-space15">
<div class="layui-col-md3">
<div class="stat-card">
<div class="label">总插件数</div>
<div class="value" id="totaladdon">-</div>
</div>
</div>
<div class="layui-col-md3">
<div class="stat-card success">
<div class="label">健康插件</div>
<div class="value" id="healthyaddon">-</div>
</div>
</div>
<div class="layui-col-md3">
<div class="stat-card warning">
<div class="label">问题插件</div>
<div class="value" id="problematicaddon">-</div>
</div>
</div>
<div class="layui-col-md3">
<div class="stat-card">
<div class="label">平均执行时间</div>
<div class="value"><span id="avgExecutionTime">-</span> ms</div>
</div>
</div>
</div>
<!-- 性能图表 -->
<div class="layui-row layui-col-space15" style="margin-top: 15px;">
<div class="layui-col-md6">
<div class="chart-container">
<div style="margin-bottom: 15px;">
<button class="layui-btn layui-btn-sm layui-btn-primary" data-type="execution_time">执行时间</button>
<button class="layui-btn layui-btn-sm layui-btn-primary" data-type="memory_usage">内存使用</button>
<button class="layui-btn layui-btn-sm layui-btn-primary" data-type="success_rate">成功率</button>
<button class="layui-btn layui-btn-sm layui-btn-primary" data-type="call_count">调用次数</button>
</div>
<div id="performanceChart" style="height: 350px;"></div>
</div>
</div>
<div class="layui-col-md6">
<div class="chart-container">
<div style="margin-bottom: 15px;">
<span style="font-weight: bold;">插件性能列表</span>
<button class="layui-btn layui-btn-sm layui-btn-normal" id="refreshBtn" style="float: right;">
<i class="layui-icon layui-icon-refresh"></i> 刷新
</button>
</div>
<div id="addonList" style="height: 350px; overflow-y: auto;">
<div style="text-align: center; color: #999; padding-top: 100px;">
加载中...
</div>
</div>
</div>
</div>
</div>
<!-- 热重载统计 -->
<div class="layui-row layui-col-space15" style="margin-top: 15px;">
<div class="layui-col-md12">
<div class="stat-card">
<div style="margin-bottom: 15px;">
<span style="font-weight: bold;">热重载统计</span>
<button class="layui-btn layui-btn-sm layui-btn-danger" id="clearPerfBtn" style="float: right;">
<i class="layui-icon layui-icon-delete"></i> 清除性能数据
</button>
</div>
<div id="reloadStats">
<div style="text-align: center; color: #999; padding: 20px;">
加载中...
</div>
</div>
</div>
</div>
</div>
</div>
<script src="/assets/layui/layui.js"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<script>
layui.use(['jquery', 'layer', 'http'], function() {
var $ = layui.jquery;
var layer = layui.layer;
var http = layui.http;
var performanceChart = null;
var currentChartType = 'execution_time';
// 初始化图表
function initChart() {
performanceChart = echarts.init(document.getElementById('performanceChart'));
loadChartData(currentChartType);
}
// 加载图表数据
function loadChartData(type) {
currentChartType = type;
http.get('/backend/addon_monitor/chart', { type: type }).then(function(res) {
if (res.code === 0) {
updateChart(res.data.chart_data, type);
} else {
layer.msg('加载图表数据失败', {icon: 2});
}
}).catch(function() { layer.msg('请求失败', {icon: 2}); });
}
// 更新图表
function updateChart(data, type) {
var names = data.map(item => item.name);
var values = data.map(item => item.value);
var title = '';
var unit = '';
switch(type) {
case 'execution_time':
title = '执行时间 (ms)';
unit = 'ms';
break;
case 'memory_usage':
title = '内存使用 (KB)';
unit = 'KB';
break;
case 'success_rate':
title = '成功率 (%)';
unit = '%';
break;
case 'call_count':
title = '调用次数';
unit = '次';
break;
}
var option = {
title: {
text: title,
left: 'center'
},
tooltip: {
trigger: 'axis',
formatter: function(params) {
return params[0].name + ': ' + params[0].value + ' ' + unit;
}
},
xAxis: {
type: 'category',
data: names,
axisLabel: {
rotate: 45,
interval: 0
}
},
yAxis: {
type: 'value',
name: unit
},
series: [{
data: values,
type: 'bar',
itemStyle: {
color: '#1e9fff'
}
}]
};
performanceChart.setOption(option);
}
// 加载概览数据
function loadOverview() {
http.get('/backend/addon_monitor', { action: 'overview' }).then(function(res) {
if (res.code === 0) {
var data = res.data;
$('#totaladdon').text(data.total_addon);
$('#healthyaddon').text(data.healthy_addon);
$('#problematicaddon').text(data.problematic_addon);
$('#avgExecutionTime').text(data.avg_execution_time);
// 更新重载统计
updateReloadStats(data.reload_stats);
} else {
layer.msg('加载概览数据失败', {icon: 2});
}
}).catch(function() { layer.msg('请求失败', {icon: 2}); });
}
// 更新重载统计
function updateReloadStats(stats) {
if (!stats || stats.total_reloads === 0) {
$('#reloadStats').html('<div style="text-align: center; color: #999; padding: 20px;">暂无重载数据</div>');
return;
}
var html = '<div class="layui-row">';
html += '<div class="layui-col-md3"><div class="label">总重载次数</div><div class="value">' + stats.total_reloads + '</div></div>';
html += '<div class="layui-col-md3"><div class="label">成功次数</div><div class="value success">' + stats.successful_reloads + '</div></div>';
html += '<div class="layui-col-md3"><div class="label">失败次数</div><div class="value danger">' + stats.failed_reloads + '</div></div>';
html += '<div class="layui-col-md3"><div class="label">成功率</div><div class="value">' + (stats.total_reloads > 0 ? ((stats.successful_reloads / stats.total_reloads) * 100).toFixed(2) : 0) + '%</div></div>';
html += '</div>';
if (stats.addon && Object.keys(stats.addon).length > 0) {
html += '<div style="margin-top: 20px;"><strong>各插件重载状态:</strong></div>';
html += '<div style="margin-top: 10px;">';
for (var addon in stats.addon) {
var status = stats.addon[addon];
var statusClass = status.status === 'success' ? 'success' : 'danger';
var statusText = status.status === 'success' ? '成功' : '失败';
var lastReload = status.reload_time ? new Date(status.reload_time * 1000).toLocaleString() : '未重载';
html += '<div style="padding: 8px 0; border-bottom: 1px solid #f0f0f0;">';
html += '<span class="layui-badge layui-bg-' + statusClass + '">' + addon + '</span>';
html += ' <span style="color: #666;">' + lastReload + '</span>';
html += ' <span style="color: ' + (status.status === 'success' ? '#5fb878' : '#ff5722') + ';">' + statusText + '</span>';
html += '</div>';
}
html += '</div>';
}
$('#reloadStats').html(html);
}
// 加载插件列表
function loadAddonList() {
http.get('/backend/addon_monitor', { action: 'performance' }).then(function(res) {
if (res.code === 0) {
renderAddonList(res.data);
} else {
layer.msg('加载插件列表失败', {icon: 2});
}
}).catch(function() { layer.msg('请求失败', {icon: 2}); });
}
// 渲染插件列表
function renderAddonList(addon) {
if (!addon || Object.keys(addon).length === 0) {
$('#addonList').html('<div style="text-align: center; color: #999; padding-top: 100px;">暂无插件数据</div>');
return;
}
var html = '';
for (var addon in addon) {
var stats = addon[addon];
var statusClass = 'healthy';
var statusText = '健康';
if (stats.success_rate < 90 || stats.avg_execution_time > 1.0) {
statusClass = 'danger';
statusText = '异常';
} else if (stats.avg_execution_time > 0.5 || stats.success_rate < 95) {
statusClass = 'warning';
statusText = '警告';
}
var execTimePercent = Math.min((stats.avg_execution_time / 2.0) * 100, 100);
var execTimeClass = stats.avg_execution_time > 1.0 ? 'danger' : (stats.avg_execution_time > 0.5 ? 'warning' : '');
html += '<div class="addon-list-item">';
html += '<div style="display: flex; justify-content: space-between; align-items: center;">';
html += '<div><span class="addon-status ' + statusClass + '"></span><strong>' + addon + '</strong></div>';
html += '<span style="font-size: 12px; color: #666;">' + statusText + '</span>';
html += '</div>';
html += '<div style="margin-top: 10px; font-size: 12px; color: #666;">';
html += '<div>调用次数: ' + stats.total_calls + ' | 成功率: ' + stats.success_rate.toFixed(2) + '%</div>';
html += '<div style="margin-top: 5px;">平均执行时间: ' + (stats.avg_execution_time * 1000).toFixed(2) + ' ms</div>';
html += '<div class="performance-bar">';
html += '<div class="performance-bar-fill ' + execTimeClass + '" style="width: ' + execTimePercent + '%;"></div>';
html += '</div>';
html += '</div>';
html += '</div>';
}
$('#addonList').html(html);
}
// 绑定事件
$('button[data-type]').on('click', function() {
var type = $(this).data('type');
$('button[data-type]').removeClass('layui-btn-normal').addClass('layui-btn-primary');
$(this).removeClass('layui-btn-primary').addClass('layui-btn-normal');
loadChartData(type);
});
$('#refreshBtn').on('click', function() {
loadOverview();
loadAddonList();
loadChartData(currentChartType);
layer.msg('数据已刷新', {icon: 1});
});
$('#clearPerfBtn').on('click', function() {
layer.confirm('确定要清除所有性能数据吗?', function(index) {
http.get('/backend/addon_monitor', { action: 'clear_performance' }).then(function(res) {
layer.close(index);
if (res.code === 0) {
layer.msg('性能数据已清除', {icon: 1});
loadOverview();
loadAddonList();
} else {
layer.msg('清除失败', {icon: 2});
}
}).catch(function() { layer.msg('请求失败', {icon: 2}); });
});
});
// 初始化
initChart();
loadOverview();
loadAddonList();
// 定时刷新
setInterval(function() {
loadOverview();
}, 30000); // 30秒刷新一次
});
</script>
</body>
</html>
+130
View File
@@ -0,0 +1,130 @@
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header"> </div>
<div class="layui-card-body">
<table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
</div>
</div>
</div>
<script type="text/html" id="tableBar">
<div class="layui-btn-group">
<a class="layui-btn layui-btn-sm layui-btn-primary" title="新建节点" lay-event="dataCreate"> <i class="layui-icon layui-icon-add-1"></i> </a>
<a class="layui-btn layui-btn-sm layui-btn-primary" title="删除资源" lay-event="dataDelete"><i class="layui-icon layui-icon-delete"></i> </a>
<a class="layui-btn layui-btn-sm layui-btn-primary" title="资源回收站" lay-event="dataRecybin"><i class="layui-icon layui-icon-home"></i> </a>
</div>
</script>
<script type="text/html" id="dataBar">
<div class="layui-btn-group">
<a class="layui-btn layui-btn-sm layui-btn-primary" title="编辑节点" lay-event="update" data-perm="admin:edit"><i class="layui-icon layui-icon-edit"></i> </a>
{{# if(!d.is_super) { }}
<a class="layui-btn layui-btn-sm layui-btn-primary" title="删除节点" lay-event="delete" data-perm="admin:delete"><i class="layui-icon layui-icon-delete"></i> </a>
{{# } }}
</div>
</script>
<!-- 表格回收站 -->
<script type="text/html" id="dataRecybinTpl">
<div class="layui-card">
<div class="layui-card-header"> </div>
<div class="layui-card-body">
<table class="layui-hide" id="dataRecybinTable" lay-filter="dataRecybinTable"></table>
</div>
</div>
</script>
<!-- 回收站数据工具条 -->
<script type="text/html" id="dataRecybinBarTpl">
<div class="layui-btn-group">
<a class="layui-btn layui-btn-sm layui-btn-primary" title="恢复数据" lay-event="update"><i class="layui-icon layui-icon-edit"></i> </a>
<a class="layui-btn layui-btn-sm layui-btn-primary" title="删除节点" lay-event="delete"><i class="layui-icon layui-icon-delete"></i> </a>
</div>
</script>
<!-- 状态开关 -->
<script type="text/html" id="statusTpl">
<input type="checkbox" name="status" value="{{d.id}}" lay-skin="switch" lay-text="启用|禁用" lay-filter="statusSwitch" {{ d.status == 1 ? 'checked' : '' }}>
</script>
<!-- 角色显示 -->
<script type="text/html" id="roleTpl">
{{# if(d.is_super == 1) { }}
<span class="role-super">超级管理员</span>
{{# } else if(d.role_id == 1) { }}
<span class="role-super">超级管理员</span>
{{# } else if(d.role_id == 2) { }}
<span>普通管理员</span>
{{# } else { }}
<span>未知角色</span>
{{# } }}
</script>
<!-- 登录信息 -->
<script type="text/html" id="loginInfoTpl">
<div>登录次数{{d.login_count}}</div>
<div>最后登录{{ d.login_time }}</div>
</script>
<!-- 添加/编辑管理员表单 -->
<script type="text/html" id="dataFormTpl">
<form class="layui-form" style="padding: 20px 20px 0;">
<input type="hidden" name="id" value="{{d.id||''}}">
<div class="layui-form-item">
<label class="layui-form-label">账户</label>
<div class="layui-input-block">
<input type="text" name="account" required lay-verify="required|account" placeholder="请输入用户名" autocomplete="off" class="layui-input" value="{{d.account||''}}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">昵称</label>
<div class="layui-input-block">
<input type="text" name="nickname" required lay-verify="required" placeholder="请输入真实姓名" autocomplete="off" class="layui-input" value="{{d.nickname||''}}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">角色</label>
<div class="layui-input-block">
<select name="role_id" lay-verify="required">
<option value="">请选择角色</option>
<option value="1" {{ (d.role_id==1||d.is_super==1) ? 'selected' : '' }}>超级管理员</option>
<option value="2" {{ d.role_id==2 ? 'selected' : '' }}>普通管理员</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">邮箱</label>
<div class="layui-input-block">
<input type="text" name="email" lay-verify="email" placeholder="请输入邮箱" autocomplete="off" class="layui-input" value="{{d.email||''}}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">手机号</label>
<div class="layui-input-block">
<input type="text" name="mobile" lay-verify="phone" placeholder="请输入手机号" autocomplete="off" class="layui-input" value="{{d.mobile||''}}">
</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="启用" {{ d.status==undefined || d.status==1 ? 'checked' : '' }}>
<input type="radio" name="status" value="0" title="禁用" {{ d.status==0 ? 'checked' : '' }}>
</div>
</div>
<div class="layui-form-item" id="passwordGroup" {{ d.id ? 'style="display:none;"' : '' }}>
<label class="layui-form-label">密码</label>
<div class="layui-input-block">
<input type="password" name="password" required lay-verify="required|pass" placeholder="请输入密码" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-hide" id="resetPwdGroup">
<label class="layui-form-label">新密码</label>
<div class="layui-input-block">
<input type="password" name="new_password" required lay-verify="required|pass" placeholder="请输入新密码" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">备注</label>
<div class="layui-input-block">
<textarea name="remark" placeholder="请输入备注信息" class="layui-textarea">{{d.remark||''}}</textarea>
</div>
</div>
</form>
</script>
<script>
layui.use('admin', function () { });
</script>
+126
View File
@@ -0,0 +1,126 @@
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">充值卡密管理</div>
<div class="layui-card-body">
<form class="layui-form" lay-filter="data-search-form">
<div class="layui-form-item">
<div class="layui-inline">
<input type="text" name="cardno" placeholder="卡号" autocomplete="off" class="layui-input">
</div>
<div class="layui-inline">
<select name="status">
<option value="">全部状态</option>
<option value="0">未售</option>
<option value="1">已售</option>
<option value="2">已兑换</option>
</select>
</div>
<div class="layui-inline">
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="data-search-btn"><i class="layui-icon">&#xe615;</i> 搜索</button>
<button type="button" class="layui-btn layui-btn-primary" id="btn-reset">重置</button>
</div>
</div>
</form>
<table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
<script type="text/html" id="tableBar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="dataCreate" data-perm="card:add"><i class="layui-icon">&#xe654;</i> </button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-event="dataBatch" data-perm="card:add"><i class="layui-icon">&#xe674;</i> </button>
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-event="dataDelete" data-perm="card:del"><i class="layui-icon">&#xe640;</i> </button>
<button class="layui-btn layui-btn-sm" lay-event="dataRecybin" data-perm="card:restore"><i class="layui-icon">&#xe64b;</i> </button>
</div>
</script>
<script type="text/html" id="dataBar">
<a class="layui-btn layui-btn-xs" lay-event="update" data-perm="card:edit">编辑</a>
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="delete" data-perm="card:del">删除</a>
</script>
<script type="text/html" id="statusTpl">
<input type="checkbox" name="status" value="{{ d.id }}" lay-skin="switch" lay-text="开|关" lay-filter="statusSwitch" {{ d.status == 1 ? 'checked' : '' }}>
</script>
</div>
</div>
</div>
<script type="text/html" id="dataFormTpl">
<form class="layui-form" id="wxapp-form" lay-filter="wxapp-form" style="padding:15px;">
<input type="hidden" name="id" value="{{ d.id || '' }}">
<div class="layui-form-item">
<label class="layui-form-label">卡号</label>
<div class="layui-input-block">
<input type="text" name="cardno" 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="password" 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="amount" placeholder="请输入面值" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<input type="checkbox" name="status" lay-skin="switch" lay-text="启用|禁用" value="1" checked>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">排序</label>
<div class="layui-input-block">
<input type="text" name="sort" placeholder="请输入排序" class="layui-input">
</div>
</div>
</form>
</script>
<script type="text/html" id="batchFormTpl">
<form class="layui-form" id="wxapp-batch-form" lay-filter="wxapp-batch-form" style="padding:15px;">
<div class="layui-form-item">
<label class="layui-form-label">生成数量</label>
<div class="layui-input-block">
<input type="number" name="count" value="10" min="1" max="200" class="layui-input" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">面值()</label>
<div class="layui-input-block">
<input type="number" name="amount" value="10" step="0.01" min="0.01" class="layui-input" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">卡号前缀</label>
<div class="layui-input-block">
<input type="text" name="prefix" placeholder="可选,如 YX" class="layui-input">
</div>
</div>
</form>
</script>
<script type="text/html" id="batchResultTpl">
<div class="layui-form" style="padding:10px;">
<blockquote class="layui-elem-quote">已生成 {{ d.list.length }} 张卡密请妥善保管</blockquote>
<table class="layui-table">
<thead><tr><th>卡号</th><th></th><th></th></tr></thead>
<tbody>
{{# layui.each(d.list, function(i, item){ }}
<tr><td>{{ item.cardno }}</td><td>{{ item.password }}</td><td>{{ d.amount }}</td></tr>
{{# }); }}
</tbody>
</table>
</div>
</script>
<script type="text/html" id="dataRecybinTpl">
<table class="layui-hide" id="dataRecybinTable" lay-filter="dataRecybinTable"></table>
<script type="text/html" id="dataRecybinBarTpl">
<a class="layui-btn layui-btn-xs" lay-event="restore" data-perm="card:restore">恢复</a>
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="forcedelete" data-perm="card:destroy">彻底删除</a>
</script>
</script>
<script>
layui.use('card', layui.factory('card'));
</script>
+11
View File
@@ -0,0 +1,11 @@
<div class="layui-card" style="margin:15px;">
<div class="layui-card-header">聊天管理</div>
<div class="layui-card-body">
<p>聊天功能由 <b>wxchat</b> 插件(基于 GatewayWorker)提供,本页为后台入口占位说明。</p>
<ul class="layui-text" style="line-height:2;">
<li>实时聊天、好友 / 群组、消息记录由 wxchat 插件服务承载;</li>
<li>相关配置请在「插件管理 → 配置」中维护 <b>wxchat</b> 插件;</li>
<li>若需查看在线连接 / 消息吞吐,请前往 MQTT 代理(<b>mqttbroker</b> 插件)后台。</li>
</ul>
</div>
</div>
+81
View File
@@ -0,0 +1,81 @@
<!--
* @Author: YwxApp <ywx@ywxapp.cn>
* @Date: 2026-08-06 22:05:38
* @LastEditors: YwxApp <ywx@ywxapp.cn>
* @LastEditTime: 2026-08-07 09:12:10
* @Description:
* @FilePath: \ywxapp_dev\app\backend\view\common\layout.html
* @CustomString: Copyright (c) 2026 YwxApp
-->
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>{$title | default='YwxApp'}</title>
<meta name="renderer" content="webkit" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1" />
<link href="/assets/layui/css/layui.css" rel="stylesheet" />
<link href="/assets/ywxapp/css/ywxapp.css" rel="stylesheet" />
<style>
html,
body {
height: 100%;
display: flex;
flex-direction: column;
}
.layui-fluid {
margin: 5px;
}
.layui-card {
flex: 1;
display: flex;
flex-direction: column;
}
.layui-card-body {
display: flex;
flex-direction: column;
flex: 1;
}
.layui-table-view {
display: flex;
flex-direction: column;
flex: 1;
}
.layui-table-box {
display: flex;
flex-direction: column;
flex: 1;
}
.layui-table-body {
display: flex;
flex-direction: column;
flex: 1;
}
</style>
<script src="/assets/layui/layui.js"></script>
<script type="text/javascript">
layui.app = {
root: "{$site.root|default=''}",
assetUrl: "/assets",
module: "{$site.module}",
app: "{$site.app}",
routeBase: "{$route_base}",
controller: "{$site.controller}",
action: "{$site.action}"
};
</script>
<script src="/assets/ywxapp/ywxapp.js" module="{$site.app}"></script>
</head>
<body>
{__CONTENT__}
</body>
</html>
+127
View File
@@ -0,0 +1,127 @@
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header"></div>
<div class="layui-card-header">
<div class="form-container">
<div id="formView"></div>
</div>
</div>
</div>
</div>
<!-- 表单模板 -->
<script type="text/html" id="formTpl">
<div class="layui-tab layui-tab-brief" lay-filter="formTab">
<ul class="layui-tab-title">
{{# layui.each(d.groups, function(index, group){ }}
<li class="{{# if(index === 0){ }}layui-this{{# } }}">{{ group }}</li>
{{# }); }}
</ul>
<div class="layui-tab-content">
{{# layui.each(d.groups, function(groupIndex, group){ }}
<div class="layui-tab-item {{# if(groupIndex === 0){ }}layui-show{{# } }}">
<form class="layui-form" lay-filter="form-{{ group }}">
<div class="form-group-title">{{ group }} 设置</div>
{{# layui.each(d.data[group], function(fieldIndex, field){ }}
<div class="layui-form-item">
<label class="layui-form-label">{{ field.title }}</label>
<div class="layui-input-block">
{{# if(field.type === 'string' || field.type === 'text'){ }}
<!-- 文本输入框 -->
<input type="text"
name="{{ field.name }}"
value="{{ field.value || '' }}"
placeholder="{{ field.tip || '请输入' + field.title }}"
class="layui-input"
{{# if(field.rule === 'required'){ }}lay-verify="required"{{# } }}>
{{# } else if(field.type === 'textarea'){ }}
<!-- 文本域 -->
<textarea name="{{ field.name }}"
placeholder="{{ field.tip || '请输入' + field.title }}"
class="layui-textarea"
{{# if(field.rule === 'required'){ }}lay-verify="required"{{# } }}>{{ field.value || '' }}</textarea>
{{# } else if(field.type === 'select'){ }}
<!-- 下拉选择框 -->
<select name="{{ field.name }}"
{{# if(field.rule === 'required'){ }}lay-verify="required"{{# } }}>
<option value="">请选择{{ field.title }}</option>
{{#
var options = field.content ? field.content.split(',') : [];
layui.each(options, function(optIndex, option){
}}
<option value="{{ option }}" {{# if(field.value == option){ }}selected{{# } }}>{{ option }}</option>
{{# }); }}
</select>
{{# } else if(field.type === 'checkbox'){ }}
<!-- 复选框 -->
{{#
var checkboxes = field.content ? field.content.split(',') : [];
layui.each(checkboxes, function(cbIndex, cbValue){
}}
<input type="checkbox"
name="{{ field.name }}"
title="{{ cbValue }}"
value="{{ cbValue }}"
{{# if(field.value && field.value.indexOf(cbValue) !== -1){ }}checked{{# } }}
lay-skin="primary">
{{# }); }}
{{# } else if(field.type === 'radio'){ }}
<!-- 单选框 -->
{{#
var radios = field.content ? field.content.split(',') : [];
layui.each(radios, function(rdIndex, rdValue){
}}
<input type="radio"
name="{{ field.name }}"
title="{{ rdValue }}"
value="{{ rdValue }}"
{{# if(field.value == rdValue){ }}checked{{# } }}>
{{# }); }}
{{# } else if(field.type === 'number'){ }}
<!-- 数字输入框 -->
<input type="number"
name="{{ field.name }}"
value="{{ field.value || '' }}"
placeholder="{{ field.tip || '请输入' + field.title }}"
class="layui-input"
{{# if(field.rule === 'required'){ }}lay-verify="required|number"{{# } }}>
{{# } else if(field.type === 'date'){ }}
<!-- 日期选择器 -->
<input type="text"
name="{{ field.name }}"
value="{{ field.value || '' }}"
placeholder="{{ field.tip || '请选择日期' }}"
class="layui-input"
id="date-{{ field.id }}">
{{# } }}
{{# if(field.tip){ }}
<div class="form-item-tip">{{ field.tip }}</div>
{{# } }}
</div>
</div>
{{# }); }}
<div class="layui-form-item">
<div class="layui-input-block">
<button type="button" class="layui-btn" lay-submit lay-filter="submit-{{ group }}">保存设置</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</div>
</form>
</div>
{{# }); }}
</div>
</div>
</script>
<script>
layui.use('configure')
</script>
+302
View File
@@ -0,0 +1,302 @@
<div class="layui-fluid">
<div class="layui-row layui-col-space15">
<div class="layui-col-sm6 layui-col-md3">
<div class="layui-card">
<div class="layui-card-header">
访问量
<span class="layui-badge layui-bg-blue layuiadmin-badge"></span>
</div>
<div class="layui-card-body layuiadmin-card-list">
<p class="layuiadmin-big-font">9,999,888</p>
<p>
总计访问量
<span class="layuiadmin-span-color">88万 <i class="layui-inline layui-icon layui-icon-flag"></i></span>
</p>
</div>
</div>
</div>
<div class="layui-col-sm6 layui-col-md3">
<div class="layui-card">
<div class="layui-card-header">
下载
<span class="layui-badge layui-bg-cyan layuiadmin-badge"></span>
</div>
<div class="layui-card-body layuiadmin-card-list">
<p class="layuiadmin-big-font">33,555</p>
<p>
新下载
<span class="layuiadmin-span-color">10% <i class="layui-inline layui-icon layui-icon-face-smile-b"></i></span>
</p>
</div>
</div>
</div>
<div class="layui-col-sm6 layui-col-md3">
<div class="layui-card">
<div class="layui-card-header">
点赞
<span class="layui-badge layui-bg-green layuiadmin-badge"></span>
</div>
<div class="layui-card-body layuiadmin-card-list">
<p class="layuiadmin-big-font">999,888</p>
<p>
总赞
<span class="layuiadmin-span-color">*** <i class="layui-inline layui-icon layui-icon-heart"></i></span>
</p>
</div>
</div>
</div>
<div class="layui-col-sm6 layui-col-md3">
<div class="layui-card">
<div class="layui-card-header">
活跃用户
<span class="layui-badge layui-bg-orange layuiadmin-badge"></span>
</div>
<div class="layui-card-body layuiadmin-card-list">
<p class="layuiadmin-big-font">66,888</p>
<p>
最近一个月
<span class="layuiadmin-span-color">15% <i class="layui-inline layui-icon layui-icon-user"></i></span>
</p>
</div>
</div>
</div>
<div class="layui-col-sm12">
<div class="layui-card">
<div class="layui-card-header">
访问量
<div class="layui-btn-group layuiadmin-btn-group">
<a href="javascript:;" class="layui-btn layui-btn-primary layui-btn-xs">去年</a>
<a href="javascript:;" class="layui-btn layui-btn-primary layui-btn-xs">今年</a>
</div>
</div>
<div class="layui-card-body">
<div class="layui-row">
<div class="layui-col-sm8">
<div class="layui-carousel layadmin-carousel layadmin-dataview" data-anim="fade" lay-filter="LAY-index-pagetwo">
<div carousel-item id="LAY-index-pagetwo">
<div><i class="layui-icon layui-icon-loading1 layadmin-loading"></i></div>
</div>
</div>
</div>
<div class="layui-col-sm4">
<div class="layuiadmin-card-list">
<p class="layuiadmin-normal-font">月访问数</p>
<span>同上期增长</span>
<div class="layui-progress layui-progress-big" lay-showPercent="yes">
<div class="layui-progress-bar" lay-percent="30%"></div>
</div>
</div>
<div class="layuiadmin-card-list">
<p class="layuiadmin-normal-font">月下载数</p>
<span>同上期增长</span>
<div class="layui-progress layui-progress-big" lay-showPercent="yes">
<div class="layui-progress-bar" lay-percent="20%"></div>
</div>
</div>
<div class="layuiadmin-card-list">
<p class="layuiadmin-normal-font">月点赞数</p>
<span>同上期增长</span>
<div class="layui-progress layui-progress-big" lay-showPercent="yes">
<div class="layui-progress-bar" lay-percent="25%"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="layui-col-sm4">
<div class="layui-card">
<div class="layui-card-header">用户留言</div>
<div class="layui-card-body">
<ul class="layuiadmin-card-status layuiadmin-home2-usernote">
<li>
<h3>张三</h3>
<p>哦好吧</p>
<span>5月30日 00:00</span>
<a href="javascript:;" layadmin-event="replyNote" data-id="7" class="layui-btn layui-btn-xs layuiadmin-reply">回复</a>
</li>
<li>
<h3>李白</h3>
<p>应是天仙狂醉,乱把白云揉碎</p>
<span>5月02日 00:00</span>
<a href="javascript:;" layadmin-event="replyNote" data-id="5" class="layui-btn layui-btn-xs layuiadmin-reply">回复</a>
</li>
<li>
<h3>张三</h3>
<p>嗯嗯</p>
<span>5月11日 00:00</span>
<a href="javascript:;" layadmin-event="replyNote" data-id="6" class="layui-btn layui-btn-xs layuiadmin-reply">回复</a>
</li>
<li>
<h3>杜甫</h3>
<p>人才虽高,不务学问,不能致圣。刘向十日画一水,五日画一石。</p>
<span>4月11日 00:00</span>
<a href="javascript:;" layadmin-event="replyNote" data-id="2" class="layui-btn layui-btn-xs layuiadmin-reply">回复</a>
</li>
<li>
<h3>鲁迅</h3>
<p>路本是无所谓有和无的,走的人多了,就没路了。。</p>
<span>4月28日 00:00</span>
<a href="javascript:;" layadmin-event="replyNote" data-id="4" class="layui-btn layui-btn-xs layuiadmin-reply">回复</a>
</li>
<li>
<h3>张爱玲</h3>
<p>于千万人之中遇到你所要遇到的人,于千万年之中,时间的无涯的荒野中,没有早一步,也没有晚一步,刚巧赶上了,那也没有别的话好说,唯有轻轻的问一声:“噢,原来你也在这里?”</p>
<span>4月11日 00:00</span>
<a href="javascript:;" layadmin-event="replyNote" data-id="1" class="layui-btn layui-btn-xs layuiadmin-reply">回复</a>
</li>
</ul>
</div>
</div>
</div>
<div class="layui-col-sm8">
<div class="layui-row layui-col-space15">
<div class="layui-col-sm6">
<div class="layui-card">
<div class="layui-card-header">本周活跃用户列表</div>
<div class="layui-card-body">
<table class="layui-table layuiadmin-page-table" lay-skin="line">
<thead>
<tr>
<th>用户名</th>
<th>最后登录时间</th>
<th>状态</th>
<th>获得赞</th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="first">张三</span></td>
<td><i class="layui-icon layui-icon-log"> 11:20</i></td>
<td><span>在线</span></td>
<td>22 <i class="layui-icon layui-icon-praise"></i></td>
</tr>
<tr>
<td><span class="second">张三</span></td>
<td><i class="layui-icon layui-icon-log"> 10:40</i></td>
<td><span>在线</span></td>
<td>21 <i class="layui-icon layui-icon-praise"></i></td>
</tr>
<tr>
<td><span class="third">李四</span></td>
<td><i class="layui-icon layui-icon-log"> 01:30</i></td>
<td><i>离线</i></td>
<td>66 <i class="layui-icon layui-icon-praise"></i></td>
</tr>
<tr>
<td>王五</td>
<td><i class="layui-icon layui-icon-log"> 21:18</i></td>
<td><i>离线</i></td>
<td>45 <i class="layui-icon layui-icon-praise"></i></td>
</tr>
<tr>
<td>许上进</td>
<td><i class="layui-icon layui-icon-log"> 09:30</i></td>
<td><span>在线</span></td>
<td>21 <i class="layui-icon layui-icon-praise"></i></td>
</tr>
<tr>
<td>小蚊子</td>
<td><i class="layui-icon layui-icon-log"> 21:18</i></td>
<td><i>在线</i></td>
<td>45 <i class="layui-icon layui-icon-praise"></i></td>
</tr>
<tr>
<td>张三</td>
<td><i class="layui-icon layui-icon-log"> 09:30</i></td>
<td><span>在线</span></td>
<td>21 <i class="layui-icon layui-icon-praise"></i></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="layui-col-sm6">
<div class="layui-card">
<div class="layui-card-header">项目进展</div>
<div class="layui-card-body">
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
<table id="LAY-index-prograss"></table>
</div>
</div>
</div>
</div>
</div>
<div class="layui-col-sm12">
<div class="layui-card">
<div class="layui-card-header">访客地区分布</div>
<div class="layui-card-body">
<div class="layui-row layui-col-space15">
<div class="layui-col-sm4">
<table class="layui-table layuiadmin-page-table" lay-skin="line">
<thead>
<tr>
<th>排名</th>
<th>地区</th>
<th>人数</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>浙江</td>
<td>62310</td>
</tr>
<tr>
<td>2</td>
<td>上海</td>
<td>59190</td>
</tr>
<tr>
<td>3</td>
<td>广东</td>
<td>55891</td>
</tr>
<tr>
<td>4</td>
<td>北京</td>
<td>51919</td>
</tr>
<tr>
<td>5</td>
<td>山东</td>
<td>39231</td>
</tr>
<tr>
<td>6</td>
<td>湖北</td>
<td>37109</td>
</tr>
</tbody>
</table>
</div>
<div class="layui-col-sm8">
<div class="layui-carousel layadmin-carousel layadmin-dataview" data-anim="fade" lay-filter="LAY-index-pagethree">
<div carousel-item id="LAY-index-pagethree">
<div><i class="layui-icon layui-icon-loading1 layadmin-loading"></i></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="/assets/layui/layui.js?t=1"></script>
<script src="/assets/ywxapp/ywxapp.js" module="backend"></script>
<script>
layui.use('console');
</script>
+136
View File
@@ -0,0 +1,136 @@
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header"></div>
<div class="layui-card-body">
<table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
</div>
</div>
</div>
<!-- 表格顶部工具条 -->
<script type="text/html" id="tableBarTpl">
<div class="layui-btn-group">
<a
class="layui-btn layui-btn-sm layui-btn-primary"
title="新建数据"
lay-event="dataCreate"
data-perm="group:add">
<i class="layui-icon layui-icon-add-1"></i>
</a>
<a
class="layui-btn layui-btn-sm layui-btn-primary"
title="删除资源"
lay-event="dataDelete"
data-perm="group:delete"
><i class="layui-icon layui-icon-delete"></i>
</a>
<a
class="layui-btn layui-btn-sm layui-btn-primary"
title="资源回收站"
lay-event="dataRecybin"
data-perm="group:recyclebin"
><i class="layui-icon layui-icon-home"></i>
</a>
</div>
</script>
<!-- 表格数据工具条 -->
<script type="text/html" id="dataBarTpl">
<div class="layui-btn-group">
<a
class="layui-btn layui-btn-sm layui-btn-primary"
title="编辑数据"
lay-event="update"
data-perm="group:edit"
><i class="layui-icon layui-icon-edit"></i>
</a>
<a
class="layui-btn layui-btn-sm layui-btn-primary"
title="权限分配"
lay-event="permission"
data-perm="group:*"
><i class="layui-icon layui-icon-auz"></i>
</a>
{{# if(d.id > 1) { }}
<a
class="layui-btn layui-btn-sm layui-btn-primary"
title="删除数据"
lay-event="delete"
data-perm="group:delete"
><i class="layui-icon layui-icon-delete"></i>
</a>
{{# } }}
</div>
</script>
<!-- 表格回收站 -->
<script type="text/html" id="dataRecybinTpl">
<div class="layui-card">
<div class="layui-card-header"></div>
<div class="layui-card-body">
<table
class="layui-hide"
id="dataRecybinTable"
lay-filter="dataRecybinTable"></table>
</div>
</div>
</script>
<!-- 角色状态 -->
<script type="text/html" id="statusTpl">
{{# if(d.status == 1) { }}
<span class="layui-badge layui-bg-green">正常</span>
{{# } else { }}
<span class="layui-badge layui-bg-orange">禁用</span>
{{# } }}
</script>
<!-- 添加/编辑角色表单 -->
<script type="text/html" id="dataFormTpl">
<form class="layui-form layui-form-pane" lay-filter="wxapp-form" id="wxapp-form">
<input type="hidden" name="id" value="{{d.id||''}}">
<div class="layui-form-item">
<label class="layui-form-label">角色名称</label>
<div class="layui-input-block">
<input type="text" name="title" required lay-verify="required" placeholder="请输入角色名称" autocomplete="off" class="layui-input" value="{{d.title||''}}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">编码</label>
<div class="layui-input-block">
<input type="text" name="name" required lay-verify="required" placeholder="请输入分组编码" autocomplete="off" class="layui-input" value="{{d.name||''}}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">角色描述</label>
<div class="layui-input-block">
<textarea name="description" placeholder="请输入角色描述" class="layui-textarea">{{d.description||''}}</textarea>
</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="启用" {{ d.status==undefined || d.status==1 ? 'checked' : '' }}>
<input type="radio" name="status" value="0" title="禁用" {{ d.status==0 ? 'checked' : '' }}>
</div>
</div>
<div class="layui-form-item layui-hide">
<button class="layui-btn" lay-submit="" lay-filter="wxapp-form-submit" id="wxapp-form-submit">提交</button>
</div>
</form>
</script>
<!-- 权限分配弹窗 -->
<script type="text/html" id="permissionFormTpl">
<div class="layui-card">
<div class="layui-card-header">
角色{{d.name}}
<span style="color:#999;font-weight:normal;">(ID: {{d.id}})</span>
</div>
<div class="layui-card-body">
<div id="permissionTree"></div>
</div>
</div>
</script>
<script>
layui.use(["group"], function () {});
</script>
+93
View File
@@ -0,0 +1,93 @@
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">站点帮助管理</div>
<div class="layui-card-body">
<form class="layui-form" lay-filter="data-search-form">
<div class="layui-form-item">
<div class="layui-inline">
<input type="text" name="title" placeholder="标题" autocomplete="off" class="layui-input">
</div>
<div class="layui-inline">
<input type="text" name="category" placeholder="分类" autocomplete="off" class="layui-input">
</div>
<div class="layui-inline">
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="data-search-btn"><i class="layui-icon">&#xe615;</i> 搜索</button>
<button type="button" class="layui-btn layui-btn-primary" id="btn-reset">重置</button>
</div>
</div>
</form>
<table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
<script type="text/html" id="tableBar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="dataCreate" data-perm="help:add"><i class="layui-icon">&#xe654;</i> </button>
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-event="dataDelete" data-perm="help:del"><i class="layui-icon">&#xe640;</i> </button>
<button class="layui-btn layui-btn-sm" lay-event="dataRecybin" data-perm="help:restore"><i class="layui-icon">&#xe64b;</i> </button>
</div>
</script>
<script type="text/html" id="dataBar">
<a class="layui-btn layui-btn-xs" lay-event="update" data-perm="help:edit">编辑</a>
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="delete" data-perm="help:del">删除</a>
</script>
<script type="text/html" id="categoryTpl">
{{# if(d.category){ }}
<span class="layui-badge layui-bg-cyan">{{ d.category }}</span>
{{# } else { }}
<span style="color:#ccc;">未分类</span>
{{# } }}
</script>
<script type="text/html" id="statusTpl">
<input type="checkbox" name="status" value="{{ d.id }}" lay-skin="switch" lay-text="开|关" lay-filter="statusSwitch" {{ d.status == 1 ? 'checked' : '' }}>
</script>
</div>
</div>
</div>
<script type="text/html" id="dataFormTpl">
<form class="layui-form" id="wxapp-form" lay-filter="wxapp-form" style="padding:15px;">
<input type="hidden" name="id" value="{{ d.id || '' }}">
<div class="layui-form-item">
<label class="layui-form-label">分类</label>
<div class="layui-input-block">
<input type="text" name="category" 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="title" placeholder="请输入标题" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">内容</label>
<div class="layui-input-block">
<textarea name="content" placeholder="请输入帮助内容" class="layui-textarea"></textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">排序</label>
<div class="layui-input-block">
<input type="text" name="sort" placeholder="请输入排序(越大越靠前)" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<input type="checkbox" name="status" lay-skin="switch" lay-text="启用|禁用" value="1" checked>
</div>
</div>
<div class="layui-form-item layui-hide">
<button class="layui-btn" id="wxapp-form-submit" lay-submit lay-filter="wxapp-form-submit">提交</button>
</div>
</form>
</script>
<script type="text/html" id="dataRecybinTpl">
<table class="layui-hide" id="dataRecybinTable" lay-filter="dataRecybinTable"></table>
<script type="text/html" id="dataRecybinBarTpl">
<a class="layui-btn layui-btn-xs" lay-event="restore" data-perm="help:restore">恢复</a>
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="forcedelete" data-perm="help:destroy">彻底删除</a>
</script>
</script>
<script>
layui.use('help', layui.factory('help'));
</script>
+175
View File
@@ -0,0 +1,175 @@
<!DOCTYPE html>
<html>
<head>
<script>
/^http(s*):\/\//.test(location.href) || alert('请通过 localhost 访问');;
</script>
</head>
<meta charset="UTF-8">
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link href="/assets/layui/css/layui.css" rel="stylesheet">
<link href="/assets/ywxapp/css/ywxapp.css" rel="stylesheet">
<body class="layui-layout-body" id="LAY_home_iframe">
<div id="LAY_app">
<div class="layui-layout layui-layout-admin">
<div class="layui-header">
<!-- 头部区域 -->
<ul class="layui-nav layui-layout-left">
<li class="layui-nav-item layadmin-flexible" lay-unselect>
<a href="javascript:;" layadmin-event="flexible" title="侧边伸缩">
<i class="layui-icon layui-icon-shrink-right" id="LAY_app_flexible"></i>
</a>
</li>
<li class="layui-nav-item layui-hide-xs" lay-unselect>
<a href="/" target="_blank" title="前台">
<i class="layui-icon layui-icon-website"></i>
</a>
</li>
<li class="layui-nav-item" lay-unselect>
<a href="javascript:;" layadmin-event="refresh" title="刷新">
<i class="layui-icon layui-icon-refresh-3"></i>
</a>
</li>
<li class="layui-nav-item layui-hide-xs" lay-unselect>
<input type="text" placeholder="搜索..." autocomplete="off" class="layui-input layui-input-search" layadmin-event="serach" lay-action="template/search.html?keywords=">
</li>
</ul>
<ul class="layui-nav layui-layout-right" lay-filter="layadmin-layout-right">
<li class="layui-nav-item" lay-unselect>
<a lay-href="app/message/index.html" layadmin-event="message" lay-text="消息中心">
<i class="layui-icon layui-icon-notice"></i>
<!-- 如果有新消息,则显示小圆点 -->
<span class="layui-badge-dot"></span>
</a>
</li>
<li class="layui-nav-item layui-hide-xs" lay-unselect>
<a href="javascript:;" layadmin-event="note">
<i class="layui-icon layui-icon-note"></i>
</a>
</li>
<li class="layui-nav-item layui-hide-xs" lay-unselect>
<a href="javascript:;" layadmin-event="theme">
<i class="layui-icon layui-icon-theme"></i>
</a>
</li>
<li class="layui-nav-item layui-hide-xs" lay-unselect>
<a href="javascript:;" layadmin-event="fullscreen">
<i class="layui-icon layui-icon-screen-full"></i>
</a>
</li>
<li class="layui-nav-item" lay-unselect>
<a href="javascript:;">
<img src="{$user.avatar|default='/static/common/images/avatar.jpg'}" class="layui-nav-img">
<cite>{$user.nickname|default='管理员'}</cite>
</a>
<dl class="layui-nav-child">
<dd><a href="javascript:;" lay-href="{:url('Profile/index')}" lay-text="设置用户信息">设置用户信息</a></dd>
<dd><a href="javascript:;" lay-href="{:url('Profile/avatar')}" lay-text="设置头像">设置头像</a></dd>
<dd><a href="javascript:;" lay-href="{:url('Profile/password')}" lay-text="设置密码">设置密码</a></dd>
<hr>
<dd layadmin-event="logout" style="text-align: center;"><a>退出</a></dd>
</dl>
</li>
<li class="layui-nav-item layui-hide-xs" lay-unselect>
<a href="javascript:;" layadmin-event="about"><i class="layui-icon layui-icon-more-vertical"></i></a>
</li>
<li class="layui-nav-item layui-show-xs-inline-block layui-hide-sm" lay-unselect>
<a href="javascript:;" layadmin-event="more"><i class="layui-icon layui-icon-more-vertical"></i></a>
</li>
</ul>
</div>
<!-- 侧边菜单 -->
<div class="layui-side layui-side-menu">
<div class="layui-side-scroll">
<div class="layui-logo" lay-href="home/console.html">
<span>YwxApp</span>
</div>
<ul class="layui-nav layui-nav-tree" lay-accordion id="LAY-system-side-menu" lay-filter="layadmin-system-side-menu">
<li data-name="home" class="layui-nav-item layui-nav-itemed">
<a href="javascript:;" lay-tips="主页" lay-direction="2">
<i class="layui-icon layui-icon-home"></i>
<cite>主页</cite>
</a>
</li>
<li data-name="get" class="layui-nav-item">
<a href="javascript:;" layadmin-event="about" lay-tips="授权" lay-direction="2">
<i class="layui-icon layui-icon-auz"></i>
<cite>关于</cite>
</a>
</li>
</ul>
</div>
</div>
<!-- 页面标签 -->
<div class="layadmin-pagetabs" id="LAY_app_tabs">
<div class="layui-icon layadmin-tabs-control layui-icon-prev" layadmin-event="leftPage"></div>
<div class="layui-icon layadmin-tabs-control layui-icon-next" layadmin-event="rightPage"></div>
<div class="layui-icon layadmin-tabs-control layui-icon-down">
<ul class="layui-nav layadmin-tabs-select" lay-filter="layadmin-pagetabs-nav">
<li class="layui-nav-item" lay-unselect>
<a href="javascript:;"></a>
<dl class="layui-nav-child layui-anim-fadein">
<dd layadmin-event="closeThisTabs"><a href="javascript:;">关闭当前标签页</a></dd>
<dd layadmin-event="closeOtherTabs"><a href="javascript:;">关闭其它标签页</a></dd>
<dd layadmin-event="closeRightTabs"><a href="javascript:;">关闭右侧标签页</a></dd>
<dd layadmin-event="closeAllTabs"><a href="javascript:;">关闭全部标签页</a></dd>
</dl>
</li>
</ul>
</div>
<div class="layui-tab" lay-unauto lay-allowClose="true" lay-filter="layadmin-layout-tabs">
<ul class="layui-tab-title" id="LAY_app_tabsheader">
<li lay-id="/admin/console/index.html" lay-attr="/admin/console/index.html" class="layui-this"><i class="layui-icon layui-icon-home"></i></li>
</ul>
</div>
</div>
<!-- 主体内容 -->
<div class="layui-body" id="LAY_app_body">
<div class="layadmin-tabsbody-item layui-show">
<iframe src="/admin/console/index.html" frameborder="0" class="layadmin-iframe"></iframe>
</div>
</div>
<!-- 辅助元素,一般用于移动设备下遮罩 -->
<div class="layadmin-body-shade" layadmin-event="shade"></div>
</div>
</div>
<script src="/assets/layui/layui.js"></script>
<script type="text/javascript">
layui.app = {
root: "{$site.root|default=''}",
assetUrl: "/assets",
module: "{$site.module}",
app: "{$site.app}",
routeBase: "{$route_base}",
controller: "{$site.controller}",
action: "{$site.action}"
};
</script>
<script src="/assets/ywxapp/ywxapp.js" module="{$site.app}"></script>
<script type="text/javascript">
layui.use(['index']);
</script>
</body>
</html>
+114
View File
@@ -0,0 +1,114 @@
<div class="layui-fluid"><div class="layui-card">
<div class="layui-card-header">友情链接管理</div>
<div class="layui-card-header">
<!-- 搜索栏 -->
<div class="layui-form layui-card-header-boxed" lay-filter="search-form">
<div class="layui-form-item">
<div class="layui-inline">
<input type="text" name="title" placeholder="请输入网站标题" autocomplete="off" class="layui-input">
</div>
<div class="layui-inline">
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="data-search-btn">
<i class="layui-icon layui-icon-search"></i>
</button>
<button class="layui-btn layui-btn-primary" id="btn-reset">
<i class="layui-icon layui-icon-refresh"></i>
</button>
</div>
</div>
</div>
</div>
<div class="layui-card-body">
<!-- 表格容器 -->
<table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
</div>
</div></div>
<!-- 顶部工具条 -->
<script type="text/html" id="tableBar">
<div class="layui-btn-group">
<a class="layui-btn layui-btn-sm layui-btn-primary" title="新建链接" lay-event="dataCreate" data-perm="links:add"> <i class="layui-icon layui-icon-add-1"></i> </a>
<a class="layui-btn layui-btn-sm layui-btn-primary" title="删除资源" lay-event="dataDelete" data-perm="links:delete"><i class="layui-icon layui-icon-delete"></i> </a>
<a class="layui-btn layui-btn-sm layui-btn-primary" title="资源回收站" lay-event="dataRecybin" data-perm="links:recyclebin"><i class="layui-icon layui-icon-home"></i> </a>
</div>
</script>
<!-- 操作列模板 -->
<script type="text/html" id="dataBar">
<a class="layui-btn layui-btn-xs" lay-event="update" data-perm="links:edit">编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="delete" data-perm="links:delete">删除</a>
</script>
<!-- 状态开关模板 -->
<script type="text/html" id="statusTpl">
<input type="checkbox" name="status" value="{{d.id}}" lay-skin="switch" lay-text="启用|禁用" lay-filter="statusSwitch" {{ d.status == 1 ? 'checked' : '' }}>
</script>
<!-- 添加/编辑友链表单(抽屉) -->
<script type="text/html" id="dataFormTpl">
<form class="layui-form" style="padding: 20px 20px 0;">
<input type="hidden" name="id" value="{{d.id||''}}">
<div class="layui-form-item">
<label class="layui-form-label">标题</label>
<div class="layui-input-block">
<input type="text" name="title" required lay-verify="required" placeholder="网站标题" autocomplete="off" class="layui-input" value="{{d.title||''}}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">链接</label>
<div class="layui-input-block">
<input type="text" name="url" required lay-verify="required|url" placeholder="链接地址" autocomplete="off" class="layui-input" value="{{d.url||''}}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">logo</label>
<div class="layui-input-block">
<input type="text" name="logo" placeholder="请输入logo地址" autocomplete="off" class="layui-input" value="{{d.logo||''}}">
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">描述</label>
<div class="layui-input-block">
<textarea name="description" placeholder="请输入网站描述" class="layui-textarea">{{d.description||''}}</textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">排序</label>
<div class="layui-input-block">
<input type="text" name="sort" lay-verify="required|number" placeholder="请输入排序" autocomplete="off" class="layui-input" value="{{d.sort||'0'}}">
</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="启用" {{ d.status==undefined || d.status==1 ? 'checked' : '' }}>
<input type="radio" name="status" value="0" title="禁用" {{ d.status==0 ? 'checked' : '' }}>
</div>
</div>
<div class="layui-form-item layui-hide">
<button class="layui-btn" lay-submit="" lay-filter="wxapp-form-submit" id="wxapp-form-submit">提交</button>
</div>
</form>
</script>
<!-- 回收站抽屉 -->
<script type="text/html" id="dataRecybinTpl">
<div class="layui-card">
<div class="layui-card-header">已删除的友链可恢复</div>
<div class="layui-card-body">
<table class="layui-hide" id="dataRecybinTable" lay-filter="dataRecybinTable"></table>
</div>
</div>
</script>
<!-- 回收站行操作 -->
<script type="text/html" id="dataRecybinBarTpl">
<div class="layui-btn-group">
<a class="layui-btn layui-btn-sm layui-btn-primary" title="恢复" lay-event="restore" data-perm="links:restore"><i class="layui-icon layui-icon-edit"></i></a>
<a class="layui-btn layui-btn-sm layui-btn-primary" title="彻底删除" lay-event="forcedelete" data-perm="links:delete"><i class="layui-icon layui-icon-delete"></i></a>
</div>
</script>
<script type="text/javascript">
layui.use('links', function () { });
</script>
+105
View File
@@ -0,0 +1,105 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{$title | default='登录'}</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link href="/assets/layui/css/layui.css" rel="stylesheet">
<link href="/assets/ywxapp/css/template.css" rel="stylesheet">
<script src="/assets/layui/layui.js"></script>
<style>
body {
background-color: #f2f3f5;
}
</style>
<script type="text/javascript">
// 全局路径配置:仅注入部署参数,路由方法来自 layui 的 route 模块。
// app 为 ThinkPHP 真实应用目录名(backend/member/frontend/home),对应 public/static/<app> 目录。
layui.app = {
root: "{$site.root|default=''}",
assetUrl: "/assets",
module: "{$site.module}",
realModule: "{$site.app}",
routeBase: "{$route_base}",
controller: "{$site.controller}",
action: "{$site.action}",
devToken: "{$site.devToken}",
devAddon: "{$site.devAddon}",
};
</script>
<script src="/assets/ywxapp/ywxapp.js" module="{$site.app}"></script>
</head>
<body>
<div class="layadmin-user-login layadmin-user-display-show" id="LAY-user-login">
<div class="layadmin-user-login-main">
<div class="layadmin-user-login-box layadmin-user-login-header">
<h2>YwxApp后台管理系统</h2>
<p> </p>
</div>
<div class="layadmin-user-login-box layadmin-user-login-body layui-form">
<div class="layui-form-item">
<label class="layadmin-user-login-icon layui-icon layui-icon-username" for="LAY-user-login-username"></label>
<input type="text" name="username" id="LAY-user-login-username" lay-verify="required" placeholder="用户名" class="layui-input">
</div>
<div class="layui-form-item">
<label class="layadmin-user-login-icon layui-icon layui-icon-password" for="LAY-user-login-password"></label>
<input type="password" name="password" id="LAY-user-login-password" lay-verify="required" placeholder="密码" class="layui-input">
</div>
<div class="layui-form-item">
<div class="layui-row">
<div class="layui-col-xs7">
<label class="layadmin-user-login-icon layui-icon layui-icon-vercode" for="LAY-user-login-vercode"></label>
<input type="text" name="captcha" id="LAY-user-login-vercode" lay-verify="required" placeholder="图形验证码" class="layui-input">
</div>
<div class="layui-col-xs5">
<div style="margin-left: 10px;">
<img class="layadmin-user-login-codeimg verification-img" lay-filter="verifyCode" src="{:url('ajax/verify')}" alt="点击刷新验证码" style="cursor:pointer;transition:opacity 0.3s" title="点击刷新验证码" />
</div>
</div>
</div>
</div>
<div class="layui-form-item" style="margin-bottom: 20px;">
<input type="checkbox" name="remember" lay-skin="primary" title="记住密码">
<a href="forget.html" class="layadmin-user-jump-change layadmin-link" style="margin-top: 7px;">忘记密码?</a>
</div>
<div class="layui-form-item">
<button class="layui-btn layui-btn-fluid" lay-submit lay-filter="dataSubmit">登 入</button>
</div>
</div>
</div>
<div class="layui-trans layadmin-user-login-footer">
<p>© All Rights Reserved</p>
</div>
<!--<div class="ladmin-user-login-theme">
<script type="text/html" template>
<ul>
<li data-theme=""><img src="{{ layui.setter.paths.base }}style/imgs/bg-none.jpg"></li>
<li data-theme="#03152A" style="background-color: #03152A;"></li>
<li data-theme="#2E241B" style="background-color: #2E241B;"></li>
<li data-theme="#50314F" style="background-color: #50314F;"></li>
<li data-theme="#344058" style="background-color: #344058;"></li>
<li data-theme="#20222A" style="background-color: #20222A;"></li>
</ul>
</script>
</div>-->
</div>
<!-- 资 源 引 入 -->
<script>
layui.use('login', function () { });
</script>
</body>
</html>
+81
View File
@@ -0,0 +1,81 @@
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">勋章中心管理</div>
<div class="layui-card-body">
<form class="layui-form" lay-filter="data-search-form">
<div class="layui-form-item">
<div class="layui-inline">
<input type="text" name="title" placeholder="标题" autocomplete="off" class="layui-input">
</div>
<div class="layui-inline">
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="data-search-btn"><i class="layui-icon">&#xe615;</i> 搜索</button>
<button type="button" class="layui-btn layui-btn-primary" id="btn-reset">重置</button>
</div>
</div>
</form>
<table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
<script type="text/html" id="tableBar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="dataCreate" data-perm="medal:add"><i class="layui-icon">&#xe654;</i> </button>
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-event="dataDelete" data-perm="medal:del"><i class="layui-icon">&#xe640;</i> </button>
<button class="layui-btn layui-btn-sm" lay-event="dataRecybin" data-perm="medal:restore"><i class="layui-icon">&#xe64b;</i> </button>
</div>
</script>
<script type="text/html" id="dataBar">
<a class="layui-btn layui-btn-xs" lay-event="update" data-perm="medal:edit">编辑</a>
<a class="layui-btn layui-btn-xs layui-btn-normal" lay-event="grant" data-perm="medal:grant">授予</a>
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="delete" data-perm="medal:del">删除</a>
</script>
<script type="text/html" id="statusTpl">
<input type="checkbox" name="status" value="{{ d.id }}" lay-skin="switch" lay-text="开|关" lay-filter="statusSwitch" {{ d.status == 1 ? 'checked' : '' }}>
</script>
</div>
</div>
</div>
<script type="text/html" id="dataFormTpl">
<form class="layui-form" id="wxapp-form" lay-filter="wxapp-form" style="padding:15px;">
<input type="hidden" name="id" value="{{ d.id || '' }}">
<div class="layui-form-item">
<label class="layui-form-label">勋章名称</label>
<div class="layui-input-block">
<input type="text" name="title" 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="image" placeholder="请输入图片" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">描述</label>
<div class="layui-input-block">
<textarea name="description" placeholder="请输入描述" class="layui-textarea"></textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">排序</label>
<div class="layui-input-block">
<input type="text" name="sort" placeholder="请输入排序" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<input type="checkbox" name="status" lay-skin="switch" lay-text="启用|禁用" value="1" checked>
</div>
</div>
</form>
</script>
<script type="text/html" id="dataRecybinTpl">
<table class="layui-hide" id="dataRecybinTable" lay-filter="dataRecybinTable"></table>
<script type="text/html" id="dataRecybinBarTpl">
<a class="layui-btn layui-btn-xs" lay-event="restore" data-perm="medal:restore">恢复</a>
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="forcedelete" data-perm="medal:destroy">彻底删除</a>
</script>
</script>
<script>
layui.use('medal', layui.factory('medal'));
</script>
+114
View File
@@ -0,0 +1,114 @@
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">主导航菜单管理(前台官网顶部导航,支持两级下拉)</div>
<div class="layui-card-header">
<div class="layui-form layui-card-header-boxed" lay-filter="search-form">
<div class="layui-form-item">
<div class="layui-inline">
<input type="text" name="title" placeholder="请输入菜单名称" autocomplete="off" class="layui-input">
</div>
<div class="layui-inline">
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="data-search-btn">
<i class="layui-icon layui-icon-search"></i>
</button>
<button class="layui-btn layui-btn-primary" id="btn-reset">
<i class="layui-icon layui-icon-refresh"></i>
</button>
</div>
</div>
</div>
</div>
<div class="layui-card-body">
<table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
</div>
</div>
</div>
<!-- 顶部工具条 -->
<script type="text/html" id="tableBar">
<div class="layui-btn-group">
<a class="layui-btn layui-btn-sm layui-btn-primary" title="新建菜单" lay-event="dataCreate" data-perm="nav:add"> <i class="layui-icon layui-icon-add-1"></i> </a>
</div>
</script>
<!-- 操作列模板 -->
<script type="text/html" id="dataBar">
<a class="layui-btn layui-btn-xs" lay-event="update" data-perm="nav:edit">编辑</a>
{{# if(d.parent_id == 0) { }}
<a class="layui-btn layui-btn-xs" lay-event="addChild" data-perm="nav:add">加子项</a>
{{# } }}
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="delete" data-perm="nav:delete">删除</a>
</script>
<!-- 状态开关模板 -->
<script type="text/html" id="statusTpl">
<input type="checkbox" name="status" value="{{d.id}}" lay-skin="switch" lay-text="显示|隐藏" lay-filter="statusSwitch" {{ d.status == 1 ? 'checked' : '' }}>
</script>
<!-- 父级标识 -->
<script type="text/html" id="parentTpl">
{{# if(d.parent_id == 0) { }}
<span class="layui-badge layui-bg-blue">一级</span>
{{# } else { }}
<span class="layui-badge-rim">二级</span>
{{# } }}
</script>
<!-- 添加/编辑表单(抽屉) -->
<script type="text/html" id="dataFormTpl">
<form class="layui-form" style="padding: 20px 20px 0;">
<input type="hidden" name="id" value="{{d.id||''}}">
<div class="layui-form-item">
<label class="layui-form-label">所属上级</label>
<div class="layui-input-block">
<select name="parent_id">
<option value="0">顶级菜单</option>
{{# layui.each(d.parents || [], function(idx, p){ }}
<option value="{{p.id}}" {{ d.parent_id == p.id ? 'selected' : '' }}>{{p.title}}</option>
{{# }); }}
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">菜单名称</label>
<div class="layui-input-block">
<input type="text" name="title" required lay-verify="required" placeholder="如:首页 / 产品 / 案例" autocomplete="off" class="layui-input" value="{{d.title||''}}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">链接</label>
<div class="layui-input-block">
<input type="text" name="url" placeholder="controller/action 或 外链 http(s)://" autocomplete="off" class="layui-input" value="{{d.url||''}}">
<div class="layui-form-mid layui-word-aux">留空表示仅作为含有子项的父菜单下拉</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">图标</label>
<div class="layui-input-block">
<input type="text" name="icon" placeholder="layui-icon 类名(可选)" autocomplete="off" class="layui-input" value="{{d.icon||''}}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">排序</label>
<div class="layui-input-block">
<input type="number" name="sort" placeholder="数字越小越靠前" autocomplete="off" class="layui-input" value="{{d.sort||0}}">
</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="显示" {{ d.status==undefined || d.status==1 ? 'checked' : '' }}>
<input type="radio" name="status" value="0" title="隐藏" {{ d.status==0 ? 'checked' : '' }}>
</div>
</div>
<div class="layui-form-item layui-hide">
<button class="layui-btn" lay-submit lay-filter="wxapp-form-submit" id="wxapp-form-submit">提交</button>
</div>
</form>
</script>
<script type="text/javascript">
console.log('nav index');
layui.use('navbar', function () { });
console.log(layui.nav);
</script>
+145
View File
@@ -0,0 +1,145 @@
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">站点公告管理</div>
<div class="layui-card-body">
<form class="layui-form" lay-filter="data-search-form">
<div class="layui-form-item">
<div class="layui-inline">
<input type="text" name="title" placeholder="标题" autocomplete="off" class="layui-input">
</div>
<div class="layui-inline">
<select name="type" lay-search="">
<option value="">全部类型</option>
<option value="1">系统维护</option>
<option value="2">活动公告</option>
<option value="3">版本更新</option>
<option value="4">其他</option>
</select>
</div>
<div class="layui-inline">
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="data-search-btn"><i class="layui-icon">&#xe615;</i> 搜索</button>
<button type="button" class="layui-btn layui-btn-primary" id="btn-reset">重置</button>
</div>
</div>
</form>
<table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
<script type="text/html" id="tableBar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="dataCreate" data-perm="notice:add"><i class="layui-icon">&#xe654;</i> </button>
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-event="dataDelete" data-perm="notice:del"><i class="layui-icon">&#xe640;</i> </button>
<button class="layui-btn layui-btn-sm" lay-event="dataRecybin" data-perm="notice:restore"><i class="layui-icon">&#xe64b;</i> </button>
</div>
</script>
<script type="text/html" id="dataBar">
<a class="layui-btn layui-btn-xs" lay-event="update" data-perm="notice:edit">编辑</a>
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="delete" data-perm="notice:del">删除</a>
</script>
<script type="text/html" id="statusTpl">
<input type="checkbox" name="status" value="{{ d.id }}" lay-skin="switch" lay-text="开|关" lay-filter="statusSwitch" {{ d.status == 1 ? 'checked' : '' }}>
</script>
<script type="text/html" id="typeTpl">
{{# if(d.type == 1){ }}
<span class="layui-badge layui-bg-blue">系统维护</span>
{{# } else if(d.type == 2){ }}
<span class="layui-badge layui-bg-orange">活动公告</span>
{{# } else if(d.type == 3){ }}
<span class="layui-badge layui-bg-green">版本更新</span>
{{# } else { }}
<span class="layui-badge">其他</span>
{{# } }}
</script>
<script type="text/html" id="topTpl">
{{# if(d.is_top == 1){ }}
<span class="layui-badge layui-bg-red">置顶</span>
{{# } else { }}
<span style="color:#ccc;">-</span>
{{# } }}
</script>
<script type="text/html" id="startTpl">
{{# if(d.start_time > 0){ }}
{{ layui.util.toDateString(d.start_time*1000, 'yyyy-MM-dd HH:mm') }}
{{# } else { }}
<span style="color:#ccc;">长期</span>
{{# } }}
</script>
<script type="text/html" id="endTpl">
{{# if(d.end_time > 0){ }}
{{ layui.util.toDateString(d.end_time*1000, 'yyyy-MM-dd HH:mm') }}
{{# } else { }}
<span style="color:#ccc;">长期</span>
{{# } }}
</script>
</div>
</div>
</div>
<script type="text/html" id="dataFormTpl">
<form class="layui-form" id="wxapp-form" lay-filter="wxapp-form" style="padding:15px;">
<input type="hidden" name="id" value="{{ d.id || '' }}">
<div class="layui-form-item">
<label class="layui-form-label">标题</label>
<div class="layui-input-block">
<input type="text" name="title" placeholder="请输入标题" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">类型</label>
<div class="layui-input-block">
<select name="type" lay-search="">
<option value="1">系统维护</option>
<option value="2">活动公告</option>
<option value="3">版本更新</option>
<option value="4">其他</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">内容</label>
<div class="layui-input-block">
<textarea name="content" placeholder="请输入内容" class="layui-textarea"></textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">发布人</label>
<div class="layui-input-block">
<input type="text" name="author" placeholder="留空则取当前管理员" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">置顶</label>
<div class="layui-input-block">
<input type="checkbox" name="is_top" lay-skin="switch" lay-text="置顶|普通" value="1">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">展示有效期</label>
<div class="layui-input-block">
<input type="text" name="start_time" id="start_time" placeholder="开始时间(留空=长期)" autocomplete="off" class="layui-input" style="margin-bottom:8px;">
<input type="text" name="end_time" id="end_time" placeholder="结束时间(留空=长期)" autocomplete="off" 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="sort" placeholder="请输入排序" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<input type="checkbox" name="status" lay-skin="switch" lay-text="启用|禁用" value="1" checked>
</div>
</div>
</form>
</script>
<script type="text/html" id="dataRecybinTpl">
<table class="layui-hide" id="dataRecybinTable" lay-filter="dataRecybinTable"></table>
<script type="text/html" id="dataRecybinBarTpl">
<a class="layui-btn layui-btn-xs" lay-event="restore" data-perm="notice:restore">恢复</a>
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="forcedelete" data-perm="notice:destroy">彻底删除</a>
</script>
</script>
<script>
layui.use('notice', layui.factory('notice'));
</script>
+12
View File
@@ -0,0 +1,12 @@
<div class="layui-card" style="margin:15px;">
<div class="layui-card-header">关于系统</div>
<div class="layui-card-body">
<table class="layui-table">
<tbody>
<tr><td>系统名称</td><td>YwxApp</td></tr>
<tr><td>框架版本</td><td>{:think\facade\App::version()}</td></tr>
<tr><td>PHP 版本</td><td>{:PHP_VERSION}</td></tr>
</tbody>
</table>
</div>
</div>
+41
View File
@@ -0,0 +1,41 @@
<div class="layui-card" style="margin:15px;">
<div class="layui-card-header">全局搜索(文章标题)</div>
<div class="layui-card-body">
<form class="layui-form" lay-filter="searchForm">
<div class="layui-form-item">
<div class="layui-input-inline" style="width:320px;">
<input type="text" name="q" placeholder="输入文章标题关键词" class="layui-input">
</div>
<button class="layui-btn" lay-submit lay-filter="searchBtn"><i class="layui-icon layui-icon-search"></i> 搜索</button>
</div>
</form>
<table class="layui-table" style="margin-top:10px;">
<thead>
<tr><th>ID</th><th>标题</th><th>创建时间</th></tr>
</thead>
<tbody id="resultBody">
<tr><td colspan="3" style="text-align:center;color:#999;">请输入关键词搜索</td></tr>
</tbody>
</table>
</div>
</div>
<script src="/assets/layui/layui.js"></script>
<script>
layui.use(['form', 'jquery'], function () {
var form = layui.form, $ = layui.jquery;
form.on('submit(searchBtn)', function (data) {
$.get('/backend/other/search', { q: data.field.q }, function (res) {
var html = '';
if (res.code === 0 && res.data && res.data.length) {
res.data.forEach(function (r) {
html += '<tr><td>' + (r.id || '') + '</td><td>' + (r.title || '') + '</td><td>' + (r.create_at || '') + '</td></tr>';
});
} else {
html = '<tr><td colspan="3" style="text-align:center;color:#999;">无结果</td></tr>';
}
$('#resultBody').html(html);
});
return false;
});
});
</script>
+111
View File
@@ -0,0 +1,111 @@
<!--
* @Author: YwxApp <ywx@ywxapp.cn>
* @Date: 2026-04-29 02:32:33
* @LastEditors: YwxApp <ywx@ywxapp.cn>
* @LastEditTime: 2026-08-03 13:02:39
* @Description:
* @FilePath: \ywxapp_dev\app\backend\view\power\index.html
* @CustomString: Copyright (c) 2026 YwxApp
-->
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header"> </div>
<div class="layui-card-body">
<table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
</div>
</div>
</div>
<script type="text/html" id="tableBarTpl">
<div class="layui-btn-group">
<a class="layui-btn layui-btn-sm layui-btn-primary" title="新建节点" lay-event="dataCreate" data-perm="power:add"> <i class="layui-icon layui-icon-add-1"></i> </a>
<a class="layui-btn layui-btn-sm layui-btn-primary" title="删除资源" lay-event="dataDelete" data-perm="power:delete"><i class="layui-icon layui-icon-delete"></i> </a>
<a class="layui-btn layui-btn-sm layui-btn-primary" title="资源回收站" lay-event="dataRecybin" data-perm="power:recyclebin"><i class="layui-icon layui-icon-home"></i> </a>
</div>
</script>
<script type="text/html" id="dataBarTpl">
<div class="layui-btn-group">
<a class="layui-btn layui-btn-sm layui-btn-primary" title="编辑节点" lay-event="update" data-perm="power:edit"><i class="layui-icon layui-icon-edit"></i> </a>
<a class="layui-btn layui-btn-sm layui-btn-primary" title="添加子节点" lay-event="create" data-perm="power:add"><i class="layui-icon layui-icon-add-1"></i> </a>
<a class="layui-btn layui-btn-sm layui-btn-primary" title="删除节点" lay-event="delete" data-perm="power:delete"><i class="layui-icon layui-icon-delete"></i> </a>
</div>
</script>
<!-- 添加/编辑菜单表单模板 -->
<script type="text/html" id="dataFormTpl">
<form class="layui-form layui-form-pane" lay-filter="wxapp-form" id="wxapp-form">
<input type="hidden" name="id" value="{{= d.id || '' }}">
<input type="hidden" name="pid" value="{{= d.pid || 0 }}">
<div class="layui-form-item">
<label class="layui-form-label">名称</label>
<div class="layui-input-block">
<input type="text" name="title" required lay-verify="required" placeholder="请输入菜单名称" value="{{ d.title || '' }}" 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="name" required lay-verify="required" placeholder="请输入菜单标识" value="{{ d.name || '' }}" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">菜单类型</label>
<div class="layui-input-block">
<select name="type" lay-filter="menuType" lay-verify="required">
<option value="1" {{ (d.type == 1 || !d.type) ? 'selected' : '' }}>目录</option>
<option value="2" {{ d.type == 2 ? 'selected' : '' }}>菜单</option>
<option value="3" {{ d.type == 3 ? 'selected' : '' }}>按钮</option>
<option value="4" {{ d.type == 4 ? 'selected' : '' }}>API</option>
</select>
</div>
</div>
<div class="layui-form-item layui-form-item-url {{ d.type == 3 ? 'layui-hide' : '' }}" id="urlField">
<label class="layui-form-label">路由</label>
<div class="layui-input-block">
<input type="text" name="route" placeholder="请输入路由路径" value="{{ d.route || '' }}" 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="icon" id="iconPicker" placeholder="请输入图标类名" value="{{ d.icon || '' }}" 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="sort" value="{{ d.sort || 0 }}" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<input type="checkbox" name="status" lay-skin="switch" lay-text="启用|禁用" {{ d.status != 0 ? 'checked' : (!d.id ? 'checked' : '') }}>
</div>
</div>
<div class="layui-form-item layui-hide">
<button class="layui-btn" lay-submit="" lay-filter="wxapp-form-submit" id="wxapp-form-submit">提交</button>
</div>
</form>
</script>
<!-- 状态开关 -->
<script type="text/html" id="statusTpl">
<input type="checkbox" name="status" value="{{d.id}}" lay-skin="switch" lay-text="启用|禁用" lay-filter="statusSwitch" {{ d.status == 1 ? 'checked' : '' }}>
</script>
<script type="text/html" id="dataRecybinTpl">
<div class="layui-card">
<div class="layui-card-header"> </div>
<div class="layui-card-body">
<table class="layui-hide" id="dataRecybinTable" lay-filter="dataRecybinTable"></table>
</div>
</div>
</script>
</script>
<script type="text/html" id="dataRecybinTpl">
<div class="layui-btn-group">
<a class="layui-btn layui-btn-sm layui-btn-primary" title="恢复数据" lay-event="update"><i class="layui-icon layui-icon-edit"></i> </a>
<a class="layui-btn layui-btn-sm layui-btn-primary" title="删除节点" lay-event="delete"><i class="layui-icon layui-icon-delete"></i> </a>
</div>
</script>
<script>
layui.use('power', function () { });
</script>
+130
View File
@@ -0,0 +1,130 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>头像设置 - 后台管理</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/assets/layui/css/layui.css">
<link rel="stylesheet" href="/assets/ywxapp/modules/cropper/cropper.css">
<style>
body { background: #f2f3f5; }
.uc-head { background: #fff; border-bottom: 1px solid #eee; padding: 12px 20px; display: flex; align-items: center; justify-content: space-between; }
.uc-head .title { font-size: 16px; font-weight: 700; }
.uc-wrap { max-width: 960px; margin: 16px auto; padding: 0 14px; }
.readyimg { background: #f7f7f7; height: 420px; display: flex; align-items: center; justify-content: center; overflow: hidden; }
.readyimg img { max-width: 100%; max-height: 100%; display: block; }
.img-preview { width: 150px; height: 150px; overflow: hidden; border-radius: 50%; border: 1px solid #eee; background: #f7f7f7; }
.img-preview img { width: 100%; }
</style>
</head>
<body>
<div class="uc-wrap">
<div class="layui-card">
<div class="layui-card-header">设置头像</div>
<div class="layui-card-body">
<div class="layui-form-item">
<button type="button" class="layui-btn layui-btn-primary" id="pickBtn">
<i class="layui-icon">&#xe67c;</i> 选择图片
</button>
<input id="avatarImgUpload" type="file" accept="image/*" name="file" style="display:none">
<div class="layui-form-mid layui-word-aux">支持 jpg / png / gif,建议尺寸 150x150 以上,大小 4M 以内</div>
</div>
<div class="layui-row layui-col-space15">
<div class="layui-col-md9">
<div class="readyimg"><img id="cropImage" src=""></div>
</div>
<div class="layui-col-md3">
<div class="layui-form-mid">预览:</div>
<div class="img-preview" style="margin-top:8px;"></div>
</div>
</div>
<div class="layui-row layui-col-space15" style="margin-top:14px;">
<div class="layui-col-md9">
<button type="button" class="layui-btn" id="rotateLeft"><i class="layui-icon layui-icon-left"></i> 向左旋转</button>
<button type="button" class="layui-btn" id="rotateRight"><i class="layui-icon layui-icon-right"></i> 向右旋转</button>
<button type="button" class="layui-btn" id="resetImg"><i class="layui-icon layui-icon-refresh"></i> 重置</button>
</div>
<div class="layui-col-md3">
<button type="button" class="layui-btn layui-btn-fluid" id="confirmSave">保存修改</button>
</div>
</div>
</div>
</div>
</div>
<script src="/assets/layui/layui.js"></script>
<script>
layui.config({ base: '/assets/ywxapp/modules/' }).extend({ cropper: 'cropper/cropper' });
layui.use(['jquery', 'layer', 'cropper', 'http'], function () {
var $ = layui.$;
var layer = layui.layer;
var http = layui.http;
var $image = $('#cropImage');
var options = { aspectRatio: 1 / 1, preview: '.img-preview', viewMode: 1 };
var cropped = false;
$('#pickBtn').on('click', function () { $('#avatarImgUpload').click(); });
$('#avatarImgUpload').on('change', function () {
var file = this.files[0];
if (!file) return;
var reader = new FileReader();
reader.onload = function (e) {
$image.attr('src', e.target.result);
$image.off('load.crop').on('load.crop', function () {
if (cropped) { $image.cropper('destroy'); }
$image.cropper(options);
cropped = true;
});
};
reader.readAsDataURL(file);
this.value = '';
});
$('#rotateLeft').on('click', function () { if (cropped) $image.cropper('rotate', -15); });
$('#rotateRight').on('click', function () { if (cropped) $image.cropper('rotate', 15); });
$('#resetImg').on('click', function () { if (cropped) $image.cropper('reset'); });
$('#confirmSave').on('click', function () {
if (!cropped) { layer.msg('请先选择图片', { icon: 2 }); return; }
var canvas = $image.cropper('getCroppedCanvas', { width: 300, height: 300 });
if (!canvas) { layer.msg('裁剪失败,请重试', { icon: 2 }); return; }
layer.load(1, { shade: [0.3, '#fff'] });
canvas.toBlob(function (blob) {
var fd = new FormData();
fd.append('file', blob, 'avatar.png');
fd.append('type', 'avatar');
http.post("{:url('Profile/avatar')}", fd).then(function (res) {
layer.closeAll('loading');
if (res.code === 0 && res.data && res.data.src) {
layer.msg(res.msg || '头像上传成功', { icon: 1 });
refreshTopAvatar(res.data.src);
} else {
layer.msg(res.msg || '上传失败', { icon: 2 });
}
}).catch(function () {
layer.closeAll('loading');
layer.msg('网络错误,上传失败', { icon: 2 });
});
}, 'image/png');
});
function refreshTopAvatar(src) {
try {
if (window !== top && parent.layui && parent.layui.ywxapp) {
var ts = '?t=' + Date.now();
parent.document.querySelectorAll('img.layui-nav-img').forEach(function (im) {
im.src = src + ts;
});
setTimeout(function () { parent.layui.ywxapp.closeThisTabs(); }, 700);
return;
}
} catch (e) { /* 跨域等情况忽略 */ }
setTimeout(function () { location.href = '/admin/index/index'; }, 700);
}
});
</script>
</body>
</html>
+82
View File
@@ -0,0 +1,82 @@
<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" pad15>
<div class="layui-form" lay-filter="">
<!-- <div class="layui-form-item">
<label class="layui-form-label">我的角色</label>
<div class="layui-input-inline">
<select name="role" lay-verify="">
<option value="1" selected>超级管理员</option>
<option value="2" disabled>普通管理员</option>
<option value="3" disabled>审核员</option>
<option value="4" disabled>编辑人员</option>
</select>
</div>
<div class="layui-form-mid layui-word-aux">当前角色不可更改为其它角色</div>
</div> -->
<div class="layui-form-item">
<label class="layui-form-label">用户名</label>
<div class="layui-input-inline">
<input type="text" disabled name="account" value="{$info.account|default=" 这家伙很懒什么也没留下"}" readonly class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">昵称</label>
<div class="layui-input-inline">
<input type="text" name="nickname" value="{$info.nickname|default=" 这家伙很懒什么也没留下"}" lay-verify="nickname" autocomplete="off" 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="sex" value="男" title="男" checked>
<input type="radio" name="sex" value="女" title="女">
</div>
</div> -->
<div class="layui-form-item">
<label class="layui-form-label">头像</label>
<div class="layui-input-inline">
<input name="avatar" lay-verify="required" id="LAY_avatarSrc" placeholder="图片地址" value="{$info.avatar|default=" https://unpkg.com/outeres@0.0.5/demo/000.jpg"}" class="layui-input">
</div>
<div class="layui-input-inline layui-btn-container" style="width: auto;">
<button type="button" class="layui-btn layui-btn-primary" id="LAY_avatarUpload">
<i class="layui-icon">&#xe67c;</i>上传图片
</button>
<button class="layui-btn layui-btn-primary" layadmin-event="avartatPreview">查看图片</button>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">手机</label>
<div class="layui-input-inline">
<input type="text" name="mobile" value="{$info.mobile|default=""}" lay-verify="phone" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">邮箱</label>
<div class="layui-input-inline">
<input type="text" name="email" value="{$info.email|default=""}" lay-verify="email" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">备注</label>
<div class="layui-input-block">
<textarea name="remarks" placeholder="请输入内容" class="layui-textarea">{$info.remarks|default=""}</textarea>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="setmyinfo">确认修改</button>
<button type="reset" class="layui-btn layui-btn-primary">重新填写</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
+84
View File
@@ -0,0 +1,84 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>修改密码 - 后台管理</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/assets/layui/css/layui.css">
<style>
body { background: #f2f3f5; }
.uc-wrap { max-width: 520px; margin: 20px auto; padding: 0 14px; }
</style>
</head>
<body>
<div class="uc-wrap">
<div class="layui-card">
<div class="layui-card-header">重置登录密码</div>
<div class="layui-card-body" pad15>
<div class="layui-form" lay-filter="passForm">
<div class="layui-form-item">
<label class="layui-form-label">当前密码</label>
<div class="layui-input-inline">
<input type="password" name="old_password" lay-verify="required" placeholder="请输入当前密码" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">新密码</label>
<div class="layui-input-inline">
<input type="password" name="password" id="LAY_password" lay-verify="pass" placeholder="6-12 位,不含空格" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">确认新密码</label>
<div class="layui-input-inline">
<input type="password" name="password_confirm" lay-verify="repass" placeholder="再次输入新密码" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="setmypass">确认修改</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="/assets/layui/layui.js"></script>
<script>
layui.use(['form', 'layer'], function () {
var form = layui.form, layer = layui.layer, $ = layui.$;
form.verify({
pass: [/^[\S]{6,12}$/, '密码必须 6 到 12 位,且不能出现空格'],
repass: function (value) {
if (value !== $('#LAY_password').val()) {
return '两次密码输入不一致';
}
}
});
form.on('submit(setmypass)', function (obj) {
$.post('{:url("Profile/passwordSave")}', obj.field, function (res) {
if (res.code === 1 || res.code === 0) {
layer.msg(res.msg || '修改成功', { icon: 1 });
setTimeout(function () {
if (window !== top && parent.layui && parent.layui.ywxapp) {
parent.layui.ywxapp.closeThisTabs();
} else {
location.href = '/admin/index/index';
}
}, 800);
} else {
layer.msg(res.msg || '修改失败', { icon: 2 });
}
}, 'json').fail(function () {
layer.msg('请求失败', { icon: 2 });
});
return false;
});
});
</script>
</body>
</html>
+86
View File
@@ -0,0 +1,86 @@
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">道具中心管理</div>
<div class="layui-card-body">
<form class="layui-form" lay-filter="data-search-form">
<div class="layui-form-item">
<div class="layui-inline">
<input type="text" name="title" placeholder="标题" autocomplete="off" class="layui-input">
</div>
<div class="layui-inline">
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="data-search-btn"><i class="layui-icon">&#xe615;</i> 搜索</button>
<button type="button" class="layui-btn layui-btn-primary" id="btn-reset">重置</button>
</div>
</div>
</form>
<table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
<script type="text/html" id="tableBar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="dataCreate" data-perm="prop:add"><i class="layui-icon">&#xe654;</i> </button>
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-event="dataDelete" data-perm="prop:del"><i class="layui-icon">&#xe640;</i> </button>
<button class="layui-btn layui-btn-sm" lay-event="dataRecybin" data-perm="prop:restore"><i class="layui-icon">&#xe64b;</i> </button>
</div>
</script>
<script type="text/html" id="dataBar">
<a class="layui-btn layui-btn-xs" lay-event="update" data-perm="prop:edit">编辑</a>
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="delete" data-perm="prop:del">删除</a>
</script>
<script type="text/html" id="statusTpl">
<input type="checkbox" name="status" value="{{ d.id }}" lay-skin="switch" lay-text="开|关" lay-filter="statusSwitch" {{ d.status == 1 ? 'checked' : '' }}>
</script>
</div>
</div>
</div>
<script type="text/html" id="dataFormTpl">
<form class="layui-form" id="wxapp-form" lay-filter="wxapp-form" style="padding:15px;">
<input type="hidden" name="id" value="{{ d.id || '' }}">
<div class="layui-form-item">
<label class="layui-form-label">道具名称</label>
<div class="layui-input-block">
<input type="text" name="title" 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="icon" 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="price" placeholder="请输入价格" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">描述</label>
<div class="layui-input-block">
<textarea name="description" placeholder="请输入描述" class="layui-textarea"></textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">排序</label>
<div class="layui-input-block">
<input type="text" name="sort" placeholder="请输入排序" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<input type="checkbox" name="status" lay-skin="switch" lay-text="启用|禁用" value="1" checked>
</div>
</div>
</form>
</script>
<script type="text/html" id="dataRecybinTpl">
<table class="layui-hide" id="dataRecybinTable" lay-filter="dataRecybinTable"></table>
<script type="text/html" id="dataRecybinBarTpl">
<a class="layui-btn layui-btn-xs" lay-event="restore" data-perm="prop:restore">恢复</a>
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="forcedelete" data-perm="prop:destroy">彻底删除</a>
</script>
</script>
<script>
layui.use('prop', layui.factory('prop'));
</script>
+450
View File
@@ -0,0 +1,450 @@
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header"></div>
<div class="layui-card-body">
<table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
</div>
</div>
</div>
<!-- 表格顶部工具条 -->
<script type="text/html" id="tableBarTpl">
<div class="layui-btn-group">
<a
class="layui-btn layui-btn-sm layui-btn-primary"
title="新建数据"
lay-event="dataCreate"
data-perm="role:add">
<i class="layui-icon layui-icon-add-1"></i>
</a>
<a
class="layui-btn layui-btn-sm layui-btn-primary"
title="删除资源"
lay-event="dataDelete"
data-perm="role:delete"
><i class="layui-icon layui-icon-delete"></i>
</a>
<a
class="layui-btn layui-btn-sm layui-btn-primary"
title="资源回收站"
lay-event="dataRecybin"
data-perm="role:recyclebin"
><i class="layui-icon layui-icon-home"></i>
</a>
</div>
</script>
<!-- 表格数据工具条 -->
<script type="text/html" id="dataBarTpl">
<div class="layui-btn-group">
<a
class="layui-btn layui-btn-sm layui-btn-primary"
title="编辑数据"
lay-event="update"
data-perm="role:edit"
><i class="layui-icon layui-icon-edit"></i>
</a>
<a
class="layui-btn layui-btn-sm layui-btn-primary"
title="权限分配"
lay-event="permission"
data-perm="role:*"
><i class="layui-icon layui-icon-auz"></i>
</a>
{{# if(d.id > 1) { }}
<a
class="layui-btn layui-btn-sm layui-btn-primary"
title="删除数据"
lay-event="delete"
data-perm="role:delete"
><i class="layui-icon layui-icon-delete"></i>
</a>
{{# } }}
</div>
</script>
<!-- 表格回收站 -->
<script type="text/html" id="dataRecybinTpl">
<div class="layui-card">
<div class="layui-card-header"></div>
<div class="layui-card-body">
<table
class="layui-hide"
id="dataRecybinTable"
lay-filter="dataRecybinTable"></table>
</div>
</div>
</script>
<!-- 角色状态 -->
<script type="text/html" id="statusTpl">
{{# if(d.status == 1) { }}
<span class="layui-badge layui-bg-green">正常</span>
{{# } else { }}
<span class="layui-badge layui-bg-orange">禁用</span>
{{# } }}
</script>
<!-- 添加/编辑角色表单 -->
<script type="text/html" id="dataFormTpl">
<form class="layui-form layui-form-pane" lay-filter="wxapp-form" id="wxapp-form">
<input type="hidden" name="id" value="{{d.id||''}}">
<div class="layui-form-item">
<label class="layui-form-label">角色名称</label>
<div class="layui-input-block">
<input type="text" name="title" required lay-verify="required" placeholder="请输入角色名称" autocomplete="off" class="layui-input" value="{{d.title||''}}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">编码</label>
<div class="layui-input-block">
<input type="text" name="name" required lay-verify="required" placeholder="请输入分组编码" autocomplete="off" class="layui-input" value="{{d.name||''}}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">角色描述</label>
<div class="layui-input-block">
<textarea name="description" placeholder="请输入角色描述" class="layui-textarea">{{d.description||''}}</textarea>
</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="启用" {{ d.status==undefined || d.status==1 ? 'checked' : '' }}>
<input type="radio" name="status" value="0" title="禁用" {{ d.status==0 ? 'checked' : '' }}>
</div>
</div>
<div class="layui-form-item layui-hide">
<button class="layui-btn" lay-submit="" lay-filter="wxapp-form-submit" id="wxapp-form-submit">提交</button>
</div>
</form>
</script>
<!-- 权限分配弹窗 -->
<script type="text/html" id="permissionFormTpl">
<div class="layui-card">
<div class="layui-card-header">
角色{{d.name}}
<span style="color:#999;font-weight:normal;">(ID: {{d.id}})</span>
</div>
<div class="layui-card-body">
<div id="permissionTree"></div>
</div>
</div>
</script>
<script>
layui.use(
["table", "form", "layer", "jquery", "tree", "laytpl"],
function () {
var table = layui.table,
form = layui.form,
layer = layui.layer,
$ = layui.jquery,
tree = layui.tree,
laytpl = layui.laytpl;
function api(url, data, type) {
return $.ajax({
url: url,
type: type || "POST",
data: data || {},
dataType: "json",
});
}
function load() {
api("index").then(
function (res) {
if (res.code !== 0) {
layer.msg(res.message || "加载失败", { icon: 2 });
return;
}
renderTable(res.data || []);
},
function () {
layer.msg("请求失败", { icon: 2 });
},
);
}
function renderTable(list) {
table.render({
elem: "#dataTable",
data: list,
page: false,
cols: [
[
{ type: "checkbox", width: 40 },
{ field: "id", title: "ID", width: 60 },
{ field: "name", title: "编码", width: 140 },
{ field: "title", title: "角色名称", minWidth: 140 },
{ field: "description", title: "描述", minWidth: 160 },
{
field: "status",
title: "状态",
width: 90,
templet: "#statusTpl",
},
{
title: "操作",
width: 200,
templet: "#dataBarTpl",
fixed: "right",
},
],
],
});
}
// 新建 / 编辑角色
function openForm(row) {
laytpl($("#dataFormTpl").html()).render(row || {}, function (html) {
layer.open({
type: 1,
title: row ? "编辑角色" : "新建角色",
area: ["480px", "auto"],
content: html,
btn: ["保存", "取消"],
success: function () {
form.render("radio");
},
yes: function (index, layero) {
var params = $(layero).find("#wxapp-form").serialize();
if (row) {
params += "&_method=PUT";
}
api(row ? "update" : "save", params, "POST").then(
function (res) {
if (res.code !== 0) {
layer.msg(res.message || "保存失败", { icon: 2 });
return;
}
layer.msg(res.message || "已保存", { icon: 1 });
layer.close(index);
load();
},
function () {
layer.msg("保存失败", { icon: 2 });
},
);
},
});
});
}
// 权限分配(树形勾选 + 保存),后端 Role::permission 提供树与持久化
function openPermission(row) {
laytpl($("#permissionFormTpl").html()).render(row, function (html) {
layer.open({
type: 1,
title: "权限分配",
area: ["360px", "80%"],
content: html,
btn: ["保存", "取消"],
success: function (layero, index) {
api("permission?id=" + row.id, null, "GET").then(
function (res) {
if (res.code !== 0) {
layer.msg(res.message || "加载失败", { icon: 2 });
return;
}
tree.render({
elem: "#permissionTree",
data: convertTree(res.data || []),
showCheckbox: true,
id: "permTree",
});
// 超级管理员恒为 *,禁用保存
if (res.message && res.message.indexOf("超级管理员") >= 0) {
$(layero)
.find(".layui-layer-btn0")
.addClass("layui-btn-disabled");
layer.msg("超级管理员拥有全部权限(*),无需分配", {
icon: 0,
});
}
},
function () {
layer.msg("权限树加载失败", { icon: 2 });
},
);
},
yes: function (index) {
var ids = collectIds(tree.getChecked("permTree"), []);
api(
"permission?id=" + row.id,
{ permissions: ids, _method: "PUT" },
"POST",
).then(
function (res) {
if (res.code !== 0) {
layer.msg(res.message || "保存失败", { icon: 2 });
return;
}
layer.msg(res.message || "权限已保存", { icon: 1 });
layer.close(index);
},
function () {
layer.msg("保存失败", { icon: 2 });
},
);
},
});
});
}
// Role::permission 返回树节点用 name 展示,转换为 layui tree 的 title 字段
function convertTree(nodes) {
return (nodes || []).map(function (n) {
return {
title: n.name,
id: n.id,
checked: !!n.checked,
spread: !!n.spread,
children: convertTree(n.children),
};
});
}
// 递归收集勾选节点(含父子)的 id,前端去重
function collectIds(nodes, acc) {
(nodes || []).forEach(function (n) {
if (n.id && acc.indexOf(n.id) < 0) {
acc.push(n.id);
}
if (n.children) {
collectIds(n.children, acc);
}
});
return acc;
}
// 回收站
function openRecycle() {
api("recyclebin").then(
function (res) {
if (res.code !== 0) {
layer.msg(res.message || "加载失败", { icon: 2 });
return;
}
var rows = (res.data || [])
.map(function (r) {
return (
"<tr><td>" +
r.id +
"</td><td>" +
(r.title || "") +
"</td>" +
'<td><button type="button" class="layui-btn layui-btn-xs" onclick="restoreRole(' +
r.id +
')">恢复</button></td></tr>'
);
})
.join("");
if (!rows) {
rows =
'<tr><td colspan="3" style="color:#999;text-align:center;">回收站为空</td></tr>';
}
var html =
'<div style="padding:15px;"><table class="layui-table"><thead><tr>' +
"<th>ID</th><th>名称</th><th>操作</th></tr></thead><tbody>" +
rows +
"</tbody></table></div>";
layer.open({
type: 1,
title: "角色回收站",
area: ["420px", "70%"],
content: html,
});
},
function () {
layer.msg("加载失败", { icon: 2 });
},
);
}
window.restoreRole = function (id) {
api("restore", { ids: id, _method: "PUT" }, "POST").then(
function (res) {
if (res.code !== 0) {
layer.msg(res.message || "恢复失败", { icon: 2 });
return;
}
layer.msg(res.message || "已恢复", { icon: 1 });
load();
},
function () {
layer.msg("恢复失败", { icon: 2 });
},
);
};
// 行内操作
table.on("tool(dataTable)", function (obj) {
var row = obj.data;
if (obj.event === "update") {
openForm(row);
} else if (obj.event === "permission") {
openPermission(row);
} else if (obj.event === "delete") {
layer.confirm(
"确认删除角色「" + (row.title || row.name) + "」?",
function () {
api("delete", { ids: row.id, _method: "DELETE" }, "POST").then(
function (res) {
if (res.code !== 0) {
layer.msg(res.message || "删除失败", { icon: 2 });
return;
}
layer.msg(res.message || "已删除", { icon: 1 });
load();
},
function () {
layer.msg("删除失败", { icon: 2 });
},
);
},
);
}
});
// 顶部工具条
table.on("toolbar(dataTable)", function (obj) {
if (obj.event === "dataCreate") {
openForm(null);
} else if (obj.event === "dataDelete") {
var check = table.checkStatus("dataTable").data;
if (!check.length) {
layer.msg("请先选择角色", { icon: 2 });
return;
}
var ids = check
.map(function (r) {
return r.id;
})
.join(",");
layer.confirm(
"确认删除选中的 " + check.length + " 个角色?",
function () {
api("delete", { ids: ids, _method: "DELETE" }, "POST").then(
function (res) {
if (res.code !== 0) {
layer.msg(res.message || "删除失败", { icon: 2 });
return;
}
layer.msg(res.message || "已删除", { icon: 1 });
load();
},
function () {
layer.msg("删除失败", { icon: 2 });
},
);
},
);
} else if (obj.event === "dataRecybin") {
openRecycle();
}
});
load();
},
);
</script>
+119
View File
@@ -0,0 +1,119 @@
<!--
* @Author: YwxApp <ywx@ywxapp.cn>
* @Date: 2026-04-29 02:32:33
* @LastEditors: YwxApp <ywx@ywxapp.cn>
* @LastEditTime: 2026-08-03 12:25:11
* @Description:
* @FilePath: \ywxapp_dev\app\backend\view\rule\index.html
* @CustomString: Copyright (c) 2026 YwxApp
-->
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header"> </div>
<div class="layui-card-body">
<table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
</div>
</div>
</div>
<script type="text/html" id="tableBarTpl">
<div class="layui-btn-group">
<a class="layui-btn layui-btn-sm layui-btn-primary" title="新建节点" lay-event="dataCreate" data-perm="rule:add"> <i class="layui-icon layui-icon-add-1"></i> </a>
<a class="layui-btn layui-btn-sm layui-btn-primary" title="删除资源" lay-event="dataDelete" data-perm="rule:delete"><i class="layui-icon layui-icon-delete"></i> </a>
<a class="layui-btn layui-btn-sm layui-btn-primary" title="资源回收站" lay-event="dataRecybin" data-perm="rule:recyclebin"><i class="layui-icon layui-icon-home"></i> </a>
</div>
</script>
<script type="text/html" id="dataBarTpl">
<div class="layui-btn-group">
<a class="layui-btn layui-btn-sm layui-btn-primary" title="编辑节点" lay-event="update" data-perm="rule:edit"><i class="layui-icon layui-icon-edit"></i> </a>
<a class="layui-btn layui-btn-sm layui-btn-primary" title="添加子节点" lay-event="create" data-perm="rule:add"><i class="layui-icon layui-icon-add-1"></i> </a>
<a class="layui-btn layui-btn-sm layui-btn-primary" title="删除节点" lay-event="delete" data-perm="rule:delete"><i class="layui-icon layui-icon-delete"></i> </a>
</div>
</script>
<!-- 添加/编辑菜单表单模板 -->
<script type="text/html" id="dataFormTpl">
<form class="layui-form layui-form-pane" lay-filter="wxapp-form" id="wxapp-form">
<input type="hidden" name="id" value="{{= d.id || '' }}">
<input type="hidden" name="pid" value="{{= d.pid || 0 }}">
<div class="layui-form-item">
<label class="layui-form-label">名称</label>
<div class="layui-input-block">
<input type="text" name="title" required lay-verify="required" placeholder="请输入菜单名称" value="{{ d.title || '' }}" 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="name" required lay-verify="required" placeholder="请输入菜单标识" value="{{ d.name || '' }}" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">菜单类型</label>
<div class="layui-input-block">
<select name="module" lay-filter="menuModule" lay-verify="required">
<option value="frontend" {{ d.module == 'frontend' ? 'selected' : '' }}>Frontend</option>
<option value="member" {{ d.module == 'member' ? 'selected' : '' }}>Member</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">菜单类型</label>
<div class="layui-input-block">
<select name="type" lay-filter="menuType" lay-verify="required">
<option value="1" {{ (d.type == 1 || !d.type) ? 'selected' : '' }}>目录</option>
<option value="2" {{ d.type == 2 ? 'selected' : '' }}>菜单</option>
<option value="3" {{ d.type == 3 ? 'selected' : '' }}>按钮</option>
</select>
</div>
</div>
<div class="layui-form-item layui-form-item-url {{ d.type == 3 ? 'layui-hide' : '' }}" id="urlField">
<label class="layui-form-label">路由</label>
<div class="layui-input-block">
<input type="text" name="route" placeholder="请输入路由路径" value="{{ d.route || '' }}" 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="icon" id="iconPicker" placeholder="请输入图标类名" value="{{ d.icon || '' }}" 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="sort" value="{{ d.sort || 0 }}" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<input type="checkbox" name="status" lay-skin="switch" lay-text="启用|禁用" {{ d.status != 0 ? 'checked' : (!d.id ? 'checked' : '') }}>
</div>
</div>
<div class="layui-form-item layui-hide">
<button class="layui-btn" lay-submit="" lay-filter="wxapp-form-submit" id="wxapp-form-submit">提交</button>
</div>
</form>
</script>
<script type="text/html" id="dataRecybinTpl">
<div class="layui-card">
<div class="layui-card-header"> </div>
<div class="layui-card-body">
<table class="layui-hide" id="dataRecybinTable" lay-filter="dataRecybinTable"></table>
</div>
</div>
</script>
</script>
<script type="text/html" id="dataRecybinTpl">
<div class="layui-btn-group">
<a class="layui-btn layui-btn-sm layui-btn-primary" title="恢复数据" lay-event="update"><i class="layui-icon layui-icon-edit"></i> </a>
<a class="layui-btn layui-btn-sm layui-btn-primary" title="删除节点" lay-event="delete"><i class="layui-icon layui-icon-delete"></i> </a>
</div>
</script>
<script>
layui.use(['rule'], function () { });
</script>
<script>
</script>
+95
View File
@@ -0,0 +1,95 @@
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">积分规则管理</div>
<div class="layui-card-body">
<form class="layui-form" lay-filter="data-search-form">
<div class="layui-form-item">
<div class="layui-inline">
<input type="text" name="name" placeholder="规则名称" autocomplete="off" class="layui-input">
</div>
<div class="layui-inline">
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="data-search-btn"><i class="layui-icon">&#xe615;</i> 搜索</button>
<button type="button" class="layui-btn layui-btn-primary" id="btn-reset">重置</button>
<a class="layui-btn layui-btn-warm" href="{:url('score/log')}"><i class="layui-icon">&#xe60a;</i> 积分流水</a>
</div>
</div>
</form>
<table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
<script type="text/html" id="tableBar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="dataCreate" data-perm="score:add"><i class="layui-icon">&#xe654;</i> 新增</button>
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-event="dataDelete" data-perm="score:del"><i class="layui-icon">&#xe640;</i> 删除</button>
<button class="layui-btn layui-btn-sm" lay-event="dataRecybin" data-perm="score:restore"><i class="layui-icon">&#xe64b;</i> 回收站</button>
</div>
</script>
<script type="text/html" id="dataBar">
<a class="layui-btn layui-btn-xs" lay-event="update" data-perm="score:edit">编辑</a>
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="delete" data-perm="score:del">删除</a>
</script>
<script type="text/html" id="typeTpl">
{{# if(d.type == 1){ }}
<span class="layui-badge layui-bg-green">获取</span>
{{# } else { }}
<span class="layui-badge layui-bg-orange">消费</span>
{{# } }}
</script>
<script type="text/html" id="statusTpl">
<input type="checkbox" name="status" value="{{ d.id }}" lay-skin="switch" lay-text="开|关" lay-filter="statusSwitch" {{ d.status == 1 ? 'checked' : '' }}>
</script>
</div>
</div>
</div>
<script type="text/html" id="dataFormTpl">
<form class="layui-form" id="wxapp-form" lay-filter="wxapp-form" style="padding:15px;">
<input type="hidden" name="id" value="{{ d.id || '' }}">
<div class="layui-form-item">
<label class="layui-form-label">规则名称</label>
<div class="layui-input-block">
<input type="text" name="name" 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="action" placeholder="如 task_complete/signin/register" 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="type" value="1" title="获取" checked>
<input type="radio" name="type" value="2" title="消费">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">变动值</label>
<div class="layui-input-block">
<input type="text" name="value" 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="sort" placeholder="请输入排序" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<input type="checkbox" name="status" lay-skin="switch" lay-text="启用|禁用" value="1" checked>
</div>
</div>
</form>
</script>
<script type="text/html" id="dataRecybinTpl">
<table class="layui-hide" id="dataRecybinTable" lay-filter="dataRecybinTable"></table>
<script type="text/html" id="dataRecybinBarTpl">
<a class="layui-btn layui-btn-xs" lay-event="restore" data-perm="score:restore">恢复</a>
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="forcedelete" data-perm="score:destroy">彻底删除</a>
</script>
</script>
<script>
layui.use('score', layui.factory('score'));
</script>
+67
View File
@@ -0,0 +1,67 @@
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">积分流水</div>
<div class="layui-card-body">
<form class="layui-form" lay-filter="data-search-form">
<div class="layui-form-item">
<div class="layui-inline">
<input type="text" name="uid" placeholder="用户ID" autocomplete="off" class="layui-input">
</div>
<div class="layui-inline">
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="data-search-btn"><i class="layui-icon">&#xe615;</i> 搜索</button>
<button type="button" class="layui-btn layui-btn-primary" id="btn-reset">重置</button>
<a class="layui-btn" href="{:url('score/index')}"><i class="layui-icon">&#xe65c;</i> 返回规则</a>
</div>
</div>
</form>
<table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
</div>
</div>
</div>
<script type="text/html" id="typeTpl">
{{# if(d.type == 2){ }}
<span class="layui-badge layui-bg-orange">支出</span>
{{# } else { }}
<span class="layui-badge layui-bg-green">收入</span>
{{# } }}
</script>
<script>
layui.use(['table', 'form'], function () {
var table = layui.table, form = layui.form, $ = layui.jquery;
function renderTable() {
var where = form.val('data-search-form');
table.render({
elem: '#dataTable',
url: "{:url('score/log')}",
method: 'post',
where: where,
page: true,
cols: [[
{field: 'id', title: 'ID', width: 70},
{field: 'uid', title: '用户ID', width: 90},
{field: 'type_text', title: '类型', width: 90, templet: '#typeTpl'},
{field: 'value', title: '变动值', width: 90},
{field: 'balance', title: '余额', width: 90},
{field: 'remark', title: '备注'},
{field: 'create_at', title: '时间', width: 170, templet: function (d) {
return layui.util.toDateString(d.create_at * 1000);
}}
]]
});
}
renderTable();
form.on('submit(data-search-btn)', function () {
renderTable();
return false;
});
$('#btn-reset').on('click', function () {
form.val('data-search-form', {uid: ''});
renderTable();
});
});
</script>
+86
View File
@@ -0,0 +1,86 @@
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">电子商务管理</div>
<div class="layui-card-body">
<form class="layui-form" lay-filter="data-search-form">
<div class="layui-form-item">
<div class="layui-inline">
<input type="text" name="title" placeholder="标题" autocomplete="off" class="layui-input">
</div>
<div class="layui-inline">
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="data-search-btn"><i class="layui-icon">&#xe615;</i> 搜索</button>
<button type="button" class="layui-btn layui-btn-primary" id="btn-reset">重置</button>
</div>
</div>
</form>
<table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
<script type="text/html" id="tableBar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="dataCreate" data-perm="shop:add"><i class="layui-icon">&#xe654;</i> 新增</button>
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-event="dataDelete" data-perm="shop:del"><i class="layui-icon">&#xe640;</i> 删除</button>
<button class="layui-btn layui-btn-sm" lay-event="dataRecybin" data-perm="shop:restore"><i class="layui-icon">&#xe64b;</i> 回收站</button>
</div>
</script>
<script type="text/html" id="dataBar">
<a class="layui-btn layui-btn-xs" lay-event="update" data-perm="shop:edit">编辑</a>
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="delete" data-perm="shop:del">删除</a>
</script>
<script type="text/html" id="statusTpl">
<input type="checkbox" name="status" value="{{ d.id }}" lay-skin="switch" lay-text="开|关" lay-filter="statusSwitch" {{ d.status == 1 ? 'checked' : '' }}>
</script>
</div>
</div>
</div>
<script type="text/html" id="dataFormTpl">
<form class="layui-form" id="wxapp-form" lay-filter="wxapp-form" style="padding:15px;">
<input type="hidden" name="id" value="{{ d.id || '' }}">
<div class="layui-form-item">
<label class="layui-form-label">商品名称</label>
<div class="layui-input-block">
<input type="text" name="title" 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="price" 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="stock" placeholder="请输入库存" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">描述</label>
<div class="layui-input-block">
<textarea name="description" placeholder="请输入描述" class="layui-textarea"></textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">排序</label>
<div class="layui-input-block">
<input type="text" name="sort" placeholder="请输入排序" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<input type="checkbox" name="status" lay-skin="switch" lay-text="启用|禁用" value="1" checked>
</div>
</div>
</form>
</script>
<script type="text/html" id="dataRecybinTpl">
<table class="layui-hide" id="dataRecybinTable" lay-filter="dataRecybinTable"></table>
<script type="text/html" id="dataRecybinBarTpl">
<a class="layui-btn layui-btn-xs" lay-event="restore" data-perm="shop:restore">恢复</a>
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="forcedelete" data-perm="shop:destroy">彻底删除</a>
</script>
</script>
<script>
layui.use('shop', layui.factory('shop'));
</script>
+80
View File
@@ -0,0 +1,80 @@
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">短信服务管理</div>
<div class="layui-card-body">
<form class="layui-form" lay-filter="data-search-form">
<div class="layui-form-item">
<div class="layui-inline">
<input type="text" name="title" placeholder="标题" autocomplete="off" class="layui-input">
</div>
<div class="layui-inline">
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="data-search-btn"><i class="layui-icon">&#xe615;</i> 搜索</button>
<button type="button" class="layui-btn layui-btn-primary" id="btn-reset">重置</button>
</div>
</div>
</form>
<table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
<script type="text/html" id="tableBar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="dataCreate" data-perm="sms:add"><i class="layui-icon">&#xe654;</i> 新增</button>
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-event="dataDelete" data-perm="sms:del"><i class="layui-icon">&#xe640;</i> 删除</button>
<button class="layui-btn layui-btn-sm" lay-event="dataRecybin" data-perm="sms:restore"><i class="layui-icon">&#xe64b;</i> 回收站</button>
</div>
</script>
<script type="text/html" id="dataBar">
<a class="layui-btn layui-btn-xs" lay-event="update" data-perm="sms:edit">编辑</a>
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="delete" data-perm="sms:del">删除</a>
</script>
<script type="text/html" id="statusTpl">
<input type="checkbox" name="status" value="{{ d.id }}" lay-skin="switch" lay-text="开|关" lay-filter="statusSwitch" {{ d.status == 1 ? 'checked' : '' }}>
</script>
</div>
</div>
</div>
<script type="text/html" id="dataFormTpl">
<form class="layui-form" id="wxapp-form" lay-filter="wxapp-form" style="padding:15px;">
<input type="hidden" name="id" value="{{ d.id || '' }}">
<div class="layui-form-item">
<label class="layui-form-label">模板名称</label>
<div class="layui-input-block">
<input type="text" name="title" 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="code" placeholder="请输入模板标识" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">模板内容</label>
<div class="layui-input-block">
<textarea name="content" placeholder="请输入模板内容" class="layui-textarea"></textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">排序</label>
<div class="layui-input-block">
<input type="text" name="sort" placeholder="请输入排序" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<input type="checkbox" name="status" lay-skin="switch" lay-text="启用|禁用" value="1" checked>
</div>
</div>
</form>
</script>
<script type="text/html" id="dataRecybinTpl">
<table class="layui-hide" id="dataRecybinTable" lay-filter="dataRecybinTable"></table>
<script type="text/html" id="dataRecybinBarTpl">
<a class="layui-btn layui-btn-xs" lay-event="restore" data-perm="sms:restore">恢复</a>
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="forcedelete" data-perm="sms:destroy">彻底删除</a>
</script>
</script>
<script>
layui.use('sms', layui.factory('sms'));
</script>
+67
View File
@@ -0,0 +1,67 @@
<div class="layui-card">
<div class="layui-card-header">蜘蛛汇总(今日 / 近7日 / 近30日 / 总量)</div>
<div class="layui-card-body">
<table class="layui-table" id="summaryTable" lay-size="sm">
<thead>
<tr>
<th>搜索引擎</th>
<th>今日</th>
<th>近7日</th>
<th>近30日</th>
<th>总量</th>
<th style="min-width:220px;">近30日趋势</th>
</tr>
</thead>
<tbody id="summaryBody">
<tr><td colspan="6" style="text-align:center;color:#999;">加载中…</td></tr>
</tbody>
</table>
</div>
</div>
<div class="layui-card">
<div class="layui-card-header">抓取明细</div>
<div class="layui-card-body">
<!-- 搜索栏 -->
<div class="layui-form layui-card-header-boxed" lay-filter="search-form">
<div class="layui-form-item">
<div class="layui-inline">
<select name="spider" id="spiderSelect">
<option value="">全部蜘蛛</option>
</select>
</div>
<div class="layui-inline">
<input type="text" name="ip" placeholder="来源IP" autocomplete="off" class="layui-input">
</div>
<div class="layui-inline">
<input type="text" name="url" placeholder="URL 关键词" autocomplete="off" class="layui-input">
</div>
<div class="layui-inline">
<input type="text" name="date" id="dateInput" placeholder="日期" autocomplete="off" class="layui-input" readonly>
</div>
<div class="layui-inline">
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="data-search-btn">
<i class="layui-icon layui-icon-search"></i>
</button>
<button class="layui-btn layui-btn-primary" id="btn-reset">
<i class="layui-icon layui-icon-refresh"></i>
</button>
</div>
</div>
</div>
<!-- 表格容器 -->
<table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
<!-- 顶部工具条 -->
<script type="text/html" id="tableBar">
<div class="layui-btn-group">
<a class="layui-btn layui-btn-sm layui-btn-primary" title="清理30天前明细" lay-event="dataClear" data-perm="spider:clear"><i class="layui-icon layui-icon-delete"></i> 清理旧日志</a>
</div>
</script>
</div>
</div>
<script type="text/javascript">
layui.use('spider', function () { });
</script>
+126
View File
@@ -0,0 +1,126 @@
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">站点任务管理</div>
<div class="layui-card-body">
<form class="layui-form" lay-filter="data-search-form">
<div class="layui-form-item">
<div class="layui-inline">
<input type="text" name="title" placeholder="标题" autocomplete="off" class="layui-input">
</div>
<div class="layui-inline">
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="data-search-btn"><i class="layui-icon">&#xe615;</i> 搜索</button>
<button type="button" class="layui-btn layui-btn-primary" id="btn-reset">重置</button>
</div>
</div>
</form>
<table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
<script type="text/html" id="tableBar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="dataCreate" data-perm="task:add"><i class="layui-icon">&#xe654;</i> 新增</button>
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-event="dataDelete" data-perm="task:del"><i class="layui-icon">&#xe640;</i> 删除</button>
<button class="layui-btn layui-btn-sm" lay-event="dataRecybin" data-perm="task:restore"><i class="layui-icon">&#xe64b;</i> 回收站</button>
</div>
</script>
<script type="text/html" id="dataBar">
<a class="layui-btn layui-btn-xs" lay-event="update" data-perm="task:edit">编辑</a>
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="delete" data-perm="task:del">删除</a>
</script>
<script type="text/html" id="statusTpl">
<input type="checkbox" name="status" value="{{ d.id }}" lay-skin="switch" lay-text="开|关" lay-filter="statusSwitch" {{ d.status == 1 ? 'checked' : '' }}>
</script>
<script type="text/html" id="rewardTypeTpl">
{{# if(d.reward_type == 1){ }}
<span class="layui-badge layui-bg-blue">积分</span>
{{# } else if(d.reward_type == 2){ }}
<span class="layui-badge layui-bg-orange">金币</span>
{{# } else if(d.reward_type == 3){ }}
<span class="layui-badge layui-bg-green">道具</span>
{{# } else { }}
<span class="layui-badge">未知</span>
{{# } }}
</script>
</div>
</div>
</div>
<script type="text/html" id="dataFormTpl">
<form class="layui-form" id="wxapp-form" lay-filter="wxapp-form" style="padding:15px;">
<input type="hidden" name="id" value="{{ d.id || '' }}">
<div class="layui-form-item">
<label class="layui-form-label">任务标题</label>
<div class="layui-input-block">
<input type="text" name="title" placeholder="请输入任务标题" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">任务描述</label>
<div class="layui-input-block">
<textarea name="description" placeholder="请输入任务描述" class="layui-textarea"></textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">奖励类型</label>
<div class="layui-input-block">
<select name="reward_type" lay-verify="required">
<option value="1">积分</option>
<option value="2">金币</option>
<option value="3">道具</option>
</select>
</div>
</div>
<div class="layui-form-item" id="rewardNumItem">
<label class="layui-form-label">奖励数量</label>
<div class="layui-input-block">
<input type="text" name="reward_num" placeholder="请输入数量" class="layui-input" lay-verify="required|number">
<div class="layui-form-mid layui-word-aux">积分/金币奖励填写数量</div>
</div>
</div>
<div class="layui-form-item" id="rewardPropItem" style="display:none;">
<label class="layui-form-label">奖励道具</label>
<div class="layui-input-block">
<select name="reward_num" id="rewardPropSelect" lay-verify="required">
<option value="">请选择道具</option>
{volist name="propList" id="pt"}
<option value="{$pt.id}">{$pt.title}</option>
{/volist}
</select>
<div class="layui-form-mid layui-word-aux">选择要发放的道具</div>
</div>
</div>
<div class="layui-form-item" id="propNumItem" style="display:none;">
<label class="layui-form-label">道具数量</label>
<div class="layui-input-block">
<input type="text" name="prop_num" placeholder="请输入道具数量" value="1" class="layui-input" lay-verify="required|number">
<div class="layui-form-mid layui-word-aux">用户领取后获得的该道具数量</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">排序</label>
<div class="layui-input-block">
<input type="text" name="sort" placeholder="请输入排序" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<input type="checkbox" name="status" lay-skin="switch" lay-text="启用|禁用" value="1" checked>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="wxapp-form-submit" id="wxapp-form-submit">立即提交</button>
</div>
</div>
</form>
</script>
<script type="text/html" id="dataRecybinTpl">
<table class="layui-hide" id="dataRecybinTable" lay-filter="dataRecybinTable"></table>
<script type="text/html" id="dataRecybinBarTpl">
<a class="layui-btn layui-btn-xs" lay-event="restore" data-perm="task:restore">恢复</a>
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="forcedelete" data-perm="task:destroy">彻底删除</a>
</script>
</script>
<script>
layui.use('task', layui.factory('task'));
</script>
+239
View File
@@ -0,0 +1,239 @@
<div class="layui-card">
<div class="layui-card-header">已安装模板 <span id="gdTip" class="layui-badge layui-bg-blue" style="margin-left:8px;"></span></div>
<div class="layui-card-body">
<table id="tplTable" lay-filter="tplTable"></table>
</div>
</div>
<div class="layui-card">
<div class="layui-card-header">插件模板绑定(整站皮肤按插件独立选择)</div>
<div class="layui-card-body">
<table class="layui-table">
<thead>
<tr><th>插件</th><th>当前激活</th><th>选择模板</th><th>操作</th></tr>
</thead>
<tbody id="bindBody"></tbody>
</table>
</div>
</div>
<div class="layui-card">
<div class="layui-card-header">安装新模板(上传 zip 包)</div>
<div class="layui-card-body">
<button type="button" class="layui-btn" id="uploadBtn"><i class="layui-icon">&#xe67c;</i>上传模板包</button>
<div style="color:#999;margin-top:8px;line-height:1.6;">
zip 内须含 <code>templates/&lt;name&gt;/template.json</code><code>static/</code> 资源,安装后落到
<code>templates/&lt;name&gt;/</code><code>public/static/templates/&lt;name&gt;/</code>
</div>
</div>
</div>
<div class="layui-card">
<div class="layui-card-header">配置备份(导入 / 导出)</div>
<div class="layui-card-body">
<button type="button" class="layui-btn layui-btn-normal" id="exportBtn"><i class="layui-icon">&#xe67d;</i>导出配置</button>
<button type="button" class="layui-btn" id="importBtn"><i class="layui-icon">&#xe681;</i>导入配置</button>
<div style="color:#999;margin-top:8px;line-height:1.6;">
导出含「插件模板绑定 + 界面设置 + 配色覆盖」的 JSON;导入会安全跳过未安装模板的绑定,可跨站点克隆皮肤方案。
</div>
</div>
</div>
<script type="text/html" id="tplOperate">
<a class="layui-btn layui-btn-xs" lay-event="setDefault">设默认</a>
<a class="layui-btn layui-btn-xs {{d.enabled ? 'layui-btn-warm' : 'layui-btn-normal'}}" lay-event="toggle">{{d.enabled ? '禁用' : '启用'}}</a>
{{# if (d.variable_defs && Object.keys(d.variable_defs).length > 0) { }}
<a class="layui-btn layui-btn-xs layui-btn-normal" lay-event="vars">配色</a>
{{# } }}
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="uninstall">卸载</a>
</script>
<script>
layui.use(['table', 'form', 'layer', 'upload', 'jquery'], function () {
var table = layui.table, form = layui.form, layer = layui.layer, upload = layui.upload, $ = layui.jquery;
var state = { installed: [], addon: [], active_map: {} };
function api(url, data) {
return $.ajax({ url: url, type: 'POST', data: data || {}, dataType: 'json' });
}
function load() {
api('index').then(function (res) {
if (res.code !== 0) { layer.msg(res.message || '加载失败', { icon: 2 }); return; }
state.installed = res.data.installed || [];
state.addon = res.data.addon || [];
state.active_map = res.data.active_map || {};
state.global_default = res.data.global_default || 'default';
$('#gdTip').text('全站默认:' + (state.global_default === 'default' ? '自带视图' : state.global_default));
renderTpl();
renderBind();
}, function () { layer.msg('请求失败', { icon: 2 }); });
}
function renderTpl() {
table.render({
elem: '#tplTable',
data: state.installed,
page: false,
cols: [[
{ field: 'preview', title: '预览', width: 90, templet: function (d) {
if (! d.preview_url) { return '<span style="color:#999"></span>'; }
var badge = (d.preview_urls && d.preview_urls.length > 1)
? ' <span class="layui-badge layui-bg-orange" style="vertical-align:top;">' + d.preview_urls.length + '图</span>' : '';
return '<img src="' + d.preview_url + '" style="max-height:40px;cursor:zoom-in;" onclick="openPreview(\'' + d.name + '\')">' + badge;
} },
{ field: 'name', title: '标识', width: 120 },
{ field: 'title', title: '名称', minWidth: 140 },
{ field: 'version', title: '版本', width: 80 },
{ field: 'target_addon', title: '适用插件', minWidth: 120, templet: function (d) {
return (d.target_addon || []).join(', ');
} },
{ field: 'applied_to', title: '已应用', minWidth: 120, templet: function (d) {
return (d.applied_to || []).join(', ') || '<span style="color:#999">未绑定</span>';
} },
{ field: 'enabled', title: '状态', width: 80, templet: function (d) {
return d.enabled
? '<span class="layui-badge layui-bg-green">启用</span>'
: '<span class="layui-badge layui-bg-gray">禁用</span>';
} },
{ title: '操作', width: 260, toolbar: '#tplOperate', fixed: 'right' }
]]
});
}
function renderBind() {
var html = '';
state.addon.forEach(function (a) {
var cur = state.active_map[a.name] || 'default';
var opts = '<option value="default">默认(自带视图)</option>';
state.installed.forEach(function (t) {
if ((t.target_addon || []).indexOf(a.name) >= 0) {
var sel = (cur === t.name) ? 'selected' : '';
opts += '<option value="' + t.name + '" ' + sel + '>' + t.title + ' (' + t.version + ')</option>';
}
});
html += '<tr>'
+ '<td>' + a.title + ' <span style="color:#999">' + a.name + '</span></td>'
+ '<td>' + (cur === 'default' ? '默认' : cur) + '</td>'
+ '<td><select class="layui-input-inline" data-addon="' + a.name + '" style="width:220px;">' + opts + '</select></td>'
+ '<td><button type="button" class="layui-btn layui-btn-xs" onclick="saveBind(\'' + a.name + '\', this)">保存</button></td>'
+ '</tr>';
});
$('#bindBody').html(html);
form.render('select');
}
window.saveBind = function (addon, btn) {
var sel = $(btn).closest('tr').find('select').val();
api('setActive', { addon: addon, template: sel }).then(function (res) {
layer.msg(res.message || '已保存', { icon: 1 });
load();
}, function () { layer.msg('保存失败', { icon: 2 }); });
};
// 预览灯箱:多图走 layer.photos 画廊,单图直接放大
window.openPreview = function (name) {
var tpl = null;
for (var i = 0; i < state.installed.length; i++) {
if (state.installed[i].name === name) { tpl = state.installed[i]; break; }
}
if (! tpl || ! tpl.preview_urls || ! tpl.preview_urls.length) { return; }
if (tpl.preview_urls.length === 1) {
layer.open({ type: 1, title: tpl.title || name, area: 'auto',
content: '<img src="' + tpl.preview_urls[0] + '" style="max-width:90vw;max-height:80vh;">' });
} else {
var data = tpl.preview_urls.map(function (u) { return { src: u, thumb: u, alt: tpl.title || name }; });
layer.photos({ photos: { title: tpl.title || name, data: data }, anim: 5 });
}
};
table.on('tool(tplTable)', function (obj) {
if (obj.event === 'setDefault') {
api('setDefault', { template: obj.data.name }).then(function (res) {
layer.msg(res.message || '已设默认', { icon: 1 });
load();
}, function () { layer.msg('操作失败', { icon: 2 }); });
} else if (obj.event === 'toggle') {
var enabled = obj.data.enabled ? 0 : 1;
api('toggle', { name: obj.data.name, enabled: enabled }).then(function (res) {
layer.msg(res.message || '已更新', { icon: 1 });
load();
}, function () { layer.msg('操作失败', { icon: 2 }); });
} else if (obj.event === 'vars') {
openVarsModal(obj.data);
} else if (obj.event === 'uninstall') {
layer.confirm('确认卸载模板「' + obj.data.name + '」?将删除其文件并解除绑定。', function (i) {
api('uninstall', { name: obj.data.name }).then(function (res) {
layer.msg(res.message || '已卸载', { icon: 1 });
load();
}, function () { layer.msg('卸载失败', { icon: 2 }); });
});
}
});
function openVarsModal(tpl) {
var defs = tpl.variable_defs || {};
var overrides = tpl.variable_overrides || {};
var rows = '';
Object.keys(defs).forEach(function (k) {
var d = defs[k];
var cur = overrides[k] != null ? overrides[k] : (d.default || '');
rows += '<div class="layui-form-item">'
+ '<label class="layui-form-label">' + (d.label || k) + '</label>'
+ '<div class="layui-input-block">'
+ '<input type="text" class="layui-input" data-k="' + k + '" value="' + cur + '" placeholder="默认:' + (d.default || '') + '">'
+ (d.type === 'color' ? ' <span style="color:#999;font-size:12px;">颜色填 #xxxxxx</span>' : '')
+ '</div></div>';
});
if (rows === '') { rows = '<div style="padding:10px;color:#999;">该模板未声明可配置变量</div>'; }
var html = '<form class="layui-form" style="padding:15px;" id="varsForm">'
+ '<div style="color:#999;margin-bottom:10px;">模板:' + tpl.title + '' + tpl.name + ')— 保存后无需改 HTML 即生效</div>'
+ rows + '</form>';
layer.open({
type: 1, title: '风格配色 - ' + tpl.title, area: ['520px', 'auto'], content: html,
btn: ['保存', '取消'],
yes: function (idx) {
var data = {};
$('#varsForm input[data-k]').each(function () {
data[$(this).data('k')] = $(this).val();
});
api('saveVariables', { name: tpl.name, vars: data }).then(function (res) {
layer.msg(res.message || '已保存', { icon: 1 });
layer.close(idx);
load();
}, function () { layer.msg('保存失败', { icon: 2 }); });
}
});
}
upload.render({
elem: '#uploadBtn',
url: 'upload',
accept: 'file',
exts: 'zip',
done: function (res) {
if (res.code === 0) { layer.msg('安装成功', { icon: 1 }); load(); }
else { layer.msg(res.message || '安装失败', { icon: 2 }); }
},
error: function () { layer.msg('上传失败', { icon: 2 }); }
});
// 导出配置:直接触发浏览器下载
$('#exportBtn').on('click', function () {
window.location.href = 'exportConfig';
});
// 导入配置:复用 upload 组件,仅接受 .json
upload.render({
elem: '#importBtn',
url: 'importConfig',
accept: 'file',
exts: 'json',
done: function (res) {
if (res.code === 0) { layer.msg(res.message || '导入成功', { icon: 1 }); load(); }
else { layer.msg(res.message || '导入失败', { icon: 2 }); }
},
error: function () { layer.msg('上传失败', { icon: 2 }); }
});
load();
});
</script>
+63
View File
@@ -0,0 +1,63 @@
<div class="layui-card" style="margin:15px;">
<div class="layui-card-header">界面设置</div>
<div class="layui-card-body">
<form class="layui-form" id="settingForm">
<div class="layui-form-item">
<label class="layui-form-label">全站默认模板</label>
<div class="layui-input-inline" style="width:320px;">
<select name="default_template" id="defaultTemplate">
<option value="default">默认(自带视图)</option>
</select>
</div>
<div class="layui-form-mid layui-word-aux">未单独绑定模板的插件,统一使用此默认模板</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">会员自选风格</label>
<div class="layui-input-inline">
<input type="checkbox" name="allow_member_select" id="allowSelect" lay-skin="switch" lay-text="开启|关闭">
</div>
<div class="layui-form-mid layui-word-aux">允许前台会员切换风格(开启后,已安装模板提供覆盖视图的插件页面右下角自动出现切换器,按访客 cookie 生效,无需登录)</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="saveSetting">保存设置</button>
</div>
</div>
</form>
</div>
</div>
<script>
layui.use(['form', 'layer', 'jquery'], function () {
var form = layui.form, layer = layui.layer, $ = layui.jquery;
// 拉取已安装模板填充下拉 + 当前设置
$.ajax({
url: 'index', type: 'POST', dataType: 'json',
success: function (res) {
if (res.code !== 0) { return; }
var inst = res.data.installed || [];
var gd = res.data.global_default || 'default';
var sel = $('#defaultTemplate');
inst.forEach(function (t) {
sel.append('<option value="' + t.name + '">' + t.title + ' (' + t.version + ')</option>');
});
sel.val(gd);
$('#allowSelect').prop('checked', !!res.data.allow_member_select);
form.render('select');
form.render('checkbox');
}
});
form.on('submit(saveSetting)', function (data) {
$.ajax({
url: 'setting', type: 'POST', data: data.field, dataType: 'json',
success: function (res) {
if (res.code === 0) { layer.msg('已保存', { icon: 1 }); }
else { layer.msg(res.message || '保存失败', { icon: 2 }); }
},
error: function () { layer.msg('请求失败', { icon: 2 }); }
});
return false;
});
});
</script>
+113
View File
@@ -0,0 +1,113 @@
<div class="layui-container" style="margin-top:20px;">
<div class="layui-card">
<div class="layui-card-header">主框架在线升级</div>
<div class="layui-card-body">
<p>当前版本:<span class="layui-badge layui-bg-blue" id="current">{$current}</span></p>
<div id="status" class="layui-text" style="margin:12px 0;min-height:24px;"></div>
<div id="pkgTypeBox" style="display:none;margin:12px 0;padding:10px 12px;background:#fafafa;border:1px solid #eee;border-radius:4px;">
<div style="margin-bottom:8px;color:#555;">升级包类型:</div>
<div class="layui-btn-group" id="pkgTypeGroup">
<button type="button" class="layui-btn layui-btn-sm layui-btn-normal pkg-opt" data-type="auto">自动(推荐)</button>
<button type="button" class="layui-btn layui-btn-sm layui-btn-primary pkg-opt" data-type="full">完整包</button>
<button type="button" class="layui-btn layui-btn-sm layui-btn-primary pkg-opt" data-type="patch">增量小包</button>
</div>
<div id="pkgTypeTip" style="margin-top:8px;color:#e67700;font-size:13px;"></div>
</div>
<button class="layui-btn layui-btn-normal" id="btnCheck">
<i class="layui-icon layui-icon-refresh"></i> 检查更新
</button>
<button class="layui-btn layui-btn-danger" id="btnUpgrade" style="display:none;">
<i class="layui-icon layui-icon-release"></i> 立即升级
</button>
</div>
</div>
</div>
<script>
layui.use(['jquery', 'layer'], function () {
var $ = layui.jquery, layer = layui.layer;
var selectedType = 'auto';
function setDisabled($el, dis) {
if (dis) {
$el.addClass('layui-btn-disabled').prop('disabled', true);
} else {
$el.removeClass('layui-btn-disabled').prop('disabled', false);
}
}
$('#pkgTypeGroup').on('click', '.pkg-opt', function () {
if ($(this).prop('disabled')) return;
selectedType = $(this).data('type');
$('#pkgTypeGroup .pkg-opt').removeClass('layui-btn-normal').addClass('layui-btn-primary');
$(this).removeClass('layui-btn-primary').addClass('layui-btn-normal');
});
function check() {
$('#status').html('<i class="layui-icon layui-icon-loading layui-anim layui-anim-rotate"></i> 正在检查...');
$.get('{:url("framework/check")}', function (res) {
if (res.code === 0) {
var d = res.data;
if (d.has_update) {
var autoMode = d.use_patch ? '增量补丁' : '整包';
var html = '发现新版本:<b>' + d.latest + '</b>'
+ ' <span class="layui-badge layui-bg-orange">自动模式将使用' + autoMode + '</span>';
if (d.use_patch && d.patch_from) html += '(基于 ' + d.patch_from + '';
if (d.has_full && d.has_patch) html += ' <span class="layui-badge layui-bg-gray">已同时发布完整包与增量小包</span>';
if (d.title) html += '' + d.title + '';
if (d.error) {
html += '<br><span style="color:#dc2626;">' + d.error + '</span>';
}
html += '<br>更新内容:<pre style="margin-top:6px;">' + (d.changelog || '') + '</pre>';
$('#status').html(html);
if (d.error) {
$('#btnUpgrade').hide();
$('#pkgTypeBox').hide();
} else {
$('#btnUpgrade').show();
// 显示升级包类型选择(按钮组,不依赖 layui.form)
$('#pkgTypeBox').show();
selectedType = 'auto';
$('#pkgTypeGroup .pkg-opt').removeClass('layui-btn-normal').addClass('layui-btn-primary');
$('#pkgTypeGroup .pkg-opt[data-type=auto]').removeClass('layui-btn-primary').addClass('layui-btn-normal');
setDisabled($('#pkgTypeGroup .pkg-opt[data-type=full]'), !d.has_full);
setDisabled($('#pkgTypeGroup .pkg-opt[data-type=patch]'), !d.use_patch);
var tips = [];
if (!d.has_full) tips.push('当前无完整包可用');
if (!d.use_patch) tips.push('当前版本(' + (d.current || '') + ')不支持增量小包,需基于 ' + (d.patch_from || '?'));
$('#pkgTypeTip').text(tips.length ? '提示:' + tips.join('') + ',建议选「自动」' : '可手动选择完整包或增量小包');
}
} else {
$('#status').html('<span style="color:#16a34a;">✓ 已是最新版本(' + d.current + '</span>');
$('#btnUpgrade').hide();
$('#pkgTypeBox').hide();
}
} else {
$('#status').html('<span style="color:#dc2626;">' + (res.message || '检查失败') + '</span>');
}
});
}
$('#btnCheck').on('click', check);
$('#btnUpgrade').on('click', function () {
var label = {auto: '自动模式', full: '完整包', patch: '增量小包'}[selectedType] || selectedType;
layer.confirm('将以【' + label + '】升级,将备份当前核心并覆盖,确定继续?', function () {
$('#status').html('<i class="layui-icon layui-icon-loading layui-anim layui-anim-rotate"></i> 正在下载并升级...');
$.post('{:url("framework/upgrade")}', {type: selectedType}, function (res) {
if (res.code === 0) {
$('#status').html('<span style="color:#16a34a;">✓ ' + res.message + '</span>');
$('#btnUpgrade').hide();
$('#pkgTypeBox').hide();
} else {
$('#status').html('<span style="color:#dc2626;">' + (res.message || '升级失败') + '</span>');
}
});
});
});
check();
});
</script>
+179
View File
@@ -0,0 +1,179 @@
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header"></div>
<div class="layui-card-body">
<table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
</div>
</div>
</div>
<script type="text/html" id="tableBar">
<div class="layui-btn-group">
<a
class="layui-btn layui-btn-sm layui-btn-primary"
title="新建数据"
lay-event="dataCreate"
data-perm="user:add">
<i class="layui-icon layui-icon-add-1"></i>
</a>
<a
class="layui-btn layui-btn-sm layui-btn-primary"
title="删除资源"
lay-event="dataDelete"
data-perm="user:delete"
><i class="layui-icon layui-icon-delete"></i>
</a>
<a
class="layui-btn layui-btn-sm layui-btn-primary"
title="资源回收站"
lay-event="dataRecybin"
data-perm="user:recyclebin"
><i class="layui-icon layui-icon-home"></i>
</a>
</div>
</script>
<script type="text/html" id="dataBar">
<div class="layui-btn-group">
<a
class="layui-btn layui-btn-sm layui-btn-primary"
title="编辑数据"
lay-event="update"
data-perm="user:edit"
><i class="layui-icon layui-icon-edit"></i>
</a>
{{# if(!d.is_super) { }}
<a
class="layui-btn layui-btn-sm layui-btn-primary"
title="删除数据"
lay-event="delete"
data-perm="user:delete"
><i class="layui-icon layui-icon-delete"></i>
</a>
{{# } }}
</div>
</script>
<!-- 表格回收站 -->
<script type="text/html" id="dataRecybinTpl">
<div class="layui-card">
<div class="layui-card-header"></div>
<div class="layui-card-body">
<table
class="layui-hide"
id="dataRecybinTable"
lay-filter="dataRecybinTable"></table>
</div>
</div>
</script>
<!-- 回收站数据工具条 -->
<script type="text/html" id="dataRecybinBarTpl">
<div class="layui-btn-group">
<a
class="layui-btn layui-btn-sm layui-btn-primary"
title="恢复数据"
lay-event="update"
><i class="layui-icon layui-icon-edit"></i>
</a>
<a
class="layui-btn layui-btn-sm layui-btn-primary"
title="删除数据"
lay-event="delete"
><i class="layui-icon layui-icon-delete"></i>
</a>
</div>
</script>
<!-- 状态开关 -->
<script type="text/html" id="statusTpl">
<input type="checkbox" name="status" value="{{d.id}}" lay-skin="switch" lay-text="启用|禁用" lay-filter="statusSwitch" {{ d.status == 1 ? 'checked' : '' }}>
</script>
<!-- 角色显示 -->
<script type="text/html" id="roleTpl">
{{# if(d.is_super == 1) { }}
<span class="role-super">超级管理员</span>
{{# } else if(d.role_id == 1) { }}
<span class="role-super">超级管理员</span>
{{# } else if(d.role_id == 2) { }}
<span>普通管理员</span>
{{# } else { }}
<span>未知角色</span>
{{# } }}
</script>
<!-- 登录信息 -->
<script type="text/html" id="loginInfoTpl">
<div>登录次数:{{d.login_count}}</div>
<div>最后登录:{{ d.login_time }}</div>
</script>
<!-- 添加/编辑管理员表单 -->
<script type="text/html" id="dataFormTpl">
<form class="layui-form" style="padding: 20px 20px 0;">
<input type="hidden" name="uid" value="{{d.uid||''}}">
<div class="layui-form-item">
<label class="layui-form-label">账户</label>
<div class="layui-input-block">
<input type="text" name="account" required lay-verify="required|account" placeholder="请输入用户名" autocomplete="off" class="layui-input" value="{{d.account||''}}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">昵称</label>
<div class="layui-input-block">
<input type="text" name="nickname" required lay-verify="required" placeholder="请输入真实姓名" autocomplete="off" class="layui-input" value="{{d.nickname||''}}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">角色</label>
<div class="layui-input-block">
<select name="role_id" lay-verify="required">
<option value="">请选择角色</option>
<option value="1" {{ (d.role_id==1||d.is_super==1) ? 'selected' : '' }}>超级管理员</option>
<option value="2" {{ d.role_id==2 ? 'selected' : '' }}>普通管理员</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">邮箱</label>
<div class="layui-input-block">
<input type="text" name="email" lay-verify="email" placeholder="请输入邮箱" autocomplete="off" class="layui-input" value="{{d.email||''}}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">手机号</label>
<div class="layui-input-block">
<input type="text" name="mobile" lay-verify="phone" placeholder="请输入手机号" autocomplete="off" class="layui-input" value="{{d.mobile||''}}">
</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="启用" {{ d.status==undefined || d.status==1 ? 'checked' : '' }}>
<input type="radio" name="status" value="0" title="禁用" {{ d.status==0 ? 'checked' : '' }}>
</div>
</div>
<div class="layui-form-item" id="passwordGroup" {{ d.uid ? 'style="display:none;"' : '' }}>
<label class="layui-form-label">密码</label>
<div class="layui-input-block">
<input type="password" name="password" required lay-verify="required|pass" placeholder="请输入密码" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-hide" id="resetPwdGroup">
<label class="layui-form-label">新密码</label>
<div class="layui-input-block">
<input type="password" name="new_password" required lay-verify="required|pass" placeholder="请输入新密码" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">备注</label>
<div class="layui-input-block">
<textarea name="remark" placeholder="请输入备注信息" class="layui-textarea">{{d.remark||''}}</textarea>
</div>
</div>
</form>
</script>
<script>
layui.use(["user"], function () { });
</script>