chore: 重写初始提交(清空历史,整理后全量提交)

This commit is contained in:
ywxapp
2026-08-16 16:54:14 +08:00
commit 6c1a106bc1
1808 changed files with 238144 additions and 0 deletions
+271
View File
@@ -0,0 +1,271 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>广告管理</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link href="/assets/layui/css/layui.css" rel="stylesheet">
<link href="/assets/ywxapp/css/ywxapp.css" rel="stylesheet">
<script type="text/javascript">
var config = { root: "/" };
</script>
</head>
<body>
<div class="layui-card">
<div class="layui-form layui-card-header layuiadmin-card-header-auto">
<div class="layui-form-item">
<div class="layui-inline">
<select name="slot" id="slotFilter">
<option value="">全部广告位</option>
</select>
</div>
<div class="layui-inline">
<select name="status">
<option value="">全部状态</option>
<option value="1">启用</option>
<option value="0">禁用</option>
</select>
</div>
<div class="layui-inline">
<button class="layui-btn layuiadmin-btn-useradmin" lay-submit lay-filter="tableSearchButton">
<i class="layui-icon layui-icon-search"></i>
</button>
</div>
</div>
</div>
<div class="layui-card-body">
<table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
</div>
</div>
<script type="text/html" id="dataBar">
<div class="layui-btn-group">
<a class="layui-btn layui-btn-sm layui-btn-primary" title="编辑" lay-event="update"><i class="layui-icon layui-icon-edit"></i></a>
<a class="layui-btn layui-btn-sm layui-btn-primary" title="删除" lay-event="delete"><i class="layui-icon layui-icon-delete"></i></a>
</div>
</script>
<script type="text/html" id="statusTpl">
<input type="checkbox" name="status" value="{{d.id}}" lay-skin="switch" lay-text="启用|禁用" lay-filter="statusSwitch" {{ d.status == 1 ? 'checked' : '' }}>
</script>
<script type="text/html" id="typeTpl">
{{# if(d.type == 1){ }}图片{{# } else { }}代码{{# } }}
</script>
<script type="text/html" id="slotTpl">
{{# var m={'home_top':'首页顶部','home_bottom':'首页底部','detail_inline':'详情页内联','sidebar':'侧边栏'}; }}
{{ m[d.slot] || d.slot }}
</script>
<!-- 添加/编辑表单 -->
<script type="text/html" id="dataFormTpl">
<form class="layui-form" style="padding:20px 20px 0;">
<input type="hidden" name="id" value="{{d.id||''}}">
<div class="layui-form-item">
<label class="layui-form-label">广告位</label>
<div class="layui-input-block">
<select name="slot" lay-verify="required">
<option value="">请选择广告位</option>
{{# d.slots.forEach(function(item){ }}
<option value="{{item.id}}" {{ d.slot == item.id ? 'selected' : '' }}>{{item.title}}</option>
{{# }); }}
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">标题</label>
<div class="layui-input-block">
<input type="text" name="title" required lay-verify="required" placeholder="广告标题" class="layui-input" value="{{d.title||''}}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">类型</label>
<div class="layui-input-block">
<input type="radio" name="type" value="1" title="图片广告" {{ d.type==undefined || d.type==1 ? 'checked' : '' }}>
<input type="radio" name="type" value="2" title="代码广告" {{ d.type==2 ? 'checked' : '' }}>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">图片</label>
<div class="layui-input-block">
<input type="text" name="image" placeholder="图片URL(图片广告必填)" class="layui-input" value="{{d.image||''}}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">跳转链接</label>
<div class="layui-input-block">
<input type="text" name="url" placeholder="点击图片后跳转的URL" class="layui-input" value="{{d.url||''}}">
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">广告代码</label>
<div class="layui-input-block">
<textarea name="code" placeholder="自定义HTML/JS代码(联盟广告等),代码广告必填" class="layui-textarea">{{d.code||''}}</textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">排序</label>
<div class="layui-input-block">
<input type="number" name="sort" class="layui-input" value="{{d.sort||0}}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<input type="radio" name="status" value="1" title="启用" {{ d.status==undefined || d.status==1 ? 'checked' : '' }}>
<input type="radio" name="status" value="0" title="禁用" {{ d.status==0 ? 'checked' : '' }}>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">生效起</label>
<div class="layui-input-block">
<input type="text" name="start_at" id="startAt" placeholder="留空=立即生效" class="layui-input" value="{{d.start_at_text||''}}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">生效止</label>
<div class="layui-input-block">
<input type="text" name="end_at" id="endAt" placeholder="留空=长期有效" class="layui-input" value="{{d.end_at_text||''}}">
</div>
</div>
<div class="layui-form-item layui-hide">
<button class="layui-btn" lay-submit="" lay-filter="wxapp-form-submit" id="wxapp-form-submit">提交</button>
</div>
</form>
</script>
<script src="/assets/layui/layui.js"></script>
<script src="/assets/ywxapp/ywxapp.js" module="backend"></script>
<script>
layui.use(['http', 'table', 'form', 'laytpl', 'laydate', 'layer'], function () {
var $ = layui.$, table = layui.table, form = layui.form, laytpl = layui.laytpl, laydate = layui.laydate, http = layui.http, layer = layui.layer;
var slots = [];
http.get('/haonav/backend/ad/create').then(function (res) {
if (res.code === 0) {
slots = res.data || [];
var sel = document.getElementById('slotFilter');
slots.forEach(function (s) { sel.appendChild(new Option(s.title, s.id)); });
form.render('select');
} else { layer.msg(res.msg || '加载失败'); }
});
var dataTable = table.render({
elem: '#dataTable',
defaultToolbar: [{ title: '创建广告', layEvent: 'dataCreate', icon: 'layui-icon-add-1' }, 'filter', 'exports', 'print' ],
url: '/haonav/backend/ad/index',
page: true, limit: 20,
parseData: function (res) {
return { code: res.code, msg: res.message || '', count: res.count || 0, data: res.data || [] };
},
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ field: 'id', title: 'ID', width: 70, sort: true },
{ field: 'slot', title: '广告位', width: 110, templet: '#slotTpl' },
{ field: 'title', title: '标题', minWidth: 120 },
{ field: 'type', title: '类型', width: 90, templet: '#typeTpl' },
{ field: 'image', title: '预览', width: 90, align: 'center', templet: function (d) { return d.type == 1 && d.image ? '<img src="' + d.image + '" style="max-height:32px;max-width:60px;">' : (d.type == 2 ? '<span style="color:#999">代码</span>' : '-'); } },
{ field: 'url', title: '链接', minWidth: 120, templet: function (d) { return d.url ? '<a href="' + d.url + '" target="_blank" style="color:#01AAED;">' + d.url + '</a>' : '-'; } },
{ field: 'click_count', title: '点击', width: 80, sort: true },
{ field: 'sort', title: '排序', width: 80, sort: true, edit: 'text' },
{ field: 'status', title: '状态', width: 90, align: 'center', templet: '#statusTpl', unresize: true },
{ field: 'create_at', title: '创建时间', width: 160, align: 'center', templet: function (d) { return d.create_at ? d.create_at : '-'; } },
{ title: '操作', width: 110, align: 'center', toolbar: '#dataBar', fixed: 'right' }
]]
});
table.on('toolbar(dataTable)', function (obj) {
if (obj.event === 'dataCreate') { active.dataCreate(); }
});
table.on('edit(dataTable)', function (obj) {
if (obj.field === 'sort') {
http.post('sort', { id: obj.data.id, sort: obj.value }).then(function (res) {
if (res.code === 0) { layer.msg(res.message || '已保存'); }
else { layer.msg(res.message || '保存失败'); table.reload('dataTable', {}, true); }
});
}
});
table.on('tool(dataTable)', function (elem) {
if (elem.event === 'update') { active.dataEdit(elem.data); }
else if (elem.event === 'delete') { active.dataDelete([elem.data.id]); }
});
form.on('submit(tableSearchButton)', function (data) {
table.reload('dataTable', { where: data.field });
});
form.on('switch(statusSwitch)', function (obj) {
var id = this.value, st = obj.elem.checked ? 1 : 0;
http.post('status', { id: id, status: st }).then(function (res) {
if (res.code === 0) { layer.msg(res.message || '已保存'); }
else { layer.msg(res.message || '失败'); obj.elem.checked = !obj.elem.checked; form.render('checkbox'); }
});
});
var dataFromFun = function (data, callback, done) {
var html = laytpl($('#dataFormTpl').html()).render(data || {});
layer.open({
title: data.id ? '编辑广告' : '添加广告',
content: html, anim: 'slideLeft', offset: 'r',
btnAlign: 'l', area: ['440px', '99%'], shade: 0.1, shadeClose: true,
btn: ['确定', '取消'],
success: function (layero, index) {
callback(layero, index);
form.render();
laydate.render({ elem: '#startAt', type: 'datetime' });
laydate.render({ elem: '#endAt', type: 'datetime' });
},
yes: function (index, layero) {
layui.form.on('submit(wxapp-form-submit)', function (elem) {
done(layero, index, elem); layui.off('submit(wxapp-form-submit)'); return false;
});
layero.contents().find('#wxapp-form-submit').trigger('click');
}
});
};
var active = {
dataCreate: function () {
dataFromFun({ slots: slots }, function () { }, function (layero, index, elem) {
var field = elem.field;
http.post('save', field).then(function (res) {
if (res.code === 0) { layer.close(index); table.reload('dataTable', {}, true); }
else { layer.msg(res.message || '失败'); }
});
});
},
dataEdit: function (data) {
http.get('edit?id=' + data.id).then(function (res) {
if (res.code !== 0) { return layer.msg(res.msg || '加载失败'); }
var d = res.data.info || {};
d.slots = res.data.slots || slots;
dataFromFun(d, function () { }, function (layero, index, elem) {
var field = elem.field;
http.put('update', field).then(function (r) {
if (r.code === 0) { layer.close(index); table.reload('dataTable', {}, true); }
else { layer.msg(r.message || '失败'); }
});
});
});
},
dataDelete: function (ids) {
if (!ids.length) { return layer.msg('请选择数据'); }
layer.confirm('确定删除吗?', function (index) {
http.delete('delete', { ids: ids.join(',') }).then(function (res) {
if (res.code === 0) { layer.close(index); table.reload('dataTable', {}, true); }
else { layer.msg(res.message || '失败'); }
});
});
}
};
});
</script>
</body>
</html>
+185
View File
@@ -0,0 +1,185 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>申请审核</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link href="/assets/layui/css/layui.css" rel="stylesheet">
<link href="/assets/ywxapp/css/ywxapp.css" rel="stylesheet">
<script type="text/javascript">
var config = { root: "/" };
</script>
</head>
<body>
<div class="layui-card">
<div class="layui-form layui-card-header layuiadmin-card-header-auto">
<div class="layui-form-item">
<div class="layui-inline">
<select name="type">
<option value="">全部类型</option>
<option value="1">收录/友链</option>
<option value="2">广告合作</option>
</select>
</div>
<div class="layui-inline">
<select name="status">
<option value="">全部状态</option>
<option value="0">待审核</option>
<option value="1">已通过</option>
<option value="2">已拒绝</option>
</select>
</div>
<div class="layui-inline">
<button class="layui-btn layuiadmin-btn-useradmin" lay-submit lay-filter="tableSearchButton">
<i class="layui-icon layui-icon-search"></i>
</button>
</div>
</div>
</div>
<div class="layui-card-body">
<table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
</div>
</div>
<script type="text/html" id="dataBar">
<div class="layui-btn-group">
{{# if(d.status == 0){ }}
<a class="layui-btn layui-btn-sm layui-btn-normal" title="通过" lay-event="approve"><i class="layui-icon layui-icon-ok"></i></a>
<a class="layui-btn layui-btn-sm layui-btn-warm" title="拒绝" lay-event="reject"><i class="layui-icon layui-icon-close"></i></a>
{{# } }}
<a class="layui-btn layui-btn-sm layui-btn-primary" title="删除" lay-event="delete"><i class="layui-icon layui-icon-delete"></i></a>
</div>
</script>
<script type="text/html" id="typeTpl">
{{# if(d.type == 1){ }}<span class="layui-badge layui-bg-blue">收录/友链</span>{{# } else { }}<span class="layui-badge layui-bg-orange">广</span>{{# } }}
</script>
<script type="text/html" id="statusTpl">
{{# if(d.status == 0){ }}<span class="layui-badge layui-bg-gray">待审核</span>
{{# } else if(d.status == 1){ }}<span class="layui-badge layui-bg-green">已通过</span>
{{# } else { }}<span class="layui-badge">已拒绝</span>{{# } }}
</script>
<!-- 通过(友链需选分类) -->
<script type="text/html" id="approveFormTpl">
<form class="layui-form" style="padding:20px 20px 0;">
<input type="hidden" name="id" value="{{d.id}}">
{{# if(d.type == 1){ }}
<div class="layui-form-item">
<label class="layui-form-label">收录分类</label>
<div class="layui-input-block">
<select name="cid" lay-verify="required">
<option value="">请选择分类</option>
{{# d.cates.forEach(function(c){ }}
<option value="{{c.id}}">{{c.title}}</option>
{{# }); }}
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">上架方式</label>
<div class="layui-input-block">
<input type="radio" name="online" value="1" title="直接上架" checked>
<input type="radio" name="online" value="0" title="进待审核">
</div>
</div>
{{# } }}
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">审核备注</label>
<div class="layui-input-block">
<textarea name="reply" placeholder="选填,将保留在申请记录中" class="layui-textarea"></textarea>
</div>
</div>
<div class="layui-form-item layui-hide">
<button class="layui-btn" lay-submit="" lay-filter="wxapp-form-submit" id="wxapp-form-submit">提交</button>
</div>
</form>
</script>
<script src="/assets/layui/layui.js"></script>
<script src="/assets/ywxapp/ywxapp.js" module="backend"></script>
<script>
layui.use(['http', 'table', 'form', 'laytpl', 'layer'], function () {
var $ = layui.$, table = layui.table, form = layui.form, laytpl = layui.laytpl, http = layui.http, layer = layui.layer;
var cates = [];
http.get('/haonav/backend/apply/create').then(function (res) {
if (res.code === 0) { cates = res.data || []; }
});
table.render({
elem: '#dataTable',
defaultToolbar: ['filter', 'exports', 'print'],
url: '/haonav/backend/apply/index',
page: true, limit: 20,
parseData: function (res) {
return { code: res.code, msg: res.message || '', count: res.count || 0, data: res.data || [] };
},
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ field: 'id', title: 'ID', width: 70, sort: true },
{ field: 'type', title: '类型', width: 110, templet: '#typeTpl' },
{ field: 'title', title: '名称', minWidth: 120 },
{ field: 'url', title: '网址', minWidth: 160, templet: function (d) { return d.url ? '<a href="' + d.url + '" target="_blank" rel="nofollow" style="color:#01AAED;">' + d.url + '</a>' : '-'; } },
{ field: 'contact', title: '联系方式', width: 140 },
{ field: 'slot', title: '意向广告位', width: 110, templet: function (d) { var m = { 'home_top': '首页顶部', 'home_bottom': '首页底部', 'detail_inline': '详情页内联', 'sidebar': '侧边栏' }; return m[d.slot] || (d.slot || '-'); } },
{ field: 'description', title: '说明', minWidth: 140 },
{ field: 'status', title: '状态', width: 90, align: 'center', templet: '#statusTpl' },
{ field: 'reply', title: '审核备注', minWidth: 110, templet: function (d) { return d.reply || '-'; } },
{ field: 'ip', title: 'IP', width: 130 },
{ field: 'create_at', title: '提交时间', width: 160, align: 'center', templet: function (d) { return d.create_at || '-'; } },
{ title: '操作', width: 150, align: 'center', toolbar: '#dataBar', fixed: 'right' }
]]
});
form.on('submit(tableSearchButton)', function (data) {
table.reload('dataTable', { where: data.field });
});
table.on('tool(dataTable)', function (elem) {
var d = elem.data;
if (elem.event === 'approve') {
d.cates = cates;
var html = laytpl($('#approveFormTpl').html()).render(d);
layer.open({
title: '通过申请 #' + d.id,
content: html, anim: 'slideLeft', offset: 'r',
btnAlign: 'l', area: ['420px', '99%'], shade: 0.1, shadeClose: true,
btn: ['确定', '取消'],
success: function () { form.render(); },
yes: function (index, layero) {
layui.form.on('submit(wxapp-form-submit)', function (el) {
http.post('approve', el.field).then(function (res) {
if (res.code === 0) { layer.msg(res.message || '已通过'); layer.close(index); table.reload('dataTable', {}, true); }
else { layer.msg(res.message || '失败'); }
});
layui.off('submit(wxapp-form-submit)'); return false;
});
layero.contents().find('#wxapp-form-submit').trigger('click');
}
});
} else if (elem.event === 'reject') {
layer.prompt({ formType: 2, title: '拒绝原因(将保留在记录中,可留空)' }, function (val, index) {
http.post('reject', { id: d.id, reply: val || '' }).then(function (res) {
if (res.code === 0) { layer.msg(res.message || '已拒绝'); layer.close(index); table.reload('dataTable', {}, true); }
else { layer.msg(res.message || '失败'); }
});
});
} else if (elem.event === 'delete') {
layer.confirm('确定删除该申请吗?', function (index) {
http.delete('delete', { ids: String(d.id) }).then(function (res) {
if (res.code === 0) { layer.close(index); table.reload('dataTable', {}, true); }
else { layer.msg(res.message || '失败'); }
});
});
}
});
});
</script>
</body>
</html>
@@ -0,0 +1,420 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{$title | default='YwxApp'}</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link href="/assets/layui/css/layui.css" rel="stylesheet">
<link href="/assets/ywxapp/css/ywxapp.css" rel="stylesheet">
<!-- HTML5 shim, for IE6-8 support of HTML5 elements. All other JS at the end of file. -->
<!--[if lt IE 9]>
<script src="__CDN__/assets/js/html5shiv.js"></script>
<script src="__CDN__/assets/js/respond.min.js"></script>
<![endif]-->
<script type="text/javascript">
var config = {
root: "/",
};
var require = {
};
</script>
</head>
<body>
<div class="layui-card">
<div class="layui-form layui-card-header layuiadmin-card-header-auto">
<div class="layui-form-item">
<div class="layui-inline">
<input type="text" name="title" placeholder="请输入标题" autocomplete="off" class="layui-input">
</div>
<div class="layui-inline">
<button class="layui-btn layuiadmin-btn-useradmin" lay-submit lay-filter="tableSearchButton">
<i class="layui-icon layui-icon-search layuiadmin-button-btn"></i>
</button>
</div>
</div>
</div>
<div class="layui-card-body">
<table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
</div>
</div>
<script type="text/html" id="dataBarTpl">
<div class="layui-btn-group">
<a class="layui-btn layui-btn-sm layui-btn-primary" title="编辑节点" lay-event="update"><i class="layui-icon layui-icon-edit"></i> </a>
<a class="layui-btn layui-btn-sm layui-btn-primary" title="添加子节点" lay-event="create"><i class="layui-icon layui-icon-add-1"></i> </a>
<a class="layui-btn layui-btn-sm layui-btn-primary" title="删除节点" lay-event="delete"><i class="layui-icon layui-icon-delete"></i> </a>
</div>
</script>
<!-- 添加/编辑菜单表单模板 -->
<script type="text/html" id="dataFormTpl">
<form class="layui-form layui-form-pane" lay-filter="wxapp-form" id="wxapp-form">
<input type="hidden" name="id" value="{{= d.id || '' }}">
<input type="hidden" name="pid" value="{{= d.pid || 0 }}">
<div class="layui-form-item">
<label class="layui-form-label">标题</label>
<div class="layui-input-block">
<input type="text" name="title" required lay-verify="required" placeholder="请输入标题" value="{{ d.title || '' }}" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">图标</label>
<div class="layui-input-block">
<input type="text" name="icon" placeholder="请选择图标" value="{{ d.icon ? d.icon.replace('layui-icon ', '') : '' }}" class="layui-input">
</div>
</div>
<div class="layui-form-item ">
<label class="layui-form-label">摘要</label>
<div class="layui-input-block">
<input name="summary" placeholder="请输入摘要" class="layui-input" value="{{ d.summary || '' }}" />
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">关键词</label>
<div class="layui-input-block">
<textarea name="keywords" placeholder="请输入关键词" class="layui-textarea">{{d.keywords||''}}</textarea>
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">描述</label>
<div class="layui-input-block">
<textarea name="description" placeholder="请输入描述信息" class="layui-textarea">{{d.description||''}}</textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">排序</label>
<div class="layui-input-block">
<input type="number" name="sort" value="{{ d.sort || 0 }}" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<input type="checkbox" name="status" lay-skin="switch" lay-text="启用|禁用" {{ d.status != 0 ? 'checked' : (!d.id ? 'checked' : '') }}>
</div>
</div>
<div class="layui-form-item layui-hide">
<button class="layui-btn" lay-submit="" lay-filter="wxapp-form-submit" id="wxapp-form-submit">提交</button>
</div>
</form>
</script>
<!-- 状态开关 -->
<script type="text/html" id="statusTpl">
<input type="checkbox" name="status" value="{{d.id}}" lay-skin="switch" lay-text="启用|禁用" lay-filter="statusSwitch" {{ d.status == 1 ? 'checked' : '' }}>
</script>
<script type="text/html" id="dataRecybinTpl">
<div class="layui-card">
<div class="layui-card-header"> </div>
<div class="layui-card-body">
<table class="layui-hide" id="dataRecybinTable" lay-filter="dataRecybinTable"></table>
</div>
</div>
</script>
</script>
<script src="/assets/layui/layui.js"></script>
<script src="/assets/ywxapp/ywxapp.js" module="backend"></script>
<script>
layui.use(['layer', 'http'], function () {
var $ = layui.$;
var treeTable = layui.treeTable;
var form = layui.form;
var layer = layui.layer;
var laytpl = layui.laytpl;
var http = layui.http;
console.log('layui.treeTable:', layui.setter.layerArea()); // 调试输出
treeTable.render({
defaultToolbar: [{
title: '创建资源', layEvent: 'dataCreate', icon: 'layui-icon-add-1'
}, {
title: '删除资源', layEvent: 'dataDelete', icon: 'layui-icon-delete'
}, {
title: '资源回收站', layEvent: 'dataRecybin', icon: 'layui-icon-home'
}, 'filter', 'exports', 'print'
],
elem: '#dataTable',
url: 'category/index',
parseData: function (res) { // 预处理返回数据
console.log('原始数据:', res); // 调试输入
return {
code: res.code === 0 ? 0 : 1, // 0 标识成功
data: res.data || [], // 数据数组
msg: res.message || '' // 错误信息
};
},
tree: {
customName: {
children: "children",
isParent: "is_parent",
name: "title",
id: "id",
pid: "pid",
icon: "icon"
},
data: { isSimpleData: true, rootPid: 0 },
view: {},
async: {},
callback: {}
},
height: 'full-100',
toolbar: '#tableBarTpl',
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ field: 'id', title: 'ID', width: 80, sort: true, fixed: 'left' },
{ field: 'title', title: '名称', width: 180, fixed: 'left' },
{ field: 'icon', title: '图标', width: 80 },
{ field: 'sort', title: '排序', width: 80, sort: true },
{ field: 'status', title: '状态', width: 96, align: 'center', templet: '#statusTpl' },
{ fixed: "right", title: "操作", align: "center", toolbar: "#dataBarTpl", fixed: 'right' }
]],
page: true,
done: function (res, curr, count, origin) { }
});
treeTable.on('toolbar(dataTable)', function (obj) {
var options = obj.config;
switch (obj.event) {
case 'dataCreate':
active.dataCreate({ pid: 0, type: 1 });
break;
case 'dataDelete':
var checkStatus = treeTable.checkStatus('dataTable'), checkData = checkStatus.data;
let ids = checkData.map((item, index, array) => { return item.id; });
active.dataDelete(ids);
break;
case 'dataRecybin':
active.dataRecybin();
break;
};
});
treeTable.on('tool(dataTable)', function (elem) {
var data = elem.data;
switch (elem.event) {
case 'update':
active.dataEdit(data);
break;
case 'create':
active.dataCreate({ pid: data.id });
break;
case 'delete':
active.dataDelete([data.id]);
break;
default:
break;
}
});
form.on('submit(tableSearchButton)', function (data) {
var field = data.field;
treeTable.reload('dataTable', {
where: field
});
});
// 状态开关
form.on('switch(statusSwitch)', function (obj) {
var id = this.value;
var status = obj.elem.checked ? 1 : 0;
layer.confirm('确定要' + (status ? '启用' : '禁用') + '该数据吗?', function (index) {
layui.http.put('update', { id: id, status: status }).then(function (res) {
if (res.code == 0) {
layer.msg(res.msg, { icon: 1 });
table.reload('dataTable', {}, true);
} else {
layer.msg(res.msg, { icon: 2 });
obj.elem.checked = !obj.elem.checked;
form.render('checkbox');
}
});
layer.close(index);
}, function () {
obj.elem.checked = !obj.elem.checked;
form.render('checkbox');
});
});
var dataFromFun = function (data, callback, done) {
var formHtml = laytpl($('#dataFormTpl').html()).render(data || {});
layer.open({
title: data.id ? '编辑数据' : (data.pid ? '添加子数据' : '添加根数据'),
content: formHtml,
anim: "slideLeft",
offset: "r",
btnAlign: "l",
area: layui.setter.layerArea(),
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 = {
dataCreate: function (data = {}) {
dataFromFun(data,
function (layero, index) { },
function (layero, index, elem) {
var field = elem.field;
field.icon = 'layui-icon ' + field.icon;
field.status = field.status ? 1 : 0;
http.post('save', field)
.then((res) => {
if (res.code === 0) {
layer.close(index);
treeTable.reload('dataTable', {}, true);
} else {
layer.msg(res.msg || '操作失败');
}
});
}
);
},
dataEdit: function (data = {}) {
dataFromFun(data,
function (layero, index) { },
function (layero, index, elem) {
var field = elem.field;
field.icon = 'layui-icon ' + field.icon;
field.status = field.status ? 1 : 0;
http.put('update', field)
.then((res) => {
if (res.code === 0) {
layer.close(index);
treeTable.reload('dataTable', {}, true);
} else {
layer.msg(res.msg || '操作失败');
}
});
}
);
},
dataDelete: function (ids, force = 0) {
if (ids.length === 0) {
return layer.msg('请选择数据');
}
layer.prompt({
formType: 1
, title: '敏感操作,请验证口令'
}, function (value, index) {
layer.close(index);
layer.confirm('确定删除吗?', function (index) {
http.delete('delete', { ids: ids.join(','), force: force })
.then((res) => {
if (res.code === 0) {
layer.close(index);
treeTable.reload('dataTable', {}, true);
} else {
layer.msg(res.msg || '操作失败');
}
});
layer.msg('已删除');
treeTable.reload('dataTable', {}, true);
});
});
},
dataRecybin: function (data = {}) {
var formHtml = laytpl($('#dataRecybinTpl').html()).render(data || {});
layer.open({
title: '资源回收站',
content: formHtml,
anim: "slideLeft",
offset: "r",
area: ['60%', '99%'],
shade: 0.1,
shadeClose: true,
success: function (layero, index) {
layui.table.render({
elem: '#dataRecybinTable',
url: 'recyclebin',
height: 'full-100',
defaultToolbar: [{
title: '批量删除数据',
layEvent: 'dataDelete',
icon: 'layui-icon-delete',
onClick: function (obj) {
var checkStatus = treeTable.checkStatus('dataRecybinTable'), checkData = checkStatus.data;
let ids = checkData.map((item, index, array) => { return item.id; });
console.log(ids);
}
}, 'filter', 'exports', 'print'],
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ field: 'id', title: 'ID', width: 80, sort: true, fixed: 'left' },
{ field: 'title', title: '标题', width: 180, fixed: 'left' },
{
fixed: "right", title: "操作", width: 120, align: "center", templet: function (d) {
return `<div class="layui-btn-group">
<a class="layui-btn layui-btn-sm" title="恢复数据" lay-event="restore" > <i class="layui-icon layui-icon-edit"></i> </a >
<a class="layui-btn layui-btn-sm" title="删除数据" lay-event="delete"><i class="layui-icon layui-icon-delete"></i> </a>
</div >`;
}
}
]],
page: true,
done: function (res, curr, count, origin) {
layui.table.on('tool(dataRecybinTable)', function (elem) {
var data = elem.data;
switch (elem.event) {
case 'restore':
active.dataRestore([data.id]);
break;
case 'delete':
active.dataDelete([data.id], 1);
break;
}
});
layui.table.on('toolbar(dataRecybinTable)', function (elem) {
switch (elem.event) {
case 'dataDelete':
var checkStatus = table.checkStatus('dataRecybinTable'), checkData = checkStatus.data;
let idss = checkData.map((item, index, array) => { return item.id; });
active.dataDelete(idss, 1);
break;
case 'dataRestore':
var checkStatus = table.checkStatus('dataRecybinTable'), checkData = checkStatus.data;
let ids = checkData.map((item, index, array) => { return item.id; });
active.dataRestore(ids);
break;
};
});
}
});
}
});
},
dataRestore: function (ids) {
if (ids.length === 0) {
return layer.msg('请选择数据');
}
http.put('restore', { ids: ids.join(',') })
.then((res) => {
if (res.code === 0) {
layer.msg('恢复成功');
table.reload('dataTable', {}, true);
} else {
layer.msg(res.msg || '恢复失败');
}
});
}
};
});
</script>
</body>
</html>
@@ -0,0 +1,351 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{$title | default='YwxApp'}</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link href="/assets/layui/css/layui.css" rel="stylesheet">
<link href="/assets/ywxapp/css/ywxapp.css" rel="stylesheet">
<!-- HTML5 shim, for IE6-8 support of HTML5 elements. All other JS at the end of file. -->
<!--[if lt IE 9]>
<script src="__CDN__/assets/js/html5shiv.js"></script>
<script src="__CDN__/assets/js/respond.min.js"></script>
<![endif]-->
<script type="text/javascript">
var config = {
root: "/",
};
var require = {
};
</script>
<style>
body {
padding: 20px;
background-color: #f2f2f2;
}
.form-container {
background: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.layui-tab-content {
padding: 20px 0;
}
.form-item-tip {
color: #999;
font-size: 12px;
margin-top: 5px;
}
.form-group-title {
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
font-weight: bold;
color: #333;
}
</style>
</head>
<body>
<div class="form-container">
<div id="formView"></div>
</div>
<!-- 表单模板 -->
<script type="text/html" id="formTpl">
<div class="layui-tab layui-tab-brief" lay-filter="formTab">
<ul class="layui-tab-title">
{{# layui.each(d.groups, function(index, group){ }}
<li class="{{# if(index === 0){ }}layui-this{{# } }}">{{ group }}</li>
{{# }); }}
</ul>
<div class="layui-tab-content">
{{# layui.each(d.groups, function(groupIndex, group){ }}
<div class="layui-tab-item {{# if(groupIndex === 0){ }}layui-show{{# } }}">
<form class="layui-form" lay-filter="form-{{ group }}">
<div class="form-group-title">{{ group }} 设置</div>
{{# layui.each(d.data[group], function(fieldIndex, field){ }}
<div class="layui-form-item">
<label class="layui-form-label">{{ field.title }}</label>
<div class="layui-input-block">
{{# if(field.type === 'string' || field.type === 'text'){ }}
<!-- 文本输入框 -->
<input type="text"
name="{{ field.name }}"
value="{{ field.value || '' }}"
placeholder="{{ field.tip || '请输入' + field.title }}"
class="layui-input"
{{# if(field.rule === 'required'){ }}lay-verify="required"{{# } }}>
{{# } else if(field.type === 'textarea'){ }}
<!-- 文本域 -->
<textarea name="{{ field.name }}"
placeholder="{{ field.tip || '请输入' + field.title }}"
class="layui-textarea"
{{# if(field.rule === 'required'){ }}lay-verify="required"{{# } }}>{{ field.value || '' }}</textarea>
{{# } else if(field.type === 'select'){ }}
<!-- 下拉选择框 -->
<select name="{{ field.name }}"
{{# if(field.rule === 'required'){ }}lay-verify="required"{{# } }}>
<option value="">请选择{{ field.title }}</option>
{{#
var options = field.content ? field.content.split(',') : [];
layui.each(options, function(optIndex, option){
}}
<option value="{{ option }}" {{# if(field.value == option){ }}selected{{# } }}>{{ option }}</option>
{{# }); }}
</select>
{{# } else if(field.type === 'checkbox'){ }}
<!-- 复选框 -->
{{#
var checkboxes = field.content ? field.content.split(',') : [];
layui.each(checkboxes, function(cbIndex, cbValue){
}}
<input type="checkbox"
name="{{ field.name }}"
title="{{ cbValue }}"
value="{{ cbValue }}"
{{# if(field.value && field.value.indexOf(cbValue) !== -1){ }}checked{{# } }}
lay-skin="primary">
{{# }); }}
{{# } else if(field.type === 'radio'){ }}
<!-- 单选框 -->
{{#
var radios = field.content ? field.content.split(',') : [];
layui.each(radios, function(rdIndex, rdValue){
}}
<input type="radio"
name="{{ field.name }}"
title="{{ rdValue }}"
value="{{ rdValue }}"
{{# if(field.value == rdValue){ }}checked{{# } }}>
{{# }); }}
{{# } else if(field.type === 'number'){ }}
<!-- 数字输入框 -->
<input type="number"
name="{{ field.name }}"
value="{{ field.value || '' }}"
placeholder="{{ field.tip || '请输入' + field.title }}"
class="layui-input"
{{# if(field.rule === 'required'){ }}lay-verify="required|number"{{# } }}>
{{# } else if(field.type === 'date'){ }}
<!-- 日期选择器 -->
<input type="text"
name="{{ field.name }}"
value="{{ field.value || '' }}"
placeholder="{{ field.tip || '请选择日期' }}"
class="layui-input"
id="date-{{ field.id }}">
{{# } }}
{{# if(field.tip){ }}
<div class="form-item-tip">{{ field.tip }}</div>
{{# } }}
</div>
</div>
{{# }); }}
<div class="layui-form-item">
<div class="layui-input-block">
<button type="button" class="layui-btn" lay-submit lay-filter="submit-{{ group }}">保存设置</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</div>
</form>
</div>
{{# }); }}
</div>
</div>
</script>
<script src="/assets/layui/layui.js"></script>
<script src="/assets/ywxapp/ywxapp.js" module="backend"></script>
<script>// 原始数据
var formData = [
{
"id": 1,
"name": "site_name",
"group": "basic",
"title": "站点名称",
"tip": "请填写站点名称",
"type": "string",
"value": "YFCMF-TP6",
"content": "",
"rule": "required",
"extend": "",
"setting": ""
},
{
"id": 2,
"name": "beian",
"group": "basic",
"title": "备案号",
"tip": "",
"type": "string",
"value": "",
"content": "",
"rule": "",
"extend": "",
"setting": ""
},
{
"id": 3,
"name": "site_description",
"group": "basic",
"title": "站点描述",
"tip": "请填写站点描述",
"type": "textarea",
"value": "这是一个基于TP6的CMS系统",
"content": "",
"rule": "",
"extend": "",
"setting": ""
},
{
"id": 4,
"name": "site_status",
"group": "advanced",
"title": "站点状态",
"tip": "选择站点是否开启",
"type": "select",
"value": "1",
"content": "开启,关闭,维护中",
"rule": "required",
"extend": "",
"setting": ""
},
{
"id": 5,
"name": "allow_comment",
"group": "advanced",
"title": "允许评论",
"tip": "是否允许访客评论",
"type": "radio",
"value": "1",
"content": "是,否",
"rule": "",
"extend": "",
"setting": ""
},
{
"id": 6,
"name": "features",
"group": "advanced",
"title": "功能模块",
"tip": "选择启用的功能模块",
"type": "checkbox",
"value": "文章,评论",
"content": "文章,评论,用户,统计",
"rule": "",
"extend": "",
"setting": ""
},
{
"id": 7,
"name": "max_upload_size",
"group": "upload",
"title": "最大上传大小",
"tip": "单位:MB",
"type": "number",
"value": "10",
"content": "",
"rule": "required",
"extend": "",
"setting": ""
},
{
"id": 8,
"name": "upload_date",
"group": "upload",
"title": "上传日期限制",
"tip": "选择可上传文件的日期范围",
"type": "date",
"value": "2024-01-01",
"content": "",
"rule": "",
"extend": "",
"setting": ""
}
];
// 处理数据:按group分组
function processData(data) {
var groups = {};
var groupNames = [];
// 收集所有分组
data.forEach(function (item) {
if (!groups[item.group]) {
groups[item.group] = [];
groupNames.push(item.group);
}
groups[item.group].push(item);
});
return {
groups: groupNames,
data: groups
};
}
// 初始化Layui
layui.use(['laytpl', 'form', 'element', 'laydate', 'http'], function (exports) {
var laytpl = layui.laytpl;
var form = layui.form;
var element = layui.element;
var laydate = layui.laydate;
var http = layui.http;
http.get('index').then(respon => {
let respona = processData(respon.data && respon.data.length ? respon.data : formData);
var getTpl = document.getElementById('formTpl').innerHTML;
var view = document.getElementById('formView');
laytpl(getTpl).render(respona, function (html) {
view.innerHTML = html;
// 重新渲染表单元素
form.render();
element.render('tab');
// 初始化日期选择器
formData.forEach(function (item) {
if (item.type === 'date') {
laydate.render({
elem: '#date-' + item.id,
format: 'yyyy-MM-dd'
});
}
});
// 监听表单提交
form.on('submit', function (data) {
console.log('表单提交数据:', data.field);
http.post('update', data.field).then(respon => {
console.log(respon)
});
layer.msg('表单提交成功!已输出到控制台');
return false;
});
});
})
// 处理数据
//var processedData = processData(formData);
// 渲染模板
// exports('configure', {});
});
</script>
</body>
</html>
@@ -0,0 +1,155 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{$title | default='数据概览'}</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link href="/assets/layui/css/layui.css" rel="stylesheet">
<link href="/assets/ywxapp/css/ywxapp.css" rel="stylesheet">
<style>
body { padding: 16px; background: #f5f6fa; }
.stat-cards { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 16px; }
.stat-card { background: #fff; border-radius: 8px; padding: 16px; box-shadow: 0 1px 6px rgba(0,0,0,.06); }
.stat-card .num { font-size: 26px; font-weight: 700; color: #1E9FFF; }
.stat-card .lbl { color: #666; font-size: 13px; margin-top: 4px; }
.panel { background: #fff; border-radius: 8px; padding: 16px; margin-bottom: 16px; box-shadow: 0 1px 6px rgba(0,0,0,.06); }
.panel h3 { font-size: 15px; margin-bottom: 12px; border-left: 3px solid #1E9FFF; padding-left: 8px; }
.bar-row { display: flex; align-items: center; margin-bottom: 8px; }
.bar-row .name { width: 120px; color: #333; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bar-row .bar { flex: 1; background: #eef1f6; border-radius: 4px; height: 16px; margin: 0 8px; overflow: hidden; }
.bar-row .bar > i { display: block; height: 100%; background: linear-gradient(90deg,#1E9FFF,#5FB878); }
.bar-row .val { width: 60px; text-align: right; color: #999; font-size: 12px; }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 900px) { .stat-cards { grid-template-columns: repeat(2,1fr); } .grid2 { grid-template-columns: 1fr; } }
.heat-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(130px,1fr)); gap: 8px; }
.heat-cell { border-radius: 6px; padding: 10px 12px; color: #333; min-height: 54px; box-shadow: 0 1px 3px rgba(0,0,0,.06); line-height: 1.5; }
.heat-cell b { font-size: 18px; }
.heat-table { border-collapse: collapse; width: 100%; }
.heat-table th { font-size: 11px; color: #999; font-weight: normal; padding: 2px 1px; }
.heat-table td { height: 16px; border-radius: 2px; }
.hh { width: 14px; }
.dh { text-align: right; padding-right: 6px; white-space: nowrap; }
</style>
</head>
<body>
<div class="stat-cards" id="statCards"></div>
<div class="grid2">
<div class="panel">
<h3>Top 分类(按链接数)</h3>
<div id="topCategories"></div>
</div>
<div class="panel">
<h3>热门点击 Top10</h3>
<table class="layui-table" lay-size="sm">
<thead><tr><th>网站</th><th style="width:90px">点击</th></tr></thead>
<tbody id="topClicks"></tbody>
</table>
</div>
</div>
<div class="panel">
<h3>点击热力图 · 星期×小时 <span id="timeMeta" style="font-weight:normal;color:#999;font-size:12px;"></span></h3>
<div id="timeHeat"></div>
</div>
<div class="panel">
<h3>分类点击热度</h3>
<div id="categoryHeat" class="heat-grid"></div>
</div>
<div class="panel">
<h3>死链概览 <span id="deadMeta" style="font-weight:normal;color:#999;font-size:12px;"></span></h3>
<table class="layui-table" lay-size="sm">
<thead><tr><th>网站</th><th style="width:280px">网址</th><th style="width:90px">状态码</th><th style="width:140px">最近检测</th></tr></thead>
<tbody id="deadList"></tbody>
</table>
</div>
<script src="/assets/layui/layui.js"></script>
<script src="/assets/ywxapp/ywxapp.js" module="backend"></script>
<script>
function esc(s){ return String(s==null?'':s).replace(/[&<>"']/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c];}); }
// 点击密度 -> 颜色(浅米黄=低,深红=高)
function heatColor(ratio){
ratio = Math.max(0, Math.min(1, ratio));
var r = 255, g = Math.round(245 - 200*ratio), b = Math.round(230 - 210*ratio);
return 'rgb('+r+','+g+','+b+')';
}
layui.use(['http'], function () {
var http = layui.http;
http.get('index').then(function (res) {
var d = (res && res.data) || {};
renderCards(d.summary || {});
renderCategories(d.topCategories || []);
renderClicks(d.topClicks || []);
renderDead(d.dead || {});
if (d.heatmap) {
renderTimeHeat(d.heatmap.time || [], d.heatmap.timeRange || '', d.heatmap.timeTotal || 0);
renderCategoryHeat(d.heatmap.category || []);
}
});
});
function renderCards(s){
var cards = [
['链接总数', s.links], ['已上架', s.online], ['待审核', s.pending], ['已禁用', s.disabled],
['分类数', s.categories], ['热门', s.hot], ['推荐', s.recommend], ['总点击', s.clicks]
];
document.getElementById('statCards').innerHTML = cards.map(function(c){
return '<div class="stat-card"><div class="num">'+(c[1]||0)+'</div><div class="lbl">'+c[0]+'</div></div>';
}).join('');
}
function renderCategories(list){
var max = 1; list.forEach(function(c){ if(c.count>max) max=c.count; });
document.getElementById('topCategories').innerHTML = list.map(function(c){
var pct = Math.round(c.count/max*100);
return '<div class="bar-row"><span class="name">'+(c.icon||'')+' '+esc(c.title)+'</span>'+
'<span class="bar"><i style="width:'+pct+'%"></i></span><span class="val">'+c.count+'</span></div>';
}).join('') || '<p style="color:#999">暂无数据</p>';
}
function renderClicks(list){
document.getElementById('topClicks').innerHTML = list.map(function(it){
return '<tr><td><a href="'+esc(it.url)+'" target="_blank">'+esc(it.title)+'</a></td><td>'+(it.click_count||0)+'</td></tr>';
}).join('') || '<tr><td colspan="2" style="color:#999">暂无数据</td></tr>';
}
function renderTimeHeat(heat, meta, total){
var days = ['周日','周一','周二','周三','周四','周五','周六'];
var max = 1;
for (var d = 0; d < 7; d++) { for (var h = 0; h < 24; h++) { var v = (heat[d] && heat[d][h]) || 0; if (v > max) max = v; } }
document.getElementById('timeMeta').textContent = '' + meta + ',共 ' + total + ' 次点击)';
if (!heat.length) { document.getElementById('timeHeat').innerHTML = '<p style="color:#999">暂无点击数据(需先有访客点击跳转)</p>'; return; }
var html = '<table class="heat-table"><thead><tr><th></th>';
for (var h = 0; h < 24; h++) { html += '<th class="hh">' + (h % 24) + '</th>'; }
html += '</tr></thead><tbody>';
for (var d = 0; d < 7; d++) {
html += '<tr><th class="dh">' + days[d] + '</th>';
for (var h = 0; h < 24; h++) {
var v = (heat[d] && heat[d][h]) || 0;
var ratio = v / max;
html += '<td style="background:' + heatColor(ratio) + '" title="' + days[d] + ' ' + h + ':00 点击 ' + v + ' 次"></td>';
}
html += '</tr>';
}
html += '</tbody></table>';
document.getElementById('timeHeat').innerHTML = html;
}
function renderCategoryHeat(list){
if (!list || !list.length) { document.getElementById('categoryHeat').innerHTML = '<p style="color:#999">暂无数据</p>'; return; }
var max = 1; list.forEach(function(c){ if (c.clicks > max) max = c.clicks; });
document.getElementById('categoryHeat').innerHTML = list.map(function(c){
var ratio = c.clicks / max;
var txt = ratio > 0.55 ? '#fff' : '#333';
return '<div class="heat-cell" style="background:' + heatColor(ratio) + ';color:' + txt + '" title="' + esc(c.title) + '' + (c.clicks || 0) + ' 次">' +
(c.icon ? c.icon + ' ' : '') + esc(c.title) + '<br><b>' + (c.clicks || 0) + '</b></div>';
}).join('');
}
function renderDead(d){
document.getElementById('deadMeta').textContent = d.last_check_at ? ('(共 '+ (d.count||0) +' 条,最近检测 '+d.last_check_at+'') : '(尚未执行死链检测)';
var list = d.list || [];
document.getElementById('deadList').innerHTML = list.map(function(it){
var t = it.last_check_at ? new Date(it.last_check_at*1000).toLocaleString() : '-';
return '<tr><td>'+esc(it.title)+'</td><td style="word-break:break-all">'+esc(it.url)+'</td><td>'+(it.status_code===0?'无法访问':it.status_code)+'</td><td>'+t+'</td></tr>';
}).join('') || '<tr><td colspan="4" style="color:#999">无死链或尚未检测</td></tr>';
}
</script>
</body>
</html>
+508
View File
@@ -0,0 +1,508 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{$title | default='网址管理'}</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link href="/assets/layui/css/layui.css" rel="stylesheet">
<link href="/assets/ywxapp/css/ywxapp.css" rel="stylesheet">
<script type="text/javascript">
var config = { root: "/" };
</script>
</head>
<body>
<div class="layui-card">
<div class="layui-form layui-card-header layuiadmin-card-header-auto">
<div class="layui-form-item">
<div class="layui-inline">
<input type="text" name="title" placeholder="请输入标题" autocomplete="off" class="layui-input">
</div>
<div class="layui-inline">
<input type="text" name="url" placeholder="请输入URL" autocomplete="off" class="layui-input">
</div>
<div class="layui-inline">
<input type="text" name="keywords" placeholder="请输入关键词" autocomplete="off" class="layui-input">
</div>
<div class="layui-inline">
<input type="text" name="description" placeholder="请输入描述" autocomplete="off" class="layui-input">
</div>
<div class="layui-inline">
<select name="status" lay-filter="statusFilter">
<option value="">全部状态</option>
<option value="1">启用</option>
<option value="0">禁用</option>
<option value="2">待审核</option>
<option value="dead">死链(检测异常)</option>
</select>
</div>
<div class="layui-inline">
<button class="layui-btn layuiadmin-btn-useradmin" lay-submit lay-filter="tableSearchButton">
<i class="layui-icon layui-icon-search layuiadmin-button-btn"></i>
</button>
</div>
</div>
</div>
<div class="layui-card-body">
<table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
</div>
</div>
<!-- 顶部工具栏 -->
<script type="text/html" id="tableBar">
<button class="layui-btn layui-btn-sm" lay-event="checkLinks">检测死链</button>
<button class="layui-btn layui-btn-sm" lay-event="refreshFavicon">刷新图标</button>
<button class="layui-btn layui-btn-sm" lay-event="import">导入书签</button>
<button class="layui-btn layui-btn-sm" lay-event="export">导出书签</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-event="batchEnable">批量启用</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-event="batchDisable">批量禁用</button>
<button class="layui-btn layui-btn-sm" lay-event="recoverdead">恢复死链</button>
<button class="layui-btn layui-btn-sm layui-btn-primary" lay-event="batchMove">批量移动分类</button>
</script>
<script type="text/html" id="dataBar">
<div class="layui-btn-group">
<a class="layui-btn layui-btn-sm layui-btn-primary" title="编辑" lay-event="update"><i class="layui-icon layui-icon-edit"></i></a>
{{# if(!d.is_super) { }}
<a class="layui-btn layui-btn-sm layui-btn-primary" title="删除" lay-event="delete"><i class="layui-icon layui-icon-delete"></i></a>
{{# } }}
<a class="layui-btn layui-btn-sm layui-btn-normal" title="通过" lay-event="approve" {{# if(d.status != 2){ }}style="display:none"{{# } }}><i class="layui-icon layui-icon-ok"></i></a>
<a class="layui-btn layui-btn-sm layui-btn-danger" title="拒绝" lay-event="reject" {{# if(d.status != 2){ }}style="display:none"{{# } }}><i class="layui-icon layui-icon-close"></i></a>
</div>
</script>
<script type="text/html" id="dataRecybinTpl">
<div class="layui-card">
<div class="layui-card-header"></div>
<div class="layui-card-body">
<table class="layui-hide" id="dataRecybinTable" lay-filter="dataRecybinTable"></table>
</div>
</div>
</script>
<script type="text/html" id="dataRecybinBarTpl">
<div class="layui-btn-group">
<a class="layui-btn layui-btn-sm layui-btn-primary" title="恢复数据" lay-event="update"><i class="layui-icon layui-icon-edit"></i></a>
<a class="layui-btn layui-btn-sm layui-btn-primary" title="删除节点" lay-event="delete"><i class="layui-icon layui-icon-delete"></i></a>
</div>
</script>
<script type="text/html" id="statusTpl">
<input type="checkbox" name="status" value="{{d.id}}" lay-skin="switch" lay-text="启用|禁用" lay-filter="statusSwitch" {{ d.status == 1 ? 'checked' : '' }}>
</script>
<script type="text/html" id="hotspotTpl">
<input type="checkbox" name="is_hot" value="{{d.id}}" lay-skin="switch" lay-text="启用|禁用" lay-filter="hotspotSwitch" {{ d.is_hot == 1 ? 'checked' : '' }}>
</script>
<!-- 导入弹层 -->
<script type="text/html" id="importTpl">
<form class="layui-form" style="padding:20px;">
<div class="layui-form-item">
<label class="layui-form-label">目标分类</label>
<div class="layui-input-block">
<select name="cid" id="importCid">
<option value="">默认首个分类</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">书签文件</label>
<div class="layui-input-block">
<input type="file" name="file" id="importFile" accept=".html,.htm,.json" class="layui-input">
<div style="color:#999;font-size:12px;margin-top:6px;">支持浏览器导出的 Netscape 书签 HTML JSON 数组</div>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button type="button" class="layui-btn" id="doImport">开始导入</button>
</div>
</div>
</form>
</script>
<!-- 添加/编辑表单 -->
<script type="text/html" id="dataFormTpl">
<form class="layui-form" style="padding: 20px 20px 0;">
<input type="hidden" name="id" value="{{d.id||''}}">
<div class="layui-form-item">
<label class="layui-form-label">标题</label>
<div class="layui-input-block">
<input type="text" name="title" required lay-verify="required|account" placeholder="请输入标题" autocomplete="off" class="layui-input" value="{{d.title||''}}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">分类</label>
<div class="layui-input-block">
<select name="cid" lay-verify="required">
<option value="">请选择分类</option>
{{# d.cates.forEach(function(item) { }}
<option value="{{item.id}}" {{ d.cid == item.id ? 'selected' : '' }}>{{item.title}}</option>
{{# }); }}
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">链接</label>
<div class="layui-input-block">
<input type="text" name="url" lay-verify="url" placeholder="请输入链接" autocomplete="off" class="layui-input" value="{{d.url||''}}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">返利PID</label>
<div class="layui-input-block">
<input type="text" name="pid" placeholder="选填,如淘宝/京东联盟推广位PID,点击跳转自动拼接返利参数" autocomplete="off" class="layui-input" value="{{d.pid||''}}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">图标</label>
<div class="layui-input-block">
<input type="text" name="icon" placeholder="请输入图标链接" autocomplete="off" class="layui-input" value="{{d.icon||''}}">
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">关键词</label>
<div class="layui-input-block">
<textarea name="keywords" placeholder="请输入关键词" class="layui-textarea">{{d.keywords||''}}</textarea>
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">描述</label>
<div class="layui-input-block">
<textarea name="description" placeholder="请输入描述信息" class="layui-textarea">{{d.description||''}}</textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<input type="radio" name="status" value="1" title="启用" {{ d.status==undefined || d.status==1 ? 'checked' : '' }}>
<input type="radio" name="status" value="0" title="禁用" {{ d.status==0 ? 'checked' : '' }}>
<input type="radio" name="status" value="2" title="待审核" {{ d.status==2 ? 'checked' : '' }}>
</div>
</div>
<div class="layui-form-item layui-hide">
<button class="layui-btn" lay-submit="" lay-filter="wxapp-form-submit" id="wxapp-form-submit">提交</button>
</div>
</form>
</script>
<script src="/assets/layui/layui.js"></script>
<script src="/assets/ywxapp/ywxapp.js" module="backend"></script>
<script>
layui.use(['http', 'util'], function () {
var $ = layui.$, table = layui.table, form = layui.form, laytpl = layui.laytpl, http = layui.http, layer = layui.layer, util = layui.util;
var cates = [];
http.get('/haonav/backend/links/create').then((res) => {
if (res.code === 0) { cates = res.data || []; } else { layer.msg(res.msg || '数据加载失败'); }
});
var dataTable = table.render({
elem: '#dataTable',
defaultToolbar: [{
title: '创建资源', layEvent: 'dataCreate', icon: 'layui-icon-add-1'
}, {
title: '删除资源', layEvent: 'dataDelete', icon: 'layui-icon-delete'
}, {
title: '资源回收站', layEvent: 'dataRecybin', icon: 'layui-icon-home'
}, 'filter', 'exports', 'print'
]
, url: "/haonav/backend/links/index"
, page: true,
limit: 20,
limits: [10, 15, 20, 30],
parseData: function (res) {
return { "code": res.code, "msg": res.message || '', "count": res.count || 0, "data": res.data || [] };
},
toolbar: '#tableBar'
, cols: [[
{ type: 'checkbox', fixed: 'left' }
, { field: 'id', title: 'ID', width: 72, sort: true, fixed: 'left' }
, { field: 'title', title: '名称', width: 130, fixed: 'left' }
, { field: 'cate', title: '分类', width: 100, sort: true }
, { field: 'url', title: '链接', minWidth: 160, templet: function (d) { return '<a href="' + (d.url || '') + '" target="_blank" style="color:#01AAED;">' + (d.url || '') + '</a>'; } }
, { field: 'pid', title: '返利PID', width: 130, align: 'center', templet: function (d) { return d.pid ? '<span style="color:#e67e22;">' + d.pid + '</span>' : '-'; } }
, { field: 'icon', title: '图标', width: 90, align: 'center', templet: function (d) { return d.icon ? '<img src="' + d.icon + '" style="width:24px;height:24px;">' : '-'; } }
, { field: 'status_code', title: '检测码', width: 90, align: 'center', templet: function (d) { return d.status_code === null || d.status_code === '' ? '-' : (d.status_code >= 200 && d.status_code < 400 ? '<span style="color:#2ecc71">' + d.status_code + '</span>' : '<span style="color:#e74c3c">' + d.status_code + '</span>'); } }
, { field: 'fail_count', title: '连败', width: 70, align: 'center', templet: function (d) { return (d.fail_count && d.fail_count > 0) ? '<span style="color:#e67e22;">' + d.fail_count + '</span>' : '0'; } }
, { field: 'dead_at', title: '死链时间', width: 115, align: 'center', templet: function (d) { return (d.dead_at && d.dead_at > 0) ? '<span style="color:#e74c3c;">' + util.toDateString(d.dead_at * 1000, 'yyyy-MM-dd') + '</span>' : '-'; } }
, { field: 'keywords', title: '关键词', width: 120, align: 'center' }
, { field: 'description', title: '描述', minWidth: 160 }
, { field: 'status', title: '状态', width: 90, align: 'center', templet: '#statusTpl', unresize: true }
, { field: 'is_hot', title: '热点', width: 90, align: 'center', templet: '#hotspotTpl', unresize: true }
, { field: 'click_count', title: '点击', width: 90, sort: true }
, { field: 'sort', title: '排序', width: 80, sort: true, edit: 'text' }
, { field: 'create_at', title: '创建时间', width: 160, align: 'center', sort: true }
, { title: '操作', width: 150, align: 'left', toolbar: '#dataBar', fixed: 'right', unresize: true }
]],
done: function () { }
});
table.on('toolbar(dataTable)', function (obj) {
var options = obj.config;
switch (obj.event) {
case 'dataCreate': active.dataCreate({ pid: 0, type: 1 }); break;
case 'dataDelete':
var cs = table.checkStatus('dataTable'), cd = cs.data;
active.dataDelete(cd.map((i) => i.id));
break;
case 'dataRecybin': active.dataRecybin(); break;
case 'checkLinks':
layer.confirm('将逐一检测全部链接可达性(可能耗时较长),确定开始?', function (index) {
layer.close(index);
var load = layer.load(2);
http.post('checkLinks', {}).then(function (res) {
layer.close(load);
if (res.code == 0) {
var msg = '检测完成:共 ' + res.data.total + ' 条,正常 ' + res.data.ok + ' 条,异常 ' + res.data.dead + ' 条';
if (res.data.offlined) { msg += ',已自动下线 ' + res.data.offlined + ' 条'; }
if (res.data.recovered) { msg += ',已自动恢复 ' + res.data.recovered + ' 条'; }
layer.alert(msg);
table.reload('dataTable', {}, true);
} else { layer.msg(res.msg || '检测失败'); }
}).catch(function () { layer.close(load); layer.msg('请求失败'); });
});
break;
case 'refreshFavicon':
var cs = table.checkStatus('dataTable'), cd = cs.data;
if (cd.length === 0) { return layer.msg('请先勾选要刷新的链接'); }
http.post('refreshFavicon', { ids: cd.map((i) => i.id).join(',') }).then(function (res) {
if (res.code == 0) { layer.msg(res.msg || '已刷新'); table.reload('dataTable', {}, true); }
else { layer.msg(res.msg || '失败'); }
});
break;
case 'import': active.dataImport(); break;
case 'export': window.open('export'); break;
case 'batchEnable': active.batchOp('enable', '确定批量启用选中链接?'); break;
case 'batchDisable': active.batchOp('disable', '确定批量禁用选中链接?'); break;
case 'batchMove': active.batchMove(); break;
case 'recoverdead': active.batchOp('recoverdead', '确定恢复选中死链(重新启用并清空失败计数)?'); break;
};
});
// 排序列行内编辑:失焦即保存
table.on('edit(dataTable)', function (obj) {
if (obj.field === 'sort') {
http.post('batch', { op: 'sort', ids: String(obj.data.id), sort: obj.value }).then(function (res) {
if (res.code === 0) { layer.msg(res.message || '排序已保存'); }
else { layer.msg(res.message || res.msg || '保存失败'); table.reload('dataTable', {}, true); }
});
}
});
table.on('tool(dataTable)', function (elem) {
var data = elem.data;
switch (elem.event) {
case 'update': active.dataEdit(data); break;
case 'create': active.dataCreate({ pid: data.id }); break;
case 'delete': active.dataDelete([data.id]); break;
case 'approve':
http.post('approve', { id: data.id }).then(function (res) {
if (res.code == 0) { layer.msg('已通过'); table.reload('dataTable', {}, true); }
else { layer.msg(res.msg || '操作失败'); }
});
break;
case 'reject':
layer.confirm('拒绝后该投稿将被禁用,确定?', function (index) {
layer.close(index);
http.post('reject', { id: data.id }).then(function (res) {
if (res.code == 0) { layer.msg('已拒绝'); table.reload('dataTable', {}, true); }
else { layer.msg(res.msg || '操作失败'); }
});
});
break;
default: break;
}
});
form.on('submit(tableSearchButton)', function (data) {
table.reload('dataTable', { where: data.field });
});
form.on('switch(statusSwitch)', function (obj) {
var id = this.value, status = obj.elem.checked ? 1 : 0;
layer.confirm('确定要' + (status ? '启用' : '禁用') + '该数据吗?', function (index) {
layui.http.put('update', { id: id, status: status }).then(function (res) {
if (res.code == 0) { layer.msg(res.msg, { icon: 1 }); table.reload('dataTable', {}, true); }
else { layer.msg(res.msg, { icon: 2 }); obj.elem.checked = !obj.elem.checked; form.render('checkbox'); }
});
layer.close(index);
}, function () {
obj.elem.checked = !obj.elem.checked; form.render('checkbox');
});
});
form.on('switch(hotspotSwitch)', function (obj) {
var id = this.value, is_hot = obj.elem.checked ? 1 : 0;
layer.confirm('确定要' + (is_hot ? '推荐' : '不推荐') + '该数据吗?', function (index) {
layui.http.put('update', { id: id, is_hot: is_hot }).then(function (res) {
if (res.code == 0) { layer.msg(res.msg, { icon: 1 }); table.reload('dataTable', {}, true); }
else { layer.msg(res.msg, { icon: 2 }); obj.elem.checked = !obj.elem.checked; form.render('checkbox'); }
});
layer.close(index);
}, function () {
obj.elem.checked = !obj.elem.checked; form.render('checkbox');
});
});
var dataFromFun = function (data, callback, done) {
var formHtml = laytpl($('#dataFormTpl').html()).render(data || {});
layer.open({
title: data.id ? '编辑数据' : '添加数据',
content: formHtml, anim: "slideLeft", offset: "r",
btnAlign: "l", area: [window.innerWidth > 1200 ? '30%' : (window.innerWidth > 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 = {
dataCreate: function (data = {}) {
data.cates = cates;
dataFromFun(data, function () { }, function (layero, index, elem) {
var field = elem.field; field.status = field.status ? 1 : 0;
http.post('save', field).then((res) => {
if (res.code === 0) { layer.close(index); table.reload('dataTable', {}, true); }
else { layer.msg(res.msg || '操作失败'); }
});
});
},
dataEdit: function (data = {}) {
data.cates = cates;
dataFromFun(data, function () { }, function (layero, index, elem) {
var field = elem.field; field.status = field.status ? 1 : 0;
http.put('update', field).then((res) => {
if (res.code === 0) { layer.close(index); table.reload('dataTable', {}, true); }
else { layer.msg(res.msg || '操作失败'); }
});
});
},
dataImport: function () {
var html = laytpl($('#importTpl').html()).render({});
layer.open({
title: '导入书签', content: html, area: ['480px', '320px'],
success: function (layero) {
var sel = layero.find('#importCid');
cates.forEach(function (c) { sel.append('<option value="' + c.id + '">' + c.title + '</option>'); });
form.render('select');
layero.find('#doImport').on('click', function () {
var fileInput = layero.find('#importFile')[0];
if (!fileInput.files.length) { return layer.msg('请选择书签文件'); }
var fd = new FormData();
fd.append('file', fileInput.files[0]);
fd.append('cid', layero.find('#importCid').val());
layer.msg('导入中...', { icon: 16, time: 0 });
fetch('import', { method: 'POST', body: fd }).then(r => r.json()).then(function (res) {
layer.closeAll();
if (res.code == 0) { layer.msg(res.msg || '导入成功'); table.reload('dataTable', {}, true); }
else { layer.msg(res.msg || '导入失败'); }
}).catch(function () { layer.closeAll(); layer.msg('导入失败'); });
});
}
});
},
dataDelete: function (ids, force = 0) {
if (ids.length === 0) { return layer.msg('请选择数据'); }
layer.prompt({ formType: 1, title: '敏感操作,请验证口令' }, function (value, index) {
layer.close(index);
layer.confirm('确定删除吗?', function (index) {
http.delete('delete', { ids: ids.join(','), force: force }).then((res) => {
if (res.code === 0) { layer.close(index); table.reload('dataTable', {}, true); }
else { layer.msg(res.msg || '操作失败'); }
});
layer.msg('已删除'); table.reload('dataTable', {}, true);
});
});
},
dataRecybin: function (data = {}) {
var formHtml = laytpl($('#dataRecybinTpl').html()).render(data || {});
layer.open({
title: '资源回收站', content: formHtml, anim: "slideLeft", offset: "r",
area: ['60%', '99%'], shade: 0.1, shadeClose: true,
success: function (layero, index) {
layui.table.render({
elem: '#dataRecybinTable', url: 'recyclebin', height: 'full-100',
defaultToolbar: [{
title: '批量删除数据', layEvent: 'dataDelete', icon: 'layui-icon-delete',
onClick: function (obj) {
var cs = table.checkStatus('dataRecybinTable'), cd = cs.data;
active.dataDelete(cd.map((i) => i.id), 1);
}
}, 'filter', 'exports', 'print'],
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ field: 'id', title: 'ID', width: 80, sort: true, fixed: 'left' },
{ field: 'title', title: 'title', width: 180, fixed: 'left' },
{
fixed: "right", title: "操作", width: 120, align: "center", templet: function (d) {
return `<div class="layui-btn-group">
<a class="layui-btn layui-btn-sm" title="恢复数据" lay-event="restore" > <i class="layui-icon layui-icon-edit"></i> </a >
<a class="layui-btn layui-btn-sm" title="删除数据" lay-event="delete"><i class="layui-icon layui-icon-delete"></i> </a>
</div >`;
}
}
]],
page: true,
done: function () {
layui.table.on('tool(dataRecybinTable)', function (elem) {
var data = elem.data;
if (elem.event === 'restore') { active.dataRestore([data.id]); }
else if (elem.event === 'delete') { active.dataDelete([data.id], 1); }
});
}
});
}
});
},
batchOp: function (op, confirmText, extra) {
var cd = table.checkStatus('dataTable').data;
if (!cd.length) { return layer.msg('请先勾选要操作的链接'); }
var ids = cd.map(function (i) { return i.id; }).join(',');
layer.confirm(confirmText || '确定执行批量操作?', function (index) {
layer.close(index);
var params = Object.assign({ op: op, ids: ids }, extra || {});
http.post('batch', params).then(function (res) {
if (res.code === 0) { layer.msg(res.message || '操作成功'); table.reload('dataTable', {}, true); }
else { layer.msg(res.message || res.msg || '操作失败'); }
});
});
},
batchMove: function () {
var cd = table.checkStatus('dataTable').data;
if (!cd.length) { return layer.msg('请先勾选要移动的链接'); }
var opts = cates.map(function (c) { return '<option value="' + c.id + '">' + c.title + '</option>'; }).join('');
layer.open({
title: '批量移动分类(已选 ' + cd.length + ' 条)',
area: ['420px', '220px'],
content: '<div class="layui-form" style="padding:24px;"><div class="layui-form-item"><label class="layui-form-label">目标分类</label><div class="layui-input-block"><select id="batchMoveCid" lay-filter="batchMoveCid"><option value="">请选择分类</option>' + opts + '</select></div></div></div>',
btn: ['确定移动', '取消'],
success: function () { form.render('select'); },
yes: function (index) {
var cid = $('#batchMoveCid').val();
if (!cid) { return layer.msg('请选择目标分类'); }
var ids = cd.map(function (i) { return i.id; }).join(',');
http.post('batch', { op: 'move', ids: ids, cid: cid }).then(function (res) {
if (res.code === 0) { layer.close(index); layer.msg(res.message || '已移动'); table.reload('dataTable', {}, true); }
else { layer.msg(res.message || res.msg || '移动失败'); }
});
}
});
},
dataRestore: function (ids) {
if (ids.length === 0) { return layer.msg('请选择数据'); }
http.put('restore', { ids: ids.join(',') }).then((res) => {
if (res.code === 0) { layer.msg('恢复成功'); table.reload('dataTable', {}, true); }
else { layer.msg(res.msg || '恢复失败'); }
});
}
};
});
</script>
</body>
</html>