chore: 重写初始提交(清空历史,整理后全量提交)
This commit is contained in:
@@ -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">填写合法 JSON;events 含 bind/listen/subscribe,middleware 含 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>
|
||||
@@ -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>
|
||||
@@ -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 返回 1,a<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, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
.replace(/"/g, '"').replace(/'/g, ''');
|
||||
}
|
||||
// 换行转 <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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user