feat: 上线前安全加固与发布工程化;appmall 由插件迁移为独立应用
安全(P0) - 发布包签名:新增 scripts/package_keygen.php、scripts/package_verify.php、 ywxapp\service\PackageVerifier;FrameworkService::apply() 解压前强制验签 (ywxapp.framework_package_verify,默认开启) - JWT 收紧:config/jwt.php 移除公开默认密钥,JwtService::assertStrongSecret() 校验非空 / 长度≥32 / 已知弱值;.env.production.example 键名由 JWT_SECRET_KEY 修正为 JWT_SECRET - /api/v1/Article:免登录白名单收窄为只读接口,写操作字段白名单 + assertOwner() 越权校验 - 开发者令牌:查找接口不再回显明文、统一失败文案 + 限流 - TemplateInstaller:文件名白名单 + realpath 归一化 + 目录内断言,修复 Zip Slip;补 use ZipArchive - 后台登录:接入验证码、失败锁定与登录限流(config/ratelimit.php) - 缓存 / Session 驱动可由 .env 覆盖(CACHE_DRIVER / SESSION_TYPE),集群部署改 redis - paycenter 支付日志开关改为 PAY_LOG,不再跟随 APP_DEBUG 工程化 / 交付 - 新增 CHANGELOG.md 与 .env.production.example;composer.lock 入库锁定依赖版本 - 插件离线安装 CLI 增加 --uid,后台上传安装透传 uid,付费插件授权校验可通过 - 授权拒绝文案区分「未携带 uid」与「未找到有效授权」,便于排障 - .gitignore 放行 .env.production.example / .env.addon.example / .gitattributes / public/.htaccess 结构 - appmall 由 addon/appmall 迁移为独立应用 app/appmall(市场 / 开发者中心 / 审核 / 分润)
This commit is contained in:
1 parent
1d49e6f5ee
commit
18101cf11a
636 files changed
+58517
-9519
No files matched your search
@@ -47,14 +47,8 @@ layui.define(['form', 'table', 'http', 'auth'], function (exports) {
|
||||
skin: 'line',
|
||||
done: function () {
|
||||
var view = $('#dataTable').next('.layui-table-view');
|
||||
view.find('[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
$('.layui-btn[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
auth.filterDom(view);
|
||||
auth.filterDom('.layui-btn[data-perm]');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -15,6 +15,37 @@ layui.define(['http', 'setter', 'auth', 'upload', 'form', 'layer', 'table'], fun
|
||||
// 兼容旧键名(历史版本曾写入 'menu')
|
||||
layui.data(appName, { key: 'menu', remove: true });
|
||||
}
|
||||
// 安装/升级失败的可读化:后端 AddonService::validateDependencies() 收集式校验后,
|
||||
// 会在 data.problems 里带上完整清单(缺哪些依赖、该装还是该升),这里逐条展示并给
|
||||
// 「去应用市场安装」入口,避免只弹出一长串难以扫读的文案。
|
||||
function showDependencyProblems(res) {
|
||||
var data = res && res.data;
|
||||
var list = (data && data.problems) || [];
|
||||
if (!list.length) { return false; }
|
||||
var badgeText = { addon: '插件', extension: 'PHP 扩展', framework: '框架' };
|
||||
var rows = list.map(function (it) {
|
||||
var badge = badgeText[it.type] || '依赖';
|
||||
return '<li style="padding:6px 0;border-bottom:1px dashed #eee;">'
|
||||
+ '<span class="layui-badge layui-bg-cyan" style="margin-right:6px;">' + badge + '</span>'
|
||||
+ $('<div>').text(it.text).html()
|
||||
+ '</li>';
|
||||
}).join('');
|
||||
var missing = (data && data.missing_addons) || [];
|
||||
var extra = missing.length
|
||||
? '<div style="margin-top:12px;"><a class="layui-btn layui-btn-normal layui-btn-xs" href="market" target="_blank">'
|
||||
+ '去应用市场安装缺失插件(' + $('<span>').text(missing.join('、')).html() + ')</a></div>'
|
||||
: '';
|
||||
layer.open({
|
||||
type: 1,
|
||||
title: '依赖检查未通过(' + list.length + ' 项)',
|
||||
area: ['620px', 'auto'],
|
||||
content: '<div style="padding:16px 18px;font-size:13px;">'
|
||||
+ '<ul style="list-style:none;margin:0;padding:0;">' + rows + '</ul>' + extra + '</div>',
|
||||
btn: ['知道了']
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
// ===== 渲染核心表格 =====
|
||||
var { access_token, refresh_token } = layui.setter.getTokens();
|
||||
table.render({
|
||||
@@ -24,7 +55,7 @@ layui.define(['http', 'setter', 'auth', 'upload', 'form', 'layer', 'table'], fun
|
||||
{ field: 'title', title: '插件名称', minWidth: 180 },
|
||||
{ field: 'name', title: '标识', width: 120 },
|
||||
{ field: 'author', title: '开发者', width: 120 }
|
||||
, { field: 'version', title: '版本号', width: 100, align: 'center' }
|
||||
, { field: 'version', title: '版本号', width: 170, align: 'center', templet: '#versionTpl' }
|
||||
, { field: 'description', title: '功能描述', minWidth: 250 }
|
||||
, { field: 'status', title: '运行状态', width: 110, align: 'center', templet: '#switchTpl' } // 绑定开关模板
|
||||
, { fixed: 'right', title: '操作', toolbar: '#operateTpl', width: 300, align: 'right' } // 绑定操作栏模板
|
||||
@@ -42,14 +73,8 @@ layui.define(['http', 'setter', 'auth', 'upload', 'form', 'layer', 'table'], fun
|
||||
}
|
||||
, done: function (res, curr, count) {
|
||||
var view = $('#dataTable').next('.layui-table-view');
|
||||
view.find('[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
$('.layui-btn[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
auth.filterDom(view);
|
||||
auth.filterDom('.layui-btn[data-perm]');
|
||||
// 开发模式关闭时隐藏操作列的「设计」按钮
|
||||
// if (!ADDON_DEV) { $('.dev-design').remove(); }
|
||||
}
|
||||
@@ -79,6 +104,10 @@ layui.define(['http', 'setter', 'auth', 'upload', 'form', 'layer', 'table'], fun
|
||||
if (!addonName) { layer.msg('缺少插件标识', { icon: 2 }); break; }
|
||||
location.href = 'setting?addon=' + encodeURIComponent(addonName);
|
||||
break;
|
||||
case 'upgrade':
|
||||
if (!addonName) { layer.msg('缺少插件标识', { icon: 2 }); break; }
|
||||
active.upgrade(addonName, data.latest_version || '');
|
||||
break;
|
||||
case 'uninstall':
|
||||
if (!addonName) { layer.msg('缺少插件标识', { icon: 2 }); break; }
|
||||
active.uninstall(addonName);
|
||||
@@ -146,22 +175,69 @@ layui.define(['http', 'setter', 'auth', 'upload', 'form', 'layer', 'table'], fun
|
||||
|
||||
// 顶部功能按钮 + 行内操作共用
|
||||
var active = {
|
||||
// 一键升级到市场最新版(version 为空时由服务端取最新版)
|
||||
upgrade: function (name, version) {
|
||||
var loadIdx = layer.load();
|
||||
// 升级含下载+备份+覆盖,放宽超时到 10 分钟,避免半途被中断
|
||||
http.post('upgrade', { name: name, version: version }, { timeout: 600000 })
|
||||
.then(function (res) {
|
||||
layer.close(loadIdx);
|
||||
if (res.code === 0) {
|
||||
layer.msg(res.message || '升级成功', { icon: 1 });
|
||||
clearMenuCache(); // 插件菜单可能变更
|
||||
table.reload('dataTable');
|
||||
} else if (!showDependencyProblems(res)) {
|
||||
layer.msg(res.message || '升级失败', { icon: 2 });
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
layer.close(loadIdx);
|
||||
layer.msg('请求失败', { icon: 2 });
|
||||
});
|
||||
},
|
||||
uninstall: function (name) {
|
||||
layer.prompt({
|
||||
formType: 1
|
||||
, title: '敏感操作,请验证口令'
|
||||
}, function (value, index) {
|
||||
layer.close(index);
|
||||
http.delete('uninstall', { name: name })
|
||||
.then((res) => {
|
||||
if (res.code === 0) {
|
||||
layer.close(index);
|
||||
clearMenuCache(); // 插件菜单已删除,清掉侧栏菜单缓存
|
||||
location.reload(); // 重新加载整页,侧栏立即刷新
|
||||
} else {
|
||||
layer.msg(res.msg || '操作失败');
|
||||
}
|
||||
});
|
||||
// 卸载选项:是否备份数据 / 是否删除数据表(对应 AddonService::uninstall() 的 options)
|
||||
layer.open({
|
||||
type: 1,
|
||||
title: '卸载插件:' + name,
|
||||
area: ['460px', 'auto'],
|
||||
content: '<div style="padding:16px 18px;">'
|
||||
+ '<label style="display:block;margin-bottom:10px;">'
|
||||
+ '<input type="checkbox" id="unin_backup" checked> 卸载前备份数据(SQL 落到 runtime/addon/backup/)'
|
||||
+ '</label>'
|
||||
+ '<label style="display:block;">'
|
||||
+ '<input type="checkbox" id="unin_delete" checked> 同时 DROP 插件数据表(不勾选则保留数据)'
|
||||
+ '</label>'
|
||||
+ '<div class="layui-word-aux" style="margin-top:10px;">'
|
||||
+ '提示:删除数据表后数据不可恢复,建议保持「备份数据」勾选。</div>'
|
||||
+ '</div>',
|
||||
btn: ['下一步', '取消'],
|
||||
yes: function (index) {
|
||||
var opts = {
|
||||
backup_data: document.getElementById('unin_backup').checked ? 1 : 0,
|
||||
delete_data: document.getElementById('unin_delete').checked ? 1 : 0
|
||||
};
|
||||
layer.close(index);
|
||||
layer.prompt({
|
||||
formType: 1
|
||||
, title: '敏感操作,请验证口令'
|
||||
}, function (value, index2) {
|
||||
layer.close(index2);
|
||||
http.delete('uninstall', {
|
||||
name: name,
|
||||
backup_data: opts.backup_data,
|
||||
delete_data: opts.delete_data
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.code === 0) {
|
||||
clearMenuCache(); // 插件菜单已删除,清掉侧栏菜单缓存
|
||||
location.reload(); // 重新加载整页,侧栏立即刷新
|
||||
} else {
|
||||
layer.msg(res.msg || '操作失败');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
batchDel: function () { // 批量卸载
|
||||
@@ -206,8 +282,8 @@ layui.define(['http', 'setter', 'auth', 'upload', 'form', 'layer', 'table'], fun
|
||||
layer.msg('插件安装成功!', { icon: 1 });
|
||||
clearMenuCache(); // 插件菜单已变化,清掉侧栏菜单缓存
|
||||
location.reload(); // 重新加载整页,侧栏立即刷新
|
||||
} else {
|
||||
layer.msg('安装失败: ' + res.message, { icon: 2 });
|
||||
} else if (!showDependencyProblems(res)) {
|
||||
layer.msg('安装失败: ' + (res.message || '未知错误'), { icon: 2 });
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
|
||||
@@ -17,8 +17,7 @@ layui.define(['http', 'layer', 'laydate', 'util', 'auth'], function (exports) {
|
||||
, url: "index" // 后端数据接口
|
||||
, page: true
|
||||
, limit: 15
|
||||
//, toolbar: '#tableBar'
|
||||
// , defaultToolbar: auth.renderToolbar(tableActions)
|
||||
, toolbar: '#tableBar'
|
||||
, cols: [[
|
||||
{ type: 'checkbox', fixed: 'left' }
|
||||
, { field: 'id', title: 'ID', width: 72, align: 'center', sort: true, fixed: 'left' }
|
||||
@@ -34,12 +33,7 @@ layui.define(['http', 'layer', 'laydate', 'util', 'auth'], function (exports) {
|
||||
]],
|
||||
done: function (res, curr, count) {
|
||||
// 行内按钮按真实权限显隐(data-perm 为完整权限 key,如 admin:edit)
|
||||
$('#dataTable').next('.layui-table-view').find('[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) {
|
||||
$(this).remove();
|
||||
}
|
||||
});
|
||||
auth.filterDom($('#dataTable').next('.layui-table-view'));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -57,16 +51,20 @@ layui.define(['http', 'layer', 'laydate', 'util', 'auth'], function (exports) {
|
||||
var id = this.value;
|
||||
var status = obj.elem.checked ? 1 : 0;
|
||||
layer.confirm('确定要' + (status ? '启用' : '禁用') + '该管理员账号吗?', function (index) {
|
||||
layui.request.post('update', { id: id, status: status }
|
||||
http.post('update', { id: id, status: status }
|
||||
).then(function (res) {
|
||||
if (res.code === 0) {
|
||||
if (res && res.code === 0) {
|
||||
layer.msg(res.msg, { icon: 1 });
|
||||
table.reload('dataTable', {}, true);
|
||||
} else {
|
||||
layer.msg(res.msg, { icon: 2 });
|
||||
layer.msg((res && res.msg) || '操作失败', { icon: 2 });
|
||||
obj.elem.checked = !obj.elem.checked;
|
||||
form.render('checkbox');
|
||||
}
|
||||
}).catch(function (err) {
|
||||
layer.msg('请求失败:' + (err && err.message ? err.message : '网络异常'), { icon: 2 });
|
||||
obj.elem.checked = !obj.elem.checked;
|
||||
form.render('checkbox');
|
||||
});
|
||||
layer.close(index);
|
||||
}, function () {
|
||||
@@ -280,23 +278,17 @@ layui.define(['http', 'layer', 'laydate', 'util', 'auth'], function (exports) {
|
||||
// 提交表单
|
||||
form.on('submit(adminFormSubmit)', function (formData) {
|
||||
var url = data ? "update" : "create";
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'POST',
|
||||
data: formData.field,
|
||||
success: function (res) {
|
||||
if (res.code === 1) {
|
||||
layer.msg(res.msg, { icon: 1, time: 1000 }, function () {
|
||||
layer.close(index);
|
||||
adminTable.reload();
|
||||
});
|
||||
} else {
|
||||
layer.msg(res.msg, { icon: 2 });
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
layer.msg('操作失败', { icon: 2 });
|
||||
http.post(url, formData.field).then(function (res) {
|
||||
if (res && res.code === 1) {
|
||||
layer.msg(res.msg, { icon: 1, time: 1000 }, function () {
|
||||
layer.close(index);
|
||||
adminTable.reload();
|
||||
});
|
||||
} else {
|
||||
layer.msg((res && res.msg) || '操作失败', { icon: 2 });
|
||||
}
|
||||
}).catch(function (err) {
|
||||
layer.msg('请求失败:' + (err && err.message ? err.message : '网络异常'), { icon: 2 });
|
||||
});
|
||||
|
||||
return false;
|
||||
@@ -318,21 +310,16 @@ layui.define(['http', 'layer', 'laydate', 'util', 'auth'], function (exports) {
|
||||
btn: ['确定', '取消'],
|
||||
title: '重置密码'
|
||||
}, function (index) {
|
||||
$.ajax({
|
||||
url: "resetPassword",
|
||||
type: 'POST',
|
||||
data: { id: data.id },
|
||||
success: function (res) {
|
||||
layer.close(index);
|
||||
if (res.code === 1) {
|
||||
layer.msg(res.msg, { icon: 1 });
|
||||
} else {
|
||||
layer.msg(res.msg, { icon: 2 });
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
layer.msg('操作失败', { icon: 2 });
|
||||
http.post("resetPassword", { id: data.id }).then(function (res) {
|
||||
layer.close(index);
|
||||
if (res && res.code === 1) {
|
||||
layer.msg(res.msg, { icon: 1 });
|
||||
} else {
|
||||
layer.msg((res && res.msg) || '操作失败', { icon: 2 });
|
||||
}
|
||||
}).catch(function (err) {
|
||||
layer.close(index);
|
||||
layer.msg('请求失败:' + (err && err.message ? err.message : '网络异常'), { icon: 2 });
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -344,23 +331,18 @@ layui.define(['http', 'layer', 'laydate', 'util', 'auth'], function (exports) {
|
||||
title: '删除管理员',
|
||||
icon: 3
|
||||
}, function (index) {
|
||||
$.ajax({
|
||||
url: "delete",
|
||||
type: 'POST',
|
||||
data: { id: data.id },
|
||||
success: function (res) {
|
||||
layer.close(index);
|
||||
if (res.code === 1) {
|
||||
layer.msg(res.msg, { icon: 1, time: 1000 }, function () {
|
||||
adminTable.reload();
|
||||
});
|
||||
} else {
|
||||
layer.msg(res.msg, { icon: 2 });
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
layer.msg('操作失败', { icon: 2 });
|
||||
http.post("delete", { id: data.id }).then(function (res) {
|
||||
layer.close(index);
|
||||
if (res && res.code === 1) {
|
||||
layer.msg(res.msg, { icon: 1, time: 1000 }, function () {
|
||||
adminTable.reload();
|
||||
});
|
||||
} else {
|
||||
layer.msg((res && res.msg) || '操作失败', { icon: 2 });
|
||||
}
|
||||
}).catch(function (err) {
|
||||
layer.close(index);
|
||||
layer.msg('请求失败:' + (err && err.message ? err.message : '网络异常'), { icon: 2 });
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -386,23 +368,18 @@ layui.define(['http', 'layer', 'laydate', 'util', 'auth'], function (exports) {
|
||||
title: '批量删除',
|
||||
icon: 3
|
||||
}, function (index) {
|
||||
$.ajax({
|
||||
url: "batchDelete",
|
||||
type: 'POST',
|
||||
data: { ids: ids },
|
||||
success: function (res) {
|
||||
layer.close(index);
|
||||
if (res.code === 1) {
|
||||
layer.msg(res.msg, { icon: 1, time: 1000 }, function () {
|
||||
adminTable.reload();
|
||||
});
|
||||
} else {
|
||||
layer.msg(res.msg, { icon: 2 });
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
layer.msg('操作失败', { icon: 2 });
|
||||
http.post("batchDelete", { ids: ids }).then(function (res) {
|
||||
layer.close(index);
|
||||
if (res && res.code === 1) {
|
||||
layer.msg(res.msg, { icon: 1, time: 1000 }, function () {
|
||||
adminTable.reload();
|
||||
});
|
||||
} else {
|
||||
layer.msg((res && res.msg) || '操作失败', { icon: 2 });
|
||||
}
|
||||
}).catch(function (err) {
|
||||
layer.close(index);
|
||||
layer.msg('请求失败:' + (err && err.message ? err.message : '网络异常'), { icon: 2 });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* 操作日志(backend_log)
|
||||
*
|
||||
* 权限按钮用完整权限名,表格渲染后由 auth.filterDom() 移除无权限按钮。
|
||||
*/
|
||||
layui.define(['table', 'layer', 'form', 'laydate', 'auth', 'http', 'jquery'], function (exports) {
|
||||
var table = layui.table,
|
||||
layer = layui.layer,
|
||||
form = layui.form,
|
||||
laydate = layui.laydate,
|
||||
auth = layui.auth,
|
||||
http = layui.http,
|
||||
$ = layui.jquery;
|
||||
|
||||
// 注意:http 的 baseURL 为空,URL 由浏览器按「当前页面所在目录」解析,
|
||||
// 核心后台页面的目录就是 /admin/<controller>/,所以这里只写动作名(纯相对),
|
||||
// 写成 'adminlog/index' 会被解析成 /admin/adminlog/adminlog/index(404)。
|
||||
var FUNC = {
|
||||
index: 'index',
|
||||
delete: 'delete',
|
||||
clean: 'clean'
|
||||
};
|
||||
|
||||
function parse(res) {
|
||||
return {
|
||||
code: res.code,
|
||||
msg: res.message,
|
||||
count: res.count || 0,
|
||||
data: res.data || []
|
||||
};
|
||||
}
|
||||
|
||||
function post(url, data, done) {
|
||||
var loading = layer.load(2);
|
||||
http.post(url, data).then(function (res) {
|
||||
layer.close(loading);
|
||||
if (res && res.code === 0) {
|
||||
layer.msg(res.message || '操作成功', {icon: 1});
|
||||
if (done) { done(res.data); }
|
||||
} else {
|
||||
layer.msg((res && res.message) || '操作失败', {icon: 2});
|
||||
}
|
||||
}).catch(function (err) {
|
||||
layer.close(loading);
|
||||
layer.msg('请求失败:' + (err && err.message ? err.message : '网络异常'), {icon: 2});
|
||||
});
|
||||
}
|
||||
|
||||
function checkedIds() {
|
||||
var rows = table.checkStatus('dataTable').data || [];
|
||||
var ids = [];
|
||||
for (var i = 0; i < rows.length; i++) { ids.push(rows[i].id); }
|
||||
return ids;
|
||||
}
|
||||
|
||||
var obj = {
|
||||
render: function () {
|
||||
table.render({
|
||||
elem: '#dataTable',
|
||||
url: FUNC.index,
|
||||
where: {},
|
||||
toolbar: '#tableBar',
|
||||
defaultToolbar: ['filter'],
|
||||
method: 'get',
|
||||
cols: [[
|
||||
{type: 'checkbox', width: 50},
|
||||
{field: 'id', title: 'ID', width: 80, sort: true},
|
||||
{field: 'operator', title: '操作人', width: 130},
|
||||
{field: 'action', title: '动作', width: 150},
|
||||
{field: 'ip', title: 'IP', width: 130},
|
||||
{field: 'remark', title: '备注', minWidth: 240},
|
||||
{field: 'create_at_text', title: '时间', width: 170, sort: true},
|
||||
{title: '操作', width: 90, toolbar: '#dataBar', fixed: 'right'}
|
||||
]],
|
||||
page: true,
|
||||
limit: 20,
|
||||
limits: [20, 50, 100, 200],
|
||||
response: {statusCode: 0},
|
||||
parseData: parse,
|
||||
done: function () {
|
||||
auth.filterDom($('#dataTable').next('.layui-table-view'));
|
||||
auth.filterDom('.layui-btn[data-perm]');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
events: function () {
|
||||
laydate.render({elem: '#start', type: 'date'});
|
||||
laydate.render({elem: '#end', type: 'date'});
|
||||
|
||||
table.on('toolbar(dataTable)', function (o) {
|
||||
if (o.event === 'deleteSelected') {
|
||||
var ids = checkedIds();
|
||||
if (ids.length === 0) {
|
||||
return layer.msg('请先勾选要删除的日志', {icon: 2});
|
||||
}
|
||||
layer.confirm('确定删除选中的 ' + ids.length + ' 条日志?', {title: '提示'}, function (index) {
|
||||
layer.close(index);
|
||||
post(FUNC.delete, {ids: ids}, function () {
|
||||
table.reload('dataTable');
|
||||
});
|
||||
});
|
||||
} else if (o.event === 'clean') {
|
||||
layer.prompt({
|
||||
title: '保留最近多少天的日志?',
|
||||
value: 30,
|
||||
formType: 0
|
||||
}, function (days, index) {
|
||||
days = parseInt(days, 10);
|
||||
if (isNaN(days) || days < 0 || days > 3650) {
|
||||
return layer.msg('请输入 0 ~ 3650 之间的天数(0=清空)', {icon: 2});
|
||||
}
|
||||
layer.close(index);
|
||||
layer.confirm(
|
||||
days > 0 ? ('确定清理 ' + days + ' 天前的日志?') : '确定清空全部日志?此操作不可恢复',
|
||||
{title: '危险操作'},
|
||||
function (idx) {
|
||||
layer.close(idx);
|
||||
post(FUNC.clean, {days: days}, function () {
|
||||
table.reload('dataTable');
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
table.on('tool(dataTable)', function (o) {
|
||||
if (o.event === 'delete') {
|
||||
layer.confirm('确定删除该条日志?', {title: '提示'}, function (index) {
|
||||
layer.close(index);
|
||||
post(FUNC.delete, {ids: [o.data.id]}, function () {
|
||||
table.reload('dataTable');
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
form.on('submit(data-search-btn)', function (o) {
|
||||
table.reload('dataTable', {
|
||||
where: {
|
||||
keyword: o.field.keyword,
|
||||
action: o.field.action,
|
||||
start: o.field.start,
|
||||
end: o.field.end
|
||||
},
|
||||
page: {curr: 1}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
$('#btn-reset').on('click', function () {
|
||||
setTimeout(function () {
|
||||
table.reload('dataTable', {
|
||||
where: {keyword: '', action: '', start: '', end: ''},
|
||||
page: {curr: 1}
|
||||
});
|
||||
}, 10);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
obj.render();
|
||||
obj.events();
|
||||
|
||||
exports('adminlog', obj);
|
||||
});
|
||||
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* 附件统一管理(common_attachment)
|
||||
*
|
||||
* 删除走后台 Attachment::delete():先经 FileStorageService 触发 FileDelete 事件
|
||||
* (OSS / FTP 等自定义存储在此接管物理删除),再软删记录。
|
||||
*/
|
||||
layui.define(['table', 'layer', 'form', 'auth', 'http', 'jquery'], function (exports) {
|
||||
var table = layui.table,
|
||||
layer = layui.layer,
|
||||
form = layui.form,
|
||||
auth = layui.auth,
|
||||
http = layui.http,
|
||||
$ = layui.jquery;
|
||||
|
||||
// URL 由浏览器按当前页面目录解析(/admin/attachment/),只写动作名
|
||||
var FUNC = {
|
||||
index: 'index',
|
||||
delete: 'delete'
|
||||
};
|
||||
|
||||
function parse(res) {
|
||||
return {
|
||||
code: res.code,
|
||||
msg: res.message,
|
||||
count: res.count || 0,
|
||||
data: res.data || []
|
||||
};
|
||||
}
|
||||
|
||||
function post(url, data, done) {
|
||||
var loading = layer.load(2);
|
||||
http.post(url, data).then(function (res) {
|
||||
layer.close(loading);
|
||||
if (res && res.code === 0) {
|
||||
layer.msg(res.message || '操作成功', {icon: 1});
|
||||
if (done) { done(res.data); }
|
||||
} else {
|
||||
layer.msg((res && res.message) || '操作失败', {icon: 2});
|
||||
}
|
||||
}).catch(function (err) {
|
||||
layer.close(loading);
|
||||
layer.msg('请求失败:' + (err && err.message ? err.message : '网络异常'), {icon: 2});
|
||||
});
|
||||
}
|
||||
|
||||
function checkedIds() {
|
||||
var rows = table.checkStatus('dataTable').data || [];
|
||||
var ids = [];
|
||||
for (var i = 0; i < rows.length; i++) { ids.push(rows[i].id); }
|
||||
return ids;
|
||||
}
|
||||
|
||||
var obj = {
|
||||
render: function () {
|
||||
table.render({
|
||||
elem: '#dataTable',
|
||||
url: FUNC.index,
|
||||
where: {},
|
||||
toolbar: '#tableBar',
|
||||
defaultToolbar: ['filter'],
|
||||
method: 'get',
|
||||
cols: [[
|
||||
{type: 'checkbox', width: 50},
|
||||
{field: 'id', title: 'ID', width: 70, sort: true},
|
||||
{title: '预览', width: 70, templet: '#thumbTpl', align: 'center'},
|
||||
{field: 'original', title: '文件名', minWidth: 220},
|
||||
{field: 'module', title: '模块', width: 90},
|
||||
{field: 'size_text', title: '大小', width: 100},
|
||||
{field: 'storage', title: '存储', width: 90},
|
||||
{field: 'uid', title: '上传者', width: 90},
|
||||
{field: 'create_at_text', title: '时间', width: 170, sort: true},
|
||||
{title: '操作', width: 130, toolbar: '#dataBar', fixed: 'right'}
|
||||
]],
|
||||
page: true,
|
||||
limit: 20,
|
||||
limits: [20, 50, 100],
|
||||
response: {statusCode: 0},
|
||||
parseData: parse,
|
||||
done: function () {
|
||||
auth.filterDom($('#dataTable').next('.layui-table-view'));
|
||||
auth.filterDom('.layui-btn[data-perm]');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
events: function () {
|
||||
table.on('toolbar(dataTable)', function (o) {
|
||||
if (o.event === 'deleteSelected') {
|
||||
var ids = checkedIds();
|
||||
if (ids.length === 0) {
|
||||
return layer.msg('请先勾选要删除的附件', {icon: 2});
|
||||
}
|
||||
layer.confirm(
|
||||
'确定删除选中的 ' + ids.length + ' 个附件?<br><span style="color:#ff5722">会同时删除存储上的文件</span>',
|
||||
{title: '提示'},
|
||||
function (index) {
|
||||
layer.close(index);
|
||||
post(FUNC.delete, {ids: ids}, function () {
|
||||
table.reload('dataTable');
|
||||
setTimeout(function () { location.reload(); }, 800);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
table.on('tool(dataTable)', function (o) {
|
||||
if (o.event === 'open') {
|
||||
window.open(o.data.url, '_blank');
|
||||
} else if (o.event === 'delete') {
|
||||
layer.confirm('确定删除该附件?会同时删除存储上的文件', {title: '提示'}, function (index) {
|
||||
layer.close(index);
|
||||
post(FUNC.delete, {ids: [o.data.id]}, function () {
|
||||
table.reload('dataTable');
|
||||
setTimeout(function () { location.reload(); }, 800);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
form.on('submit(data-search-btn)', function (o) {
|
||||
table.reload('dataTable', {
|
||||
where: {
|
||||
keyword: o.field.keyword,
|
||||
module: o.field.module,
|
||||
only_image: o.field.only_image ? 1 : 0
|
||||
},
|
||||
page: {curr: 1}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
$('#btn-reset').on('click', function () {
|
||||
setTimeout(function () {
|
||||
table.reload('dataTable', {
|
||||
where: {keyword: '', module: '', only_image: 0},
|
||||
page: {curr: 1}
|
||||
});
|
||||
}, 10);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
obj.render();
|
||||
obj.events();
|
||||
|
||||
exports('attachment', obj);
|
||||
});
|
||||
@@ -35,14 +35,8 @@ layui.define(['form', 'table', 'http', 'auth'], function (exports) {
|
||||
skin: 'line',
|
||||
done: function () {
|
||||
var view = $('#dataTable').next('.layui-table-view');
|
||||
view.find('[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
$('.layui-btn[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
auth.filterDom(view);
|
||||
auth.filterDom('.layui-btn[data-perm]');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -8,11 +8,12 @@ layui.define(['http', 'auth', 'table', 'layer'], function (exports) {
|
||||
auth.setController('articles:backend:category');
|
||||
auth.render();
|
||||
|
||||
var MODULE = 'articles/backend.category',
|
||||
FUNC = {
|
||||
index: MODULE + '/index',
|
||||
delete: MODULE + '/delete'
|
||||
};
|
||||
// URL 由浏览器按当前页面所在目录解析(页面即 /articles/backend/category/index.html),
|
||||
// 所以只写动作名;带前缀会拼成 .../category/articles/backend.category/index(404)。
|
||||
var FUNC = {
|
||||
index: 'index',
|
||||
delete: 'delete'
|
||||
};
|
||||
|
||||
var obj = {
|
||||
init: function () {
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
/**
|
||||
* console - homepage demo
|
||||
* 控制台首页
|
||||
*
|
||||
* 分工:
|
||||
* - 后端渲染:系统概览 card、维护按钮、框架升级按钮、统计 card、最新会员 / 最近操作日志表格
|
||||
* (数据来自 Console::collectStats() 与 SystemInfo::collect())
|
||||
* - 前端只做两件必须异步的事:近 7 天趋势图(ECharts)、两个 flush 类接口的按钮事件
|
||||
*
|
||||
* 原 layuiAdmin 模板自带的演示数据已全部清掉:球员数据雷达图、中国地图、
|
||||
* 假留言列表、假活跃用户、假地区分布、模拟接口的「项目进展」表格——
|
||||
* 这些户型要么无数据源,要么与真数据权限重复(运行环境表已覆盖)。
|
||||
*/
|
||||
|
||||
|
||||
layui.define('ywxapp',function (exports) {
|
||||
layui.define('ywxapp', function (exports) {
|
||||
var ywxapp = layui.ywxapp;
|
||||
|
||||
//区块轮播切换
|
||||
layui.use(['ywxapp', 'carousel'], function () {
|
||||
var $ = layui.$
|
||||
@@ -31,265 +39,230 @@ layui.define('ywxapp',function (exports) {
|
||||
|
||||
});
|
||||
|
||||
//八卦新闻
|
||||
// 近 7 天趋势:会员注册 / 后台操作(真实数据)
|
||||
//
|
||||
// 数据由后端渲染进 layui.stats(见 console/index.html 的 json_encode),
|
||||
// 这里直接取,不再额外发请求——首页是每人的第一屏,少一个请求少一处等待。
|
||||
layui.use(['carousel', 'echarts'], function () {
|
||||
var $ = layui.$
|
||||
, carousel = layui.carousel
|
||||
, echarts = layui.echarts;
|
||||
|
||||
var echartsApp = [], options = [
|
||||
{
|
||||
title: {
|
||||
subtext: '完全实况球员数据',
|
||||
textStyle: {
|
||||
fontSize: 14
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
legend: {
|
||||
x: 'left',
|
||||
data: ['罗纳尔多', '舍普琴科']
|
||||
},
|
||||
polar: [
|
||||
{
|
||||
indicator: [
|
||||
{ text: '进攻', max: 100 },
|
||||
{ text: '防守', max: 100 },
|
||||
{ text: '体能', max: 100 },
|
||||
{ text: '速度', max: 100 },
|
||||
{ text: '力量', max: 100 },
|
||||
{ text: '技巧', max: 100 }
|
||||
],
|
||||
radius: 130
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
type: 'radar',
|
||||
center: ['50%', '50%'],
|
||||
itemStyle: {
|
||||
normal: {
|
||||
areaStyle: {
|
||||
type: 'default'
|
||||
}
|
||||
}
|
||||
},
|
||||
data: [
|
||||
{ value: [97, 42, 88, 94, 90, 86], name: '舍普琴科' },
|
||||
{ value: [97, 32, 74, 95, 88, 92], name: '罗纳尔多' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
, elemDataView = $('#LAY-index-pageone').children('div')
|
||||
, renderDataView = function (index) {
|
||||
echartsApp[index] = echarts.init(elemDataView[index], layui.echartsTheme);
|
||||
echartsApp[index].setOption(options[index]);
|
||||
window.onresize = echartsApp[index].resize;
|
||||
};
|
||||
//没找到DOM,终止执行
|
||||
if (!elemDataView[0]) return;
|
||||
var stats = layui.stats || {};
|
||||
var series = stats.series || {};
|
||||
|
||||
renderDataView(0);
|
||||
});
|
||||
|
||||
//访问量
|
||||
layui.use(['carousel', 'echarts'], function () {
|
||||
var $ = layui.$
|
||||
, carousel = layui.carousel
|
||||
, echarts = layui.echarts;
|
||||
|
||||
var echartsApp = [], options = [
|
||||
{
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
calculable: true,
|
||||
legend: {
|
||||
data: ['访问量', '下载量', '平均访问量']
|
||||
},
|
||||
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
name: '访问量',
|
||||
axisLabel: {
|
||||
formatter: '{value} 万'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
name: '下载量',
|
||||
axisLabel: {
|
||||
formatter: '{value} 万'
|
||||
}
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: '访问量',
|
||||
type: 'line',
|
||||
data: [900, 850, 950, 1000, 1100, 1050, 1000, 1150, 1250, 1370, 1250, 1100]
|
||||
},
|
||||
{
|
||||
name: '下载量',
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
data: [850, 850, 800, 950, 1000, 950, 950, 1150, 1100, 1240, 1000, 950]
|
||||
},
|
||||
{
|
||||
name: '平均访问量',
|
||||
type: 'line',
|
||||
data: [870, 850, 850, 950, 1050, 1000, 980, 1150, 1000, 1300, 1150, 1000]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
, elemDataView = $('#LAY-index-pagetwo').children('div')
|
||||
, renderDataView = function (index) {
|
||||
echartsApp[index] = echarts.init(elemDataView[index], layui.echartsTheme);
|
||||
echartsApp[index].setOption(options[index]);
|
||||
window.onresize = echartsApp[index].resize;
|
||||
};
|
||||
//没找到DOM,终止执行
|
||||
if (!elemDataView[0]) return;
|
||||
renderDataView(0);
|
||||
|
||||
});
|
||||
|
||||
//地图
|
||||
layui.use(['carousel', 'echarts'], function () {
|
||||
var $ = layui.$
|
||||
, carousel = layui.carousel
|
||||
, echarts = layui.echarts;
|
||||
|
||||
var echartsApp = [], options = [
|
||||
{
|
||||
title: {
|
||||
text: '访客地区分布',
|
||||
subtext: '不完全统计'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
dataRange: {
|
||||
orient: 'horizontal',
|
||||
min: 0,
|
||||
max: 60000,
|
||||
text: ['高', '低'],
|
||||
splitNumber: 0
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '访客地区分布',
|
||||
type: 'map',
|
||||
mapType: 'china',
|
||||
selectedMode: 'multiple',
|
||||
itemStyle: {
|
||||
normal: { label: { show: true } },
|
||||
emphasis: { label: { show: true } }
|
||||
},
|
||||
data: [
|
||||
{ name: '西藏', value: 60 },
|
||||
{ name: '青海', value: 167 },
|
||||
{ name: '宁夏', value: 210 },
|
||||
{ name: '海南', value: 252 },
|
||||
{ name: '甘肃', value: 502 },
|
||||
{ name: '贵州', value: 570 },
|
||||
{ name: '新疆', value: 661 },
|
||||
{ name: '云南', value: 8890 },
|
||||
{ name: '重庆', value: 10010 },
|
||||
{ name: '吉林', value: 5056 },
|
||||
{ name: '山西', value: 2123 },
|
||||
{ name: '天津', value: 9130 },
|
||||
{ name: '江西', value: 10170 },
|
||||
{ name: '广西', value: 6172 },
|
||||
{ name: '陕西', value: 9251 },
|
||||
{ name: '黑龙江', value: 5125 },
|
||||
{ name: '内蒙古', value: 1435 },
|
||||
{ name: '安徽', value: 9530 },
|
||||
{ name: '北京', value: 51919 },
|
||||
{ name: '福建', value: 3756 },
|
||||
{ name: '上海', value: 59190 },
|
||||
{ name: '湖北', value: 37109 },
|
||||
{ name: '湖南', value: 8966 },
|
||||
{ name: '四川', value: 31020 },
|
||||
{ name: '辽宁', value: 7222 },
|
||||
{ name: '河北', value: 3451 },
|
||||
{ name: '河南', value: 9693 },
|
||||
{ name: '浙江', value: 62310 },
|
||||
{ name: '山东', value: 39231 },
|
||||
{ name: '江苏', value: 35911 },
|
||||
{ name: '广东', value: 55891 }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
, elemDataView = $('#LAY-index-pagethree').children('div')
|
||||
, renderDataView = function (index) {
|
||||
echartsApp[index] = echarts.init(elemDataView[index], layui.echartsTheme);
|
||||
echartsApp[index].setOption(options[index]);
|
||||
window.onresize = echartsApp[index].resize;
|
||||
};
|
||||
//没找到DOM,终止执行
|
||||
if (!elemDataView[0]) return;
|
||||
|
||||
renderDataView(0);
|
||||
});
|
||||
|
||||
//项目进展
|
||||
layui.use('table', function () {
|
||||
var $ = layui.$
|
||||
, table = layui.table;
|
||||
|
||||
table.render({
|
||||
elem: '#LAY-index-prograss'
|
||||
, url: layui.setter.paths.base + 'json/console/prograss.js' //模拟接口
|
||||
, cols: [[
|
||||
{ type: 'checkbox', fixed: 'left' }
|
||||
, { field: 'prograss', title: '任务' }
|
||||
, { field: 'time', title: '所需时间' }
|
||||
, {
|
||||
field: 'complete', title: '完成情况'
|
||||
, templet: function (d) {
|
||||
if (d.complete == '已完成') {
|
||||
return '<del style="color: #16b777;">' + d.complete + '</del>'
|
||||
} else if (d.complete == '进行中') {
|
||||
return '<span style="color: #FFB800;">' + d.complete + '</span>'
|
||||
} else {
|
||||
return '<span style="color: #FF5722;">' + d.complete + '</span>'
|
||||
}
|
||||
}
|
||||
var option = {
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
legend: {
|
||||
data: ['会员注册', '后台操作']
|
||||
},
|
||||
grid: {
|
||||
left: '3%'
|
||||
, right: '4%'
|
||||
, bottom: '3%'
|
||||
, containLabel: true
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category'
|
||||
, boundaryGap: false
|
||||
, data: stats.dates || []
|
||||
}
|
||||
]]
|
||||
, skin: 'line'
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value'
|
||||
// 都是条数,不该出现 0.5 这种刻度
|
||||
, minInterval: 1
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: '会员注册'
|
||||
, type: 'line'
|
||||
, smooth: true
|
||||
, areaStyle: {}
|
||||
, data: series.member || []
|
||||
},
|
||||
{
|
||||
name: '后台操作'
|
||||
, type: 'line'
|
||||
, smooth: true
|
||||
, areaStyle: {}
|
||||
, data: series.log || []
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var elemDataView = $('#LAY-index-trend').children('div');
|
||||
|
||||
//没找到DOM,终止执行
|
||||
if (!elemDataView[0]) return;
|
||||
|
||||
var chart = echarts.init(elemDataView[0], layui.echartsTheme);
|
||||
chart.setOption(option);
|
||||
window.onresize = chart.resize;
|
||||
});
|
||||
|
||||
// 清理缓存(控制台顶部「维护」卡片)
|
||||
//
|
||||
// 接口:Console::clean(POST /admin/console/clean)
|
||||
// 权限:console:clean,无权限时 auth.filterDom() 直接把按钮摘掉。
|
||||
// 「系统信息」页与 System 控制器已下线,清理能力收进 Console(本页),
|
||||
// 按钮 id 保持 sys-* 不变,只换 URL 与权限名。
|
||||
layui.use(['layer', 'element', 'auth', 'http', 'jquery'], function () {
|
||||
var $ = layui.$
|
||||
, layer = layui.layer
|
||||
, http = layui.http
|
||||
, auth = layui.auth;
|
||||
|
||||
auth.filterDom('.layui-btn[data-perm]');
|
||||
|
||||
function clean(type, tip) {
|
||||
layer.confirm(tip, {title: '提示'}, function (index) {
|
||||
layer.close(index);
|
||||
var loading = layer.load(2);
|
||||
http.post('/admin/console/clean', {type: type}).then(function (res) {
|
||||
layer.close(loading);
|
||||
if (res && res.code === 0) {
|
||||
layer.msg(res.message || '清理完成', {icon: 1});
|
||||
setTimeout(function () { location.reload(); }, 800);
|
||||
} else {
|
||||
layer.msg((res && res.message) || '清理失败', {icon: 2});
|
||||
}
|
||||
}).catch(function (err) {
|
||||
layer.close(loading);
|
||||
layer.msg('请求失败:' + (err && err.message ? err.message : '网络异常'), {icon: 2});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$('#sys-clean-cache').on('click', function () {
|
||||
clean('cache', '确定清理数据缓存(runtime/cache)?');
|
||||
});
|
||||
$('#sys-clean-temp').on('click', function () {
|
||||
clean('temp', '确定清理模板编译缓存(runtime/temp)?');
|
||||
});
|
||||
$('#sys-clean-all').on('click', function () {
|
||||
clean('all', '确定全量清理(数据缓存 + 模板缓存 + 路由缓存)?');
|
||||
});
|
||||
});
|
||||
|
||||
console.log('进入控制台',layui.ywxapp);
|
||||
//回复留言
|
||||
ywxapp.events.replyNote = function (othis) {
|
||||
var nid = othis.data('id');
|
||||
layer.prompt({
|
||||
title: '回复留言 ID:' + nid
|
||||
, formType: 2
|
||||
}, function (value, index) {
|
||||
//这里可以请求 Ajax
|
||||
//…
|
||||
layer.msg('得到:' + value);
|
||||
layer.close(index);
|
||||
// 框架升级(与「系统管理 → 框架升级」同源)
|
||||
//
|
||||
// 接口:/admin/framework/check(GET 检测)、/admin/framework/upgrade(POST 执行)
|
||||
// 权限:framework:check / framework:upgrade,无权限时 auth.filterDom() 移除按钮。
|
||||
// 本页目录是 /admin/console/,所以 URL 必须写绝对路径。
|
||||
layui.use(['layer', 'element', 'auth', 'http', 'jquery'], function () {
|
||||
var $ = layui.$
|
||||
, layer = layui.layer
|
||||
, http = layui.http
|
||||
, auth = layui.auth;
|
||||
|
||||
auth.filterDom('.layui-btn[data-perm]');
|
||||
|
||||
var pkgType = 'auto';
|
||||
|
||||
function setDisabled($el, dis) {
|
||||
if (dis) {
|
||||
$el.addClass('layui-btn-disabled').prop('disabled', true);
|
||||
} else {
|
||||
$el.removeClass('layui-btn-disabled').prop('disabled', false);
|
||||
}
|
||||
}
|
||||
|
||||
function status(html) {
|
||||
$('#fw-status').html(html);
|
||||
}
|
||||
|
||||
function loading(text) {
|
||||
status('<i class="layui-icon layui-icon-loading layui-anim layui-anim-rotate"></i> ' + text);
|
||||
}
|
||||
|
||||
$('#fw-pkg-group').on('click', '.fw-pkg', function () {
|
||||
if ($(this).prop('disabled')) return;
|
||||
pkgType = $(this).data('type');
|
||||
$('#fw-pkg-group .fw-pkg').removeClass('layui-btn-normal').addClass('layui-btn-primary');
|
||||
$(this).removeClass('layui-btn-primary').addClass('layui-btn-normal');
|
||||
});
|
||||
};
|
||||
|
||||
function check() {
|
||||
loading('正在检查...');
|
||||
// 控制台是首屏:检测限 8 秒,中心站不可达时不拖住整页
|
||||
http.get('/admin/framework/check', {}, {timeout: 8000}).then(function (res) {
|
||||
if (!res || res.code !== 0) {
|
||||
status('<span style="color:#dc2626;">' + ((res && res.message) || '检查失败') + '</span>');
|
||||
return;
|
||||
}
|
||||
var d = res.data;
|
||||
if (d.current) {
|
||||
$('#fw-current').text(d.current);
|
||||
}
|
||||
if (!d.has_update) {
|
||||
status('<span style="color:#16a34a;">✓ 已是最新版本(' + d.current + ')</span>');
|
||||
$('#fw-upgrade').hide();
|
||||
$('#fw-pkg-box').hide();
|
||||
return;
|
||||
}
|
||||
var html = '发现新版本:<b>' + d.latest + '</b>'
|
||||
+ ' <span class="layui-badge layui-bg-orange">自动模式将使用'
|
||||
+ (d.use_patch ? '增量补丁' : '整包') + '</span>';
|
||||
if (d.use_patch && d.patch_from) html += '(基于 ' + d.patch_from + ')';
|
||||
if (d.title) html += '(' + d.title + ')';
|
||||
if (d.error) html += '<br><span style="color:#dc2626;">' + d.error + '</span>';
|
||||
html += '<br>更新内容:<pre style="margin-top:6px;">' + (d.changelog || '') + '</pre>';
|
||||
status(html);
|
||||
|
||||
if (d.error) {
|
||||
$('#fw-upgrade').hide();
|
||||
$('#fw-pkg-box').hide();
|
||||
return;
|
||||
}
|
||||
|
||||
$('#fw-upgrade').show();
|
||||
$('#fw-pkg-box').show();
|
||||
pkgType = 'auto';
|
||||
$('#fw-pkg-group .fw-pkg').removeClass('layui-btn-normal').addClass('layui-btn-primary');
|
||||
$('#fw-pkg-group .fw-pkg[data-type=auto]').removeClass('layui-btn-primary').addClass('layui-btn-normal');
|
||||
setDisabled($('#fw-pkg-group .fw-pkg[data-type=full]'), !d.has_full);
|
||||
setDisabled($('#fw-pkg-group .fw-pkg[data-type=patch]'), !d.use_patch);
|
||||
var tips = [];
|
||||
if (!d.has_full) tips.push('当前无完整包可用');
|
||||
if (!d.use_patch) tips.push('当前版本(' + (d.current || '') + ')不支持增量小包,需基于 ' + (d.patch_from || '?') + ')');
|
||||
$('#fw-pkg-tip').text(tips.length ? '提示:' + tips.join(';') + ',建议选「自动」' : '可手动选择完整包或增量小包');
|
||||
}).catch(function (err) {
|
||||
status('<span style="color:#dc2626;">请求失败:' + (err && err.message ? err.message : '网络异常') + '</span>');
|
||||
});
|
||||
}
|
||||
|
||||
$('#fw-check').on('click', check);
|
||||
|
||||
$('#fw-upgrade').on('click', function () {
|
||||
var label = {auto: '自动模式', full: '完整包', patch: '增量小包'}[pkgType] || pkgType;
|
||||
layer.confirm('将以【' + label + '】升级,将备份当前核心并覆盖,确定继续?', {title: '提示'}, function (index) {
|
||||
layer.close(index);
|
||||
loading('正在下载并升级...');
|
||||
// 下载 + 备份 + 覆盖可能很久,放宽到 10 分钟,别在半途被超时打断
|
||||
http.post('/admin/framework/upgrade', {type: pkgType}, {timeout: 600000}).then(function (res) {
|
||||
if (res && res.code === 0) {
|
||||
status('<span style="color:#16a34a;">✓ ' + res.message + '</span>');
|
||||
$('#fw-upgrade').hide();
|
||||
$('#fw-pkg-box').hide();
|
||||
if (res.data && res.data.version) {
|
||||
$('#fw-current').text(res.data.version);
|
||||
}
|
||||
} else {
|
||||
status('<span style="color:#dc2626;">' + ((res && res.message) || '升级失败') + '</span>');
|
||||
}
|
||||
}).catch(function (err) {
|
||||
status('<span style="color:#dc2626;">请求失败:' + (err && err.message ? err.message : '网络异常') + '</span>');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
check();
|
||||
});
|
||||
|
||||
exports('console', {})
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* 数据库备份 / 还原
|
||||
*
|
||||
* 权限按钮用完整权限名(database:backup / database:restore / database:delete / database:optimize),
|
||||
* 表格渲染后由 auth.filterDom() 移除无权限按钮。
|
||||
*/
|
||||
layui.define(['table', 'layer', 'form', 'util', 'element', 'auth', 'http', 'jquery'], function (exports) {
|
||||
var table = layui.table,
|
||||
layer = layui.layer,
|
||||
form = layui.form,
|
||||
auth = layui.auth,
|
||||
http = layui.http,
|
||||
$ = layui.jquery;
|
||||
|
||||
// 注意:http 的 baseURL 为空,URL 由浏览器按「当前页面所在目录」解析,
|
||||
// 核心后台页面的目录就是 /admin/<controller>/,所以这里只写动作名(纯相对),
|
||||
// 写成 'database/index' 会被解析成 /admin/database/database/index(404)。
|
||||
var FUNC = {
|
||||
index: 'index',
|
||||
backup: 'backup',
|
||||
recovery: 'recovery',
|
||||
delete: 'delete',
|
||||
optimize: 'optimize',
|
||||
download: 'download'
|
||||
};
|
||||
|
||||
/**
|
||||
* 统一的响应处理:Result 返回 {code, message, data, count}
|
||||
*/
|
||||
function parse(res) {
|
||||
return {
|
||||
code: res.code,
|
||||
msg: res.message,
|
||||
count: res.count || 0,
|
||||
data: res.data || []
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交型请求
|
||||
*/
|
||||
function post(url, data, okTip, done) {
|
||||
var loading = layer.load(2);
|
||||
http.post(url, data).then(function (res) {
|
||||
layer.close(loading);
|
||||
if (res && res.code === 0) {
|
||||
layer.msg(okTip || res.message || '操作成功', {icon: 1});
|
||||
if (done) { done(res.data); }
|
||||
} else {
|
||||
layer.msg((res && res.message) || '操作失败', {icon: 2});
|
||||
}
|
||||
}).catch(function (err) {
|
||||
layer.close(loading);
|
||||
layer.msg('请求失败:' + (err && err.message ? err.message : '网络异常'), {icon: 2});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 取当前勾选的表名
|
||||
*/
|
||||
function checkedTables() {
|
||||
var rows = table.checkStatus('tableList').data || [];
|
||||
var names = [];
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
names.push(rows[i].name);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
var obj = {
|
||||
renderTable: function () {
|
||||
table.render({
|
||||
elem: '#tableList',
|
||||
url: FUNC.index,
|
||||
where: {type: 'table'},
|
||||
toolbar: '#tableBar',
|
||||
defaultToolbar: ['filter'],
|
||||
method: 'get',
|
||||
cols: [[
|
||||
{type: 'checkbox', width: 50},
|
||||
{field: 'name', title: '表名', minWidth: 220},
|
||||
{field: 'comment', title: '备注', minWidth: 160},
|
||||
{field: 'engine', title: '引擎', width: 100},
|
||||
{field: 'rows', title: '行数', width: 110, sort: true},
|
||||
{field: 'size_text', title: '大小', width: 110}
|
||||
]],
|
||||
page: true,
|
||||
limit: 15,
|
||||
limits: [15, 30, 50, 100],
|
||||
response: {statusCode: 0},
|
||||
parseData: parse,
|
||||
done: function () {
|
||||
var view = $('#tableList').next('.layui-table-view');
|
||||
auth.filterDom(view);
|
||||
auth.filterDom('.layui-btn[data-perm]');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
renderBackup: function () {
|
||||
table.render({
|
||||
elem: '#backupList',
|
||||
url: FUNC.index,
|
||||
where: {type: 'backup'},
|
||||
toolbar: '#backupBar',
|
||||
method: 'get',
|
||||
cols: [[
|
||||
{field: 'name', title: '备份名', minWidth: 220},
|
||||
{field: 'parts', title: '分卷', width: 80},
|
||||
{field: 'size_text', title: '大小', width: 110},
|
||||
{field: 'time_text', title: '时间', width: 180, sort: true},
|
||||
{title: '操作', width: 190, templet: '#backupAction', fixed: 'right'}
|
||||
]],
|
||||
page: true,
|
||||
limit: 15,
|
||||
response: {statusCode: 0},
|
||||
parseData: parse,
|
||||
done: function () {
|
||||
var view = $('#backupList').next('.layui-table-view');
|
||||
auth.filterDom(view);
|
||||
auth.filterDom('.layui-btn[data-perm]');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
events: function () {
|
||||
// 数据表工具条
|
||||
table.on('toolbar(tableList)', function (o) {
|
||||
if (o.event === 'backupSelected' || o.event === 'backupAll') {
|
||||
var names = o.event === 'backupAll' ? [] : checkedTables();
|
||||
if (o.event === 'backupSelected' && names.length === 0) {
|
||||
return layer.msg('请先勾选要备份的表', {icon: 2});
|
||||
}
|
||||
layer.confirm(
|
||||
o.event === 'backupAll' ? '确认备份全部数据表?' : '确认备份选中的 ' + names.length + ' 张表?',
|
||||
{title: '备份确认'},
|
||||
function (index) {
|
||||
layer.close(index);
|
||||
post(FUNC.backup, {tables: names, part_size: 2097152}, '备份完成', function () {
|
||||
table.reload('backupList');
|
||||
});
|
||||
}
|
||||
);
|
||||
} else if (o.event === 'optimize') {
|
||||
var opts = checkedTables();
|
||||
if (opts.length === 0) {
|
||||
return layer.msg('请先勾选要优化的表', {icon: 2});
|
||||
}
|
||||
post(FUNC.optimize, {tables: opts}, '优化完成', function () {
|
||||
table.reload('tableList');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 备份记录工具条
|
||||
table.on('toolbar(backupList)', function (o) {
|
||||
if (o.event === 'reload') {
|
||||
table.reload('backupList');
|
||||
}
|
||||
});
|
||||
|
||||
// 备份记录行操作
|
||||
table.on('tool(backupList)', function (o) {
|
||||
if (o.event === 'download') {
|
||||
window.open(FUNC.download + '?name=' + encodeURIComponent(o.data.name));
|
||||
} else if (o.event === 'recovery') {
|
||||
layer.confirm(
|
||||
'还原会覆盖当前同名表的数据,确定继续?<br><span style="color:#ff5722">建议先做一次新备份</span>',
|
||||
{title: '危险操作', btn: ['确定还原', '取消']},
|
||||
function (index) {
|
||||
layer.close(index);
|
||||
post(FUNC.recovery, {name: o.data.name}, '还原完成', function () {
|
||||
table.reload('tableList');
|
||||
});
|
||||
}
|
||||
);
|
||||
} else if (o.event === 'delete') {
|
||||
layer.confirm('确定删除该备份?', {title: '提示'}, function (index) {
|
||||
layer.close(index);
|
||||
post(FUNC.delete, {name: o.data.name}, '已删除', function () {
|
||||
table.reload('backupList');
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 搜索
|
||||
form.on('submit(data-search-btn)', function (o) {
|
||||
table.reload('tableList', {where: {keyword: o.field.keyword}, page: {curr: 1}});
|
||||
return false;
|
||||
});
|
||||
$('#btn-reset').on('click', function () {
|
||||
setTimeout(function () {
|
||||
table.reload('tableList', {where: {keyword: ''}, page: {curr: 1}});
|
||||
}, 10);
|
||||
});
|
||||
|
||||
// 切 tab 时修正列宽(隐藏时渲染的表格宽度会算错)
|
||||
layui.element.on('tab(dbTab)', function () {
|
||||
table.resize('tableList');
|
||||
table.resize('backupList');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
obj.renderTable();
|
||||
obj.renderBackup();
|
||||
obj.events();
|
||||
|
||||
exports('database', obj);
|
||||
});
|
||||
@@ -0,0 +1,408 @@
|
||||
/**
|
||||
* 数据表管理:列表 / 可视化建表 / 查看结构 / 优化 / 删表
|
||||
*
|
||||
* 与 database.js 同构:URL 只写动作名(当前页面目录即 /admin/datatable/),
|
||||
* 权限按钮用完整权限名,渲染后由 auth.filterDom() 移除无权限按钮。
|
||||
*/
|
||||
layui.define(['table', 'layer', 'form', 'element', 'auth', 'http', 'jquery'], function (exports) {
|
||||
var table = layui.table,
|
||||
layer = layui.layer,
|
||||
form = layui.form,
|
||||
element = layui.element,
|
||||
auth = layui.auth,
|
||||
http = layui.http,
|
||||
$ = layui.jquery;
|
||||
|
||||
var FUNC = {
|
||||
index: 'index',
|
||||
structure: 'structure',
|
||||
preview: 'preview',
|
||||
create: 'create',
|
||||
drop: 'drop',
|
||||
optimize: 'optimize',
|
||||
export: 'export'
|
||||
};
|
||||
|
||||
// 与后端 Datatable::TYPES 保持一致
|
||||
var TYPES = [
|
||||
'tinyint', 'smallint', 'mediumint', 'int', 'bigint',
|
||||
'decimal', 'float', 'double',
|
||||
'char', 'varchar', 'text', 'mediumtext', 'longtext',
|
||||
'date', 'datetime', 'timestamp', 'time', 'year',
|
||||
'json', 'blob', 'longblob'
|
||||
];
|
||||
|
||||
function parse(res) {
|
||||
return {
|
||||
code: res.code,
|
||||
msg: res.message,
|
||||
count: res.count || 0,
|
||||
data: res.data || []
|
||||
};
|
||||
}
|
||||
|
||||
function post(url, data, okTip, done) {
|
||||
var loading = layer.load(2);
|
||||
http.post(url, data).then(function (res) {
|
||||
layer.close(loading);
|
||||
if (res && res.code === 0) {
|
||||
layer.msg(okTip || res.message || '操作成功', {icon: 1});
|
||||
if (done) { done(res.data); }
|
||||
} else {
|
||||
layer.msg((res && res.message) || '操作失败', {icon: 2});
|
||||
}
|
||||
}).catch(function (err) {
|
||||
layer.close(loading);
|
||||
layer.msg('请求失败:' + (err && err.message ? err.message : '网络异常'), {icon: 2});
|
||||
});
|
||||
}
|
||||
|
||||
function checkedTables() {
|
||||
var rows = table.checkStatus('tableList').data || [];
|
||||
var names = [];
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
names.push(rows[i].name);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
/* ====================== 建表弹窗 ====================== */
|
||||
|
||||
/**
|
||||
* 单行字段
|
||||
*/
|
||||
function fieldRow(data) {
|
||||
data = data || {};
|
||||
var opts = '';
|
||||
for (var i = 0; i < TYPES.length; i++) {
|
||||
opts += '<option value="' + TYPES[i] + '"' +
|
||||
(data.type === TYPES[i] ? ' selected' : '') + '>' + TYPES[i] + '</option>';
|
||||
}
|
||||
return '' +
|
||||
'<div class="layui-form-item field-row" style="margin-bottom:4px">' +
|
||||
' <div class="layui-inline" style="width:120px"><input type="text" name="name" value="' + (data.name || '') + '" placeholder="字段名" class="layui-input layui-input-sm"></div>' +
|
||||
' <div class="layui-inline" style="width:120px"><select name="type">' + opts + '</select></div>' +
|
||||
' <div class="layui-inline" style="width:70px"><input type="text" name="length" value="' + (data.length || '') + '" placeholder="长度" class="layui-input"></div>' +
|
||||
' <div class="layui-inline" style="width:100px"><input type="text" name="default" value="' + (data.default || '') + '" placeholder="默认值" class="layui-input"></div>' +
|
||||
' <div class="layui-inline" style="width:130px"><input type="text" name="comment" value="' + (data.comment || '') + '" placeholder="注释" class="layui-input"></div>' +
|
||||
' <div class="layui-inline" style="width:74px"><input type="checkbox" name="unsigned" value="1" title="无符号"' + (data.unsigned ? ' checked' : '') + '></div>' +
|
||||
' <div class="layui-inline" style="width:60px"><input type="checkbox" name="nullable" value="1" title="可空"' + (data.nullable ? ' checked' : '') + '></div>' +
|
||||
' <div class="layui-inline" style="width:100px"><input type="checkbox" name="autoinc" value="1" title="自增"' + (data.autoinc ? ' checked' : '') + '></div>' +
|
||||
' <div class="layui-inline" style="width:60px"><input type="checkbox" name="pk" value="1" title="主键"' + (data.pk ? ' checked' : '') + '></div>' +
|
||||
' <div class="layui-inline" style="width:90px"><select name="index">' +
|
||||
' <option value="">无索引</option>' +
|
||||
' <option value="index"' + (data.index === 'index' ? ' selected' : '') + '>普通</option>' +
|
||||
' <option value="unique"' + (data.index === 'unique' ? ' selected' : '') + '>唯一</option>' +
|
||||
' </select></div>' +
|
||||
' <button type="button" class="layui-btn layui-btn-xs layui-btn-danger" data-act="remove-field">删</button>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
function addRow(data) {
|
||||
$('#fieldRows').append(fieldRow(data));
|
||||
form.render(null, 'create-form');
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集表单里的字段定义
|
||||
*/
|
||||
function collect() {
|
||||
var base = form.val('create-form');
|
||||
var fields = [];
|
||||
$('#fieldRows .field-row').each(function () {
|
||||
var r = $(this);
|
||||
fields.push({
|
||||
name: $.trim(r.find('input[name=name]').val() || ''),
|
||||
type: r.find('select[name=type]').val(),
|
||||
length: $.trim(r.find('input[name=length]').val() || ''),
|
||||
default: $.trim(r.find('input[name=default]').val() || ''),
|
||||
comment: $.trim(r.find('input[name=comment]').val() || ''),
|
||||
unsigned: r.find('input[name=unsigned]').is(':checked') ? 1 : 0,
|
||||
nullable: r.find('input[name=nullable]').is(':checked') ? 1 : 0,
|
||||
autoinc: r.find('input[name=autoinc]').is(':checked') ? 1 : 0,
|
||||
pk: r.find('input[name=pk]').is(':checked') ? 1 : 0,
|
||||
index: r.find('select[name=index]').val()
|
||||
});
|
||||
});
|
||||
return {
|
||||
table: $.trim(base.table || ''),
|
||||
comment: $.trim(base.comment || ''),
|
||||
engine: base.engine,
|
||||
charset: base.charset,
|
||||
collation: '',
|
||||
fields: fields
|
||||
};
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
var idx = layer.open({
|
||||
type: 1,
|
||||
title: '新建数据表',
|
||||
area: ['1080px', '640px'],
|
||||
content: $('#createTpl').html(),
|
||||
success: function () {
|
||||
form.render(null, 'create-form');
|
||||
$('#fieldRows').html('');
|
||||
addRow({name: 'id', type: 'bigint', unsigned: 1, autoinc: 1, pk: 1, comment: 'ID'});
|
||||
addRow({name: 'name', type: 'varchar', length: '100', default: '', comment: '名称'});
|
||||
addRow({name: 'create_at', type: 'int', length: '', default: '0', comment: '创建时间'});
|
||||
element.render();
|
||||
}
|
||||
});
|
||||
|
||||
$(document).off('click', '#btn-add-field').on('click', '#btn-add-field', function () {
|
||||
addRow({type: 'varchar', length: '255'});
|
||||
});
|
||||
$(document).off('click', '#btn-add-id').on('click', '#btn-add-id', function () {
|
||||
addRow({name: 'id', type: 'bigint', unsigned: 1, autoinc: 1, pk: 1, comment: 'ID'});
|
||||
});
|
||||
$(document).off('click', '[data-act=remove-field]').on('click', '[data-act=remove-field]', function () {
|
||||
if ($('#fieldRows .field-row').length <= 1) {
|
||||
return layer.msg('至少保留一个字段', {icon: 2});
|
||||
}
|
||||
$(this).closest('.field-row').remove();
|
||||
});
|
||||
|
||||
// 生成预览(不执行)
|
||||
$(document).off('click', '#btn-preview').on('click', '#btn-preview', function () {
|
||||
var payload = collect();
|
||||
if (payload.table === '') {
|
||||
return layer.msg('请填写表名', {icon: 2});
|
||||
}
|
||||
post(FUNC.preview, payload, '', function (d) {
|
||||
$('#sqlPreview').val(d.sql);
|
||||
layer.msg('已生成,请确认后执行', {icon: 1});
|
||||
});
|
||||
});
|
||||
|
||||
// 执行建表
|
||||
$(document).off('click', '#btn-exec').on('click', '#btn-exec', function () {
|
||||
var payload = collect();
|
||||
if (payload.table === '') {
|
||||
return layer.msg('请填写表名', {icon: 2});
|
||||
}
|
||||
layer.confirm(
|
||||
'确定创建表 <b>' + payload.table + '</b>?<br><span class="layui-word-aux">建议先「生成 SQL 预览」确认无误</span>',
|
||||
{title: '建表确认'},
|
||||
function (i) {
|
||||
layer.close(i);
|
||||
post(FUNC.create, payload, '创建成功', function (d) {
|
||||
layer.close(idx);
|
||||
layer.alert(
|
||||
'表:<b>' + d.table + '</b><br>下一步生成 CRUD:<br><code>' + d.command + '</code>',
|
||||
{title: '建表完成', btn: ['知道了']}
|
||||
);
|
||||
table.reload('tableList');
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/* ====================== 结构弹窗 ====================== */
|
||||
|
||||
function openStructure(name) {
|
||||
http.get(FUNC.structure, {table: name}).then(function (res) {
|
||||
if (!res || res.code !== 0) {
|
||||
return layer.msg((res && res.message) || '读取失败', {icon: 2});
|
||||
}
|
||||
var d = res.data;
|
||||
var html = '';
|
||||
for (var i = 0; i < d.columns.length; i++) {
|
||||
var c = d.columns[i];
|
||||
html += '<tr><td>' + c.field + '</td><td>' + c.type + '</td><td>' + c.null + '</td>' +
|
||||
'<td>' + (c.default === null ? 'NULL' : c.default) + '</td>' +
|
||||
'<td>' + (c.key || '-') + '</td><td>' + (c.extra || '-') + '</td><td>' + c.comment + '</td></tr>';
|
||||
}
|
||||
layer.open({
|
||||
type: 1,
|
||||
title: '表结构:' + name,
|
||||
area: ['900px', '580px'],
|
||||
content: $('#structureTpl').html(),
|
||||
success: function () {
|
||||
$('#columnBody').html(html || '<tr><td colspan="7">无字段</td></tr>');
|
||||
$('#createSqlText').val(d.sql);
|
||||
element.render();
|
||||
}
|
||||
});
|
||||
}).catch(function (err) {
|
||||
layer.msg('请求失败:' + (err && err.message ? err.message : '网络异常'), {icon: 2});
|
||||
});
|
||||
}
|
||||
|
||||
/* ====================== 导出安装 SQL ====================== */
|
||||
|
||||
/**
|
||||
* 取选中表的 CREATE TABLE(__PREFIX__ 形式),弹窗展示 + 复制 / 下载
|
||||
*/
|
||||
function openExport(names) {
|
||||
if (!names.length) {
|
||||
return layer.msg('请先勾选要导出的表', {icon: 2});
|
||||
}
|
||||
var loading = layer.load(2);
|
||||
http.post(FUNC.export, {tables: names}).then(function (res) {
|
||||
layer.close(loading);
|
||||
if (!res || res.code !== 0) {
|
||||
return layer.msg((res && res.message) || '导出失败', {icon: 2});
|
||||
}
|
||||
var d = res.data;
|
||||
var rows = '';
|
||||
for (var i = 0; i < d.items.length; i++) {
|
||||
var it = d.items[i];
|
||||
var badge = it.registered
|
||||
? '<span class="layui-badge layui-bg-green">已归档</span>'
|
||||
: '<span class="layui-badge layui-bg-orange">未归档</span>';
|
||||
rows += '<tr><td><code>' + it.short + '</code></td><td>' + badge + '</td>' +
|
||||
'<td>' + (it.target ? '<code>' + it.target + '</code>' : '<span class="layui-word-aux">需人工指定</span>') + '</td></tr>';
|
||||
}
|
||||
layer.open({
|
||||
type: 1,
|
||||
title: '导出安装 SQL(' + d.items.length + ' 张表)',
|
||||
area: ['860px', '640px'],
|
||||
content: $('#exportTpl').html(),
|
||||
success: function () {
|
||||
$('#exportBody').html(rows);
|
||||
$('#exportSqlText').val(d.text);
|
||||
$('#exportSummary').html(
|
||||
(d.unregistered && d.unregistered.length
|
||||
? '其中未归档:' + d.unregistered.join('、') + ',请确认写入哪个 install.sql。'
|
||||
: '所选表均已登记,导出可用于核对结构是否漂移。')
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
$(document).off('click', '#btn-copy-sql').on('click', '#btn-copy-sql', function () {
|
||||
var el = document.getElementById('exportSqlText');
|
||||
el.select();
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
layer.msg('已复制', {icon: 1});
|
||||
} catch (e) {
|
||||
layer.msg('复制失败,请手动选中复制', {icon: 2});
|
||||
}
|
||||
});
|
||||
$(document).off('click', '#btn-download-sql').on('click', '#btn-download-sql', function () {
|
||||
var blob = new Blob([d.text], {type: 'text/sql;charset=utf-8'});
|
||||
var a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = 'install_' + new Date().getTime() + '.sql';
|
||||
a.click();
|
||||
setTimeout(function () { URL.revokeObjectURL(a.href); }, 1000);
|
||||
});
|
||||
}).catch(function (err) {
|
||||
layer.close(loading);
|
||||
layer.msg('请求失败:' + (err && err.message ? err.message : '网络异常'), {icon: 2});
|
||||
});
|
||||
}
|
||||
|
||||
/* ====================== 删表 ====================== */
|
||||
|
||||
function dropTable(name) {
|
||||
layer.prompt({
|
||||
title: '危险:删除表不可恢复,请输入表名确认',
|
||||
formType: 0,
|
||||
value: ''
|
||||
}, function (val, i) {
|
||||
layer.close(i);
|
||||
if (val !== name) {
|
||||
return layer.msg('输入的表名不一致,已取消', {icon: 2});
|
||||
}
|
||||
post(FUNC.drop, {table: name, confirm: val}, '已删除', function () {
|
||||
table.reload('tableList');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var obj = {
|
||||
renderTable: function () {
|
||||
table.render({
|
||||
elem: '#tableList',
|
||||
url: FUNC.index,
|
||||
toolbar: '#tableBar',
|
||||
defaultToolbar: ['filter'],
|
||||
method: 'get',
|
||||
cols: [[
|
||||
{type: 'checkbox', width: 50},
|
||||
{field: 'name', title: '表名', minWidth: 240},
|
||||
{field: 'comment', title: '备注', minWidth: 160},
|
||||
{field: 'engine', title: '引擎', width: 100},
|
||||
{field: 'registered', title: '归档', width: 110, templet: function (d) {
|
||||
return d.registered
|
||||
? '<span class="layui-badge layui-bg-green">已归档</span>'
|
||||
: '<span class="layui-badge layui-bg-orange" title="不在任何 install.sql,全新安装/自愈都不会建这张表">未归档</span>';
|
||||
}},
|
||||
{field: 'target', title: 'install.sql', minWidth: 170, templet: function (d) {
|
||||
return d.target ? '<code>' + d.target + '</code>' : '<span class="layui-word-aux">—</span>';
|
||||
}},
|
||||
{field: 'rows', title: '行数', width: 110, sort: true},
|
||||
{field: 'size_text', title: '大小', width: 110, sort: true},
|
||||
{field: 'collation', title: '字符集', width: 160},
|
||||
{title: '操作', width: 170, templet: '#tableAction', fixed: 'right'}
|
||||
]],
|
||||
page: true,
|
||||
limit: 15,
|
||||
limits: [15, 30, 50, 100],
|
||||
response: {statusCode: 0},
|
||||
parseData: parse,
|
||||
done: function () {
|
||||
var view = $('#tableList').next('.layui-table-view');
|
||||
auth.filterDom(view);
|
||||
auth.filterDom('.layui-btn[data-perm]');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
events: function () {
|
||||
table.on('toolbar(tableList)', function (o) {
|
||||
if (o.event === 'create') {
|
||||
openCreate();
|
||||
} else if (o.event === 'reload') {
|
||||
table.reload('tableList');
|
||||
} else if (o.event === 'optimize') {
|
||||
var names = checkedTables();
|
||||
if (!names.length) {
|
||||
return layer.msg('请先勾选要优化的表', {icon: 2});
|
||||
}
|
||||
post(FUNC.optimize, {tables: names}, '优化完成', function () {
|
||||
table.reload('tableList');
|
||||
});
|
||||
} else if (o.event === 'export') {
|
||||
openExport(checkedTables());
|
||||
}
|
||||
});
|
||||
|
||||
table.on('tool(tableList)', function (o) {
|
||||
if (o.event === 'structure') {
|
||||
openStructure(o.data.name);
|
||||
} else if (o.event === 'optimizeRow') {
|
||||
post(FUNC.optimize, {tables: [o.data.name]}, '优化完成', function () {
|
||||
table.reload('tableList');
|
||||
});
|
||||
} else if (o.event === 'exportRow') {
|
||||
openExport([o.data.name]);
|
||||
} else if (o.event === 'drop') {
|
||||
dropTable(o.data.name);
|
||||
}
|
||||
});
|
||||
|
||||
form.on('submit(data-search-btn)', function (o) {
|
||||
table.reload('tableList', {
|
||||
where: {keyword: o.field.keyword, unregistered: o.field.unregistered || '0'},
|
||||
page: {curr: 1}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
$('#btn-reset').on('click', function () {
|
||||
setTimeout(function () {
|
||||
table.reload('tableList', {where: {keyword: '', unregistered: '0'}, page: {curr: 1}});
|
||||
}, 10);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
form.render(null, 'search-form'); // 归档筛选下拉要 layui 渲染,否则是原生 select
|
||||
obj.renderTable();
|
||||
obj.events();
|
||||
|
||||
exports('datatable', obj);
|
||||
});
|
||||
@@ -24,14 +24,8 @@ layui.define(['http', 'auth'], function (exports) {
|
||||
]]
|
||||
, done: function (res, curr, count) {
|
||||
var view = $('#dataTable').next('.layui-table-view');
|
||||
view.find('[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
$('.layui-btn[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
auth.filterDom(view);
|
||||
auth.filterDom('.layui-btn[data-perm]');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -32,14 +32,8 @@ layui.define(['form', 'table', 'http', 'auth'], function (exports) {
|
||||
skin: 'line',
|
||||
done: function () {
|
||||
var view = $('#dataTable').next('.layui-table-view');
|
||||
view.find('[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
$('.layui-btn[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
auth.filterDom(view);
|
||||
auth.filterDom('.layui-btn[data-perm]');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -130,6 +130,11 @@ layui.define(["ywxapp", "http", "route"], function (exports) {
|
||||
isTop = isTop !== false;
|
||||
var html = '';
|
||||
$.each(data || [], function (i, item) {
|
||||
// 分组标题(type=5):纯视觉分组,不可点击、不跳转
|
||||
if (parseInt(item.type, 10) === 5) {
|
||||
html += '<dd class="nav-group-title">' + item.title + '</dd>';
|
||||
return;
|
||||
}
|
||||
var hasChild = item.child && item.child.length > 0;
|
||||
var tag = isTop ? 'li' : 'dd';
|
||||
var cls = isTop ? 'layui-nav-item' : '';
|
||||
|
||||
@@ -48,14 +48,8 @@ layui.define(['form', 'table', 'http', 'auth'], function (exports) {
|
||||
skin: 'line',
|
||||
done: function (res, curr, count) {
|
||||
var view = $('#dataTable').next('.layui-table-view');
|
||||
view.find('[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
$('.layui-btn[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
auth.filterDom(view);
|
||||
auth.filterDom('.layui-btn[data-perm]');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -15,6 +15,24 @@ layui.define(['form', 'http'], function (exports) {
|
||||
if (verifyImg) {
|
||||
verifyImg.addEventListener('click', refreshVerifyCode);
|
||||
}
|
||||
/**
|
||||
* 登录成功后自动跳转(兼容 iframe 场景)
|
||||
* 原实现用 layer.alert 弹「请稍后」对话框,必须用户点「确定」(或等 15 秒倒计时)才跳转,
|
||||
* 体验很差;这里改为轻提示 + 自动跳转。
|
||||
* @param {string} url 目标地址
|
||||
*/
|
||||
function jump(url) {
|
||||
layer.msg('登录成功,正在跳转…', { time: 800 });
|
||||
setTimeout(function () {
|
||||
if (window.self !== window.top) {
|
||||
// 在 iframe 中,跳转顶级页面
|
||||
window.top.location.href = url;
|
||||
} else {
|
||||
// 顶级窗口,直接跳转
|
||||
window.location.href = url;
|
||||
}
|
||||
}, 600);
|
||||
}
|
||||
form.on('submit(dataSubmit)', function (obj) {
|
||||
let data = obj.field;
|
||||
layui.http.post("save", data)
|
||||
@@ -26,18 +44,8 @@ layui.define(['form', 'http'], function (exports) {
|
||||
} else {
|
||||
layui.setter.setMenus(res.data.menus);
|
||||
layui.setter.permissions(res.data.permissions);
|
||||
layer.alert('请稍后!', {
|
||||
time: 15000,
|
||||
end: function () {
|
||||
if (window.self !== window.top) {
|
||||
// 在 iframe 中,跳转顶级页面
|
||||
window.top.location.href = "/admin";
|
||||
} else {
|
||||
// 顶级窗口,直接跳转
|
||||
window.location.href = "/admin";
|
||||
}
|
||||
}
|
||||
})
|
||||
// 自动跳转,无需再点「确定」
|
||||
jump('/admin');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -32,14 +32,8 @@ layui.define(['form', 'table', 'http', 'auth'], function (exports) {
|
||||
skin: 'line',
|
||||
done: function () {
|
||||
var view = $('#dataTable').next('.layui-table-view');
|
||||
view.find('[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
$('.layui-btn[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
auth.filterDom(view);
|
||||
auth.filterDom('.layui-btn[data-perm]');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -37,14 +37,8 @@ layui.define(['form', 'table', 'http', 'auth'], function (exports) {
|
||||
skin: 'line',
|
||||
done: function () {
|
||||
var view = $('#dataTable').next('.layui-table-view');
|
||||
view.find('[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
$('.layui-btn[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
auth.filterDom(view);
|
||||
auth.filterDom('.layui-btn[data-perm]');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -52,6 +52,8 @@ layui.define(['layer', 'http', 'iconPicker', 'auth'], function (exports) {
|
||||
return '<span class="layui-badge layui-bg-gray">按钮</span>';
|
||||
} else if (d.type === 4) {
|
||||
return '<span class="layui-badge layui-bg-gray">API</span>';
|
||||
} else if (d.type === 5) {
|
||||
return '<span class="layui-badge">分组标题</span>';
|
||||
}
|
||||
}
|
||||
}, { field: 'route', title: '路由', minWidth: 200, sort: true },
|
||||
@@ -67,14 +69,8 @@ layui.define(['layer', 'http', 'iconPicker', 'auth'], function (exports) {
|
||||
page: true,
|
||||
done: function (res, curr, count, origin) {
|
||||
var view = $('#dataTable').next('.layui-table-view');
|
||||
view.find('[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
$('.layui-btn[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
auth.filterDom(view);
|
||||
auth.filterDom('.layui-btn[data-perm]');
|
||||
}
|
||||
});
|
||||
treeTable.on('toolbar(dataTable)', function (obj) {
|
||||
@@ -115,15 +111,19 @@ layui.define(['layer', 'http', 'iconPicker', 'auth'], function (exports) {
|
||||
var id = this.value;
|
||||
var status = obj.elem.checked ? 1 : 0;
|
||||
layer.confirm('确定要' + (status ? '启用' : '禁用') + '该数据吗?', function (index) {
|
||||
layui.request.post('update', { id: id, status: status }).then(function (res) {
|
||||
if (res.code === 0) {
|
||||
http.post('update', { id: id, status: status }).then(function (res) {
|
||||
if (res && res.code === 0) {
|
||||
layer.msg(res.msg, { icon: 1 });
|
||||
table.reload('dataTable', {}, true);
|
||||
} else {
|
||||
layer.msg(res.msg, { icon: 2 });
|
||||
layer.msg((res && res.msg) || '操作失败', { icon: 2 });
|
||||
obj.elem.checked = !obj.elem.checked;
|
||||
form.render('checkbox');
|
||||
}
|
||||
}).catch(function (err) {
|
||||
layer.msg('请求失败:' + (err && err.message ? err.message : '网络异常'), { icon: 2 });
|
||||
obj.elem.checked = !obj.elem.checked;
|
||||
form.render('checkbox');
|
||||
});
|
||||
layer.close(index);
|
||||
}, function () {
|
||||
|
||||
@@ -32,14 +32,8 @@ layui.define(['form', 'table', 'http', 'auth'], function (exports) {
|
||||
skin: 'line',
|
||||
done: function () {
|
||||
var view = $('#dataTable').next('.layui-table-view');
|
||||
view.find('[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
$('.layui-btn[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
auth.filterDom(view);
|
||||
auth.filterDom('.layui-btn[data-perm]');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -23,14 +23,8 @@ layui.define(['http', 'auth'], function (exports) {
|
||||
]]
|
||||
, done: function (res, curr, count) {
|
||||
var view = $('#dataTable').next('.layui-table-view');
|
||||
view.find('[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
$('.layui-btn[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
auth.filterDom(view);
|
||||
auth.filterDom('.layui-btn[data-perm]');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -72,14 +72,8 @@ layui.define(['layer', 'http', 'iconPicker', 'auth'], function (exports) {
|
||||
skin: 'line',
|
||||
done: function (res, curr, count, origin) {
|
||||
var view = $('#dataTable').next('.layui-table-view');
|
||||
view.find('[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
$('.layui-btn[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
auth.filterDom(view);
|
||||
auth.filterDom('.layui-btn[data-perm]');
|
||||
}
|
||||
});
|
||||
treeTable.on('toolbar(dataTable)', function (obj) {
|
||||
|
||||
@@ -9,6 +9,26 @@ layui.define(['form', 'table', 'http', 'auth'], function (exports) {
|
||||
auth.init(_savedPerms);
|
||||
auth.setController('score');
|
||||
|
||||
// 动作候选(核心预置 + 插件注册 + 规则表已使用),供 datalist 提示
|
||||
var actionList = [];
|
||||
function loadActions() {
|
||||
http.get('actions').then(function (res) {
|
||||
if (res.code === 0 && res.data) {
|
||||
actionList = res.data;
|
||||
var html = '';
|
||||
layui.$.each(actionList, function (i, item) {
|
||||
html += '<option value="' + item.action + '">' + item.title + (item.addon ? '(插件:' + item.addon + ')' : '') + '</option>';
|
||||
});
|
||||
$('#action-list').html(html);
|
||||
}
|
||||
}).catch(function () { });
|
||||
}
|
||||
loadActions();
|
||||
|
||||
function dateStr(v) {
|
||||
return v ? layui.util.toDateString(v * 1000) : '-';
|
||||
}
|
||||
|
||||
table.render({
|
||||
elem: '#dataTable',
|
||||
url: "index",
|
||||
@@ -20,13 +40,15 @@ layui.define(['form', 'table', 'http', 'auth'], function (exports) {
|
||||
cols: [[
|
||||
{ type: "checkbox", width: 50 },
|
||||
{ field: 'id', width: 80, title: 'ID', sort: true },
|
||||
{ field: 'name', minWidth: 160, title: '规则名称' },
|
||||
{ field: 'action', width: 160, title: '触发动作' },
|
||||
{ field: 'title', minWidth: 160, title: '规则名称' },
|
||||
{ field: 'action', width: 170, title: '触发动作' },
|
||||
{ field: 'type_text', width: 90, title: '类型', templet: '#typeTpl' },
|
||||
{ field: 'value', width: 90, title: '变动值', sort: true },
|
||||
{ field: 'sort', width: 90, title: '排序', sort: true },
|
||||
{ field: 'limit_text', width: 110, title: '限次' },
|
||||
{ field: 'addon', width: 100, title: '归属', templet: '#addonTpl' },
|
||||
{ field: 'sort', width: 80, title: '排序', sort: true },
|
||||
{ field: 'status', width: 100, title: '状态', templet: '#statusTpl' },
|
||||
{ field: 'create_time', width: 180, title: '创建时间' },
|
||||
{ field: 'create_at', width: 170, title: '创建时间', templet: function (d) { return dateStr(d.create_at); } },
|
||||
{ title: '操作', minWidth: 120, toolbar: '#dataBar', align: "center" }
|
||||
]],
|
||||
limits: [10, 15, 20, 25, 50, 100],
|
||||
@@ -35,24 +57,20 @@ layui.define(['form', 'table', 'http', 'auth'], function (exports) {
|
||||
skin: 'line',
|
||||
done: function () {
|
||||
var view = $('#dataTable').next('.layui-table-view');
|
||||
view.find('[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
$('.layui-btn[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
auth.filterDom(view);
|
||||
auth.filterDom('.layui-btn[data-perm]');
|
||||
}
|
||||
});
|
||||
|
||||
form.on('submit(data-search-btn)', function (data) {
|
||||
table.reload('dataTable', { page: { curr: 1 }, where: { name: data.field.name } });
|
||||
table.reload('dataTable', { page: { curr: 1 }, where: { title: data.field.title, action: data.field.action } });
|
||||
return false;
|
||||
});
|
||||
$('#btn-reset').on('click', function () {
|
||||
$('input[name="name"]').val('');
|
||||
table.reload('dataTable', { page: { curr: 1 }, where: { name: '' } });
|
||||
$('input[name="title"]').val('');
|
||||
$('select[name="action"]').val('');
|
||||
form.render('select');
|
||||
table.reload('dataTable', { page: { curr: 1 }, where: { title: '', action: '' } });
|
||||
});
|
||||
|
||||
table.on('toolbar(dataTable)', function (elem) {
|
||||
@@ -91,7 +109,15 @@ layui.define(['form', 'table', 'http', 'auth'], function (exports) {
|
||||
title: title, content: formHtml, anim: "slideLeft", offset: "r",
|
||||
btnAlign: "l", area: [window.width > 768 ? '50%' : '99%', '99%'],
|
||||
shade: 0.1, shadeClose: true, btn: ['确定', '取消'],
|
||||
success: function (layero, index) { callback(layero, index); form.render(); },
|
||||
success: function (layero, index) {
|
||||
// 重新填充动作候选(弹层是新建 DOM)
|
||||
var html = '';
|
||||
layui.$.each(actionList, function (i, item) {
|
||||
html += '<option value="' + item.action + '">' + item.title + (item.addon ? '(插件:' + item.addon + ')' : '') + '</option>';
|
||||
});
|
||||
layero.find('#action-list').html(html);
|
||||
callback(layero, index); form.render();
|
||||
},
|
||||
yes: function (index, layero) {
|
||||
window.layui.form.on('submit(wxapp-form-submit)', function (elem) {
|
||||
done(layero, index, elem); layui.off('submit(wxapp-form)', 'from'); return false;
|
||||
@@ -145,9 +171,9 @@ layui.define(['form', 'table', 'http', 'auth'], function (exports) {
|
||||
cols: [[
|
||||
{ type: 'checkbox', fixed: 'left' },
|
||||
{ field: 'id', title: 'ID', width: 80, sort: true, fixed: 'left' },
|
||||
{ field: 'name', title: '规则名称', minWidth: 160, fixed: 'left' },
|
||||
{ field: 'title', title: '规则名称', minWidth: 160, fixed: 'left' },
|
||||
{ field: 'sort', title: '排序', width: 80 },
|
||||
{ field: 'delete_at', title: '删除时间', width: 160 },
|
||||
{ field: 'delete_at', title: '删除时间', width: 170, templet: function (d) { return dateStr(d.delete_at); } },
|
||||
{ fixed: "right", title: "操作", width: 140, align: "center", toolbar: "#dataRecybinBarTpl" }
|
||||
]],
|
||||
page: true
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
layui.define(['form', 'table', 'http', 'auth'], function (exports) {
|
||||
var $ = layui.jquery,
|
||||
form = layui.form,
|
||||
table = layui.table,
|
||||
layer = layui.layer,
|
||||
http = layui.http, laytpl = layui.laytpl
|
||||
, auth = layui.auth;
|
||||
var _savedPerms = (layui.data('backend').permission) || [];
|
||||
auth.init(_savedPerms);
|
||||
auth.setController('shop');
|
||||
|
||||
table.render({
|
||||
elem: '#dataTable',
|
||||
url: "index",
|
||||
toolbar: '#tableBar',
|
||||
defaultToolbar: ['filter', 'exports', 'print'],
|
||||
parseData: function (res) {
|
||||
return { code: res.code === 0 ? 0 : 1, msg: res.message || '', count: res.count || 0, data: res.data || [] };
|
||||
},
|
||||
cols: [[
|
||||
{ type: "checkbox", width: 50 },
|
||||
{ field: 'id', width: 80, title: 'ID', sort: true },
|
||||
{ field: 'title', minWidth: 160, title: '商品名称' },
|
||||
{ field: 'sort', width: 100, title: '排序', sort: true },
|
||||
{ field: 'status', width: 100, title: '状态', templet: '#statusTpl' },
|
||||
{ field: 'create_time', width: 180, title: '创建时间' },
|
||||
{ title: '操作', minWidth: 150, toolbar: '#dataBar', align: "center" }
|
||||
]],
|
||||
limits: [10, 15, 20, 25, 50, 100],
|
||||
limit: 15,
|
||||
page: true,
|
||||
skin: 'line',
|
||||
done: function () {
|
||||
var view = $('#dataTable').next('.layui-table-view');
|
||||
view.find('[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
$('.layui-btn[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
form.on('submit(data-search-btn)', function (data) {
|
||||
table.reload('dataTable', { page: { curr: 1 }, where: { title: data.field.title } });
|
||||
return false;
|
||||
});
|
||||
$('#btn-reset').on('click', function () {
|
||||
$('input[name=\"title\"]').val('');
|
||||
table.reload('dataTable', { page: { curr: 1 }, where: { title: '' } });
|
||||
});
|
||||
|
||||
table.on('toolbar(dataTable)', function (elem) {
|
||||
switch (elem.event) {
|
||||
case 'dataCreate': active.dataCreate({}); break;
|
||||
case 'dataDelete':
|
||||
var check = table.checkStatus('dataTable').data;
|
||||
active.dataDelete(check.map(function (i) { return i.id; }));
|
||||
break;
|
||||
case 'dataRecybin': active.dataRecybin(); break;
|
||||
}
|
||||
});
|
||||
table.on('tool(dataTable)', function (elem) {
|
||||
var data = elem.data;
|
||||
switch (elem.event) {
|
||||
case 'update': active.dataEdit(data); break;
|
||||
case 'delete': active.dataDelete([data.id]); break;
|
||||
}
|
||||
});
|
||||
|
||||
form.on('switch(statusSwitch)', function (obj) {
|
||||
var status = obj.elem.checked ? 1 : 0;
|
||||
http.put('update', { id: this.value, status: status })
|
||||
.then(function (res) {
|
||||
if (res.code === 0) { layer.msg('操作成功'); }
|
||||
else { layer.msg(res.msg || '操作失败'); }
|
||||
table.reload('dataTable', {}, true);
|
||||
})
|
||||
.catch(function () { table.reload('dataTable', {}, true); });
|
||||
});
|
||||
|
||||
var dataFromFun = function (data, callback, done) {
|
||||
var title = data.id ? '编辑电子商务' : '添加电子商务';
|
||||
var formHtml = laytpl($('#dataFormTpl').html()).render(data || {});
|
||||
layer.open({
|
||||
title: title, content: formHtml, anim: "slideLeft", offset: "r",
|
||||
btnAlign: "l", area: [window.width > 768 ? '50%' : '99%', '99%'],
|
||||
shade: 0.1, shadeClose: true, btn: ['确定', '取消'],
|
||||
success: function (layero, index) { callback(layero, index); form.render(); },
|
||||
yes: function (index, layero) {
|
||||
window.layui.form.on('submit(wxapp-form-submit)', function (elem) {
|
||||
done(layero, index, elem); layui.off('submit(wxapp-form)', 'from'); return false;
|
||||
});
|
||||
layero.contents().find("#wxapp-form-submit").trigger('click');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var active = {
|
||||
dataDelete: function (ids, force) {
|
||||
if (ids.length === 0) { return layer.msg('请选择数据'); }
|
||||
layer.confirm('确定删除吗?' + (force ? '(彻底删除,不可恢复)' : '(可到回收站恢复)'), function (index) {
|
||||
layer.close(index);
|
||||
http.delete('delete', { ids: ids.join(','), force: force ? 1 : 0 })
|
||||
.then(function (res) { if (res.code === 0) { table.reload('dataTable', {}, true); } else { layer.msg(res.msg || '操作失败'); } });
|
||||
});
|
||||
},
|
||||
dataCreate: function (data) {
|
||||
dataFromFun(data, function () {}, function (layero, index, elem) {
|
||||
var field = elem.field;
|
||||
if (field.status === undefined) field.status = 0;
|
||||
field.status = field.status ? 1 : 0;
|
||||
http.post('save', field).then(function (res) {
|
||||
if (res.code === 0) { layer.close(index); table.reload('dataTable', {}, true); }
|
||||
else { layer.msg(res.msg || '操作失败'); }
|
||||
});
|
||||
});
|
||||
},
|
||||
dataEdit: function (data) {
|
||||
dataFromFun(data, function () {}, function (layero, index, elem) {
|
||||
var field = elem.field;
|
||||
field.status = field.status ? 1 : 0;
|
||||
http.put('update', field).then(function (res) {
|
||||
if (res.code === 0) { layer.close(index); table.reload('dataTable', {}, true); }
|
||||
else { layer.msg(res.msg || '操作失败'); }
|
||||
});
|
||||
});
|
||||
},
|
||||
dataRecybin: function () {
|
||||
var formHtml = laytpl($('#dataRecybinTpl').html()).render({});
|
||||
layer.open({
|
||||
title: '资源回收站', content: formHtml, anim: "slideLeft", offset: "r",
|
||||
area: ['50%', '99%'], shade: 0.1, shadeClose: true,
|
||||
success: function () {
|
||||
table.render({
|
||||
elem: '#dataRecybinTable', url: "recyclebin", height: 'full-120',
|
||||
parseData: function (res) { return { code: res.code === 0 ? 0 : 1, msg: res.message || '', count: res.count || 0, data: res.data || [] }; },
|
||||
cols: [[
|
||||
{ type: 'checkbox', fixed: 'left' },
|
||||
{ field: 'id', title: 'ID', width: 80, sort: true, fixed: 'left' },
|
||||
{ field: 'title', title: '商品名称', minWidth: 160, fixed: 'left' },
|
||||
{ field: 'sort', title: '排序', width: 80 },
|
||||
{ field: 'delete_at', title: '删除时间', width: 160 },
|
||||
{ fixed: "right", title: "操作", width: 140, align: "center", toolbar: "#dataRecybinBarTpl" }
|
||||
]],
|
||||
page: true
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
dataRestore: function (data) {
|
||||
http.put("restore", { ids: data.id }).then(function (res) {
|
||||
if (res.code === 0) { layer.msg('恢复成功'); table.reload('dataRecybinTable', {}, true); table.reload('dataTable', {}, true); }
|
||||
else { layer.msg(res.msg || '恢复失败'); }
|
||||
});
|
||||
},
|
||||
dataForceDelete: function (data) {
|
||||
layer.confirm('彻底删除后不可恢复,确定吗?', function (index) {
|
||||
layer.close(index);
|
||||
http.delete('delete', { ids: data.id, force: 1 }).then(function (res) {
|
||||
if (res.code === 0) { layer.msg('已彻底删除'); table.reload('dataRecybinTable', {}, true); }
|
||||
else { layer.msg(res.msg || '操作失败'); }
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
table.on('tool(dataRecybinTable)', function (elem) {
|
||||
var data = elem.data;
|
||||
switch (elem.event) {
|
||||
case 'restore': active.dataRestore(data); break;
|
||||
case 'forcedelete': active.dataForceDelete(data); break;
|
||||
}
|
||||
});
|
||||
|
||||
exports('shop', {});
|
||||
});
|
||||
@@ -32,14 +32,8 @@ layui.define(['form', 'table', 'http', 'auth'], function (exports) {
|
||||
skin: 'line',
|
||||
done: function () {
|
||||
var view = $('#dataTable').next('.layui-table-view');
|
||||
view.find('[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
$('.layui-btn[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
auth.filterDom(view);
|
||||
auth.filterDom('.layui-btn[data-perm]');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -14,35 +14,71 @@ layui.define(['form', 'table', 'http', 'auth', 'laydate'], function (exports) {
|
||||
laydate.render({ elem: '#dateInput' });
|
||||
|
||||
// 汇总 + 趋势(纯 CSS 迷你条形图,不依赖 echarts)
|
||||
http.get('stats').then(function (res) {
|
||||
if (res.code !== 0) { return; }
|
||||
var summary = (res.data && res.data.summary) || [];
|
||||
var trend = (res.data && res.data.trend) || { dates: [], series: [] };
|
||||
var $body = $('#summaryBody').empty();
|
||||
if (summary.length === 0) {
|
||||
$body.append('<tr><td colspan="6" style="text-align:center;color:#999;">暂无蜘蛛来访记录</td></tr>');
|
||||
}
|
||||
var seriesMap = {};
|
||||
(trend.series || []).forEach(function (s) { seriesMap[s.name] = s.data; });
|
||||
summary.forEach(function (row) {
|
||||
var line = seriesMap[row.label] || [];
|
||||
var max = Math.max.apply(null, line.concat([1]));
|
||||
var bars = line.map(function (v, i) {
|
||||
var h = v > 0 ? Math.max(2, Math.round(v / max * 28)) : 1;
|
||||
return '<span title="' + (trend.dates[i] || '') + ': ' + v + '" style="display:inline-block;width:5px;margin-right:1px;height:' + h + 'px;background:' + (v > 0 ? '#1E9FFF' : '#e2e2e2') + ';vertical-align:bottom;"></span>';
|
||||
}).join('');
|
||||
$body.append('<tr>'
|
||||
+ '<td>' + row.label + ' <span style="color:#999;">(' + row.spider + ')</span></td>'
|
||||
+ '<td>' + row.today + '</td>'
|
||||
+ '<td>' + row.week + '</td>'
|
||||
+ '<td>' + row.month + '</td>'
|
||||
+ '<td>' + row.total + '</td>'
|
||||
+ '<td><div style="height:30px;line-height:30px;">' + bars + '</div></td>'
|
||||
+ '</tr>');
|
||||
$('#spiderSelect').append('<option value="' + row.spider + '">' + row.label + '</option>');
|
||||
function loadStats() {
|
||||
http.get('stats').then(function (res) {
|
||||
if (res.code !== 0) { return; }
|
||||
var summary = (res.data && res.data.summary) || [];
|
||||
var trend = (res.data && res.data.trend) || { dates: [], series: [] };
|
||||
var $body = $('#summaryBody').empty();
|
||||
|
||||
// DNS 熔断提示
|
||||
if (res.data && res.data.dns_down) {
|
||||
$('#dnsWarn').show().html('DNS 查询连续失败,真伪校验已临时降级(5 分钟后自动恢复)。请检查服务器 DNS 配置或 /etc/resolv.conf。');
|
||||
} else {
|
||||
$('#dnsWarn').hide();
|
||||
}
|
||||
|
||||
if (summary.length === 0) {
|
||||
$body.append('<tr><td colspan="8" style="text-align:center;color:#999;">暂无蜘蛛来访记录</td></tr>');
|
||||
return;
|
||||
}
|
||||
|
||||
var seriesMap = {};
|
||||
(trend.series || []).forEach(function (s) { seriesMap[s.name] = s.data; });
|
||||
|
||||
// 蜘蛛下拉(去重,避免重复追加)
|
||||
var $sel = $('#spiderSelect');
|
||||
if ($sel.find('option').length <= 1) {
|
||||
summary.forEach(function (row) {
|
||||
$sel.append('<option value="' + row.spider + '">' + row.label + '</option>');
|
||||
});
|
||||
form.render('select');
|
||||
}
|
||||
|
||||
summary.forEach(function (row) {
|
||||
var line = seriesMap[row.label] || [];
|
||||
var max = Math.max.apply(null, line.concat([1]));
|
||||
var bars = line.map(function (v, i) {
|
||||
var h = v > 0 ? Math.max(2, Math.round(v / max * 28)) : 1;
|
||||
return '<span title="' + (trend.dates[i] || '') + ': ' + v + '" style="display:inline-block;width:5px;margin-right:1px;height:' + h + 'px;background:' + (v > 0 ? '#1E9FFF' : '#e2e2e2') + ';vertical-align:bottom;"></span>';
|
||||
}).join('');
|
||||
|
||||
// 不支持 rDNS 校验的蜘蛛,真伪列显示占位说明
|
||||
var realCell, fakeCell;
|
||||
if (row.verifiable === false) {
|
||||
realCell = '<span style="color:#999;" title="该蜘蛛官方不提供 rDNS,无法校验真伪">—</span>';
|
||||
fakeCell = '<span style="color:#999;">—</span>';
|
||||
} else {
|
||||
realCell = '<span style="color:#118c26;">' + (row.real || 0) + '</span>';
|
||||
fakeCell = (row.fake > 0)
|
||||
? '<span style="color:#d81e06;font-weight:600;" title="伪造 UA 的抓取次数">' + row.fake + '</span>'
|
||||
: '0';
|
||||
}
|
||||
|
||||
$body.append('<tr>'
|
||||
+ '<td>' + row.label + ' <span style="color:#999;">(' + row.spider + ')</span></td>'
|
||||
+ '<td>' + row.today + '</td>'
|
||||
+ '<td>' + row.week + '</td>'
|
||||
+ '<td>' + row.month + '</td>'
|
||||
+ '<td>' + row.total + '</td>'
|
||||
+ '<td>' + realCell + '</td>'
|
||||
+ '<td>' + fakeCell + '</td>'
|
||||
+ '<td><div style="height:30px;line-height:30px;">' + bars + '</div></td>'
|
||||
+ '</tr>');
|
||||
});
|
||||
});
|
||||
form.render('select');
|
||||
});
|
||||
}
|
||||
loadStats();
|
||||
|
||||
// 明细表
|
||||
table.render({
|
||||
@@ -61,10 +97,22 @@ layui.define(['form', 'table', 'http', 'auth', 'laydate'], function (exports) {
|
||||
cols: [[
|
||||
{ field: 'id', width: 90, title: 'ID', sort: true },
|
||||
{ field: 'spider_text', width: 110, title: '蜘蛛' },
|
||||
{ field: 'url', title: '抓取URL', minWidth: 240 },
|
||||
{ field: 'verified_text', width: 100, title: '真伪', templet: function (d) {
|
||||
if (d.verified === 1) {
|
||||
return '<span style="color:#118c26;">真蜘蛛</span>';
|
||||
}
|
||||
if (d.verified === 2) {
|
||||
return '<span style="color:#d81e06;font-weight:600;" title="' + (d.rdns || '无 rDNS 记录') + '">假蜘蛛</span>';
|
||||
}
|
||||
return '<span style="color:#999;" title="' + (d.verifiable ? '尚未校验或 DNS 暂不可用' : '该蜘蛛不支持 rDNS 校验') + '">待验证</span>';
|
||||
}},
|
||||
{ field: 'rdns', width: 220, title: 'rDNS', templet: function (d) {
|
||||
return d.rdns ? '<span style="font-size:12px;">' + d.rdns + '</span>' : '<span style="color:#ccc;">—</span>';
|
||||
}},
|
||||
{ field: 'url', title: '抓取URL', minWidth: 220 },
|
||||
{ field: 'ip', width: 140, title: '来源IP' },
|
||||
{ field: 'app', width: 100, title: '应用' },
|
||||
{ field: 'http_code', width: 90, title: '状态码' },
|
||||
{ field: 'app', width: 90, title: '应用' },
|
||||
{ field: 'http_code', width: 80, title: '状态码' },
|
||||
{ field: 'time_text', width: 170, title: '抓取时间' }
|
||||
]],
|
||||
limits: [15, 30, 50, 100],
|
||||
@@ -73,10 +121,7 @@ layui.define(['form', 'table', 'http', 'auth', 'laydate'], function (exports) {
|
||||
skin: 'line',
|
||||
done: function () {
|
||||
var view = $('#dataTable').next('.layui-table-view');
|
||||
view.find('[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
auth.filterDom(view);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -89,12 +134,34 @@ layui.define(['form', 'table', 'http', 'auth', 'laydate'], function (exports) {
|
||||
$('#btn-reset').on('click', function () {
|
||||
$('input[name="ip"],input[name="url"],input[name="date"]').val('');
|
||||
$('#spiderSelect').val('');
|
||||
$('#verifiedSelect').val('');
|
||||
form.render('select');
|
||||
table.reload('dataTable', { page: { curr: 1 }, where: { spider: '', ip: '', url: '', date: '' } });
|
||||
table.reload('dataTable', { page: { curr: 1 }, where: { spider: '', verified: '', ip: '', url: '', date: '' } });
|
||||
});
|
||||
|
||||
// 工具条:清理旧日志
|
||||
// 工具条:重新校验 / 清理旧日志
|
||||
table.on('toolbar(dataTable)', function (elem) {
|
||||
if (elem.event === 'dataRecheck') {
|
||||
layer.confirm('将对历史上「待验证」的蜘蛛记录重新做 rDNS 双向校验(每 IP 一次 DNS 查询,最多 200 个 IP),可能耗时数秒。是否继续?', {
|
||||
title: '重新校验真伪',
|
||||
btn: ['开始校验', '取消']
|
||||
}, function (index) {
|
||||
layer.close(index);
|
||||
var loadIndex = layer.load(2, { shade: [0.3, '#000'] });
|
||||
http.post('recheck', { limit: 200 }).then(function (res) {
|
||||
layer.close(loadIndex);
|
||||
if (res.code === 0) {
|
||||
layer.msg(res.message || '校验完成', { icon: 1, time: 5000 });
|
||||
table.reload('dataTable', {}, true);
|
||||
loadStats();
|
||||
} else {
|
||||
layer.msg(res.msg || res.message || '校验失败', { icon: 2 });
|
||||
}
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (elem.event === 'dataClear') {
|
||||
layer.prompt({ title: '保留最近多少天的明细?(聚合报表不受影响)', value: '30' }, function (value, index) {
|
||||
layer.close(index);
|
||||
|
||||
@@ -34,14 +34,8 @@ layui.define(['form', 'table', 'http', 'auth'], function (exports) {
|
||||
skin: 'line',
|
||||
done: function () {
|
||||
var view = $('#dataTable').next('.layui-table-view');
|
||||
view.find('[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
$('.layui-btn[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
auth.filterDom(view);
|
||||
auth.filterDom('.layui-btn[data-perm]');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -34,14 +34,8 @@ layui.define(['http', 'layer', 'laydate', 'util', 'auth'], function (exports) {
|
||||
]],
|
||||
done: function (res, curr, count) {
|
||||
var view = $('#dataTable').next('.layui-table-view');
|
||||
view.find('[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
$('.layui-btn[data-perm]').each(function () {
|
||||
var perm = $(this).attr('data-perm');
|
||||
if (perm && !auth.has(perm)) { $(this).remove(); }
|
||||
});
|
||||
auth.filterDom(view);
|
||||
auth.filterDom('.layui-btn[data-perm]');
|
||||
}
|
||||
});
|
||||
form.on('submit(searchBtn)', function (data) {
|
||||
@@ -274,23 +268,17 @@ layui.define(['http', 'layer', 'laydate', 'util', 'auth'], function (exports) {
|
||||
// 提交表单
|
||||
form.on('submit(adminFormSubmit)', function (formData) {
|
||||
var url = data ? 'update' : 'create';
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'POST',
|
||||
data: formData.field,
|
||||
success: function (res) {
|
||||
if (res.code === 1) {
|
||||
layer.msg(res.msg, { icon: 1, time: 1000 }, function () {
|
||||
layer.close(index);
|
||||
adminTable.reload();
|
||||
});
|
||||
} else {
|
||||
layer.msg(res.msg, { icon: 2 });
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
layer.msg('操作失败', { icon: 2 });
|
||||
http.post(url, formData.field).then(function (res) {
|
||||
if (res && res.code === 1) {
|
||||
layer.msg(res.msg, { icon: 1, time: 1000 }, function () {
|
||||
layer.close(index);
|
||||
adminTable.reload();
|
||||
});
|
||||
} else {
|
||||
layer.msg((res && res.msg) || '操作失败', { icon: 2 });
|
||||
}
|
||||
}).catch(function (err) {
|
||||
layer.msg('请求失败:' + (err && err.message ? err.message : '网络异常'), { icon: 2 });
|
||||
});
|
||||
|
||||
return false;
|
||||
@@ -312,21 +300,16 @@ layui.define(['http', 'layer', 'laydate', 'util', 'auth'], function (exports) {
|
||||
btn: ['确定', '取消'],
|
||||
title: '重置密码'
|
||||
}, function (index) {
|
||||
$.ajax({
|
||||
url: 'resetPassword',
|
||||
type: 'POST',
|
||||
data: { id: data.uid },
|
||||
success: function (res) {
|
||||
layer.close(index);
|
||||
if (res.code === 1) {
|
||||
layer.msg(res.msg, { icon: 1 });
|
||||
} else {
|
||||
layer.msg(res.msg, { icon: 2 });
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
layer.msg('操作失败', { icon: 2 });
|
||||
http.post('resetPassword', { id: data.uid }).then(function (res) {
|
||||
layer.close(index);
|
||||
if (res && res.code === 1) {
|
||||
layer.msg(res.msg, { icon: 1 });
|
||||
} else {
|
||||
layer.msg((res && res.msg) || '操作失败', { icon: 2 });
|
||||
}
|
||||
}).catch(function (err) {
|
||||
layer.close(index);
|
||||
layer.msg('请求失败:' + (err && err.message ? err.message : '网络异常'), { icon: 2 });
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -338,23 +321,18 @@ layui.define(['http', 'layer', 'laydate', 'util', 'auth'], function (exports) {
|
||||
title: '删除管理员',
|
||||
icon: 3
|
||||
}, function (index) {
|
||||
$.ajax({
|
||||
url: 'delete',
|
||||
type: 'POST',
|
||||
data: { uid: data.uid },
|
||||
success: function (res) {
|
||||
layer.close(index);
|
||||
if (res.code === 1) {
|
||||
layer.msg(res.msg, { icon: 1, time: 1000 }, function () {
|
||||
adminTable.reload();
|
||||
});
|
||||
} else {
|
||||
layer.msg(res.msg, { icon: 2 });
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
layer.msg('操作失败', { icon: 2 });
|
||||
http.post('delete', { uid: data.uid }).then(function (res) {
|
||||
layer.close(index);
|
||||
if (res && res.code === 1) {
|
||||
layer.msg(res.msg, { icon: 1, time: 1000 }, function () {
|
||||
adminTable.reload();
|
||||
});
|
||||
} else {
|
||||
layer.msg((res && res.msg) || '操作失败', { icon: 2 });
|
||||
}
|
||||
}).catch(function (err) {
|
||||
layer.close(index);
|
||||
layer.msg('请求失败:' + (err && err.message ? err.message : '网络异常'), { icon: 2 });
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -380,23 +358,18 @@ layui.define(['http', 'layer', 'laydate', 'util', 'auth'], function (exports) {
|
||||
title: '批量删除',
|
||||
icon: 3
|
||||
}, function (index) {
|
||||
$.ajax({
|
||||
url: 'batchDelete',
|
||||
type: 'POST',
|
||||
data: { ids: ids },
|
||||
success: function (res) {
|
||||
layer.close(index);
|
||||
if (res.code === 1) {
|
||||
layer.msg(res.msg, { icon: 1, time: 1000 }, function () {
|
||||
adminTable.reload();
|
||||
});
|
||||
} else {
|
||||
layer.msg(res.msg, { icon: 2 });
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
layer.msg('操作失败', { icon: 2 });
|
||||
http.post('batchDelete', { ids: ids }).then(function (res) {
|
||||
layer.close(index);
|
||||
if (res && res.code === 1) {
|
||||
layer.msg(res.msg, { icon: 1, time: 1000 }, function () {
|
||||
adminTable.reload();
|
||||
});
|
||||
} else {
|
||||
layer.msg((res && res.msg) || '操作失败', { icon: 2 });
|
||||
}
|
||||
}).catch(function (err) {
|
||||
layer.close(index);
|
||||
layer.msg('请求失败:' + (err && err.message ? err.message : '网络异常'), { icon: 2 });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user