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
+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>