添加项目文件。

This commit is contained in:
2026-10-12 16:22:04 +08:00
parent 7c68a0ca0a
commit aad11be43d
1000 changed files with 120807 additions and 0 deletions
@@ -0,0 +1 @@
<div style="text-align:center;padding:40px;color:#999;">此页面已废弃,奖池配置已整合到列表页</div>
@@ -0,0 +1,708 @@
<title>奖池抽奖活动</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>活动</cite></a>
<a><cite>奖池抽奖活动</cite></a>
</div>
</div>
<div class="layui-fluid">
<!-- 活动配置 -->
<div class="layui-card">
<div class="layui-card-header">活动配置</div>
<div class="layui-card-body">
<form class="layui-form" lay-filter="lottery-form">
<input type="hidden" name="activityId" id="activityId">
<div class="layui-form-item">
<label class="layui-form-label">活动名称</label>
<div class="layui-input-inline" style="width:400px;">
<input type="text" name="activityName" lay-verify="required" placeholder="请输入活动名称" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">状态</label>
<div class="layui-input-inline">
<select name="isOpen">
<option value="false">关闭</option>
<option value="true">开启</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">开始时间</label>
<div class="layui-input-inline">
<input type="text" name="startTime" id="startTime" placeholder="yyyy-MM-dd HH:mm:ss" autocomplete="off" class="layui-input">
</div>
<div class="layui-form-mid layui-word-aux">留空=立即开始</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">结束时间</label>
<div class="layui-input-inline">
<input type="text" name="endTime" id="endTime" placeholder="yyyy-MM-dd HH:mm:ss" autocomplete="off" class="layui-input">
</div>
<div class="layui-form-mid layui-word-aux">留空=不限制</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">炮台价格</label>
<div class="layui-input-inline" style="width:400px;"><input type="text" name="costsCannon" class="layui-input" placeholder="炮台奖池每次抽取消耗(必填)"></div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">战神价格</label>
<div class="layui-input-inline" style="width:400px;"><input type="text" name="costsGodWar" class="layui-input" placeholder="战神奖池每次抽取消耗(必填)"></div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">翅膀价格</label>
<div class="layui-input-inline" style="width:400px;"><input type="text" name="costsWing" class="layui-input" placeholder="翅膀奖池每次抽取消耗(必填)"></div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">每人上限</label>
<div class="layui-input-inline" style="width:120px;">
<input type="number" name="userMaxDraw" value="100" placeholder="次数" autocomplete="off" class="layui-input">
</div>
<div class="layui-form-mid layui-word-aux">单用户最大抽奖次数(0=不限制)</div>
</div>
</form>
</div>
</div>
<!-- 奖池等级 -->
<div class="layui-card">
<div class="layui-card-header">奖池等级</div>
<div class="layui-card-body">
<div class="layui-tab layui-tab-brief" lay-filter="lottery-pool-type">
<ul class="layui-tab-title">
<li class="layui-this" lay-id="1">炮台奖池</li>
<li lay-id="2">战神奖池</li>
<li lay-id="3">翅膀奖池</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
<div class="layui-btn-container" style="margin-bottom:10px;">
<button class="layui-btn layui-btn-sm" id="btn-add-pool"><i class="layui-icon layui-icon-add-1"></i> 添加等级</button>
<button class="layui-btn layui-btn-sm layui-btn-primary" id="btn-init-lottery"><i class="layui-icon layui-icon-refresh"></i> 初始化整个配置数据</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" id="btn-save"><i class="layui-icon layui-icon-ok"></i> 保存配置</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" id="btn-reload"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
</div>
<table id="table-pool" lay-filter="table-pool"></table>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 奖池等级操作栏 -->
<script type="text/html" id="bar-pool">
<a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a>
<a class="layui-btn layui-btn-xs layui-btn-normal" lay-event="prize">奖项</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>
<!-- 奖项管理对话框模板 -->
<script type="text/html" id="prize-dialog-tpl">
<div class="prize-container">
<div class="prize-left">
<div class="prize-toolbar">
<button class="layui-btn layui-btn-sm" id="btn-add-prize"><i class="layui-icon layui-icon-add-1"></i> 新增</button>
<button class="layui-btn layui-btn-sm layui-btn-danger" id="btn-delete-prize"><i class="layui-icon layui-icon-delete"></i> 删除</button>
</div>
<div class="prize-table-wrap">
<table id="table-prize-list"></table>
</div>
</div>
<div class="prize-right">
<h4>奖项详情</h4>
<div class="layui-form" lay-filter="prize-form">
<div class="layui-form-item">
<label class="layui-form-label">奖项ID</label>
<div class="layui-input-block">
<input type="text" name="prizeId" id="prizeId" class="layui-input" placeholder="唯一标识">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">奖项名称</label>
<div class="layui-input-block">
<input type="text" name="prizeName" id="prizeName" class="layui-input" placeholder="如:1000金币">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">图标</label>
<div class="layui-input-block">
<input type="text" name="prizeIcon" id="prizeIcon" class="layui-input" placeholder="图标URL">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">类型</label>
<div class="layui-input-block">
<select name="prizeType" id="prizeType" lay-filter="prizeType">
<option value="1">金币</option>
<option value="2">钻石</option>
<option value="3">道具</option>
<option value="4">道具碎片</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">数量</label>
<div class="layui-input-block">
<input type="number" name="prizeCount" id="prizeCount" class="layui-input" placeholder="奖励数量">
</div>
</div>
<div class="layui-form-item prize-prop-select" style="display:none;">
<label class="layui-form-label">道具编号</label>
<div class="layui-input-block">
<select name="prizeOtherId" id="prizeOtherId" lay-filter="prizeOtherId">
<option value="">请选择道具/碎片</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">权重</label>
<div class="layui-input-block">
<input type="number" name="prizeWeight" id="prizeWeight" class="layui-input" placeholder="抽中权重,越大越易中">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">总库存</label>
<div class="layui-input-block">
<input type="number" name="prizeTotalCount" id="prizeTotalCount" class="layui-input" placeholder="0=无限">
</div>
</div>
<div class="layui-form-item" style="margin-top:20px;">
<button class="layui-btn layui-btn-normal" id="btn-save-prize" style="width:100%;">保存</button>
</div>
</div>
</div>
</div>
</script>
<script>
layui.use(['admin', 'table', 'form', 'layer', 'laydate', 'util', 'element'], function () {
var $ = layui.$,
admin = layui.admin,
table = layui.table,
form = layui.form,
layer = layui.layer,
laydate = layui.laydate,
util = layui.util,
element = layui.element,
setter = layui.setter;
// ============ 数据 ============
var activityData = null; // 完整活动数据(camelCase keys,来自服务端)
var poolsData = []; // 奖池等级列表(本地编辑,camelCase keys)
var currentPoolIndex = -1; // 当前编辑的奖池等级索引
var currentPrizeIndex = -1; // 当前编辑的奖项索引
var currentPoolType = 1; // 1=炮台,2=战神,3=翅膀
var propList = []; // 道具列表
var propFragmentList = []; // 碎片单独存于 PropFragments,不在 PropConfig 中
function propIcon(number) {
var key = String(number || '').trim();
var prop = propList.find(function(p) { return String(p.number) === key; });
if (prop) return prop.imageUrl || prop.icon || '';
var fragment = propFragmentList.find(function(p) { return String(p.number) === key; });
if (!fragment) return '';
var parent = propList.find(function(p) { return String(p.number) === String(fragment.productNumber); });
return fragment.imageUrl || (parent && parent.imageUrl) || '';
}
function prizeIcon(prize) {
// 道具和碎片使用最新配置图片,兼容历史奖项保存的失效 icon。
var type = parseInt(prize.type, 10);
return ((type === 3 || type === 4) ? propIcon(prize.otherId) : '') || prize.icon || (prize.product && prize.product.imageUrl) || '';
}
function newClientId() {
return 'pool_' + Date.now() + '_' + Math.random().toString(16).slice(2);
}
function findPoolIndex(row) {
if (!row) return -1;
return poolsData.findIndex(function(p) { return p._clientId === row._clientId; });
}
// 初始化:防止loading残影
setTimeout(function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
$('.layui-layer-shade').remove();
layer.closeAll();
}, 100);
// ============ 数据加载 ============
function loadActivity() {
admin.req({
method: 'get',
url: setter.baseurl + 'adminapi/Lottery/list',
done: function (res) {
if (res.code === 0) {
if (res.data && res.data.length > 0) {
activityData = res.data[0];
} else {
activityData = {
activityId: '',
activityName: '',
isOpen: false,
startTime: 0,
endTime: 0,
costsByPool: {},
userMaxDraw: 100,
pools: []
};
}
poolsData = (activityData.pools || []).map(function(p) {
var item = $.extend({}, p);
item.poolType = parseInt(item.poolType) || 1;
item._clientId = newClientId();
return item;
});
fillForm();
renderPoolTable();
}
}
});
}
function loadProps() {
admin.req({
method: 'get',
url: setter.baseurl + 'adminapi/GameConfig',
done: function (res) {
if (res.code === 0 && res.data) {
propList = res.data.propConfig || [];
propFragmentList = res.data.propFragments || [];
renderPoolTable();
}
}
});
}
// ============ 表单回填 ============
function fillForm() {
if (!activityData) return;
// 先用 jQuery 清空再赋值,避免 layui form.val 累积
var costsByPool = activityData.costsByPool || {};
$('input[name="costsCannon"]').val((costsByPool['1'] || []).join(','));
$('input[name="costsGodWar"]').val((costsByPool['2'] || []).join(','));
$('input[name="costsWing"]').val((costsByPool['3'] || []).join(','));
$('input[name="activityName"]').val(activityData.activityName || '');
$('input[name="userMaxDraw"]').val(activityData.userMaxDraw || 100);
$('select[name="isOpen"]').val(activityData.isOpen ? 'true' : 'false');
$('#activityId').val(activityData.activityId || '');
if (activityData.startTime) {
var sd = new Date(activityData.startTime * 1000);
$('#startTime').val(util.toDateString(sd, 'yyyy-MM-dd HH:mm:ss'));
}
if (activityData.endTime) {
var ed = new Date(activityData.endTime * 1000);
$('#endTime').val(util.toDateString(ed, 'yyyy-MM-dd HH:mm:ss'));
}
form.render();
}
// ============ 日期选择器 ============
laydate.render({ elem: '#startTime', type: 'datetime' });
laydate.render({ elem: '#endTime', type: 'datetime' });
// ============ 奖池等级表格 ============
function renderPoolTable() {
table.render({
elem: '#table-pool',
data: poolsData.filter(function(p) { return (parseInt(p.poolType) || 1) === currentPoolType; }),
page: false,
cols: [[
{ type: 'numbers', title: '序号', width: 60 },
{ field: 'level', title: '等级', width: 100 },
{ field: 'count', title: '数量', width: 80 },
{ field: 'weight', title: '权重', width: 80 },
{ title: '奖项数', width: 80, templet: function(d) {
return (d.prizeList && d.prizeList.length) ? d.prizeList.length + '项' : '无';
}},
{ title: '操作', width: 200, fixed: 'right', toolbar: '#bar-pool' }
]]
});
}
// ============ 奖池等级事件 ============
table.on('tool(table-pool)', function(obj) {
var idx = findPoolIndex(obj.data);
if (idx < 0) {
layer.msg('未找到当前奖池,请刷新页面后重试');
return;
}
var data = poolsData[idx];
if (obj.event === 'del') {
layer.confirm('确定删除等级「' + (data.level || '') + '」吗?', function(ix) {
poolsData.splice(idx, 1);
renderPoolTable();
layer.close(ix);
});
} else if (obj.event === 'edit') {
showPoolEdit(data, false);
} else if (obj.event === 'prize') {
currentPoolIndex = idx;
showPrizeDialog(data);
}
});
// 添加等级
$('#btn-add-pool').on('click', function() {
var newItem = { _clientId: newClientId(), poolType: currentPoolType, level: '', count: 0, weight: 10, prizeList: [] };
showPoolEdit(newItem, true);
});
// 保存配置
$('#btn-save').on('click', function() {
saveActivity();
});
$('#btn-init-lottery').on('click', function() {
layer.confirm('将用当前线上奖池制作的模板覆盖整个奖池活动配置(3组奖池、等级、奖项、权重和价格)。活动会保持关闭,玩家抽奖历史不清除。确定继续吗?', function(index) {
layer.close(index);
admin.req({
method: 'post',
url: setter.baseurl + 'adminapi/Lottery/initialize',
done: function(res) {
if (res.code === 0) {
layer.msg('初始化成功,活动保持关闭');
loadActivity();
} else {
layer.msg(res.info || '初始化失败');
}
}
});
});
});
// 刷新
$('#btn-reload').on('click', function() {
loadActivity();
layer.msg('刷新成功');
});
element.on('tab(lottery-pool-type)', function() {
currentPoolType = parseInt($(this).attr('lay-id')) || 1;
currentPoolIndex = -1;
currentPrizeIndex = -1;
renderPoolTable();
});
// ============ 奖池等级编辑弹窗 ============
function showPoolEdit(data, isAdd) {
var title = isAdd ? '添加等级' : '编辑等级';
var html = '<div style="padding:20px;">' +
'<form class="layui-form">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">等级</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="level" value="' + (data.level || '') + '" placeholder="如: A, B, C" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">数量</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="count" value="' + (data.count || 0) + '" placeholder="奖项总数量" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">权重</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="weight" value="' + (data.weight || 10) + '" placeholder="等级权重,越大越易中" class="layui-input">' +
'</div>' +
'</div>' +
'</form>' +
'</div>';
layer.open({
type: 1,
title: title,
area: ['450px', '300px'],
content: html,
btn: ['保存', '取消'],
success: function(layero) { form.render(); },
yes: function(idx, layero) {
var level = layero.find('input[name="level"]').val();
var count = parseInt(layero.find('input[name="count"]').val()) || 0;
var weight = parseInt(layero.find('input[name="weight"]').val()) || 10;
if (!level) { layer.msg('等级不能为空'); return; }
if (isAdd) {
poolsData.push({ _clientId: data._clientId || newClientId(), poolType: currentPoolType, level: level, count: count, weight: weight, prizeList: [] });
} else {
data.level = level;
data.count = count;
data.weight = weight;
}
layer.close(idx);
renderPoolTable();
}
});
}
// ============ 奖项管理弹窗 ============
function showPrizeDialog(poolData) {
if (!poolData.prizeList) poolData.prizeList = [];
currentPrizeIndex = -1;
var dialogContent = $('#prize-dialog-tpl').html();
layer.open({
type: 1,
title: '等级「' + (poolData.level || '') + '」- 奖项管理',
area: ['1000px', '620px'],
content: dialogContent,
shadeClose: true,
success: function(layero) {
// 必须先渲染 form,否则 select 不显示
form.render(null, 'prize-form');
renderPrizeTable(layero, poolData);
// 绑定按钮事件
layero.find('#btn-add-prize').on('click', function() {
poolData.prizeList.push({
prizeId: '', name: '', icon: '', type: 1, count: 0,
otherId: '', weight: 10, totalCount: 0, drawnCount: 0
});
currentPrizeIndex = poolData.prizeList.length - 1;
renderPrizeTable(layero, poolData, currentPrizeIndex);
loadPrizeForm(poolData.prizeList[currentPrizeIndex], layero);
});
layero.find('#btn-delete-prize').on('click', function() {
if (currentPrizeIndex < 0) return layer.msg('请先选择要删除的奖项');
poolData.prizeList.splice(currentPrizeIndex, 1);
currentPrizeIndex = -1;
clearPrizeForm(layero);
renderPrizeTable(layero, poolData, -1);
});
layero.find('#btn-save-prize').on('click', function() {
savePrizeItem(layero, poolData);
});
// 类型切换(filter 名必须与 HTML lay-filter 一致)
form.on('select(prizeType)', function(obj) {
if (obj.value === '3' || obj.value === '4') {
layero.find('.prize-prop-select').show();
renderPrizeProductOptions(layero, parseInt(obj.value, 10), '');
} else {
layero.find('.prize-prop-select').hide();
}
});
form.on('select(prizeOtherId)', function(obj) {
var type = parseInt(layero.find('#prizeType').val(), 10);
var source = type === 4 ? propFragmentList : propList;
var product = source.find(function(item) { return String(item.number) === String(obj.value); });
if (!product) return;
layero.find('#prizeName').val(product.name || '');
layero.find('#prizeIcon').val(propIcon(product.number));
});
}
});
}
function renderPrizeTable(layero, poolData, selectIndex) {
var list = poolData.prizeList || [];
table.render({
elem: layero.find('#table-prize-list'),
data: list,
page: false,
height: 380,
cols: [[
{ type: 'numbers', title: '序号', width: 60 },
{ field: 'prizeId', title: 'ID', width: 80 },
{ field: 'name', title: '名称', minWidth: 100 },
{ title: '图标', width: 70, templet: function(d) {
var src = prizeIcon(d);
return src ? '<img src="' + src + '" style="width:32px;height:32px;object-fit:contain" alt="图标">' : '';
}},
{ field: 'type', title: '类型', width: 80, templet: function(d) {
var map = {1: '金币', 2: '钻石', 3: '道具', 4: '碎片'};
return map[d.type] || '金币';
}},
{ field: 'count', title: '数量', width: 80 },
{ field: 'weight', title: '权重', width: 60 },
{ field: 'totalCount', title: '库存', width: 60, templet: function(d) {
return d.totalCount || '∞';
}}
]],
done: function() {
if (list.length > 0 && (selectIndex === undefined || selectIndex < 0)) {
selectIndex = 0;
}
if (selectIndex >= 0 && selectIndex < list.length) {
currentPrizeIndex = selectIndex;
var $tr = layero.find('#table-prize-list + .layui-table-view tr[data-index="' + selectIndex + '"]');
if ($tr.length) {
$tr.addClass('layui-table-click');
loadPrizeForm(list[selectIndex], layero);
}
}
}
});
// 行点击切换
layero.find('#table-prize-list + .layui-table-view').off('click').on('click', 'tr[data-index]', function() {
var idx = parseInt($(this).attr('data-index'));
if (idx >= 0 && idx < list.length) {
currentPrizeIndex = idx;
layero.find('tr[data-index]').removeClass('layui-table-click');
$(this).addClass('layui-table-click');
loadPrizeForm(list[idx], layero);
}
});
}
function loadPrizeForm(data, layero) {
layero.find('#prizeId').val(data.prizeId || '');
layero.find('#prizeName').val(data.name || '');
layero.find('#prizeIcon').val(prizeIcon(data));
layero.find('#prizeType').val(data.type || 1);
layero.find('#prizeCount').val(data.count || 0);
renderPrizeProductOptions(layero, parseInt(data.type, 10) || 1, data.otherId || '');
layero.find('#prizeWeight').val(data.weight || 10);
layero.find('#prizeTotalCount').val(data.totalCount || 0);
if (data.type === 3 || data.type === 4) {
layero.find('.prize-prop-select').show();
} else {
layero.find('.prize-prop-select').hide();
}
form.render('select');
}
function renderPrizeProductOptions(layero, type, selected) {
var select = layero.find('#prizeOtherId');
var source = type === 4 ? propFragmentList : propList;
select.empty().append('<option value="">请选择道具/碎片</option>');
source.forEach(function(item) {
var number = String(item.number || '');
select.append($('<option></option>').val(number).text(number + ' - ' + (item.name || '')));
});
select.val(String(selected || ''));
form.render('select', 'prize-form');
}
function clearPrizeForm(layero) {
layero.find('#prizeId').val('');
layero.find('#prizeName').val('');
layero.find('#prizeIcon').val('');
layero.find('#prizeType').val('1');
layero.find('#prizeCount').val('0');
layero.find('#prizeOtherId').val('');
layero.find('#prizeWeight').val('10');
layero.find('#prizeTotalCount').val('0');
layero.find('.prize-prop-select').hide();
form.render('select');
}
function savePrizeItem(layero, poolData) {
if (currentPrizeIndex < 0) return layer.msg('请先选择要编辑的奖项');
var list = poolData.prizeList;
list[currentPrizeIndex] = {
prizeId: layero.find('#prizeId').val() || '',
name: layero.find('#prizeName').val() || '',
icon: layero.find('#prizeIcon').val() || propIcon(layero.find('#prizeOtherId').val()) || '',
type: parseInt(layero.find('#prizeType').val()) || 1,
count: parseInt(layero.find('#prizeCount').val()) || 0,
otherId: layero.find('#prizeOtherId').val() || '',
weight: parseInt(layero.find('#prizeWeight').val()) || 10,
totalCount: parseInt(layero.find('#prizeTotalCount').val()) || 0,
drawnCount: 0
};
renderPrizeTable(layero, poolData, currentPrizeIndex);
renderPoolTable();
layer.msg('保存成功');
}
// ============ 保存活动 ============
function saveActivity() {
var activityName = $('input[name="activityName"]').val();
if (!activityName) { layer.msg('活动名称不能为空'); return; }
var startTimeStr = $('input[name="startTime"]').val();
var endTimeStr = $('input[name="endTime"]').val();
function parseCosts(name) {
var value = $('input[name="' + name + '"]').val() || '';
return value.split(',').map(function(s) { return parseInt(s.trim()) || 0; }).filter(function(n) { return n > 0; });
}
var costsCannon = parseCosts('costsCannon');
var costsGodWar = parseCosts('costsGodWar');
var costsWing = parseCosts('costsWing');
if (costsCannon.length === 0) { layer.msg('请配置炮台奖池消耗金币'); return; }
if (costsGodWar.length === 0) { layer.msg('请配置战神奖池消耗金币'); return; }
if (costsWing.length === 0) { layer.msg('请配置翅膀奖池消耗金币'); return; }
var startTs = startTimeStr ? Math.floor(new Date(startTimeStr).getTime() / 1000) : 0;
var endTs = endTimeStr ? Math.floor(new Date(endTimeStr).getTime() / 1000) : 0;
var param = {
activityId: $('#activityId').val() || '',
activityName: activityName,
isOpen: $('select[name="isOpen"]').val() === 'true',
startTime: startTs,
endTime: endTs,
userMaxDraw: parseInt($('input[name="userMaxDraw"]').val()) || 100,
costsByPool: {
1: costsCannon,
2: costsGodWar,
3: costsWing
},
// _clientId 仅用于解决 Layui 行副本定位问题,不提交到服务端。
pools: poolsData.map(function(p) {
var copy = $.extend(true, {}, p);
delete copy._clientId;
(copy.prizeList || []).forEach(function(prize) {
var type = parseInt(prize.type, 10);
if (type === 3 || type === 4) {
prize.icon = prize.icon || propIcon(prize.otherId) || '';
}
});
return copy;
})
};
admin.req({
method: 'post',
contentType: 'application/json;charset=UTF-8',
url: setter.baseurl + 'adminapi/Lottery/save',
data: JSON.stringify(param),
done: function(res) {
if (res.code === 0) {
layer.msg('保存成功');
loadActivity();
} else {
layer.msg(res.info || '保存失败');
}
}
});
}
// ============ 启动 ============
loadProps();
loadActivity();
});
</script>
<style>
.prize-container { display: flex; height: 480px; padding: 10px; }
.prize-left { flex: 1; margin-right: 15px; }
.prize-table-wrap { height: 400px; overflow: auto; }
.prize-right { width: 300px; border-left: 1px solid #e6e6e6; padding-left: 15px; }
.prize-right h4 { margin-bottom: 15px; color: #333; font-size: 16px; padding-bottom: 10px; border-bottom: 1px solid #eee; }
.prize-toolbar { margin-bottom: 10px; }
.prize-toolbar .layui-btn { margin-right: 5px; }
.prize-right .layui-form-item { margin-bottom: 10px; }
.prize-right .layui-form-label { padding: 9px 0; width: 60px; }
.prize-right .layui-input-block { margin-left: 70px; }
</style>
@@ -0,0 +1,298 @@
<!DOCTYPE html>
<html>
<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 rel="stylesheet" href="../../../layui/css/layui.css">
<script src="../../../layui/layui.js"></script>
<script src="../../../layui/xm-select.js"></script>
</head>
<body>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">后台充值/扣除</div>
<div class="layui-card-body">
<!-- 操作表单 -->
<form class="layui-form" lay-filter="operation-form" style="margin-bottom:20px;">
<div class="layui-form-item">
<label class="layui-form-label">目标用户</label>
<div class="layui-input-inline" style="width:300px;">
<input type="text" name="targetUserId" id="targetUser" required lay-verify="required"
placeholder="请输入用户ID或用户名" class="layui-input">
<input type="hidden" id="searchedUserId" value="">
<div class="layui-form-mid layui-word-aux">支持ID或用户名搜索</div>
</div>
<button type="button" class="layui-btn layui-btn-normal" id="btn-search-user">
<i class="layui-icon layui-icon-search"></i> 搜索
</button>
</div>
<div class="layui-form-item">
<label class="layui-form-label">操作类型</label>
<div class="layui-input-inline">
<input type="radio" name="operationType" value="1" title="充值" checked>
<input type="radio" name="operationType" value="2" title="扣除">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">货币类型</label>
<div class="layui-input-inline">
<select name="currencyType" lay-verify="required">
<option value="1">金币</option>
<option value="2">钻石</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">操作数量</label>
<div class="layui-input-inline">
<input type="number" name="amount" required lay-verify="required|number"
placeholder="请输入数量" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">备注</label>
<div class="layui-input-inline" style="width:400px;">
<input type="text" name="remark" placeholder="备注信息" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn layui-btn-danger" lay-submit lay-filter="btn-deduct">扣除</button>
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="btn-charge">充值</button>
</div>
</div>
</form>
<!-- 操作日志查询 -->
<div class="layui-form">
<div class="layui-form-item">
<div class="layui-inline">
<input type="text" name="adminUser" id="adminUser-search"
placeholder="后台操作者ID/用户名" class="layui-input">
</div>
<div class="layui-inline">
<input type="text" name="targetUserSearch" id="targetUser-search"
placeholder="目标用户ID/用户名" class="layui-input">
</div>
<div class="layui-inline">
<select name="operationTypeSearch" id="operationType-search">
<option value="">全部</option>
<option value="1">充值</option>
<option value="2">扣除</option>
</select>
</div>
<div class="layui-inline">
<button class="layui-btn" id="btn-search-logs">
<i class="layui-icon layui-icon-search"></i> 查询
</button>
</div>
</div>
</div>
<!-- 操作日志表格 -->
<table id="table-logs" lay-filter="table-logs"></table>
</div>
</div>
</div>
<script>
layui.use(['table', 'form', 'layer'], function(){
var table = layui.table;
var form = layui.form;
var layer = layui.layer;
var $ = layui.$;
var setter = layui.setter;
// 渲染表单元素
form.render();
// 货币类型名称映射
var currencyNames = { 1: '金币', 2: '钻石' };
// 初始化操作日志表格
var tableIns = table.render({
elem: '#table-logs',
url: setter.baseurl + 'adminapi/BackgroundOperation/logs',
headers: { 'token': layui.data(setter.tableName)[setter.request.tokenName] },
method: 'get',
page: true,
limit: 20,
limits: [20, 50, 100],
where: {
adminUserId: '',
targetUserId: '',
operationType: ''
},
request: {
pageName: 'pageIndex',
limitName: 'pageSize'
},
cols: [[
{ type: 'numbers', title: '序号', width: 60 },
{ field: 'OperationTime', title: '操作时间', width: 160, templet: function(d) {
return formatDateTime(d.operationTime);
}},
{ field: 'adminUserName', title: '操作者', width: 120 },
{ field: 'targetUserName', title: '目标用户', width: 120 },
{ field: 'operationType', title: '操作类型', width: 100, templet: function(d) {
return d.operationType === 1 ?
'<span class="layui-badge layui-bg-green">充值</span>' :
'<span class="layui-badge layui-bg-red">扣除</span>';
}},
{ field: 'currencyType', title: '货币类型', width: 100, templet: function(d) {
return currencyNames[d.currencyType] || '未知(' + d.currencyType + ')';
}},
{ field: 'amount', title: '数量', width: 100 },
{ field: 'remark', title: '备注', minWidth: 200 }
]],
response: {
statusCode: 0, // 规定成功的状态码,默认:0
msgName: 'info', // 规定状态信息的字段名称,默认:msg
countName: 'count', // 规定数据总数的字段名称,默认:count
dataName: 'data' // 规定数据列表的字段名称,默认:data
}
});
// 搜索用户(用于绑定目标用户)
$('#btn-search-user').click(function(){
var keyword = $('#targetUser').val().trim();
if (!keyword) {
layer.msg('请输入用户ID或用户名');
return;
}
// 显示加载中
var loading = layer.load(1);
$.ajax({
url: setter.baseurl + 'adminapi/BackgroundOperation/search-user',
type: 'get',
data: { keyword: keyword },
headers: { 'token': layui.data(setter.tableName)[setter.request.tokenName] },
dataType: 'json',
success: function(res) {
layer.close(loading);
if (res.code === 0) {
// 搜索成功,显示用户信息并保存 userId
var user = res.data;
$('#searchedUserId').val(user.userId);
layer.alert(
'用户信息:<br>' +
'用户ID:' + user.userId + '<br>' +
'用户名:' + user.userName + '<br>' +
'手机号:' + (user.userPhone || '未绑定') + '<br>' +
'金币:' + user.gold + '<br>' +
'钻石:' + user.diamond + '<br>' +
'注册时间:' + formatDateTime(user.registerTime),
{
icon: 1,
title: '用户信息'
}
);
} else {
layer.msg(res.info || '用户不存在');
}
},
error: function(xhr, status, error) {
layer.close(loading);
console.error('搜索用户失败:', error);
layer.msg('搜索用户失败,请检查网络连接');
}
});
});
// 充值按钮
form.on('submit(btn-charge)', function(data){
var field = data.field;
field.operationType = 1; // 充值
$.ajax({
url: setter.baseurl + 'adminapi/BackgroundOperation/charge',
type: 'post',
contentType: 'application/json',
headers: { 'token': layui.data(setter.tableName)[setter.request.tokenName] },
data: JSON.stringify({
targetUserId: $('#searchedUserId').val(),
currencyType: parseInt(field.currencyType),
amount: parseFloat(field.amount),
remark: field.remark || ''
}),
success: function(res) {
if (res.code === 0) {
layer.msg('充值成功', {icon: 1});
tableIns.reload();
} else {
layer.msg(res.info || '充值失败');
}
}
});
return false;
});
// 扣除按钮
form.on('submit(btn-deduct)', function(data){
var field = data.field;
field.operationType = 2; // 扣除
layer.confirm('确认要扣除 ' + field.amount + ' ' +
(field.currencyType == 1 ? '金币' : '钻石') + '?', function(idx) {
$.ajax({
url: setter.baseurl + 'adminapi/BackgroundOperation/deduct',
type: 'post',
contentType: 'application/json',
headers: { 'token': layui.data(setter.tableName)[setter.request.tokenName] },
data: JSON.stringify({
targetUserId: $('#searchedUserId').val(),
currencyType: parseInt(field.currencyType),
amount: parseFloat(field.amount),
remark: field.remark || ''
}),
success: function(res) {
if (res.code === 0) {
layer.msg('扣除成功', {icon: 1});
tableIns.reload();
} else {
layer.msg(res.info || '扣除失败');
}
}
});
layer.close(idx);
});
return false;
});
// 查询日志
$('#btn-search-logs').click(function(){
var where = {};
var adminUser = $('#adminUser-search').val().trim();
var targetUser = $('#targetUser-search').val().trim();
var operationType = $('#operationType-search').val();
if (adminUser) where.adminUserId = adminUser;
if (targetUser) where.targetUserId = targetUser;
if (operationType) where.operationType = parseInt(operationType);
tableIns.reload({
where: where,
page: { curr: 1 }
});
});
function formatDateTime(timestamp) {
if (!timestamp) return '';
var date = new Date(timestamp * 1000);
return date.toLocaleString('zh-CN');
}
});
</script>
</body>
</html>
@@ -0,0 +1,715 @@
<title>靓号管理</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>靓号管理</cite></a>
<a><cite>靓号列表</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="layadmin-beautifulnumber-formlist">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">靓号</label>
<div class="layui-input-block">
<input type="text" name="numberSearch" placeholder="靓号搜索" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">等级</label>
<div class="layui-input-block">
<select name="level">
<option value="">全部</option>
<option value="S">S级</option>
<option value="A">A级</option>
<option value="B">B级</option>
<option value="C">C级</option>
<option value="D">D级</option>
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<select name="status">
<option value="">全部</option>
<option value="0">未售</option>
<option value="1">已售</option>
<option value="2">冻结</option>
<option value="3">普通号</option>
</select>
</div>
</div>
<div class="layui-inline">
<button class="layui-btn layuiadmin-btn-beautifulnumber" lay-submit lay-filter="LAY-beautifulnumber-front-search">
<i class="layui-icon layui-icon-search layuiadmin-button-btn"></i> 搜索
</button>
<button class="layui-btn layui-btn-primary" lay-filter="LAY-beautifulnumber-front-reset">
<i class="layui-icon layui-icon-refresh layuiadmin-button-btn"></i> 重置
</button>
</div>
</div>
</div>
<div class="layui-card">
<div class="layui-card-body">
<div class="layui-btn-group">
<button class="layui-btn layui-btn-sm" lay-event="addNumber">
<i class="layui-icon layui-icon-add-circle"></i> 添加靓号
</button>
<button class="layui-btn layui-btn-sm" lay-event="batchAddNumbers">
<i class="layui-icon layui-icon-add-circle"></i> 批量添加
</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-event="initRules">
<i class="layui-icon layui-icon-refresh"></i> 初始化规则
</button>
<button class="layui-btn layui-btn-sm" lay-event="initNumbers">
<i class="layui-icon layui-icon-refresh"></i> 根据规则生成靓号
</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-event="refresh">
<i class="layui-icon layui-icon-refresh"></i> 刷新
</button>
</div>
<br><br>
<table id="LAY-beautifulnumber-back-manage" lay-filter="LAY-beautifulnumber-back-manage"></table>
<script type="text/html" id="table-beautifulnumber-admin">
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="give"><i class="layui-icon layui-icon-gift"></i>赠送</a>
<!--<a class="layui-btn layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑</a>-->
<a class="layui-btn layui-btn-xs" lay-event="editPrice"><i class="layui-icon layui-icon-edit"></i>改价</a>
{{# if(d.status === 0){ }}
<a class="layui-btn layui-btn-warm layui-btn-xs" lay-event="freeze"><i class="layui-icon layui-icon-close"></i>冻结</a>
{{# } else if(d.status === 2){ }}
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="unfreeze"><i class="layui-icon layui-icon-ok"></i>解冻</a>
{{# } }}
{{# if(d.status !== 3){ }}
<a class="layui-btn layui-btn-primary layui-btn-xs" lay-event="setNormal"><i class="layui-icon layui-icon-set"></i>普通号</a>
{{# } }}
{{# if(d.status !== 1){ }}
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
{{# } }}
</script>
</div>
</div>
</div>
<script>
layui.use(['admin', 'table', 'form'], function () {
var i = (layui.$, layui.admin),
$ = layui.$,
admin = layui.admin,
view = layui.view,
table = layui.table,
setter = layui.setter,
form = layui.form;
form.render(null, 'layadmin-beautifulnumber-formlist');
//监听搜索
form.on('submit(LAY-beautifulnumber-front-search)', function (data) {
var field = data.field;
// 转换状态为数字
if (field.status) {
field.status = parseInt(field.status);
}
//执行重载
table.reload('LAY-beautifulnumber-back-manage', {
where: field,
page: { curr: 1 }
});
});
// 监听重置
$('.layui-btn-group').on('click', '[lay-event="refresh"]', function () {
table.reload('LAY-beautifulnumber-back-manage');
});
// 添加单个靓号
$('.layui-btn-group').on('click', '[lay-event="addNumber"]', function () {
var content =
'<form class="layui-form" style="padding: 20px;" lay-filter="layuiadmin-form-add-number">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">靓号</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="number" class="layui-input" placeholder="输入靓号" required>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">等级</label>' +
'<div class="layui-input-block">' +
'<select name="level">' +
'<option value="S">S级</option>' +
'<option value="A">A级</option>' +
'<option value="B">B级</option>' +
'<option value="C">C级</option>' +
'<option value="D" selected>D级</option>' +
'</select>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">价格</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="price" class="layui-input" placeholder="金币数量" value="0">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<div class="layui-input-block">' +
'<button class="layui-btn" lay-submit lay-filter="LAY-add-number-submit">添加</button>' +
'</div>' +
'</div>' +
'</form>';
layer.open({
type: 1,
title: '添加靓号',
area: ['600px', '450px'],
content: content
});
layui.form.render();
layui.form.on('submit(LAY-add-number-submit)', function(dataField) {
var param = {
number: dataField.field.number,
level: dataField.field.level,
price: parseInt(dataField.field.price) || 0
};
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/BeautifulNumber/add-number",
data: JSON.stringify(param),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.closeAll();
layer.msg('添加成功');
table.reload("LAY-beautifulnumber-back-manage");
} else {
layer.msg(res.info || '添加失败');
}
}
});
return false;
});
});
// 批量添加靓号
$('.layui-btn-group').on('click', '[lay-event="batchAddNumbers"]', function () {
var content =
'<form class="layui-form" style="padding: 20px;" lay-filter="layuiadmin-form-batch-add">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">靓号列表</label>' +
'<div class="layui-input-block">' +
'<textarea name="numbers" class="layui-textarea" placeholder="每行一个号码" style="height: 200px;" required></textarea>' +
'<div style="color:#999;font-size:12px;margin-top:5px;">每行输入一个靓号,系统会自动过滤已存在的号码</div>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">等级</label>' +
'<div class="layui-input-block">' +
'<select name="level">' +
'<option value="S">S级</option>' +
'<option value="A">A级</option>' +
'<option value="B">B级</option>' +
'<option value="C">C级</option>' +
'<option value="D" selected>D级</option>' +
'</select>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">价格</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="price" class="layui-input" placeholder="金币数量" value="0">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<div class="layui-input-block">' +
'<button class="layui-btn" lay-submit lay-filter="LAY-batch-add-submit">批量添加</button>' +
'</div>' +
'</div>' +
'</form>';
layer.open({
type: 1,
title: '批量添加靓号',
area: ['500px', '500px'],
content: content
});
layui.form.render();
layui.form.on('submit(LAY-batch-add-submit)', function(dataField) {
var numbersText = dataField.field.numbers.trim();
if (!numbersText) {
layer.msg('请输入靓号列表');
return false;
}
var numbers = numbersText.split('\n').map(function(n) { return n.trim(); }).filter(function(n) { return n; });
var param = {
numbers: numbers,
level: dataField.field.level,
price: parseInt(dataField.field.price) || 0
};
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/BeautifulNumber/batch-add-numbers",
data: JSON.stringify(param),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.closeAll();
layer.msg(res.data ? res.data.message : '添加成功');
table.reload("LAY-beautifulnumber-back-manage");
} else {
layer.msg(res.info || '添加失败');
}
}
});
return false;
});
});
// 监听初始化规则
$('.layui-btn-group').on('click', '[lay-event="initRules"]', function () {
layer.confirm("确定初始化靓号规则吗?", function (idx) {
admin.req({
method: "post",
url: setter.baseurl + "adminapi/BeautifulNumber/init-rules",
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('初始化成功');
} else {
layer.msg(res.info || '初始化失败');
}
layer.close(idx);
}
})
})
});
// 监听根据规则生成靓号
$('.layui-btn-group').on('click', '[lay-event="initNumbers"]', function () {
layer.confirm("确定根据规则生成靓号吗?已售出的靓号不会被删除,未售出的靓号将按规则重新生成。", function (idx) {
admin.req({
method: "post",
url: setter.baseurl + "adminapi/BeautifulNumber/init-numbers",
done: function (res) {
layer.close(idx);
if (res.code === 0 || res.code === 200) {
layer.msg(res.info || '初始化成功');
table.reload("LAY-beautifulnumber-back-manage");
} else {
layer.msg(res.info || '初始化失败');
}
}
})
})
});
table.render({
elem: "#LAY-beautifulnumber-back-manage",
url: setter.baseurl + "adminapi/BeautifulNumber/list",
method: "get",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
page: true,
where: {},
request: {
pageName: 'page',
limitName: 'size'
},
limit: 10,
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ field: "number", title: "靓号", width: 150 },
{ field: "ruleName", title: "规则", width: 120 },
{
field: "level", title: "等级", width: 80,
templet: function (d) {
var colors = { 'S': '#ff4d4f', 'A': '#faad14', 'B': '#722ed1', 'C': '#1890ff', 'D': '#8c8c8c' };
return '<span style="color:' + (colors[d.level] || '#8c8c8c') + ';font-weight:bold;">' + d.level + '</span>';
}
},
{ field: "price", title: "价格", width: 120, templet: function (d) { return formatNumber(d.price); } },
{
field: "status", title: "状态", width: 100,
templet: function (d) {
var statusMap = { 0: '<span class="layui-badge">未售</span>', 1: '<span class="layui-badge layui-bg-green">已售</span>', 2: '<span class="layui-badge layui-bg-orange">冻结</span>', 3: '<span class="layui-badge layui-bg-gray">普通号</span>' };
return statusMap[d.status] || '-';
}
},
{
field: "saleTime", title: "售出时间", width: 160,
templet: function (d) {
return d.saleTime ? formatTimestamp(d.saleTime) : '-';
}
},
{ field: "userId", title: "购买者ID", width: 150 },
{ width: 450, title: "操作", align: "center", fixed: "right", toolbar: "#table-beautifulnumber-admin" }
]],
text: "对不起,加载出现异常!",
response: {
statusName: 'code',
statusCode: 0,
msgName: 'info',
countName: 'count',
dataName: 'data'
}
});
table.on("tool(LAY-beautifulnumber-back-manage)", function (e) {
var l = e.data;
if ("del" === e.event) {
layer.confirm("确定删除此靓号么?", function (i) {
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/BeautifulNumber/delete",
data: JSON.stringify({ id: l.id }),
done: function (res) {
if (res.code === 0 || res.code === 200) {
e.del(), layer.close(i);
layer.msg('删除成功');
} else {
layer.msg(res.info || '删除失败');
}
}
})
})
}
if ("editPrice" === e.event) {
i.popup({
title: "修改价格",
area: ["400px", "200px"],
id: "LAY-popup-edit-price",
success: function (div, idx) {
$(div).html(
'<form class="layui-form" style="padding: 20px;" lay-filter="layadmin-form-edit-price">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">靓号</label>' +
'<div class="layui-input-block">' +
'<input type="text" class="layui-input" value="' + (l.number || '') + '" disabled>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">新价格</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="price" class="layui-input" value="' + (l.price || 0) + '" required>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<div class="layui-input-block">' +
'<button class="layui-btn" lay-submit lay-filter="LAY-edit-price-submit">修改</button>' +
'</div>' +
'</div>' +
'</form>'
);
form.render(null, "layadmin-form-edit-price");
form.on("submit(LAY-edit-price-submit)", function (data) {
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/BeautifulNumber/update-price",
data: JSON.stringify({ id: l.id, price: parseInt(data.field.price) }),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.close(idx);
layer.msg('修改成功');
table.reload("LAY-beautifulnumber-back-manage");
} else {
layer.msg(res.info || '修改失败');
}
}
})
return false;
});
}
})
}
if ("freeze" === e.event) {
layer.confirm("确定冻结此靓号么?", function (i) {
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/BeautifulNumber/freeze",
data: JSON.stringify({ id: l.id }),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('冻结成功');
table.reload("LAY-beautifulnumber-back-manage");
} else {
layer.msg(res.info || '冻结失败');
}
layer.close(i);
}
})
})
}
if ("unfreeze" === e.event) {
layer.confirm("确定解冻此靓号么?", function (i) {
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/BeautifulNumber/unfreeze",
data: JSON.stringify({ id: l.id }),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('解冻成功');
table.reload("LAY-beautifulnumber-back-manage");
} else {
layer.msg(res.info || '解冻失败');
}
layer.close(i);
}
})
})
}
if ("setNormal" === e.event) {
layer.confirm("确定将此靓号设为普通号么?", function (i) {
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/BeautifulNumber/set-normal",
data: JSON.stringify({ id: l.id }),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('设置成功');
table.reload("LAY-beautifulnumber-back-manage");
} else {
layer.msg(res.info || '设置失败');
}
layer.close(i);
}
})
})
}
if ("give" === e.event) {
i.popup({
title: "赠送靓号给用户",
area: ["600px", "380px"],
id: "LAY-popup-give-number",
success: function (div, idx) {
$(div).html(
'<div style="padding:20px;">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label" style="width:70px;">靓号</label>' +
'<div class="layui-input-block" style="left:90px;">' +
'<span class="layui-badge layui-bg-blue" style="padding:6px 15px;font-size:14px;">' + (l.number || '') + '</span>' +
'</div>' +
'</div>' +
'<div class="layui-form-item" style="margin-bottom:5px;">' +
'<label class="layui-form-label" style="width:70px;">选择用户</label>' +
'<div class="layui-input-block" style="left:90px;position:relative;">' +
'<input type="text" id="user-search-input" class="layui-input" style="padding-right:35px;" placeholder="输入用户名、昵称、用户编号或手机号搜索">' +
'<i class="layui-icon layui-icon-loading layui-anim layui-anim-loop" id="search-loading" style="position:absolute;right:10px;top:10px;display:none;"></i>' +
'</div>' +
'</div>' +
'<div id="user-dropdown" class="user-dropdown-list" style="display:none;position:absolute;left:90px;width:calc(100% - 90px);max-height:250px;overflow-y:auto;background:#fff;border:1px solid #e6e6e6;border-radius:2px;box-shadow:0 2px 4px rgba(0,0,0,.1);z-index:99999;"></div>' +
'<div style="margin-top:10px;color:#999;font-size:12px;margin-left:70px;">提示:可按用户名、昵称、用户编号、手机号进行模糊搜索</div>' +
'<div id="selected-user-info" style="margin-top:15px;padding:15px;background:#e8f4ff;border-radius:6px;border:1px solid #b8d6f0;display:none;margin-left:70px;">' +
'<div style="display:flex;align-items:center;justify-content:space-between;">' +
'<div>' +
'<span style="color:#666;">已选择用户:</span>' +
'<span id="selected-user-text" style="color:#1e88e5;font-weight:bold;font-size:15px;"></span>' +
'</div>' +
'<button type="button" class="layui-btn layui-btn-sm layui-btn-danger" id="btn-clear-user"><i class="layui-icon layui-icon-close"></i> 清除</button>' +
'</div>' +
'</div>' +
'<div style="margin-top:20px;text-align:center;">' +
'<button type="button" class="layui-btn layui-btn-primary layui-btn-sm" id="btn-cancel-give" style="margin-right:15px;">取消</button>' +
'<button type="button" class="layui-btn layui-btn-sm" id="btn-confirm-give"><i class="layui-icon layui-icon-ok"></i> 确认赠送</button>' +
'</div>' +
'</div>'
);
var selectedUser = null;
var searchTimer = null;
var $dropdown = $('#user-dropdown');
var $input = $('#user-search-input');
function renderDropdown(users) {
if (!users || users.length === 0) {
$dropdown.html('<div style="padding:15px;color:#999;text-align:center;">未找到匹配的用户</div>').show();
return;
}
var html = '';
users.forEach(function(user) {
var number = user.baseInfo ? user.baseInfo.number : '';
var name = user.baseInfo ? user.baseInfo.userName : '';
var mobile = user.baseInfo ? user.baseInfo.mobile : '';
html += '<div class="user-dropdown-item" data-id="' + user.id + '" data-number="' + number + '" data-name="' + name + '" data-mobile="' + mobile + '" style="padding:12px 15px;cursor:pointer;border-bottom:1px solid #f0f0f0;">' +
'<div style="display:flex;justify-content:space-between;align-items:center;">' +
'<div><span style="font-weight:bold;color:#333;font-size:14px;">' + name + '</span> <span style="color:#666;font-size:12px;">[' + number + ']</span></div>' +
(mobile ? '<span style="color:#999;font-size:12px;">' + mobile + '</span>' : '<span style="color:#999;font-size:12px;">无手机号</span>') +
'</div></div>';
});
$dropdown.html(html).show();
}
function searchUsers(keyword) {
if (!keyword || keyword.length < 1) {
$dropdown.hide();
return;
}
$('#search-loading').show();
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/AdminUser/GetUserList",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
data: JSON.stringify({ keyWord: keyword, pageIndex: 1, pageSize: 20 }),
done: function (res) {
$('#search-loading').hide();
if (res.data && res.data.length > 0) {
renderDropdown(res.data);
} else {
renderDropdown([]);
}
},
fail: function() {
$('#search-loading').hide();
renderDropdown([]);
}
});
}
$input.on('input', function() {
var keyword = $(this).val().trim();
clearTimeout(searchTimer);
if (keyword.length < 1) {
$dropdown.hide();
return;
}
searchTimer = setTimeout(function() {
searchUsers(keyword);
}, 300);
});
$input.on('focus', function() {
var keyword = $(this).val().trim();
if (keyword.length >= 1 && $dropdown.find('.user-dropdown-item').length > 0) {
$dropdown.show();
}
});
$(document).off('click', '.user-dropdown-item').on('click', '.user-dropdown-item', function() {
var userId = $(this).data('id');
var userNumber = $(this).data('number');
var userName = $(this).data('name');
var userMobile = $(this).data('mobile');
selectedUser = { id: userId, number: userNumber, name: userName, mobile: userMobile };
$input.val(name + ' [' + userNumber + ']' + (userMobile ? ' ' + userMobile : ''));
$dropdown.hide();
$('#selected-user-text').text(userName + ' [' + userNumber + ']' + (userMobile ? ' ' + userMobile : ''));
$('#selected-user-info').show();
});
$(document).on('click', function(e) {
if (!$(e.target).closest('#user-search-input, #user-dropdown').length) {
$dropdown.hide();
}
});
$('#btn-clear-user').on('click', function() {
selectedUser = null;
$input.val('').focus();
$('#selected-user-info').hide();
});
$('#btn-cancel-give').on('click', function() {
layer.close(idx);
});
$('#btn-confirm-give').on('click', function() {
if (!selectedUser) {
layer.msg('请先选择用户');
return;
}
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/BeautifulNumber/give-to-user",
data: JSON.stringify({ number: l.number, userId: selectedUser.id }),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.close(idx);
layer.msg('赠送成功');
table.reload("LAY-beautifulnumber-back-manage");
} else {
layer.msg(res.info || '赠送失败');
}
}
});
});
}
})
}
});
// 数字格式化
function formatNumber(num) {
if (!num) return '0';
return num.toString().replace(/\B(?=(\d{4})+(?!\d))/g, ",");
}
// 时间戳格式化
function formatTimestamp(timestamp, format = 'yyyy-MM-dd hh:mm:ss') {
if (!timestamp) return '-';
const date = new Date(timestamp * 1000);
const o = {
'M+': date.getMonth() + 1,
'd+': date.getDate(),
'h+': date.getHours(),
'm+': date.getMinutes(),
's+': date.getSeconds()
};
if (/(y+)/.test(format)) {
format = format.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
}
for (const k in o) {
if (new RegExp('(' + k + ')').test(format)) {
format = format.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)));
}
}
return format;
}
// e("beautifulnumber", {})
});
</script>
@@ -0,0 +1,520 @@
<title>靓号规则配置</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>靓号管理</cite></a>
<a><cite>规则配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">
<div class="layui-btn-group">
<button class="layui-btn layui-btn-sm" id="btn-add">
<i class="layui-icon layui-icon-add-circle"></i> 添加规则
</button>
<button class="layui-btn layui-btn-sm" id="btn-save">
<i class="layui-icon layui-icon-ok"></i> 保存规则
</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" id="btn-init">
<i class="layui-icon layui-icon-refresh"></i> 初始化规则
</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" id="btn-reload">
<i class="layui-icon layui-icon-refresh"></i> 刷新
</button>
</div>
</div>
<div class="layui-card-body">
<table id="LAY-rules-manage" lay-filter="LAY-rules-manage"></table>
<script type="text/html" id="table-rule-admin">
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
<script type="text/html" id="toolbar-rule">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="add">添加规则</button>
</div>
</script>
</div>
</div>
</div>
<script>
layui.use(['admin', 'table', 'form', 'layer'], function () {
var $ = layui.$,
admin = layui.admin,
table = layui.table,
form = layui.form,
layer = layui.layer,
setter = layui.setter;
var rules = [];
function loadRules() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/BeautifulNumber/rules",
done: function (res) {
if (res.code === 0 && res.data) {
rules = res.data;
renderTable();
}
}
});
}
function renderTable() {
table.render({
elem: '#LAY-rules-manage',
data: rules,
cols: [[
{ field: "ruleName", title: "规则名称", width: 150, edit: 'text' },
{ field: "rulePattern", title: "生成模式", width: 150 },
{ field: "level", title: "等级", width: 70, edit: 'text' },
{ field: "price", title: "价格(金币)", width: 100, edit: 'text' },
{ field: "sort", title: "排序", width: 70, edit: 'text' },
{
field: "isEnable",
title: "启用",
width: 80,
templet: function(d) {
return d.isEnable ? '<span class="layui-badge">是</span>' : '<span class="layui-badge layui-bg-gray">否</span>';
}
},
{ width: 250, title: "操作", align: "center", fixed: "right", toolbar: "#table-rule-admin" }
]],
toolbar: '#toolbar-rule'
});
}
$('#btn-reload').on('click', function() {
loadRules();
layer.msg('刷新成功');
});
$('#btn-add').on('click', function() {
showAddDialog();
});
$('#btn-init').on('click', function() {
layer.confirm('确定初始化靓号规则吗?这将覆盖现有规则!', function(idx) {
admin.req({
method: "post",
url: setter.baseurl + "adminapi/BeautifulNumber/init-rules",
done: function (res) {
layer.close(idx);
if (res.code === 0 || res.code === 200) {
layer.msg('初始化成功');
loadRules();
} else {
layer.msg(res.info || '初始化失败');
}
}
});
});
});
$('#btn-save').on('click', function() {
layer.confirm('确定保存所有规则吗?', function(idx) {
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/BeautifulNumber/update-rules",
data: JSON.stringify(rules),
done: function (res) {
layer.close(idx);
if (res.code === 0 || res.code === 200) {
layer.msg('保存成功');
loadRules();
} else {
layer.msg(res.info || '保存失败');
}
}
});
});
});
table.on("tool(LAY-rules-manage)", function (obj) {
var data = obj.data;
if ("del" === obj.event) {
layer.confirm("确定删除此规则么?", function (i) {
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/BeautifulNumber/delete-rule",
data: JSON.stringify({ id: data.id }),
done: function (res) {
layer.close(i);
if (res.code === 0 || res.code === 200) {
layer.msg('删除成功');
loadRules();
} else {
layer.msg(res.info || '删除失败');
}
}
});
});
}
if ("edit" === obj.event) {
showEditDialog(data, obj);
}
});
table.on('toolbar(LAY-rules-manage)', function(obj){
if(obj.event === 'add'){
showAddDialog();
}
});
function showAddDialog() {
var content =
'<form class="layui-form" style="padding: 20px;" lay-filter="layuiadmin-form-add-rule">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">规则名称</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="ruleName" class="layui-input" placeholder="如:AAAA、ABBB、ABCDEFGH、AAAABBBX">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">等级</label>' +
'<div class="layui-input-block">' +
'<select name="level">' +
'<option value="S">S级</option>' +
'<option value="A">A级</option>' +
'<option value="B">B级</option>' +
'<option value="C">C级</option>' +
'<option value="D">D级</option>' +
'</select>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">价格</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="price" class="layui-input" placeholder="金币数量">' +
'</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" placeholder="数字越小越靠前" value="0">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<div class="layui-input-block">' +
'<button class="layui-btn" lay-submit lay-filter="LAY-add-rule-submit">添加</button>' +
'</div>' +
'</div>' +
'</form>';
layer.open({
type: 1,
title: '添加规则',
area: ['650px', '500px'],
content: content
});
layui.form.render();
layui.form.on('submit(LAY-add-rule-submit)', function(dataField) {
var ruleName = dataField.field.ruleName.toUpperCase().trim();
if (!ruleName) {
layer.msg('请输入规则名称');
return false;
}
var newRule = {
ruleName: ruleName,
rulePattern: ruleName,
level: dataField.field.level,
price: parseInt(dataField.field.price) || 0,
sort: parseInt(dataField.field.sort) || 0,
isEnable: true
};
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/BeautifulNumber/add-rule",
data: JSON.stringify(newRule),
done: function (res) {
layer.closeAll();
if (res.code === 0 || res.code === 200) {
layer.msg('添加成功');
loadRules();
} else {
layer.msg(res.info || '添加失败');
}
}
});
return false;
});
}
function showEditDialog(data, obj) {
var level = data.level || 'D';
var isEnabled = data.isEnable ? 'checked' : '';
var content =
'<form class="layui-form" style="padding: 20px;" lay-filter="layuiadmin-form-edit-rule">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">规则名称</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="ruleName" class="layui-input" value="' + (data.ruleName || '') + '" placeholder="如:KKKKKKKK KKKKTTTT ABCDEFGH KKKKXXXX">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">等级</label>' +
'<div class="layui-input-block">' +
'<select name="level">' +
'<option value="S" ' + (level === 'S' ? 'selected' : '') + '>S级</option>' +
'<option value="A" ' + (level === 'A' ? 'selected' : '') + '>A级</option>' +
'<option value="B" ' + (level === 'B' ? 'selected' : '') + '>B级</option>' +
'<option value="C" ' + (level === 'C' ? 'selected' : '') + '>C级</option>' +
'<option value="D" ' + (level === 'D' ? 'selected' : '') + '>D级</option>' +
'</select>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">价格</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="price" class="layui-input" value="' + (data.price || 0) + '" placeholder="金币数量">' +
'</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="' + (data.sort || 0) + '" placeholder="数字越小越靠前">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">启用</label>' +
'<div class="layui-input-block">' +
'<input type="checkbox" name="isEnable" lay-skin="switch" lay-text="开启|关闭" ' + isEnabled + '>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<div class="layui-input-block">' +
'<button class="layui-btn" lay-submit lay-filter="LAY-edit-rule-submit">保存</button>' +
'</div>' +
'</div>' +
'</form>';
layer.open({
type: 1,
title: '编辑规则',
area: ['500px', '450px'],
content: content
});
layui.form.render();
layui.form.on('submit(LAY-edit-rule-submit)', function(formData) {
var isEnable = $(formData.form).find('input[name="isEnable"]').prop('checked');
var ruleName = formData.field.ruleName.toUpperCase().trim();
if (!ruleName) {
layer.msg('请输入规则名称');
return false;
}
var updatedRule = {
id: data.id,
ruleName: ruleName,
level: formData.field.level,
price: parseInt(formData.field.price) || 0,
sort: parseInt(formData.field.sort) || 0,
isEnable: isEnable
};
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/BeautifulNumber/update-rule",
data: JSON.stringify(updatedRule),
done: function (res) {
layer.closeAll();
if (res.code === 0 || res.code === 200) {
layer.msg('保存成功');
loadRules();
} else {
layer.msg(res.info || '保存失败');
}
}
});
return false;
});
}
function generateRegexPattern(ruleName) {
if (!ruleName) {
return { valid: false, message: '请输入规则名称' };
}
ruleName = ruleName.toUpperCase().trim();
if (ruleName.length!=8) {
return { valid: false, message: '规则名称长度必须是8位' };
}
// 检查是否全部是连续字母A-H (如 ABCDEFGH 表示8连号)
var isAllConsecutive = true;
var expectedChar = 'A';
for (var i = 0; i < ruleName.length; i++) {
if (ruleName[i] !== expectedChar) {
isAllConsecutive = false;
break;
}
expectedChar = String.fromCharCode(expectedChar.charCodeAt(0) + 1);
}
// 如果是 ABCDEFGH 这种连续字母,表示全部是连续数字
if (isAllConsecutive) {
var len = ruleName.length;
// 生成匹配连续数字的正则: 01234567, 12345678, 23456789
var patterns = [];
for (var start = 0; start <= 9 - len; start++) {
var seq = '';
for (var k = 0; k < len; k++) {
seq += (start + k);
}
if (patterns.length > 0 && patterns.length < 10) {
patterns.push(seq);
}
}
if (patterns.length > 0) {
return {
valid: true,
rulePattern: "^(" + patterns.join('|') + ")$"
};
}
}
var regex = "^";
var prevGroup = '';
for (var i = 0; i < ruleName.length; i++) {
var char = ruleName[i];
if (char === 'A') {
if (prevGroup !== 'A') {
var aLen = 0;
var j = i;
while (j < ruleName.length && ruleName[j] === 'A') {
aLen++;
j++;
}
if (aLen === 1) {
regex += "(\\d)";
} else {
regex += "(\\d)\\1{" + (aLen - 1) + "}";
}
prevGroup = 'A';
}
} else if (char === 'B') {
if (prevGroup !== 'B') {
var bLen = 0;
var j = i;
while (j < ruleName.length && ruleName[j] === 'B') {
bLen++;
j++;
}
regex += "(\\d{" + bLen + "})";
prevGroup = 'B';
}
} else if (char === 'X') {
regex += "\\d";
prevGroup = 'X';
} else if (char >= '0' && char <= '9') {
regex += char;
prevGroup = 'N';
} else if (char >= 'A' && char <= 'H') {
// C-H 也当作连续数字处理
if (prevGroup !== 'CONSECUTIVE') {
var cLen = 0;
var j = i;
while (j < ruleName.length) {
var c = ruleName[j];
if (c >= 'A' && c <= 'H') {
cLen++;
j++;
} else {
break;
}
}
regex += "(\\d{" + cLen + "})";
prevGroup = 'CONSECUTIVE';
i = j - 1;
}
} else {
return { valid: false, message: '规则名称只能包含A-H、X和数字' };
}
}
regex += "$";
try {
new RegExp(regex);
} catch(e) {
return { valid: false, message: '生成的正则表达式无效: ' + regex };
}
return {
valid: true,
rulePattern: regex
};
}
window.generateRegex = function() {
var ruleName = $('#ruleNameInput').val().toUpperCase().trim();
if (ruleName) {
var result = generateRegexPattern(ruleName);
if (result.valid) {
$('#rulePatternInput').val(result.rulePattern);
}
} else {
$('#rulePatternInput').val('');
}
};
window.generateEditRegex = function() {
var ruleName = $('#editRuleNameInput').val().toUpperCase().trim();
if (ruleName) {
var result = generateRegexPattern(ruleName);
if (result.valid) {
$('#editRulePatternInput').val(result.rulePattern);
}
} else {
$('#editRulePatternInput').val('');
}
};
table.on('edit(LAY-rules-manage)', function(obj){
var field = obj.field;
var value = obj.value;
var data = obj.data;
if (!isNaN(value) && value !== '') {
if (field === 'price' || field === 'sort') {
data[field] = parseInt(value);
}
}
if (field === 'ruleName') {
data.rulePattern = value;
renderTable();
}
});
loadRules();
});
</script>
@@ -0,0 +1,59 @@
<div class="layui-form" lay-filter="layuiadmin-form-admin" style="padding: 20px 30px 0 0;">
<input type="hidden" name="id">
<div class="layui-form-item">
<label class="layui-form-label">任务编号</label>
<div class="layui-input-inline">
<input type="text" name="number" placeholder="" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">任务描述</label>
<div class="layui-input-inline">
<input type="text" name="remark" placeholder="" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">任务类型</label>
<div class="layui-input-inline">
<select name="taskType">
<option value="1">每日任务</option>
<option value="2">在线时长</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">任务事件</label>
<div class="layui-input-inline">
<select name="taskEvent">
<option value="1">整点在线奖励</option>
<option value="2">在线时长</option>
<option value="3">捕获任意鱼</option>
<option value="4">捕获黄金鱼</option>
<option value="5">捕获Boss鱼</option>
<option value="6">赢取金币</option>
<option value="7">使用狂暴</option>
<option value="8">开炮任务</option>
<option value="9">登录任务</option>
<option value="10">充值任务</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">任务条件</label>
<div class="layui-input-inline">
<input type="number" name="taskCount" placeholder="" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">战令经验</label>
<div class="layui-input-inline">
<input type="number" name="exp" placeholder="" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-inline">
<input type="button" lay-submit lay-filter="LAY-user-back-submit" value="确认" class="layui-btn">
</div>
</div>
</div>
@@ -0,0 +1,231 @@
<title>任务管理</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏配置</cite></a>
<a><cite>任务管理</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="layadmin-taskadmin-formlist">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<select name="state">
<option value="-1">全部</option>
<option value="1">启用</option>
<option value="2">禁用</option>
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">任务类型</label>
<div class="layui-input-block">
<select name="taskType">
<option value="-1">全部</option>
<option value="1">每日任务</option>
<option value="2">在线时长</option>
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">任务事件</label>
<div class="layui-input-block">
<select name="taskEvent">
<option value="-1">全部</option>
<option value="1">整点在线奖励</option>
<option value="2">在线时长</option>
<option value="3">捕获任意鱼</option>
<option value="4">捕获黄金鱼</option>
<option value="5">捕获Boss鱼</option>
<option value="6">赢取金币</option>
<option value="7">使用狂暴</option>
<option value="8">开炮任务</option>
<option value="9">登录任务</option>
<option value="10">充值任务</option>
</select>
</div>
</div>
</div>
</div>
<div class="layui-card-body">
<div style="padding-bottom: 10px;">
<button class="layui-btn layuiadmin-btn-admin" data-type="add">添加</button>
</div>
<table id="LAY-task-back-manage" lay-filter="LAY-task-back-manage"></table>
<script type="text/html" id="table-taskadmin-admin">
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="state"><i class="layui-icon layui-icon-edit"></i>状态</a>
</script>
</div>
</div>
</div>
<script>
layui.use('task', layui.factory('task')).use(['admin','task', 'table'], function () {
var $ = layui.$
, admin = layui.admin
, view = layui.view
, table = layui.table
, setter = layui.setter
, form = layui.form;
form.render('select'); //刷新select选择框渲染
form.on('select', function (data) {
var data1 = form.val("layadmin-taskadmin-formlist");
data1.state = parseInt(data1.state);
data1.taskType = parseInt(data1.taskType);
data1.taskEvent = parseInt(data1.taskEvent);
table.reload('LAY-task-back-manage', {
where: data1
});
});
//监听搜索
form.on('submit(LAY-user-front-search)', function (data) {
var field = data.field;
//执行重载
table.reload('LAY-task-back-manage', {
where: field
});
});
table.on('tool(LAY-task-back-manage)', function (obj) {
var data = obj.data;
if (obj.event === 'edit') {
admin.popup({
title: '编辑任务'
, area: ["500px", "600px"]
, id: 'LAY-popup-user-edit'
, success: function (layero, index) {
view(this.id).render('config/TaskForm').done(function () {
form.val('layuiadmin-form-admin', {
id: data.id || '',
number: data.number || '',
remark: data.remark || '',
taskType: data.taskType || 1,
taskEvent: data.taskEvent || 1,
taskCount: data.taskCount || 0,
exp: data.exp || 0,
state: data.state || 1
});
form.render(null, 'layuiadmin-form-admin');
form.on('submit(LAY-user-back-submit)', function (formData) {
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
dataType: "json",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/task/UpdateTask",
async: false,
data: JSON.stringify(formData.field),
done: function (res) {
if (res.code === 0) {
layui.table.reload('LAY-task-back-manage');
layer.close(index);
} else {
layer.msg(res.info || '操作失败');
}
}
});
});
});
}
});
} else if (obj.event === 'del') {
layer.confirm('确定删除吗?', function (index) {
admin.req({
url: setter.baseurl + "adminapi/task/DeleteTask",
data: { id: data.id },
done: function (res) {
if (res.code === 0) {
obj.del();
layer.close(index);
} else {
layer.msg(res.info || '删除失败');
}
}
});
});
} else if (obj.event === 'state') {
var newState = data.state === 1 ? 2 : 1;
admin.req({
url: setter.baseurl + "adminapi/task/UpdateTaskState",
data: { id: data.id, state: newState },
done: function (res) {
if (res.code === 0) {
obj.update({ state: newState });
} else {
layer.msg(res.info || '状态更新失败');
}
}
});
}
});
//事件
var active = {
add: function () {
admin.popup({
title: '添加任务'
, area: ["500px", "600px"]
, id: 'LAY-popup-user-add'
, success: function (layero, index) {
view(this.id).render('config/TaskForm').done(function () {
form.render(null, 'layuiadmin-form-admin');
//监听提交
form.on('submit(LAY-user-back-submit)', function (data) {
console.log(data.field)
//提交数据
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
dataType: "json",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/task/AddTask",//实际使用请改成服务端真实接口
async: false,
data: JSON.stringify(data.field),
done: function (res) {
//$.ajax({});
layui.table.reload('LAY-task-back-manage'); //重载表格
layer.close(index); //执行关闭
}
})
//提交 Ajax 成功后,关闭当前弹层并重载表格
});
});
}
});
}
};
$('.layui-btn.layuiadmin-btn-admin').on('click', function () {
var type = $(this).data('type');
active[type] ? active[type].call(this) : '';
});
});
</script>
@@ -0,0 +1,254 @@
<title>聊天记录</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a lay-href="customer/conversation"><cite>会话列表</cite></a>
<a><cite>聊天记录</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header" style="display: flex; justify-content: space-between; align-items: center;">
<span id="conversation-title">聊天记录</span>
<div>
<button class="layui-btn layui-btn-sm layui-btn-danger" onclick="deleteAllMessages()">清空记录</button>
</div>
</div>
<div class="layui-card-body">
<table id="LAY-chatlog-manage" lay-filter="LAY-chatlog-manage"></table>
</div>
</div>
</div>
<script type="text/html" id="toolbar-chatlog">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-event="deleteSelected">批量删除</button>
</div>
</script>
<script type="text/html" id="table-chatlog-toolbar">
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="delete"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
<script>
function getParameterByName(name) {
// 优先从 hash 中查找
var hash = window.location.hash;
if (hash && hash.includes('?')) {
var searchPart = hash.substring(hash.indexOf('?'));
var params = new URLSearchParams(searchPart);
return params.get(name);
}
// 如果 hash 中没有,再从主 URL 查找
var params = new URLSearchParams(window.location.search);
return params.get(name);
}
layui.use(['admin', 'table'], function () {
var $ = layui.$,
admin = layui.admin,
table = layui.table,
setter = layui.setter;
// 获取URL参数
var conversationId = getParameterByName('conversationId') || '';
var userName = getParameterByName('userName') || '';
var customerName = getParameterByName('customerName') || '';
console.log(conversationId)
// 设置标题
document.getElementById('conversation-title').textContent = '聊天记录 - ' + userName + ' 与 ' + customerName;
console.log('conversationId:', conversationId);
table.render({
elem: "#LAY-chatlog-manage",
url: setter.baseurl + "adminapi/customer/chatlog/" + conversationId,
method: "get",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
page: false,
toolbar: '#toolbar-chatlog',
defaultToolbar: [],
cols: [[
{ type: 'checkbox', fixed: 'left' },
{
field: "senderType",
title: "发送者",
width: 100,
templet: function(d) {
return d.senderType == 1 ? '<span class="layui-badge layui-bg-blue">用户</span>' : '<span class="layui-badge">客服</span>';
}
},
{ field: "senderName", title: "发送者名称", width: 150 },
{
field: "contentType",
title: "类型",
width: 100,
templet: function(d) {
return d.contentType == 1 ? '<span class="layui-badge layui-bg-green">文字</span>' : '<span class="layui-badge">图片</span>';
}
},
{
field: "content",
title: "内容",
templet: function(d) {
if (d.contentType == 2) {
return '<img src="' + d.content + '" style="max-width:200px;max-height:100px;cursor:pointer;" lay-event="viewImage">';
}
return d.content || '-';
}
},
{
field: "createTime",
title: "发送时间",
width: 180,
templet: function(d) {
return d.createTime ? formatTimestamp(d.createTime) : '-';
}
},
{ title: "操作", width: 100, align: "center", fixed: "right", toolbar: "#table-chatlog-toolbar" }
]],
text: "对不起,加载出现异常!",
response: {
statusName: 'code',
statusCode: 0,
msgName: 'info',
countName: 'count',
dataName: 'data'
}
});
// 点击图片查看大图
table.on('tool(LAY-chatlog-manage)', function(e) {
if (e.event === 'viewImage') {
layer.open({
type: 1,
title: '图片预览',
area: ['800px', '600px'],
content: '<div style="text-align:center;padding:20px;"><img src="' + e.data.content + '" style="max-width:100%;max-height:100%;"></div>'
});
}
if (e.event === 'delete') {
layer.confirm("确定删除此消息吗?", function(index) {
admin.req({
method: "delete",
url: setter.baseurl + "adminapi/customer/message/" + e.data.id,
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
done: function (res) {
if (res.code === 0) {
layer.msg('删除成功');
table.reload("LAY-chatlog-manage");
} else {
layer.msg(res.info || '删除失败');
}
layer.close(index);
}
});
});
}
});
// 批量删除
table.on('toolbar(LAY-chatlog-manage)', function(obj) {
var checkStatus = table.checkStatus(obj.config.id);
if (obj.event === 'deleteSelected') {
var data = checkStatus.data;
if (data.length === 0) {
layer.msg('请选择要删除的消息');
return;
}
layer.confirm("确定删除选中的 " + data.length + " 条消息吗?", function(index) {
var ids = data.map(function(d) { return d.id; });
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/customer/messages/delete-batch",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
data: JSON.stringify({ ids: ids }),
done: function (res) {
if (res.code === 0) {
layer.msg('删除成功');
table.reload("LAY-chatlog-manage");
} else {
layer.msg(res.info || '删除失败');
}
layer.close(index);
}
});
});
}
});
// 清空所有记录
window.deleteAllMessages = function() {
if (!conversationId) {
layer.msg('无效的会话ID');
return;
}
layer.confirm("确定清空此会话的所有聊天记录吗?此操作不可恢复!", function(index) {
admin.req({
method: "delete",
url: setter.baseurl + "adminapi/customer/messages/" + conversationId,
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
done: function (res) {
if (res.code === 0) {
layer.msg('清空成功');
table.reload("LAY-chatlog-manage");
} else {
layer.msg(res.info || '清空失败');
}
layer.close(index);
}
});
});
};
// 时间戳格式化
function formatTimestamp(timestamp) {
if (!timestamp) return '-';
// 处理时间戳,可能是字符串或数字
var date;
if (typeof timestamp === 'string') {
// 如果是ISO格式字符串,直接解析
date = new Date(timestamp);
} else {
// 如果是时间戳,假设是毫秒级别
date = new Date(timestamp);
}
// 转换为北京时间 (UTC+8)
var utc = date.getTime() + (date.getTimezoneOffset() * 60000);
var beijingDate = new Date(utc + (3600000 * 8));
var o = {
'M+': beijingDate.getMonth() + 1,
'd+': beijingDate.getDate(),
'h+': beijingDate.getHours(),
'm+': beijingDate.getMinutes(),
's+': beijingDate.getSeconds()
};
var format = 'yyyy-MM-dd hh:mm:ss';
if (/(y+)/.test(format)) {
format = format.replace(RegExp.$1, (beijingDate.getFullYear() + '').substr(4 - RegExp.$1.length));
}
for (var k in o) {
if (new RegExp('(' + k + ')').test(format)) {
format = format.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)));
}
}
return format;
}
});
</script>
@@ -0,0 +1,215 @@
<title>会话管理</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>客服管理</cite></a>
<a><cite>会话列表</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="layadmin-conversation-formlist">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">用户ID</label>
<div class="layui-input-block">
<input type="text" name="userId" placeholder="请输入用户ID" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">客服ID</label>
<div class="layui-input-block">
<input type="text" name="customerId" placeholder="请输入客服ID" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<select name="status">
<option value="">全部</option>
<option value="0">等待中</option>
<option value="1">进行中</option>
<option value="2">已结束</option>
</select>
</div>
</div>
<div class="layui-inline">
<button class="layui-btn layuiadmin-btn-conversation" lay-submit lay-filter="LAY-conversation-front-search">
<i class="layui-icon layui-icon-search layuiadmin-button-btn"></i> 搜索
</button>
<button class="layui-btn layui-btn-primary" lay-filter="LAY-conversation-front-reset">
<i class="layui-icon layui-icon-refresh layuiadmin-button-btn"></i> 重置
</button>
</div>
</div>
</div>
<div class="layui-card">
<div class="layui-card-body">
<div class="layui-btn-group">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-event="refresh">
<i class="layui-icon layui-icon-refresh"></i> 刷新
</button>
</div>
<br><br>
<table id="LAY-conversation-manage" lay-filter="LAY-conversation-manage"></table>
<script type="text/html" id="table-conversation-admin">
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="chatlog"><i class="layui-icon layui-icon-log"></i>聊天记录</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="delete"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
</div>
</div>
</div>
<script>
layui.use(['admin', 'table', 'form', 'layer'], function () {
var $ = layui.$,
admin = layui.admin,
view = layui.view,
table = layui.table,
layer = layui.layer,
setter = layui.setter,
form = layui.form,
customerApiUrl = setter.customerApiUrl || setter.baseurl;
form.render(null, 'layadmin-conversation-formlist');
//监听搜索
form.on('submit(LAY-conversation-front-search)', function (data) {
var field = data.field;
//执行重载
table.reload('LAY-conversation-manage', {
where: field,
page: { curr: 1 }
});
});
// 监听重置
$('.layui-btn-group').on('click', '[lay-event="refresh"]', function () {
table.reload('LAY-conversation-manage');
});
table.render({
elem: "#LAY-conversation-manage",
url: customerApiUrl + "customerapi/customer/conversations",
method: "get",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
page: true,
where: {
userId: "",
customerId: "",
status: ""
},
request: {
pageName: 'pageIndex',
limitName: 'pageSize'
},
limit: 10,
cols: [[
{ field: "userName", title: "用户名称", width: 150 },
{ field: "userId", title: "用户ID", width: 200 },
{ field: "customerName", title: "客服名称", width: 150 },
{ field: "customerId", title: "客服ID", width: 200 },
{
field: "status",
title: "状态",
width: 100,
templet: function(d) {
var statusMap = {
0: '<span class="layui-badge">等待中</span>',
1: '<span class="layui-badge layui-bg-green">进行中</span>',
2: '<span class="layui-badge layui-bg-gray">已结束</span>'
};
return statusMap[d.status] || '-';
}
},
{
field: "createTime",
title: "创建时间",
width: 180,
templet: function(d) {
return d.createTime ? formatTimestamp(d.createTime) : '-';
}
},
{
field: "endTime",
title: "结束时间",
width: 180,
templet: function(d) {
return d.endTime ? formatTimestamp(d.endTime) : '-';
}
},
{ width: 190, title: "操作", align: "center", fixed: "right", toolbar: "#table-conversation-admin" }
]],
text: "对不起,加载出现异常!",
response: {
statusName: 'code',
statusCode: 0,
msgName: 'info',
countName: 'count',
dataName: 'data'
}
});
// 监听工具条
table.on("tool(LAY-conversation-manage)", function (e) {
var l = e.data;
if ("chatlog" === e.event) {
// 打开新页面查看聊天记录
var url = 'customer/chatlog.html?conversationId=' + l.id + '&userName=' + encodeURIComponent(l.userName || '') + '&customerName=' + encodeURIComponent(l.customerName || '');
window.location.hash = '/' + url;
} else if ("delete" === e.event) {
layer.confirm('确定删除该会话及全部聊天记录吗?此操作不可恢复。', function(index) {
admin.req({
method: 'delete',
// 删除属于后台管理操作,必须走带鉴权的 AdminApi。
url: setter.baseurl + 'adminapi/Customer/conversation/' + encodeURIComponent(l.id),
done: function(res) {
if (res.code === 0 || res.code === 200) {
e.del();
layer.close(index);
layer.msg(res.info || '删除成功');
table.reload('LAY-conversation-manage');
} else {
layer.msg(res.info || '删除失败');
}
}
});
});
}
});
// 时间戳格式化
function formatTimestamp(timestamp) {
if (!timestamp) return '-';
var date = new Date(timestamp);
var o = {
'M+': date.getMonth() + 1,
'd+': date.getDate(),
'h+': date.getHours(),
'm+': date.getMinutes(),
's+': date.getSeconds()
};
var format = 'yyyy-MM-dd hh:mm:ss';
if (/(y+)/.test(format)) {
format = format.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
}
for (var k in o) {
if (new RegExp('(' + k + ')').test(format)) {
format = format.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)));
}
}
return format;
}
});
</script>
@@ -0,0 +1,452 @@
<title>客服管理</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>客服管理</cite></a>
<a><cite>客服列表</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="layadmin-customer-formlist">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">客服名称</label>
<div class="layui-input-block">
<input type="text" name="userName" placeholder="请输入客服名称" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<select name="status">
<option value="">全部</option>
<option value="0">离线</option>
<option value="1">在线</option>
</select>
</div>
</div>
<div class="layui-inline">
<button class="layui-btn layuiadmin-btn-customer" lay-submit lay-filter="LAY-customer-front-search">
<i class="layui-icon layui-icon-search layuiadmin-button-btn"></i> 搜索
</button>
<button class="layui-btn layui-btn-primary" lay-filter="LAY-customer-front-reset">
<i class="layui-icon layui-icon-refresh layuiadmin-button-btn"></i> 重置
</button>
</div>
</div>
</div>
<div class="layui-card">
<div class="layui-card-body">
<div class="layui-btn-group">
<button class="layui-btn layui-btn-sm" lay-event="add">
<i class="layui-icon layui-icon-add-1"></i> 添加客服
</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-event="refresh">
<i class="layui-icon layui-icon-refresh"></i> 刷新
</button>
</div>
<br><br>
<table id="LAY-customer-manage" lay-filter="LAY-customer-manage"></table>
<script type="text/html" id="table-customer-admin">
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
</div>
</div>
</div>
<style>
.avatar-upload-box { position: relative; display: inline-block; }
.avatar-upload-box .upload-btn { position: absolute; top: 0; left: 0; width: 80px; height: 80px; opacity: 0; cursor: pointer; }
.avatar-upload-preview { width: 80px; height: 80px; border: 1px dashed #ddd; border-radius: 50%; display: flex; align-items: center; justify-content: center; background: #fafafa; cursor: pointer; overflow: hidden; }
.avatar-upload-preview:hover { border-color: #009688; }
.avatar-upload-preview img { width: 100%; height: 100%; object-fit: cover; }
.avatar-upload-preview .placeholder { color: #999; font-size: 12px; text-align: center; }
</style>
<script>
layui.use(['admin', 'table', 'form', 'layer'], function () {
var $ = layui.$,
admin = layui.admin,
view = layui.view,
table = layui.table,
setter = layui.setter,
form = layui.form,
layer = layui.layer;
form.render(null, 'layadmin-customer-formlist');
//监听搜索
form.on('submit(LAY-customer-front-search)', function (data) {
var field = data.field;
//执行重载
table.reload('LAY-customer-manage', {
where: field,
page: { curr: 1 }
});
});
// 监听重置
$('.layui-btn-group').on('click', '[lay-event="refresh"]', function () {
table.reload('LAY-customer-manage');
});
table.render({
elem: "#LAY-customer-manage",
url: setter.baseurl + "adminapi/customer/list",
method: "get",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
page: true,
where: {
userName: "",
status: ""
},
request: {
pageName: 'pageIndex',
limitName: 'pageSize'
},
limit: 10,
cols: [[
{ field: "userName", title: "客服名称", width: 150 },
{ field: "userId", title: "客服ID", width: 200 },
{
field: "avatar",
title: "头像",
width: 100,
templet: function(d) {
return d.avatar ? '<img src="' + d.avatar + '" style="width:40px;height:40px;">' : '-';
}
},
{
field: "status",
title: "状态",
width: 100,
templet: function(d) {
return d.status == 1 ? '<span class="layui-badge layui-bg-green">在线</span>' : '<span class="layui-badge">离线</span>';
}
},
{
field: "createTime",
title: "创建时间",
width: 180,
templet: function(d) {
return d.createTime ? formatTimestamp(d.createTime) : '-';
}
},
{ width: 180, title: "操作", align: "center", fixed: "right", toolbar: "#table-customer-admin" }
]],
text: "对不起,加载出现异常!",
response: {
statusName: 'code',
statusCode: 0,
msgName: 'info',
countName: 'count',
dataName: 'data'
}
});
// 监听工具条
table.on("tool(LAY-customer-manage)", function (e) {
var l = e.data;
if ("del" === e.event) {
layer.confirm("确定删除此客服吗?", function (i) {
admin.req({
method: "delete",
url: setter.baseurl + "adminapi/customer/" + e.data.id,
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
done: function (res) {
if (res.code === 0) {
e.del();
layer.close(i);
layer.msg('删除成功');
} else {
layer.msg(res.info || '删除失败');
}
}
});
});
}
if ("edit" === e.event) {
var avatarHtml = l.avatar
? '<img src="' + l.avatar + '" style="width:100%;height:100%;">'
: '<div class="placeholder">点击上传</div>';
admin.popup({
title: "编辑客服",
area: ["500px", "450px"],
id: "LAY-popup-customer-edit",
success: function (div, idx) {
$(div).html(
'<form class="layui-form" style="padding: 20px;" lay-filter="layuiadmin-form-customer">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">客服名称</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="userName" class="layui-input" value="' + (l.userName || '') + '" required>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">头像</label>' +
'<div class="layui-input-block">' +
'<div class="avatar-upload-box">' +
'<div class="avatar-upload-preview" id="avatarPreview">' + avatarHtml + '</div>' +
'<input type="file" class="upload-btn" accept="image/*" id="avatarUpload">' +
'</div>' +
'<input type="hidden" name="avatar" id="avatarInput" value="' + (l.avatar || '') + '">' +
'<div style="margin-top:8px;color:#999;font-size:12px;">点击图片可预览,支持jpg、png格式</div>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">状态</label>' +
'<div class="layui-input-block">' +
'<select name="status">' +
'<option value="0" ' + (l.status == 0 ? 'selected' : '') + '>离线</option>' +
'<option value="1" ' + (l.status == 1 ? 'selected' : '') + '>在线</option>' +
'</select>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<div class="layui-input-block">' +
'<button class="layui-btn" lay-submit lay-filter="LAY-customer-edit-submit">提交</button>' +
'</div>' +
'</div>' +
'</form>'
);
form.render(null, "layuiadmin-form-customer");
var $preview = $('#avatarPreview');
var $input = $('#avatarInput');
var $fileInput = $('#avatarUpload');
$preview.on('click', function() {
var url = $input.val();
if (url) {
layer.open({
type: 1,
title: '头像预览',
area: ['auto', 'auto'],
shadeClose: true,
content: '<div style="padding:20px;text-align:center;"><img src="' + url + '" style="max-width:300px;max-height:300px;border-radius:50%;"></div>'
});
}
});
$fileInput.on('change', function() {
var file = this.files[0];
if (file) {
var formData = new FormData();
formData.append('file', file);
$.ajax({
url: setter.baseurl + 'adminapi/ImageUpload/upload/temp',
type: 'POST',
data: formData,
processData: false,
contentType: false,
headers: { 'token': layui.data(setter.tableName)[setter.request.tokenName] },
success: function(res) {
if (res.code === 0 || res.code === 200) {
var path = res.data.path;
$input.val(path);
$preview.html('<img src="' + path + '" style="width:100%;height:100%;">');
layer.msg('上传成功');
} else {
layer.msg(res.info || '上传失败');
}
},
error: function() {
layer.msg('上传失败');
}
});
}
});
form.on("submit(LAY-customer-edit-submit)", function (data) {
var param = {
id: l.id,
userName: data.field.userName,
avatar: data.field.avatar,
status: parseInt(data.field.status)
};
admin.req({
method: "put",
contentType: "application/json;charset=UTF-8",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/customer",
data: JSON.stringify(param),
done: function (res) {
if (res.code === 0) {
layer.close(idx);
layer.msg('修改成功');
table.reload("LAY-customer-manage");
} else {
layer.msg(res.info || '修改失败');
}
}
});
return false;
});
}
});
}
});
// 添加客服
$('.layui-btn-group').on('click', '[lay-event="add"]', function () {
admin.popup({
title: "添加客服",
area: ["500px", "450px"],
id: "LAY-popup-customer-add",
success: function (div, idx) {
$(div).html(
'<form class="layui-form" style="padding: 20px;" lay-filter="layuiadmin-form-customer-add">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">客服ID</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="userId" id="userIdInput" class="layui-input" placeholder="请输入客服用户ID" required>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">客服名称</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="userName" class="layui-input" placeholder="请输入客服名称" required>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">头像</label>' +
'<div class="layui-input-block">' +
'<div class="avatar-upload-box">' +
'<div class="avatar-upload-preview" id="avatarPreviewAdd">' +
'<div class="placeholder">点击上传</div>' +
'</div>' +
'<input type="file" class="upload-btn" accept="image/*" id="avatarUploadAdd">' +
'</div>' +
'<input type="hidden" name="avatar" id="avatarInputAdd" value="">' +
'<div style="margin-top:8px;color:#999;font-size:12px;">点击图片可预览,支持jpg、png格式</div>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<div class="layui-input-block">' +
'<button class="layui-btn" lay-submit lay-filter="LAY-customer-add-submit">添加</button>' +
'</div>' +
'</div>' +
'</form>'
);
form.render(null, "layuiadmin-form-customer-add");
var $preview = $('#avatarPreviewAdd');
var $input = $('#avatarInputAdd');
var $fileInput = $('#avatarUploadAdd');
$preview.on('click', function() {
var url = $input.val();
if (url) {
layer.open({
type: 1,
title: '头像预览',
area: ['auto', 'auto'],
shadeClose: true,
content: '<div style="padding:20px;text-align:center;"><img src="' + url + '" style="max-width:300px;max-height:300px;border-radius:50%;"></div>'
});
}
});
$fileInput.on('change', function() {
var file = this.files[0];
if (file) {
var userId = $('#userIdInput').val() || ('new_' + Date.now());
var formData = new FormData();
formData.append('file', file);
$.ajax({
url: setter.baseurl + 'adminapi/ImageUpload/upload/temp',
type: 'POST',
data: formData,
processData: false,
contentType: false,
headers: { 'token': layui.data(setter.tableName)[setter.request.tokenName] },
success: function(res) {
if (res.code === 0 || res.code === 200) {
var path = res.data.path;
$input.val(path);
$preview.html('<img src="' + path + '" style="width:100%;height:100%;">');
layer.msg('上传成功');
} else {
layer.msg(res.info || '上传失败');
}
},
error: function() {
layer.msg('上传失败');
}
});
}
});
form.on("submit(LAY-customer-add-submit)", function (data) {
var param = {
userId: data.field.userId,
userName: data.field.userName,
avatar: data.field.avatar
};
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/customer",
data: JSON.stringify(param),
done: function (res) {
if (res.code === 0) {
layer.close(idx);
layer.msg('添加成功');
table.reload("LAY-customer-manage");
} else {
layer.msg(res.info || '添加失败');
}
}
});
return false;
});
}
});
});
// 时间戳格式化
function formatTimestamp(timestamp) {
if (!timestamp) return '-';
var date = new Date(timestamp);
var o = {
'M+': date.getMonth() + 1,
'd+': date.getDate(),
'h+': date.getHours(),
'm+': date.getMinutes(),
's+': date.getSeconds()
};
var format = 'yyyy-MM-dd hh:mm:ss';
if (/(y+)/.test(format)) {
format = format.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
}
for (var k in o) {
if (new RegExp('(' + k + ')').test(format)) {
format = format.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)));
}
}
return format;
}
});
</script>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,238 @@
<title>邮件管理</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>用户</cite></a>
<a><cite>邮件管理</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="layadmin-emaillist-filter">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">关键词</label>
<div class="layui-input-inline">
<input type="text" name="userNumber" placeholder="编号/昵称/标题/内容" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">已读状态</label>
<div class="layui-input-inline">
<select name="readStatus">
<option value="">全部</option>
<option value="1">已读</option>
<option value="2">未读</option>
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">领取状态</label>
<div class="layui-input-inline">
<select name="rewardStatus">
<option value="">全部</option>
<option value="1">已领取</option>
<option value="2">未领取</option>
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">发送时间</label>
<div class="layui-input-inline">
<input type="text" name="timeRange" id="timeRange" placeholder="选择时间范围" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<button class="layui-btn layuiadmin-btn-list" lay-submit lay-filter="LAY-emaillist-search">
<i class="layui-icon layui-icon-search layuiadmin-button-btn"></i> 搜索
</button>
<button class="layui-btn layui-btn-primary" lay-filter="LAY-emaillist-reset">
<i class="layui-icon layui-icon-refresh layuiadmin-button-btn"></i> 重置
</button>
</div>
</div>
</div>
<div class="layui-card">
<div class="layui-card-body">
<table id="LAY-emaillist-manage" lay-filter="LAY-emaillist-manage"></table>
<script type="text/html" id="table-emaillist-toolbar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-primary" lay-event="refresh">
<i class="layui-icon layui-icon-refresh"></i> 刷新
</button>
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-event="batchdel">
<i class="layui-icon layui-icon-delete"></i> 批量删除
</button>
</div>
</script>
<script type="text/html" id="table-emaillist-bar">
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
<script type="text/html" id="isReadTpl">
{{# if(d.isRead){ }}
<span class="layui-badge layui-bg-blue">已读</span>
{{# } else { }}
<span class="layui-badge layui-bg-gray">未读</span>
{{# } }}
</script>
<script type="text/html" id="isRewardTpl">
{{# if(d.isReward){ }}
<span class="layui-badge layui-bg-green">已领取</span>
{{# } else { }}
<span class="layui-badge layui-bg-orange">未领取</span>
{{# } }}
</script>
</div>
</div>
</div>
<script>
layui.use(['admin', 'table', 'form', 'laydate'], function () {
var $ = layui.$,
admin = layui.admin,
table = layui.table,
form = layui.form,
laydate = layui.laydate,
setter = layui.setter;
laydate.render({
elem: '#timeRange',
type: 'datetime',
range: '~'
});
form.render(null, 'layadmin-emaillist-filter');
form.on('submit(LAY-emaillist-search)', function (data) {
var field = data.field;
if (field.timeRange) {
var times = field.timeRange.split('~');
field.startTime = new Date(times[0].trim()).getTime();
field.endTime = new Date(times[1].trim()).getTime();
}
delete field.timeRange;
if (field.readStatus) field.readStatus = parseInt(field.readStatus);
if (field.rewardStatus) field.rewardStatus = parseInt(field.rewardStatus);
table.reload('LAY-emaillist-manage', {
where: field,
page: { curr: 1 }
});
});
form.on('submit(LAY-emaillist-reset)', function () {
$('input[name="userNumber"]').val('');
$('select[name="readStatus"]').val('');
$('select[name="rewardStatus"]').val('');
$('input[name="timeRange"]').val('');
form.render('select');
table.reload('LAY-emaillist-manage', {
where: {},
page: { curr: 1 }
});
});
table.render({
elem: "#LAY-emaillist-manage",
url: setter.baseurl + "adminapi/AdminUser/GetEmailList",
method: "post",
contentType: "application/json",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
page: true,
toolbar: '#table-emaillist-toolbar',
defaultToolbar: ['filter', 'exports', 'print'],
request: {
pageName: 'pageIndex',
limitName: 'pageSize'
},
limit: 10,
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ type: 'numbers', title: '序号', width: 60 },
{ field: "userId", title: "用户ID", width: 280 },
{ field: "userName", title: "用户昵称", width: 120, templet: function(d) { return d.userName || '-'; } },
{ field: "userNumber", title: "用户编号", width: 120, templet: function(d) { return d.userNumber || '-'; } },
{ field: "title", title: "邮件标题", minWidth: 200 },
{ field: "rewardText", title: "奖励内容", width: 180 },
{ field: "isRead", title: "已读", width: 70, templet: '#isReadTpl' },
{ field: "isReward", title: "领取", width: 80, templet: '#isRewardTpl' },
{ field: "sendTime", title: "发送时间", width: 160, templet: function(d) {
if (!d.sendTime) return '-';
var dt = new Date(d.sendTime * 1000);
return dt.getFullYear() + '-' + pad(dt.getMonth()+1) + '-' + pad(dt.getDate()) + ' ' + pad(dt.getHours()) + ':' + pad(dt.getMinutes()) + ':' + pad(dt.getSeconds());
}},
{ width: 80, title: "操作", align: "center", fixed: "right", toolbar: "#table-emaillist-bar" }
]],
text: "对不起,加载出现异常!",
response: {
statusName: 'code',
statusCode: 0,
msgName: 'info',
countName: 'count',
dataName: 'data'
}
});
function pad(n) { return (n < 10 ? '0' : '') + n; }
table.on("tool(LAY-emaillist-manage)", function (e) {
var l = e.data;
if ("del" === e.event) {
layer.confirm('确定删除该邮件吗?', function (idx) {
admin.req({
url: setter.baseurl + 'adminapi/AdminUser/DeleteEmail?id=' + l.id,
method: 'get',
done: function (res) {
layer.close(idx);
if (res.code === 0) {
layer.msg('删除成功');
} else {
layer.msg(res.info || '删除失败');
}
table.reload('LAY-emaillist-manage');
}
});
});
}
});
table.on('toolbar(LAY-emaillist-manage)', function (obj) {
if (obj.event === 'batchdel') {
var checkStatus = table.checkStatus('LAY-emaillist-manage');
var data = checkStatus.data;
if (data.length === 0) return layer.msg('请选择要删除的数据');
layer.confirm('确定删除选中的 ' + data.length + ' 条邮件吗?', function (idx) {
var ids = data.map(function (item) { return item.id; });
admin.req({
url: setter.baseurl + 'adminapi/AdminUser/BatchDeleteEmail',
contentType: 'application/json',
method: 'post',
data: JSON.stringify(ids),
done: function (res) {
layer.close(idx);
if (res.code === 0) {
layer.msg(res.info || '删除成功');
} else {
layer.msg(res.info || '删除失败');
}
table.reload('LAY-emaillist-manage');
}
});
});
}
});
});
</script>
@@ -0,0 +1,646 @@
<title>战令配置</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏管理</cite></a>
<a><cite>战令配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">基础配置</div>
<div class="layui-card-body">
<form class="layui-form" lay-filter="bo-form">
<div class="layui-form-item">
<label class="layui-form-label">是否开启</label>
<div class="layui-input-inline">
<input type="checkbox" name="isOpen" lay-skin="switch" lay-text="开启|关闭">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">结束时间</label>
<div class="layui-input-inline">
<input type="text" name="endTime" id="endTime" class="layui-input" placeholder="选择日期时间">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">规则描述</label>
<div class="layui-input-inline" style="width:400px;">
<input type="text" name="ruleDesc" class="layui-input" placeholder="规则描述">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">购买描述</label>
<div class="layui-input-inline" style="width:400px;">
<input type="text" name="buyDesc" class="layui-input" placeholder="购买描述">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">进阶描述</label>
<div class="layui-input-inline" style="width:400px;">
<input type="text" name="advanceGiftDesc" class="layui-input" placeholder="进阶奖励描述">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="save-base">保存基础配置</button>
</div>
</div>
</form>
</div>
</div>
<div class="layui-card">
<div class="layui-card-header">等级配置</div>
<div class="layui-card-body">
<div class="layui-btn-container" style="margin-bottom:10px;">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-filter="init-level"><i class="layui-icon layui-icon-refresh"></i> 初始化</button>
<button class="layui-btn layui-btn-sm" lay-filter="add-level"><i class="layui-icon layui-icon-add-1"></i> 添加等级</button>
<button class="layui-btn layui-btn-sm" lay-filter="save-levels"><i class="layui-icon layui-icon-ok"></i> 保存</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" onclick="saveAct('BattleOrderConfig')"><i class="layui-icon layui-icon-export"></i> 保存到初始化文件</button>
</div>
<table id="table-level" lay-filter="table-level"></table>
</div>
</div>
<div class="layui-card">
<div class="layui-card-header">进阶配置</div>
<div class="layui-card-body">
<div class="layui-btn-container" style="margin-bottom:10px;">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-filter="init-advance"><i class="layui-icon layui-icon-refresh"></i> 初始化</button>
<button class="layui-btn layui-btn-sm" lay-filter="add-advance"><i class="layui-icon layui-icon-add-1"></i> 添加进阶</button>
<button class="layui-btn layui-btn-sm" lay-filter="save-advances"><i class="layui-icon layui-icon-ok"></i> 保存</button>
</div>
<table id="table-advance" lay-filter="table-advance"></table>
</div>
</div>
</div>
<script type="text/html" id="bar-level">
<a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>
<script type="text/html" id="bar-advance">
<a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a>
<a class="layui-btn layui-btn-xs layui-btn-normal" lay-event="reward">奖励</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>
<script type="text/html" id="tpl-payType">
{{# var m = {1:'金币',2:'钻石',3:'现金'}; }}
{{= m[d.payType] || '金币' }}
</script>
<script type="text/html" id="tpl-advance-type">
{{# var m = {1:'豪华',2:'至尊',3:'豪华+至尊'}; }}
{{= m[d.type] || '豪华' }}
</script>
<script type="text/html" id="reward-dialog-tpl">
<div class="reward-tabs" style="padding:10px;">
<div class="layui-tab" lay-filter="reward-tab">
<ul class="layui-tab-title">
<li class="layui-this">普通奖励</li>
<li>豪华奖励</li>
<li>至尊奖励</li>
</ul>
<div class="layui-tab-content" style="height:480px;">
<div class="layui-tab-item layui-show" data-key="normalGiftList"></div>
<div class="layui-tab-item" data-key="luxuryGiftList"></div>
<div class="layui-tab-item" data-key="supremeGiftList"></div>
</div>
</div>
</div>
</script>
<script type="text/html" id="gift-editor-tpl">
<div class="gift-container">
<div class="gift-left">
<div class="gift-toolbar">
<button class="layui-btn layui-btn-sm" id="btn-add-gift"><i class="layui-icon layui-icon-add-1"></i> 新增</button>
<button class="layui-btn layui-btn-sm layui-btn-danger" id="btn-delete-gift"><i class="layui-icon layui-icon-delete"></i> 删除</button>
</div>
<div class="gift-table-wrap"><table id="table-gift-list"></table></div>
</div>
<div class="gift-right">
<h4>礼包项详情</h4>
<div class="layui-form">
<div class="layui-form-item">
<label class="layui-form-label">类型</label>
<div class="layui-input-block">
<select id="giftType" lay-filter="giftType">
<option value="1">金币</option>
<option value="2">钻石</option>
<option value="3">道具</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">数量</label>
<div class="layui-input-block"><input type="number" id="giftCount" class="layui-input" placeholder="数量"></div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">随机</label>
<div class="layui-input-block"><input type="checkbox" id="giftIsRandom" lay-skin="switch" lay-text="是|否"></div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">最小值</label>
<div class="layui-input-block"><input type="number" id="giftMinCount" class="layui-input" placeholder="最小值"></div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">最大值</label>
<div class="layui-input-block"><input type="number" id="giftMaxCount" class="layui-input" placeholder="最大值"></div>
</div>
<div class="layui-form-item gift-prop-select" style="display:none;">
<label class="layui-form-label">道具编号</label>
<div class="layui-input-block"><input type="text" id="giftProductNumber" class="layui-input" placeholder="输入道具编号"></div>
</div>
<div class="layui-form-item" style="margin-top:10px;">
<button class="layui-btn layui-btn-normal" id="btn-save-gift" style="width:100%;">保存</button>
</div>
</div>
</div>
</div>
</script>
<script>
layui.use(['admin', 'table', 'form', 'layer', 'laydate', 'element', 'upload'], function () {
var $ = layui.$, admin = layui.admin, table = layui.table, form = layui.form,
layer = layui.layer, laydate = layui.laydate, setter = layui.setter, element = layui.element, upload = layui.upload;
var activityConfig = null;
var rewardCtx = null; // { giftList, title }
setTimeout(function() {
$('.layui-layer-shade').remove(); layer.closeAll();
}, 100);
laydate.render({ elem: '#endTime', type: 'datetime' });
function loadConfig() {
admin.req({ method: 'get', url: setter.baseurl + 'adminapi/ActivityConfig/Get',
done: function(r2) {
activityConfig = (r2.code === 0 && r2.data) ? r2.data : { battleOrderConfig: {} };
initForm();
}
});
}
function bo() {
if (!activityConfig.battleOrderConfig) activityConfig.battleOrderConfig = {};
return activityConfig.battleOrderConfig;
}
function initForm() {
var b = bo();
$('input[name="isOpen"]').prop('checked', b.isOpen);
$('input[name="endTime"]').val(b.endTime ? new Date(b.endTime * 1000).toLocaleString() : '');
$('input[name="ruleDesc"]').val(b.ruleDesc || '');
$('input[name="buyDesc"]').val(b.buyDesc || '');
$('input[name="advanceGiftDesc"]').val(b.advanceGiftDesc || '');
form.render();
renderLevelTable();
renderAdvanceTable();
}
function save(msg) {
admin.req({
method: 'post', contentType: 'application/json;charset=UTF-8',
url: setter.baseurl + 'adminapi/ActivityConfig/UpdateBattleOrder',
data: JSON.stringify(activityConfig.battleOrderConfig),
done: function(r) {
r.code === 0 ? (layer.msg(msg || '保存成功'), loadConfig()) : layer.msg(r.info || '保存失败');
}
});
}
form.on('submit(save-base)', function(d) {
var b = bo();
b.isOpen = d.field.isOpen === 'on';
b.ruleDesc = d.field.ruleDesc || '';
b.buyDesc = d.field.buyDesc || '';
b.advanceGiftDesc = d.field.advanceGiftDesc || '';
if (d.field.endTime) b.endTime = Math.floor(new Date(d.field.endTime).getTime() / 1000);
save('保存成功'); return false;
});
// ==================== 等级表格 ====================
function renderLevelTable() {
var levels = bo().levelConfig || [];
table.render({ elem: '#table-level', data: levels, page: true,
cols: [[
{ type: 'numbers', title: '序号', width: 60 },
{ field: 'level', title: '等级', width: 70, sort: true },
{ field: 'exp', title: '升级经验', width: 90 },
{ title: '普通奖励', width: 90, templet: function(d) { return (d.normalGiftList||[]).length + '项'; } },
{ title: '豪华奖励', width: 90, templet: function(d) { return (d.luxuryGiftList||[]).length + '项'; } },
{ title: '至尊奖励', width: 90, templet: function(d) { return (d.supremeGiftList||[]).length + '项'; } },
{ field: 'payType', title: '付费', width: 70, templet: '#tpl-payType' },
{ field: 'payCount', title: '费用', width: 80 },
{ title: '操作', width: 200, fixed: 'right', toolbar: '#bar-level' }
]]
});
}
// ==================== 进阶表格 ====================
function renderAdvanceTable() {
var list = bo().advanceGift || [];
table.render({ elem: '#table-advance', data: list, page: false,
cols: [[
{ type: 'numbers', title: '序号', width: 60 },
{ field: 'type', title: '类型', width: 80, templet: '#tpl-advance-type' },
{ field: 'name', title: '名称', width: 120 },
{ field: 'count', title: '数量', width: 80 },
{ field: 'payType', title: '付费', width: 70, templet: '#tpl-payType' },
{ field: 'payCount', title: '费用', width: 80 },
{ title: '奖励', width: 80, templet: function(d) { return (d.giftList||[]).length + '项'; } },
{ title: '操作', width: 180, fixed: 'right', toolbar: '#bar-advance' }
]]
});
}
// ==================== 等级 CRUD ====================
function showLevelEdit(data, isAdd) {
var readonly = isAdd ? '' : 'readonly';
var html = '<div style="padding:15px;"><form class="layui-form">' +
'<div class="layui-row"><div class="layui-col-md6"><div class="layui-form-item"><label class="layui-form-label">等级</label><div class="layui-input-block"><input type="number" id="dlg-level" value="'+(data.level||'')+'" class="layui-input" '+readonly+'></div></div></div>' +
'<div class="layui-col-md6"><div class="layui-form-item"><label class="layui-form-label">升级经验</label><div class="layui-input-block"><input type="number" id="dlg-exp" value="'+(data.exp||0)+'" class="layui-input"></div></div></div></div>' +
'<div class="layui-row"><div class="layui-col-md6"><div class="layui-form-item"><label class="layui-form-label">付费类型</label><div class="layui-input-block"><select id="dlg-payType"><option value="1"'+(data.payType===1?' selected':'')+'>金币</option><option value="2"'+(data.payType===2?' selected':'')+'>钻石</option><option value="3"'+(data.payType===3?' selected':'')+'>现金</option></select></div></div></div>' +
'<div class="layui-col-md6"><div class="layui-form-item"><label class="layui-form-label">付费金额</label><div class="layui-input-block"><input type="number" id="dlg-payCount" value="'+(data.payCount||0)+'" class="layui-input"></div></div></div></div>' +
'<div style="margin-top:10px;border-top:1px solid #eee;padding-top:10px;"><div class="layui-tab" lay-filter="dlg-reward-tab"><ul class="layui-tab-title"><li class="layui-this">普通奖励</li><li>豪华奖励</li><li>至尊奖励</li></ul>' +
'<div class="layui-tab-content" style="height:300px;padding:5px 0;">' +
'<div class="layui-tab-item layui-show"><div class="inplace-gift" data-key="normalGiftList"></div></div>' +
'<div class="layui-tab-item"><div class="inplace-gift" data-key="luxuryGiftList"></div></div>' +
'<div class="layui-tab-item"><div class="inplace-gift" data-key="supremeGiftList"></div></div>' +
'</div></div></div></form></div>';
layer.open({ type:1, title:isAdd?'添加等级':'编辑等级', area:['850px','580px'], content:html, btn:['保存','取消'],
success:function(l){
form.render('select');
element.render('tab');
var keys = ['normalGiftList','luxuryGiftList','supremeGiftList'];
var tgt = {};
keys.forEach(function(k){ tgt[k] = data[k] || []; });
renderInplaceGift(l.find('.inplace-gift[data-key="normalGiftList"]'), tgt.normalGiftList);
element.on('tab(dlg-reward-tab)', function(o){
var k = keys[o.index];
var ph = l.find('.inplace-gift[data-key="'+k+'"]');
if (ph.is(':empty')) renderInplaceGift(ph, tgt[k]);
});
window._dlgGiftData = tgt;
},
yes:function(idx,l){
var tgt = window._dlgGiftData || {};
var item = {
level: parseInt(l.find('#dlg-level').val()) || data.level,
exp: parseInt(l.find('#dlg-exp').val()) || 0,
normalGiftList: tgt.normalGiftList || [],
luxuryGiftList: tgt.luxuryGiftList || [],
supremeGiftList: tgt.supremeGiftList || [],
payType: parseInt(l.find('#dlg-payType').val()) || 1,
payCount: parseInt(l.find('#dlg-payCount').val()) || 0
};
var b = bo();
if (!b.levelConfig) b.levelConfig = [];
if (isAdd) {
if (b.levelConfig.some(function(x) { return x.level === item.level; }))
{ layer.msg('等级已存在'); return; }
b.levelConfig.push(item);
} else {
var i = b.levelConfig.findIndex(function(x) { return x.level === data.level; });
if (i > -1) b.levelConfig[i] = item;
}
delete window._dlgGiftData;
layer.close(idx); save(isAdd?'添加成功':'保存成功');
}
});
}
function renderInplaceGift(container, giftList) {
if (!giftList) giftList = [];
var html = '<div style="margin-bottom:8px;"><button class="layui-btn layui-btn-xs layui-btn-normal btn-ip-add"><i class="layui-icon layui-icon-add-1"></i> 新增奖励项</button></div>' +
'<table class="ip-gift-table"><thead><tr><th style="width:60px">类型</th><th style="width:60px">数量</th><th style="width:60px">最小值</th><th style="width:60px">最大值</th><th style="width:60px">随机</th><th style="width:100px">道具ID</th><th style="width:60px">操作</th></tr></thead><tbody></tbody></table>';
container.html(html);
var tbody = container.find('tbody');
function renderRows(){
tbody.empty();
giftList.forEach(function(g,idx){
var typeMap = {1:'金币',2:'钻石',3:'道具'};
var tr = $('<tr><td>'+ (typeMap[g.type]||'金币') +'</td><td>'+ (g.count||0) +'</td><td>'+ (g.minCount||0) +'</td><td>'+ (g.maxCount||0) +'</td><td>'+ (g.isRandom?'是':'否') +'</td><td>'+ (g.otherId||'') +'</td><td><button class="layui-btn layui-btn-xs btn-ip-edit" data-idx="'+idx+'">编辑</button><button class="layui-btn layui-btn-xs layui-btn-danger btn-ip-del" data-idx="'+idx+'">删除</button></td></tr>');
tbody.append(tr);
});
}
renderRows();
container.find('.btn-ip-add').off('click').on('click', function(){
giftList.push({type:1,count:100,isRandom:false,minCount:0,maxCount:0,otherId:''});
renderRows();
});
container.off('click','.btn-ip-del').on('click','.btn-ip-del', function(){
var idx = parseInt($(this).data('idx'));
giftList.splice(idx,1);
renderRows();
});
container.off('click','.btn-ip-edit').on('click','.btn-ip-edit', function(){
var idx = parseInt($(this).data('idx'));
var g = giftList[idx];
if (!g) return;
var ehtml = '<div style="padding:15px;"><form class="layui-form"><div class="layui-form-item"><label class="layui-form-label">类型</label><div class="layui-input-block"><select id="ip-type"><option value="1"'+(g.type===1?' selected':'')+'>金币</option><option value="2"'+(g.type===2?' selected':'')+'>钻石</option><option value="3"'+(g.type===3?' selected':'')+'>道具</option></select></div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">数量</label><div class="layui-input-block"><input type="number" id="ip-count" value="'+(g.count||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" id="ip-random" lay-skin="switch" lay-text="是|否"'+(g.isRandom?' checked':'')+'></div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">最小值</label><div class="layui-input-block"><input type="number" id="ip-min" value="'+(g.minCount||0)+'" class="layui-input"></div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">最大值</label><div class="layui-input-block"><input type="number" id="ip-max" value="'+(g.maxCount||0)+'" class="layui-input"></div></div>' +
'<div class="layui-form-item gift-prop-select" style="'+(g.type===3?'':'display:none;')+'"><label class="layui-form-label">道具编号</label><div class="layui-input-block"><input type="text" id="ip-oid" class="layui-input" value="'+(g.otherId||'')+'" placeholder="输入道具编号"></div></div></form></div>';
layer.open({type:1,title:'编辑奖励项',area:['420px','420px'],content:ehtml,btn:['保存','取消'],
success:function(el){ form.render(); form.on('select(ip-type)', function(o){ el.find('.gift-prop-select').toggle(o.value==='3'); }); },
yes:function(ei,el){
var t = parseInt(el.find('#ip-type').val())||1;
var propNum = t===3 ? (el.find('#ip-oid').val()||'') : '';
giftList[idx] = {
type:t, count: parseInt(el.find('#ip-count').val())||0,
isRandom: el.find('#ip-random')[0].checked,
minCount: parseInt(el.find('#ip-min').val())||0,
maxCount: parseInt(el.find('#ip-max').val())||0,
otherId: propNum
};
layer.close(ei);
renderRows();
}
});
});
}
// ==================== 进阶 CRUD ====================
function showAdvanceEdit(data, isAdd) {
var readonly = isAdd ? '' : 'readonly';
var html = '<div style="padding:20px;"><form class="layui-form">' +
'<div class="layui-row"><div class="layui-col-md6"><div class="layui-form-item"><label class="layui-form-label">类型</label><div class="layui-input-block"><select id="dlg-type" '+readonly+'><option value="1"'+(data.type===1?' selected':'')+'>豪华</option><option value="2"'+(data.type===2?' selected':'')+'>至尊</option><option value="3"'+(data.type===3?' selected':'')+'>豪华+至尊</option></select></div></div></div>' +
'<div class="layui-col-md6"><div class="layui-form-item"><label class="layui-form-label">名称</label><div class="layui-input-block"><input type="text" id="dlg-name" value="'+(data.name||'')+'" class="layui-input"></div></div></div></div>' +
'<div class="layui-row"><div class="layui-col-md6"><div class="layui-form-item"><label class="layui-form-label">奖励数量</label><div class="layui-input-block"><input type="number" id="dlg-count" value="'+(data.count||0)+'" class="layui-input"></div></div></div>' +
'<div class="layui-col-md6"><div class="layui-form-item"><label class="layui-form-label">图片</label><div class="layui-input-block" style="display:flex;"><input type="text" id="dlg-image" value="'+(data.imageUrl||'')+'" class="layui-input" placeholder="图片URL" style="flex:1;margin-right:5px;"><button type="button" id="btn-upload-advance-img" class="layui-btn layui-btn-sm"><i class="layui-icon layui-icon-upload"></i></button></div></div></div></div>' +
'<div class="layui-row"><div class="layui-col-md6"><div class="layui-form-item"><label class="layui-form-label">付费类型</label><div class="layui-input-block"><select id="dlg-payType"><option value="1"'+(data.payType===1?' selected':'')+'>金币</option><option value="2"'+(data.payType===2?' selected':'')+'>钻石</option><option value="3"'+(data.payType===3?' selected':'')+'>现金</option></select></div></div></div>' +
'<div class="layui-col-md6"><div class="layui-form-item"><label class="layui-form-label">付费金额</label><div class="layui-input-block"><input type="number" id="dlg-payCount" value="'+(data.payCount||0)+'" class="layui-input"></div></div></div></div>' +
'</form></div>';
layer.open({ type:1, title:isAdd?'添加进阶':'编辑进阶', area:['550px','380px'], content:html, btn:['保存','取消'],
success:function(l){
form.render('select');
layui.upload.render({
elem: l.find('#btn-upload-advance-img'),
url: (layui.setter.baseurl||'/')+'adminapi/ImageUpload/upload/temp',
done: function(r) {
if (r.code === 0) l.find('#dlg-image').val(r.data.path);
}
});
},
yes:function(idx,l){
var item = {
type: parseInt(l.find('#dlg-type').val()) || 1,
name: l.find('#dlg-name').val() || '',
count: parseInt(l.find('#dlg-count').val()) || 0,
imageUrl: l.find('#dlg-image').val() || '',
payType: parseInt(l.find('#dlg-payType').val()) || 1,
payCount: parseInt(l.find('#dlg-payCount').val()) || 0,
giftList: data.giftList || []
};
var b = bo();
if (!b.advanceGift) b.advanceGift = [];
if (isAdd) {
if (b.advanceGift.some(function(x) { return x.type === item.type; }))
{ layer.msg('类型已存在'); return; }
b.advanceGift.push(item);
} else {
var i = b.advanceGift.findIndex(function(x) { return x.type === data.type; });
if (i > -1) b.advanceGift[i] = item;
}
layer.close(idx); save(isAdd?'添加成功':'保存成功');
}
});
}
// ==================== 奖励编辑弹窗(三层tab,与奖池页面一致) ====================
function showRewardDialog(title, levelData) {
rewardCtx = { normal: levelData.normalGiftList || [], luxury: levelData.luxuryGiftList || [], supreme: levelData.supremeGiftList || [] };
var html = '<div class="reward-tabs" style="padding:10px;"><div class="layui-tab" lay-filter="reward-tab">' +
'<ul class="layui-tab-title"><li class="layui-this">普通奖励</li><li>豪华奖励</li><li>至尊奖励</li></ul>' +
'<div class="layui-tab-content" style="height:480px;">' +
'<div class="layui-tab-item layui-show"><div class="gift-placeholder" data-key="normalGiftList"></div></div>' +
'<div class="layui-tab-item"><div class="gift-placeholder" data-key="luxuryGiftList"></div></div>' +
'<div class="layui-tab-item"><div class="gift-placeholder" data-key="supremeGiftList"></div></div>' +
'</div></div></div>';
layer.open({
type: 1, title: title, area: ['1000px', '620px'],
content: html, shade: 0.1, shadeClose: true,
success: function(layero) {
// 初始化第一个tab的编辑器
initGiftEditor(layero, rewardCtx.normal);
// tab切换时重新初始化编辑器
form.on('tab(reward-tab)', function(o) {
var key = o.index === 0 ? 'normalGiftList' : o.index === 1 ? 'luxuryGiftList' : 'supremeGiftList';
var list = rewardCtx[key];
var ph = layero.find('.gift-placeholder[data-key="' + key + '"]');
ph.html($('#gift-editor-tpl').html());
var propOpts = '<option value="">请选择道具</option>';
propList.forEach(function(p) { propOpts += '<option value="'+p.number+'">'+p.number+' - '+p.name+'</option>'; });
ph.find('#giftProductNumber').html(propOpts);
form.render();
bindGiftEvents(ph, list);
});
},
end: function() { renderLevelTable(); renderAdvanceTable(); }
});
}
function initGiftEditor(layero, giftList) {
var ph = layero.find('.gift-placeholder[data-key="normalGiftList"]');
ph.html($('#gift-editor-tpl').html());
var propOpts = '<option value="">请选择道具</option>';
propList.forEach(function(p) { propOpts += '<option value="'+p.number+'">'+p.number+' - '+p.name+'</option>'; });
ph.find('#giftProductNumber').html(propOpts);
form.render();
bindGiftEvents(ph, giftList);
}
function bindGiftEvents(container, giftList) {
var $c = $(container);
currentGiftIndex = -1;
renderGiftTable($c, giftList);
$c.find('#btn-add-gift').on('click', function() {
giftList.push({ type:1, count:100, isRandom:false, minCount:0, maxCount:0, otherId:'' });
currentGiftIndex = giftList.length - 1;
renderGiftTable($c, giftList, currentGiftIndex);
loadGiftForm(giftList[giftList.length-1], $c);
});
$c.find('#btn-delete-gift').on('click', function() {
if (currentGiftIndex < 0) return layer.msg('请先选择要删除的项');
giftList.splice(currentGiftIndex, 1);
currentGiftIndex = -1; clearGiftForm($c);
renderGiftTable($c, giftList, -1);
});
$c.find('#btn-save-gift').on('click', function() {
if (currentGiftIndex < 0) return layer.msg('请先选择要编辑的项');
var t = parseInt($c.find('#giftType').val()) || 1;
var oid = t === 3 ? ($c.find('#giftProductNumber').val()||'') : '';
giftList[currentGiftIndex] = {
type: t, count: parseInt($c.find('#giftCount').val()) || 0,
isRandom: $c.find('#giftIsRandom')[0].checked,
minCount: parseInt($c.find('#giftMinCount').val()) || 0,
maxCount: parseInt($c.find('#giftMaxCount').val()) || 0,
otherId: oid
};
renderGiftTable($c, giftList, currentGiftIndex);
layer.msg('保存成功');
});
form.on('select(giftType)', function(o) {
$c.find('.gift-prop-select').toggle(o.value === '3');
$c.find('#giftCount').val(0); $c.find('#giftIsRandom')[0].checked = false; form.render('checkbox');
});
$c.find('#giftCount').on('input', function() { $c.find('#giftIsRandom')[0].checked = false; form.render('checkbox'); });
$c.find('#giftIsRandom').on('click', function() {
if (this.checked) setTimeout(function() { $c.find('#giftCount').val(0); }, 10);
});
}
var currentGiftIndex = -1;
function renderGiftTable(container, giftList, selectIndex) {
table.render({ elem: container.find('#table-gift-list'), data: giftList, page: false, height: 380,
cols: [[
{ type: 'numbers', title: '序号', width: 60 },
{ field: 'type', title: '类型', width: 80, templet: function(d) { return ({1:'金币',2:'钻石',3:'道具'})[d.type]||'金币'; }},
{ field: 'count', title: '数量', width: 80 },
{ field: 'minCount', title: '最小值', width: 80 },
{ field: 'maxCount', title: '最大值', width: 80 },
{ field: 'isRandom', title: '随机', width: 70, templet: function(d) { return d.isRandom ? '是' : '否'; }},
{ field: 'otherId', title: '道具Id', width: 120 }
]],
done: function() {
if (selectIndex === undefined) selectIndex = giftList.length > 0 ? 0 : -1;
if (selectIndex >= 0 && selectIndex < giftList.length) {
currentGiftIndex = selectIndex;
var tr = container.find('#table-gift-list + .layui-table-view tr[data-index="'+selectIndex+'"]');
if (tr.length) { tr.addClass('layui-table-click'); loadGiftForm(giftList[selectIndex], container); }
}
}
});
container.find('#table-gift-list + .layui-table-view').off('click').on('click', 'tr[data-index]', function() {
var idx = parseInt($(this).attr('data-index'));
if (idx >= 0 && idx < giftList.length) {
currentGiftIndex = idx;
container.find('tr[data-index]').removeClass('layui-table-click');
$(this).addClass('layui-table-click');
loadGiftForm(giftList[idx], container);
}
});
}
function loadGiftForm(data, container) {
container.find('#giftType').val(data.type || 1);
container.find('#giftCount').val(data.count || 0);
container.find('#giftIsRandom')[0].checked = data.isRandom || false;
container.find('#giftMinCount').val(data.minCount || 0);
container.find('#giftMaxCount').val(data.maxCount || 0);
var oid = data.otherId || '';
container.find('#giftProductNumber').val(oid);
container.find('.gift-prop-select').toggle(data.type === 3);
form.render('checkbox'); form.render('select');
}
function clearGiftForm(container) {
container.find('#giftType').val('1');
container.find('#giftCount').val('0');
container.find('#giftIsRandom')[0].checked = false;
container.find('#giftMinCount').val('0');
container.find('#giftMaxCount').val('0');
container.find('#giftProductNumber').val('');
container.find('.gift-prop-select').hide();
form.render('checkbox'); form.render('select');
}
// ==================== 初始化按钮(从 JSON 还原) ====================
$(document).on('click', '[lay-filter="init-level"],[lay-filter="init-advance"]', function() {
layer.confirm('确定要从配置文件还原战令数据吗?', function(i) {
layer.close(i);
admin.req({
method: 'get',
url: setter.baseurl + 'adminapi/GameConfig/InitActivitySection/BattleOrderConfig',
done: function(r) {
if (r.code === 0 || r.code === 200) {
layer.msg('初始化成功');
loadConfig();
} else {
layer.msg(r.info || '初始化失败');
}
}
});
});
});
$(document).on('click', '[lay-filter="add-level"]', function() {
var b = bo(), levels = b.levelConfig || [];
var max = levels.length ? Math.max.apply(null, levels.map(function(l){return l.level;})) : 0;
showLevelEdit({ level:max+1, exp:(max+1)*100, normalGiftList:[], luxuryGiftList:[], supremeGiftList:[], payType:1, payCount:(max+1)*100 }, true);
});
$(document).on('click', '[lay-filter="add-advance"]', function() {
var b = bo(), list = b.advanceGift || [];
var used = list.map(function(a){return a.type;});
var nt = 1; while (used.indexOf(nt) > -1) nt++;
showAdvanceEdit({ type:nt, name:({1:'豪华',2:'至尊',3:'豪华+至尊'})[nt]||'进阶', count:100, payType:1, payCount:10000, giftList:[] }, true);
});
$(document).on('click', '[lay-filter="save-levels"],[lay-filter="save-advances"]', function() { save('保存成功'); });
// ==================== 表格操作事件 ====================
table.on('tool(table-level)', function(obj) {
var d = obj.data, b = bo(), i = b.levelConfig.findIndex(function(x){return x.level===d.level;});
if (i < 0) return;
if (obj.event === 'del') {
layer.confirm('确定删除等级'+d.level+'?', function(idx) { b.levelConfig.splice(i,1); layer.close(idx); save('删除成功'); });
} else if (obj.event === 'edit') {
showLevelEdit(d, false);
}
});
table.on('tool(table-advance)', function(obj) {
var d = obj.data, b = bo(), i = b.advanceGift.findIndex(function(x){return String(x.type)===String(d.type);});
if (i < 0) return;
if (obj.event === 'del') {
layer.confirm('确定删除进阶'+d.name+'?', function(idx) { b.advanceGift.splice(i,1); layer.close(idx); save('删除成功'); });
} else if (obj.event === 'edit') {
showAdvanceEdit(d);
} else if (obj.event === 'reward') {
if (!b.advanceGift[i].giftList) b.advanceGift[i].giftList = [];
var html = '<div class="gift-placeholder">'+$('#gift-editor-tpl').html()+'</div>';
layer.open({
type:1, title:'进阶 - '+d.name+' 奖励列表', area:['1000px','620px'],
content: html, shade: 0.1, shadeClose: true,
success: function(layero) {
// 奖励类型下拉与道具编号输入是两个不同字段,不能把道具选项塞进类型下拉。
form.render();
bindGiftEvents(layero.find('.gift-placeholder'), b.advanceGift[i].giftList);
},
end: function() { renderLevelTable(); renderAdvanceTable(); }
});
}
});
window.saveAct=function(n){layui.admin.req({method:'get',url:(layui.setter.baseurl||'/')+'adminapi/ActivityConfig/SaveSection/'+n,done:function(r){layui.layer.msg(r.info||(r.code===0?'保存成功':'保存失败'))}})}
loadConfig();
});
</script>
<style>
.gift-container { display: flex; height: 460px; padding: 10px; }
.gift-left { flex: 1; margin-right: 15px; }
.gift-table-wrap { height: 380px; overflow: auto; }
.gift-right { width: 280px; border-left: 1px solid #e6e6e6; padding-left: 15px; }
.gift-right h4 { margin-bottom: 12px; color: #333; font-size: 15px; padding-bottom: 8px; border-bottom: 1px solid #eee; }
.gift-toolbar { margin-bottom: 8px; }
.gift-toolbar .layui-btn { margin-right: 5px; }
.gift-right .layui-form-item { margin-bottom: 10px; }
.gift-right .layui-form-label { padding: 9px 0; width: 60px; }
.gift-right .layui-input-block { margin-left: 70px; }
.reward-tabs .layui-tab-title { height: 42px; }
.reward-tabs .layui-tab-title li { line-height: 42px; }
.reward-tabs .layui-tab-content { padding: 10px 0; }
</style>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,179 @@
<title>Boss道具掉落配置</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏管理</cite></a>
<a><cite>Boss道具掉落配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-filter="init-bossdrop"><i class="layui-icon layui-icon-refresh"></i> 初始化配置</button>
<button class="layui-btn layui-btn-sm" lay-filter="save-bossdrop"><i class="layui-icon layui-icon-ok"></i> 保存配置</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-filter="reload-bossdrop"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" onclick="saveSection('BossDropConfig')"><i class="layui-icon layui-icon-export"></i> 保存到初始化文件</button>
</div>
</div>
<div class="layui-card-body">
<form class="layui-form" lay-filter="bd-form">
<div class="layui-form-item">
<label class="layui-form-label">不掉落权重</label>
<div class="layui-input-inline" style="width:200px;">
<input type="number" name="noDropWeight" class="layui-input" placeholder="不掉落权重(值越大越难掉落)">
</div>
<div class="layui-form-mid layui-word-aux">总权重 = 不掉落权重 + 各掉落项权重之和</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="save-base">保存基础配置</button>
</div>
</div>
</form>
</div>
</div>
<div class="layui-card">
<div class="layui-card-header">掉落道具列表(所有Boss通用)</div>
<div class="layui-card-body">
<div class="layui-btn-container" style="margin-bottom:10px;">
<button class="layui-btn layui-btn-sm" lay-filter="add-item"><i class="layui-icon layui-icon-add-1"></i> 添加掉落项</button>
<button class="layui-btn layui-btn-sm" lay-filter="save-items"><i class="layui-icon layui-icon-ok"></i> 保存</button>
</div>
<table id="table-items" lay-filter="table-items"></table>
</div>
</div>
</div>
<script type="text/html" id="bar-item">
<a class="layui-btn layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
<script>
layui.use(['admin', 'table', 'form', 'layer'], function () {
var $ = layui.$, admin = layui.admin, table = layui.table, form = layui.form, layer = layui.layer, setter = layui.setter;
var gameConfig = null, propList = [];
setTimeout(function() { $('.layui-layer-shade').remove(); layer.closeAll(); }, 100);
function loadConfig() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig",
done: function (res) {
if (res.code === 0 && res.data) {
gameConfig = res.data;
if (!gameConfig.bossDropConfig) gameConfig.bossDropConfig = { noDropWeight: 5000, dropItems: [] };
propList = (gameConfig.propConfig || []).filter(function(p){ return p.isSale; }).map(function(p){ return { number: p.number, name: p.name }; });
initForm();
renderTable();
}
}
});
}
function bd() { return gameConfig.bossDropConfig; }
function initForm() {
var cfg = bd();
$('input[name="noDropWeight"]').val(cfg.noDropWeight || 5000);
form.render();
}
function saveBase() {
var cfg = bd();
cfg.noDropWeight = parseInt($('input[name="noDropWeight"]').val()) || 5000;
save();
}
function save() {
admin.req({
method: "post", contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/GameConfig/UpdateBossDrop",
data: JSON.stringify(bd()),
done: function (r) {
r.code === 0 ? (layer.msg('保存成功'), loadConfig()) : layer.msg(r.info || '保存失败');
}
});
}
function renderTable() {
var items = bd().dropItems || [];
table.render({ elem: '#table-items', data: items, page: false,
cols: [[
{ type: 'numbers', title: '序号', width: 60 },
{ field: 'propName', title: '道具名称', width: 150 },
{ field: 'count', title: '数量', width: 80 },
{ field: 'weight', title: '权重', width: 80 },
{ title: '操作', width: 160, fixed: 'right', toolbar: '#bar-item' }
]]
});
}
form.on('submit(save-base)', function() { saveBase(); return false; });
function showItemEdit(data, idx) {
var items = bd().dropItems || [];
var opts = '<option value="">请选择道具</option>';
(gameConfig.propConfig || []).forEach(function(p) { opts += '<option value="'+p.number+'"'+(p.number===data.propNumber?' selected':'')+'>'+p.number+' - '+p.name+'</option>'; });
var html = '<div style="padding:20px;"><form class="layui-form">' +
'<div class="layui-form-item"><label class="layui-form-label">道具</label><div class="layui-input-block"><select id="dlg-prop" lay-search>'+opts+'</select></div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">数量</label><div class="layui-input-block"><input type="number" id="dlg-count" value="'+(data.count||1)+'" class="layui-input"></div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">权重</label><div class="layui-input-block"><input type="number" id="dlg-weight" value="'+(data.weight||100)+'" class="layui-input"></div></div>' +
'</form></div>';
layer.open({ type:1, title: idx < 0 ? '添加掉落项' : '编辑掉落项', area:['420px','320px'], content:html, btn:['保存','取消'],
success:function(l){ form.render('select'); },
yes:function(i,l){
var item = {
propNumber: l.find('#dlg-prop').val() || '',
propName: l.find('#dlg-prop option:selected').text().replace(/^[^-]+ - /, ''),
count: parseInt(l.find('#dlg-count').val()) || 1,
weight: parseInt(l.find('#dlg-weight').val()) || 0
};
if (idx < 0) items.push(item); else items[idx] = item;
layer.close(i); renderTable(); save();
}
});
}
$(document).on('click', '[lay-filter="add-item"]', function() { showItemEdit({ propNumber:'', propName:'', count:1, weight:100 }, -1); });
$(document).on('click', '[lay-filter="save-items"]', function() { save(); });
table.on('tool(table-items)', function(obj) {
var items = bd().dropItems || [];
if (obj.event === 'del') {
layer.confirm('确定删除?', function(i) { items.splice(items.indexOf(obj.data), 1); layer.close(i); renderTable(); save(); });
} else if (obj.event === 'edit') {
var idx = items.indexOf(obj.data);
showItemEdit(obj.data, idx);
}
});
$(document).on('click', '[lay-filter="init-bossdrop"]', function() {
layer.confirm('确定要从配置文件还原Boss掉落配置吗?', function(i) {
layer.close(i);
admin.req({
method: 'get',
url: setter.baseurl + 'adminapi/GameConfig/InitSection/BossDropConfig',
done: function(r) {
if (r.code === 0 || r.code === 200) {
layer.msg('初始化成功');
loadConfig();
} else {
layer.msg(r.info || '初始化失败');
}
}
});
});
});
$(document).on('click', '[lay-filter="reload-bossdrop"]', function() { loadConfig(); layer.msg('刷新成功'); });
window.saveSection=function(n){layui.admin.req({method:'get',url:(layui.setter.baseurl||'/')+'adminapi/GameConfig/SaveSection/'+n,done:function(r){layui.layer.msg(r.info||(r.code===0?'保存成功':'保存失败'))}})}
loadConfig();
});
</script>
@@ -0,0 +1,393 @@
<title>机器人AI配置</title>
<style>
.layui-form-label { width: 200px; }
.bot-row { display: flex; flex-wrap: wrap; gap: 8px; }
.bot-row .layui-form-item { display: flex; align-items: center; margin: 0; white-space: nowrap; flex: 1; min-width: 300px; }
.bot-row .layui-form-label { width: auto; text-align: right; padding-right: 6px; font-size: 13px; }
.bot-row .layui-input-inline { width: 70px; }
.bot-row .layui-form-mid { font-size: 11px; color: #999; margin-left: 4px; }
</style>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏管理</cite></a>
<a><cite>机器人AI配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-filter="init-botai"><i class="layui-icon layui-icon-refresh"></i> 初始化配置</button>
<button class="layui-btn layui-btn-sm" lay-filter="save-botai"><i class="layui-icon layui-icon-ok"></i> 保存配置</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-filter="reload-botai"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" onclick="saveSection('BotConfig')"><i class="layui-icon layui-icon-export"></i> 保存到初始化文件</button>
</div>
</div>
<div class="layui-card-body">
<form class="layui-form" id="form-botai" style="padding: 20px;">
<fieldset class="layui-elem-field"><legend>全局设置</legend>
<div class="layui-field-box">
<div class="layui-form-item"><label class="layui-form-label">总开关</label><div class="layui-input-inline"><input type="checkbox" name="enabled" lay-skin="switch" lay-text="开启|关闭"></div><div class="layui-form-mid">机器人总开关</div></div>
<div class="layui-form-item"><label class="layui-form-label">AI状态</label><div class="layui-input-inline"><input type="text" name="aiState" class="layui-input" style="width:120px;"></div><div class="layui-form-mid">active=正常 | idle=待机 | aggressive=激进</div></div>
<div class="layui-form-item"><label class="layui-form-label">爆率因子</label><div class="layui-input-inline"><input type="number" name="luckFactor" class="layui-input" step="0.1"></div><div class="layui-form-mid">1.0=正常, >1容易杀, &lt;1难杀</div></div>
<div class="layui-form-item"><label class="layui-form-label">状态刷新间隔(秒)</label><div class="layui-input-inline"><input type="number" name="stateRefreshInterval" class="layui-input" step="0.1"></div><div class="layui-form-mid">0=不自动刷新</div></div>
</div>
</fieldset>
<fieldset class="layui-elem-field"><legend>技能配置</legend>
<div class="layui-field-box">
<div class="layui-form-item"><label class="layui-form-label">技能刷新间隔(秒)</label><div class="layui-input-inline"><input type="number" name="skillRefreshInterval" class="layui-input" step="0.1"></div><div class="layui-form-mid">每隔多少秒重新掷骰子决定技能组合</div></div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">锁定概率</label><div class="layui-input-inline"><input type="number" name="lockProb" class="layui-input" step="0.01"></div><div class="layui-form-mid">自动瞄准最近鱼</div></div>
<div class="layui-form-item"><label class="layui-form-label">锁定Min</label><div class="layui-input-inline"><input type="number" name="lockMinTime" class="layui-input" step="0.1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">锁定Max</label><div class="layui-input-inline"><input type="number" name="lockMaxTime" class="layui-input" step="0.1"></div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">狂暴概率</label><div class="layui-input-inline"><input type="number" name="frenzyProb" class="layui-input" step="0.01"></div><div class="layui-form-mid">开火速度翻倍</div></div>
<div class="layui-form-item"><label class="layui-form-label">狂暴Min</label><div class="layui-input-inline"><input type="number" name="frenzyMinTime" class="layui-input" step="0.1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">狂暴Max</label><div class="layui-input-inline"><input type="number" name="frenzyMaxTime" class="layui-input" step="0.1"></div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">三发散弹概率</label><div class="layui-input-inline"><input type="number" name="tripleProb" class="layui-input" step="0.01"></div><div class="layui-form-mid">一次3发</div></div>
<div class="layui-form-item"><label class="layui-form-label">三发Min</label><div class="layui-input-inline"><input type="number" name="tripleMinTime" class="layui-input" step="0.1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">三发Max</label><div class="layui-input-inline"><input type="number" name="tripleMaxTime" class="layui-input" step="0.1"></div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">双倍炮概率</label><div class="layui-input-inline"><input type="number" name="doubleProb" class="layui-input" step="0.01"></div><div class="layui-form-mid">消耗翻倍</div></div>
<div class="layui-form-item"><label class="layui-form-label">双倍Min</label><div class="layui-input-inline"><input type="number" name="doubleMinTime" class="layui-input" step="0.1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">双倍Max</label><div class="layui-input-inline"><input type="number" name="doubleMaxTime" class="layui-input" step="0.1"></div></div>
</div>
</div>
</fieldset>
<!-- 行为模板 -->
<!-- 三个行为模式共用同一套字段,使用前缀区分 -->
<fieldset class="layui-elem-field"><legend>Active 行为参数</legend>
<div class="layui-field-box">
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">开火间隔Min(秒)</label><div class="layui-input-inline"><input type="number" name="activeFireIntervalMin" class="layui-input" step="0.1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">开火间隔Max(秒)</label><div class="layui-input-inline"><input type="number" name="activeFireIntervalMax" class="layui-input" step="0.1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">开火概率</label><div class="layui-input-inline"><input type="number" name="activeFireProb" class="layui-input" step="0.01"></div><div class="layui-form-mid">0.9=经常开火偶尔停顿</div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">旋转速度(度/秒)</label><div class="layui-input-inline"><input type="number" name="activeRotateSpeed" class="layui-input" step="1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">目标保持Min(秒)</label><div class="layui-input-inline"><input type="number" name="activeTargetHoldTimeMin" class="layui-input" step="0.1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">目标保持Max(秒)</label><div class="layui-input-inline"><input type="number" name="activeTargetHoldTimeMax" class="layui-input" step="0.1"></div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">切换目标概率</label><div class="layui-input-inline"><input type="number" name="activeTargetSwitchChance" class="layui-input" step="0.01"></div></div>
<div class="layui-form-item"><label class="layui-form-label">高分偏好</label><div class="layui-input-inline"><input type="number" name="activeTargetPreferHighScore" class="layui-input" step="0.1"></div><div class="layui-form-mid">0=纯距离, 1=纯分数</div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">允许打Boss</label><div class="layui-input-inline"><input type="checkbox" name="activeCanTargetBoss" lay-skin="switch" lay-text="是|否"></div></div>
<div class="layui-form-item"><label class="layui-form-label">允许打特殊鱼</label><div class="layui-input-inline"><input type="checkbox" name="activeCanTargetSpecial" lay-skin="switch" lay-text="是|否"></div></div>
</div>
</div>
</fieldset>
<fieldset class="layui-elem-field"><legend>Idle 行为参数</legend>
<div class="layui-field-box">
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">开火间隔Min(秒)</label><div class="layui-input-inline"><input type="number" name="idleFireIntervalMin" class="layui-input" step="0.1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">开火间隔Max(秒)</label><div class="layui-input-inline"><input type="number" name="idleFireIntervalMax" class="layui-input" step="0.1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">开火概率</label><div class="layui-input-inline"><input type="number" name="idleFireProb" class="layui-input" step="0.01"></div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">旋转速度(度/秒)</label><div class="layui-input-inline"><input type="number" name="idleRotateSpeed" class="layui-input" step="1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">目标保持Min(秒)</label><div class="layui-input-inline"><input type="number" name="idleTargetHoldTimeMin" class="layui-input" step="0.1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">目标保持Max(秒)</label><div class="layui-input-inline"><input type="number" name="idleTargetHoldTimeMax" class="layui-input" step="0.1"></div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">切换目标概率</label><div class="layui-input-inline"><input type="number" name="idleTargetSwitchChance" class="layui-input" step="0.01"></div></div>
<div class="layui-form-item"><label class="layui-form-label">高分偏好</label><div class="layui-input-inline"><input type="number" name="idleTargetPreferHighScore" class="layui-input" step="0.1"></div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">允许打Boss</label><div class="layui-input-inline"><input type="checkbox" name="idleCanTargetBoss" lay-skin="switch" lay-text="是|否"></div></div>
<div class="layui-form-item"><label class="layui-form-label">允许打特殊鱼</label><div class="layui-input-inline"><input type="checkbox" name="idleCanTargetSpecial" lay-skin="switch" lay-text="是|否"></div></div>
</div>
</div>
</fieldset>
<fieldset class="layui-elem-field"><legend>Aggressive 行为参数</legend>
<div class="layui-field-box">
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">开火间隔Min(秒)</label><div class="layui-input-inline"><input type="number" name="aggFireIntervalMin" class="layui-input" step="0.1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">开火间隔Max(秒)</label><div class="layui-input-inline"><input type="number" name="aggFireIntervalMax" class="layui-input" step="0.1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">开火概率</label><div class="layui-input-inline"><input type="number" name="aggFireProb" class="layui-input" step="0.01"></div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">旋转速度(度/秒)</label><div class="layui-input-inline"><input type="number" name="aggRotateSpeed" class="layui-input" step="1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">目标保持Min(秒)</label><div class="layui-input-inline"><input type="number" name="aggTargetHoldTimeMin" class="layui-input" step="0.1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">目标保持Max(秒)</label><div class="layui-input-inline"><input type="number" name="aggTargetHoldTimeMax" class="layui-input" step="0.1"></div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">切换目标概率</label><div class="layui-input-inline"><input type="number" name="aggTargetSwitchChance" class="layui-input" step="0.01"></div></div>
<div class="layui-form-item"><label class="layui-form-label">高分偏好</label><div class="layui-input-inline"><input type="number" name="aggTargetPreferHighScore" class="layui-input" step="0.1"></div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">允许打Boss</label><div class="layui-input-inline"><input type="checkbox" name="aggCanTargetBoss" lay-skin="switch" lay-text="是|否"></div></div>
<div class="layui-form-item"><label class="layui-form-label">允许打特殊鱼</label><div class="layui-input-inline"><input type="checkbox" name="aggCanTargetSpecial" lay-skin="switch" lay-text="是|否"></div></div>
</div>
</div>
</fieldset>
<fieldset class="layui-elem-field"><legend>倍数策略</legend>
<div class="layui-field-box">
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">初始档位索引</label><div class="layui-input-inline"><input type="number" name="multiplierInitialLevelIndex" class="layui-input"></div><div class="layui-form-mid">对应炮倍列表下标,0=最低档</div></div>
<div class="layui-form-item"><label class="layui-form-label">降档阈值</label><div class="layui-input-inline"><input type="number" name="multiplierDecreaseThreshold" class="layui-input" step="0.1"></div><div class="layui-form-mid">当前金币/进场金币 &lt; 此值</div></div>
<div class="layui-form-item"><label class="layui-form-label">升档阈值</label><div class="layui-input-inline"><input type="number" name="multiplierIncreaseThreshold" class="layui-input" step="0.1"></div><div class="layui-form-mid">当前金币/进场金币 > 此值</div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">调整档位步数</label><div class="layui-input-inline"><input type="number" name="multiplierStep" class="layui-input"></div></div>
<div class="layui-form-item"><label class="layui-form-label">检查间隔(秒)</label><div class="layui-input-inline"><input type="number" name="multiplierCheckInterval" class="layui-input" step="0.1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">最低档位</label><div class="layui-input-inline"><input type="number" name="multiplierMinLevelIndex" class="layui-input"></div><div class="layui-form-mid">不会降到低于此值</div></div>
<div class="layui-form-item"><label class="layui-form-label">最高档位(-1不限)</label><div class="layui-input-inline"><input type="number" name="multiplierMaxLevelIndex" class="layui-input"></div></div>
</div>
</div>
</fieldset>
<fieldset class="layui-elem-field"><legend>Boss策略</legend>
<div class="layui-field-box">
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">Boss鱼类型</label><div class="layui-input-inline"><input type="text" name="bossFishTypes" class="layui-input" style="width:120px;" placeholder="2,5"></div><div class="layui-form-mid">FishTypeEnum 的 int 值,逗号分隔</div></div>
<div class="layui-form-item"><label class="layui-form-label">无Boss散射</label><div class="layui-input-inline"><input type="checkbox" name="scatterWhenNoBoss" lay-skin="switch" lay-text="开启|关闭"></div></div>
<div class="layui-form-item"><label class="layui-form-label">三发概率</label><div class="layui-input-inline"><input type="number" name="tripleShotProb" class="layui-input" step="0.01"></div><div class="layui-form-mid">无Boss时定点三发概率</div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">优先最大倍率Boss</label><div class="layui-input-inline"><input type="checkbox" name="preferMaxMultiplierBoss" lay-skin="switch" lay-text="是|否"></div></div>
<div class="layui-form-item"><label class="layui-form-label">散射阈值</label><div class="layui-input-inline"><input type="number" name="scatterModeThreshold" class="layui-input"></div><div class="layui-form-mid">Boss数 > 此值开启散射</div></div>
<div class="layui-form-item"><label class="layui-form-label">狂暴概率</label><div class="layui-input-inline"><input type="number" name="bossFrenzyProb" class="layui-input" step="0.01"></div><div class="layui-form-mid">Boss数 ≤ 散射阈值时的概率</div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">狂暴Min</label><div class="layui-input-inline"><input type="number" name="bossFrenzyMinTime" class="layui-input" step="0.1"></div></div>
<div class="layui-form-item"><label class="layui-form-label">狂暴Max</label><div class="layui-input-inline"><input type="number" name="bossFrenzyMaxTime" class="layui-input" step="0.1"></div></div>
</div>
</div>
</fieldset>
<fieldset class="layui-elem-field"><legend>退出条件</legend>
<div class="layui-field-box">
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">盈利退出(金币)</label><div class="layui-input-inline"><input type="number" name="exitWhenGoldAbove" class="layui-input"></div><div class="layui-form-mid">超过此值时退出(盈利离场),0=不检测</div></div>
<div class="layui-form-item"><label class="layui-form-label">亏损退出(金币)</label><div class="layui-input-inline"><input type="number" name="exitWhenGoldBelow" class="layui-input"></div><div class="layui-form-mid">低于此值时退出(输完离场)</div></div>
<div class="layui-form-item"><label class="layui-form-label">检查间隔(秒)</label><div class="layui-input-inline"><input type="number" name="exitCheckInterval" class="layui-input" step="0.1"></div></div>
</div>
<div class="bot-row">
<div class="layui-form-item"><label class="layui-form-label">发送离房消息</label><div class="layui-input-inline"><input type="checkbox" name="sendLeaveRoom" lay-skin="switch" lay-text="是|否"></div></div>
<div class="layui-form-item"><label class="layui-form-label">退出后显示等待</label><div class="layui-input-inline"><input type="checkbox" name="showWaitingAfterExit" lay-skin="switch" lay-text="是|否"></div></div>
</div>
</div>
</fieldset>
</form>
</div>
</div>
</div>
<script>
window.saveSection=function(n){layui.admin.req({method:'get',url:(layui.setter.baseurl||'/')+'adminapi/GameConfig/SaveSection/'+n,done:function(r){layui.layer.msg(r.info||(r.code===0?'保存成功':'保存失败'))}})};
</script>
<script>
layui.use(['admin', 'form', 'layer'], function () {
var $ = layui.$,
admin = layui.admin,
form = layui.form,
layer = layui.layer,
setter = layui.setter;
var gameConfig = null;
setTimeout(function() { $('.layui-layer-shade').remove(); layer.closeAll(); }, 100);
function loadConfig() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig",
done: function (res) {
if (res.code === 0 && res.data) { gameConfig = res.data; fillForm(); }
}
});
}
function bc() { if (!gameConfig.botConfig) gameConfig.botConfig = {}; return gameConfig.botConfig; }
function val(name, def) { var v = $('#form-botai input[name="'+name+'"]').val(); return v !== undefined && v !== '' ? v : def; }
function chk(name) { return $('#form-botai input[name="'+name+'"]').prop('checked'); }
function num(name, def) { return parseFloat(val(name, def)) || def; }
function intv(name, def) { return parseInt(val(name, def)) || def; }
function fillBehavior(prefix, data) {
if (!data) return;
$('#form-botai input[name="'+prefix+'FireIntervalMin"]').val(data.fireIntervalMin ?? 0.3);
$('#form-botai input[name="'+prefix+'FireIntervalMax"]').val(data.fireIntervalMax ?? 1.2);
$('#form-botai input[name="'+prefix+'FireProb"]').val(data.fireProb ?? 0.9);
$('#form-botai input[name="'+prefix+'RotateSpeed"]').val(data.rotateSpeed ?? 80);
$('#form-botai input[name="'+prefix+'TargetHoldTimeMin"]').val(data.targetHoldTimeMin ?? 2);
$('#form-botai input[name="'+prefix+'TargetHoldTimeMax"]').val(data.targetHoldTimeMax ?? 5);
$('#form-botai input[name="'+prefix+'TargetSwitchChance"]').val(data.targetSwitchChance ?? 0.7);
$('#form-botai input[name="'+prefix+'TargetPreferHighScore"]').val(data.targetPreferHighScore ?? 0);
$('#form-botai input[name="'+prefix+'CanTargetBoss"]').prop('checked', data.canTargetBoss !== false);
$('#form-botai input[name="'+prefix+'CanTargetSpecial"]').prop('checked', data.canTargetSpecial !== false);
}
function readBehavior(prefix) {
return {
fireIntervalMin: num(prefix+'FireIntervalMin', 0.3),
fireIntervalMax: num(prefix+'FireIntervalMax', 1.2),
fireProb: num(prefix+'FireProb', 0.9),
rotateSpeed: num(prefix+'RotateSpeed', 80),
targetHoldTimeMin: num(prefix+'TargetHoldTimeMin', 2),
targetHoldTimeMax: num(prefix+'TargetHoldTimeMax', 5),
targetSwitchChance: num(prefix+'TargetSwitchChance', 0.7),
targetPreferHighScore: num(prefix+'TargetPreferHighScore', 0),
canTargetBoss: chk(prefix+'CanTargetBoss'),
canTargetSpecial: chk(prefix+'CanTargetSpecial')
};
}
function fillForm() {
var c = bc();
$('#form-botai input[name="enabled"]').prop('checked', c.enabled !== false);
$('#form-botai input[name="aiState"]').val(c.aiState || 'active');
$('#form-botai input[name="luckFactor"]').val(c.luckFactor ?? 1);
$('#form-botai input[name="stateRefreshInterval"]').val(c.stateRefreshInterval ?? 0);
var sk = c.skills || {};
$('#form-botai input[name="skillRefreshInterval"]').val(sk.skillRefreshInterval ?? 10);
$('#form-botai input[name="lockProb"]').val(sk.lockProb ?? 0);
$('#form-botai input[name="lockMinTime"]').val(sk.lockMinTime ?? 5);
$('#form-botai input[name="lockMaxTime"]').val(sk.lockMaxTime ?? 20);
$('#form-botai input[name="frenzyProb"]').val(sk.frenzyProb ?? 0.3);
$('#form-botai input[name="frenzyMinTime"]').val(sk.frenzyMinTime ?? 3);
$('#form-botai input[name="frenzyMaxTime"]').val(sk.frenzyMaxTime ?? 8);
$('#form-botai input[name="tripleProb"]').val(sk.tripleProb ?? 0.5);
$('#form-botai input[name="tripleMinTime"]').val(sk.tripleMinTime ?? 4);
$('#form-botai input[name="tripleMaxTime"]').val(sk.tripleMaxTime ?? 12);
$('#form-botai input[name="doubleProb"]').val(sk.doubleProb ?? 0);
$('#form-botai input[name="doubleMinTime"]').val(sk.doubleMinTime ?? 3);
$('#form-botai input[name="doubleMaxTime"]').val(sk.doubleMaxTime ?? 6);
var mp = c.multiplier || {};
$('#form-botai input[name="multiplierInitialLevelIndex"]').val(mp.initialLevelIndex ?? 0);
$('#form-botai input[name="multiplierDecreaseThreshold"]').val(mp.decreaseThreshold ?? 0.5);
$('#form-botai input[name="multiplierIncreaseThreshold"]').val(mp.increaseThreshold ?? 1.5);
$('#form-botai input[name="multiplierStep"]').val(mp.step ?? 1);
$('#form-botai input[name="multiplierCheckInterval"]').val(mp.checkInterval ?? 3);
$('#form-botai input[name="multiplierMinLevelIndex"]').val(mp.minLevelIndex ?? 0);
$('#form-botai input[name="multiplierMaxLevelIndex"]').val(mp.maxLevelIndex ?? -1);
var bs = c.bossStrategy || {};
$('#form-botai input[name="bossFishTypes"]').val((bs.bossFishTypes || [2,5]).join(','));
$('#form-botai input[name="scatterWhenNoBoss"]').prop('checked', bs.scatterWhenNoBoss !== false);
$('#form-botai input[name="tripleShotProb"]').val(bs.tripleShotProb ?? 0.5);
$('#form-botai input[name="preferMaxMultiplierBoss"]').prop('checked', bs.preferMaxMultiplierBoss !== false);
$('#form-botai input[name="scatterModeThreshold"]').val(bs.scatterModeThreshold ?? 3);
$('#form-botai input[name="bossFrenzyProb"]').val(bs.frenzyProb ?? 0.6);
$('#form-botai input[name="bossFrenzyMinTime"]').val(bs.frenzyMinTime ?? 3);
$('#form-botai input[name="bossFrenzyMaxTime"]').val(bs.frenzyMaxTime ?? 8);
var ex = c.exitCondition || {};
$('#form-botai input[name="exitWhenGoldAbove"]').val(ex.exitWhenGoldAbove ?? 0);
$('#form-botai input[name="exitWhenGoldBelow"]').val(ex.exitWhenGoldBelow ?? 100);
$('#form-botai input[name="exitCheckInterval"]').val(ex.checkInterval ?? 5);
$('#form-botai input[name="sendLeaveRoom"]').prop('checked', ex.sendLeaveRoom !== false);
$('#form-botai input[name="showWaitingAfterExit"]').prop('checked', ex.showWaitingAfterExit !== false);
fillBehavior('active', c.activeBehavior);
fillBehavior('idle', c.idleBehavior);
fillBehavior('agg', c.aggressiveBehavior);
form.render();
}
function collectConfig() {
var c = bc();
c.enabled = chk('enabled');
c.aiState = val('aiState', 'active');
c.luckFactor = num('luckFactor', 1);
c.stateRefreshInterval = num('stateRefreshInterval', 0);
c.skills = {
skillRefreshInterval: num('skillRefreshInterval', 10),
lockProb: num('lockProb', 0), lockMinTime: num('lockMinTime', 5), lockMaxTime: num('lockMaxTime', 20),
frenzyProb: num('frenzyProb', 0.3), frenzyMinTime: num('frenzyMinTime', 3), frenzyMaxTime: num('frenzyMaxTime', 8),
tripleProb: num('tripleProb', 0.5), tripleMinTime: num('tripleMinTime', 4), tripleMaxTime: num('tripleMaxTime', 12),
doubleProb: num('doubleProb', 0), doubleMinTime: num('doubleMinTime', 3), doubleMaxTime: num('doubleMaxTime', 6)
};
c.multiplier = {
initialLevelIndex: intv('multiplierInitialLevelIndex', 0),
decreaseThreshold: num('multiplierDecreaseThreshold', 0.5),
increaseThreshold: num('multiplierIncreaseThreshold', 1.5),
step: intv('multiplierStep', 1),
checkInterval: num('multiplierCheckInterval', 3),
minLevelIndex: intv('multiplierMinLevelIndex', 0),
maxLevelIndex: intv('multiplierMaxLevelIndex', -1)
};
var bft = val('bossFishTypes', '2,5').split(',').map(function(s) { return parseInt(s.trim()) || 2; }).filter(function(n) { return n > 0; });
c.bossStrategy = {
bossFishTypes: bft.length > 0 ? bft : [2, 5],
scatterWhenNoBoss: chk('scatterWhenNoBoss'),
tripleShotProb: num('tripleShotProb', 0.5),
preferMaxMultiplierBoss: chk('preferMaxMultiplierBoss'),
scatterModeThreshold: intv('scatterModeThreshold', 3),
frenzyProb: num('bossFrenzyProb', 0.6),
frenzyMinTime: num('bossFrenzyMinTime', 3),
frenzyMaxTime: num('bossFrenzyMaxTime', 8)
};
c.exitCondition = {
exitWhenGoldAbove: intv('exitWhenGoldAbove', 0),
exitWhenGoldBelow: intv('exitWhenGoldBelow', 100),
checkInterval: num('exitCheckInterval', 5),
sendLeaveRoom: chk('sendLeaveRoom'),
showWaitingAfterExit: chk('showWaitingAfterExit')
};
c.activeBehavior = readBehavior('active');
c.idleBehavior = readBehavior('idle');
c.aggressiveBehavior = readBehavior('agg');
return c;
}
$(document).on('click', '[lay-filter="init-botai"]', function () {
layer.confirm('确定要初始化机器人AI配置吗?此操作将覆盖现有数据!', function(idx) {
layer.close(idx);
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig/InitSection/BotConfig",
done: function (res) {
layer.msg(res.info || (res.code === 0 ? '初始化成功' : '初始化失败'));
if (res.code === 0 || res.code === 200) loadConfig();
}
});
});
});
$(document).on('click', '[lay-filter="reload-botai"]', function () { loadConfig(); layer.msg('刷新成功'); });
$(document).on('click', '[lay-filter="save-botai"]', function () {
collectConfig();
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/GameConfig/Update",
data: JSON.stringify(gameConfig),
done: function (res) {
if (res.code === 0 || res.code === 200) { layer.msg('保存成功'); loadConfig(); }
else { layer.msg(res.info || '保存失败'); }
}
});
});
loadConfig();
});
</script>
@@ -0,0 +1,313 @@
<title>机器人头像配置</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏管理</cite></a>
<a><cite>机器人头像配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-body">
<table id="table-bot" lay-filter="table-bot"></table>
</div>
</div>
</div>
<!-- 头像预览列模板 -->
<script type="text/html" id="tpl-avatar">
{{# if(d.avatar){ }}
<img src="{{d.avatar}}" style="width:40px;height:40px;border-radius:50%;object-fit:cover;vertical-align:middle;" onerror="this.src='../../../images/face/0.png'">
{{# } else { }}
<span style="color:#aaa;">无</span>
{{# } }}
</script>
<script type="text/html" id="toolbar-bot">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-filter="add-bot"><i class="layui-icon layui-icon-add-1"></i> 添加机器人</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-filter="reload-bot"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
</div>
</script>
<script type="text/html" id="bot-bar">
<a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>
<!-- 添加/编辑弹窗内嵌表单(隐藏,动态使用) -->
<div id="bot-form-tpl" style="display:none;">
<div style="padding:24px 20px 10px;">
<form class="layui-form" id="bot-edit-form">
<input type="hidden" name="id" id="bot-id">
<div class="layui-form-item">
<label class="layui-form-label">昵称 <span style="color:red">*</span></label>
<div class="layui-input-block">
<input type="text" name="name" id="bot-name" class="layui-input" placeholder="请输入机器人昵称" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">头像</label>
<div class="layui-input-block">
<div style="display:flex;align-items:center;gap:12px;">
<!-- 预览框 -->
<div id="avatar-preview-wrap" style="width:64px;height:64px;border:1px dashed #ccc;border-radius:50%;overflow:hidden;display:flex;align-items:center;justify-content:center;background:#f8f8f8;flex-shrink:0;">
<img id="avatar-preview" src="" style="width:100%;height:100%;object-fit:cover;display:none;" onerror="this.style.display='none';document.getElementById('avatar-placeholder').style.display='flex';">
<span id="avatar-placeholder" style="font-size:12px;color:#aaa;text-align:center;display:flex;">点击上传</span>
</div>
<!-- 上传按钮 -->
<div>
<button type="button" class="layui-btn layui-btn-sm" id="btn-upload-avatar">
<i class="layui-icon layui-icon-upload"></i> 上传图片
</button>
<input type="file" id="avatar-file-input" accept="image/*" style="display:none;">
<div style="margin-top:6px;font-size:12px;color:#999;">支持 jpg/png/gif,建议正方形,≤2MB</div>
</div>
</div>
<!-- URL 输入框(上传后自动填充,也可手动填写) -->
<input type="text" name="avatar" id="bot-avatar" class="layui-input" placeholder="头像URL(上传后自动填充,或直接粘贴URL)" style="margin-top:10px;">
</div>
</div>
</form>
</div>
</div>
<script>
layui.use(['admin', 'table', 'form', 'layer', 'upload'], function () {
var $ = layui.$,
admin = layui.admin,
table = layui.table,
form = layui.form,
layer = layui.layer,
upload = layui.upload,
setter = layui.setter;
setTimeout(function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
$('.layui-layer-shade').remove();
layer.closeAll();
}, 100);
function loadData() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/BotConfig/GetAll",
done: function (res) {
if (res.code === 0) {
table.reload('table-bot', { data: res.data || [] });
} else {
layer.msg(res.info || '加载失败');
}
}
});
}
table.render({
elem: '#table-bot',
url: setter.baseurl + 'adminapi/BotConfig/GetAll',
method: 'get',
page: false,
cols: [[
{ type: 'numbers', title: '序号', width: 60 },
{ field: "avatar", title: "头像", width: 80, templet: '#tpl-avatar', align: 'center' },
{ field: "name", title: "昵称", minWidth: 200 },
{ field: "avatar", title: "头像URL", minWidth: 280 },
{ title: "操作", width: 150, fixed: 'right', toolbar: '#bot-bar' }
]],
toolbar: '#toolbar-bot',
defaultToolbar: ['filter', 'exports', 'print']
});
$(document).on('click', '[lay-filter="add-bot"]', function () {
showEditDialog(null, true);
});
$(document).on('click', '[lay-filter="reload-bot"]', function () {
loadData();
layer.msg('刷新成功');
});
table.on('tool(table-bot)', function (obj) {
if (obj.event === 'del') {
layer.confirm('确定删除「' + obj.data.name + '」吗?', function (idx) {
admin.req({
method: "post",
url: setter.baseurl + "adminapi/BotConfig/Delete?id=" + encodeURIComponent(obj.data.id || obj.data.Id || ''),
done: function (res) {
if (res.code === 0) {
obj.del();
layer.msg('删除成功');
} else {
layer.msg(res.info || '删除失败');
}
}
});
layer.close(idx);
});
} else if (obj.event === 'edit') {
showEditDialog(obj.data, false);
}
});
// =========== 上传逻辑 ===========
var currentLayer = null; // 当前弹窗 DOM 引用
// 判断是否后台相对路径,自动补全当前域名
function fixUrl(path) {
if (!path) return '';
// 已是完整URL直接返回
if (/^https?:\/\//i.test(path)) return path;
// 相对路径补全域名
if (path.indexOf('/') === 0) {
return window.location.origin + path;
}
return path;
}
function initUpload(layero) {
currentLayer = layero;
// 点击上传按钮触发文件选择
layero.find('#btn-upload-avatar').off('click').on('click', function () {
$('#avatar-file-input').val('').trigger('click');
});
// 文件选择后立即上传
$('#avatar-file-input').off('change').on('change', function () {
var file = this.files[0];
if (!file) return;
// 大小校验 2MB
if (file.size > 2 * 1024 * 1024) {
layer.msg('图片大小不能超过 2MB');
return;
}
var formData = new FormData();
formData.append('file', file);
// 显示上传中状态
var loadIdx = layer.load(2, { shade: [0.3, '#fff'] });
$.ajax({
url: setter.baseurl + 'adminapi/ImageUpload/upload/temp',
type: 'POST',
data: formData,
processData: false,
contentType: false,
headers: {
'Authorization': 'Bearer ' + (setter.token || layui.data(setter.tableName || 'layuiAdmin')[setter.tokenName || 'token'] || '')
},
success: function (res) {
layer.close(loadIdx);
if (res.code === 0 && res.data && res.data.path) {
// 后台返回相对路径,自动拼接当前域名
var url = fixUrl(res.data.path);
// 填充弹窗内 URL 输入框
currentLayer.find('#bot-avatar').val(url);
// 更新弹窗内预览
setAvatarPreview(url);
layer.msg('上传成功');
} else {
layer.msg(res.info || res.msg || '上传失败');
}
},
error: function () {
layer.close(loadIdx);
layer.msg('上传请求失败,请稍后重试');
}
});
});
// URL 输入框变化时同步预览
layero.find('#bot-avatar').off('input').on('input', function () {
setAvatarPreview($(this).val().trim());
});
}
function setAvatarPreview(url) {
if (!currentLayer) return;
if (url) {
currentLayer.find('#avatar-preview').attr('src', url).show();
currentLayer.find('#avatar-placeholder').hide();
} else {
currentLayer.find('#avatar-preview').attr('src', '').hide();
currentLayer.find('#avatar-placeholder').show();
}
}
// =========== 弹窗 ===========
function showEditDialog(data, isAdd) {
var title = isAdd ? '添加机器人' : '编辑机器人';
var formHtml = $('#bot-form-tpl').html();
layer.open({
type: 1,
title: title,
area: ['520px', '380px'],
content: formHtml,
btn: ['保存', '取消'],
shadeClose: true,
success: function (layero) {
form.render();
// 回填数据
if (data) {
layero.find('#bot-id').val(data.id || '');
layero.find('#bot-name').val(data.name || '');
layero.find('#bot-avatar').val(data.avatar || '');
setAvatarPreview(data.avatar || '');
} else {
layero.find('#bot-id').val('');
layero.find('#bot-name').val('');
layero.find('#bot-avatar').val('');
setAvatarPreview('');
}
// 初始化上传事件(传入弹窗DOM引用)
initUpload(layero);
},
yes: function (idx, layero) {
var name = layero.find('#bot-name').val().trim();
var avatar = layero.find('#bot-avatar').val().trim();
var id = layero.find('#bot-id').val();
if (!name) {
layer.msg('昵称不能为空');
return;
}
var url, sendData;
if (isAdd) {
url = setter.baseurl + 'adminapi/BotConfig/Add';
sendData = { name: name, avatar: avatar };
} else {
url = setter.baseurl + 'adminapi/BotConfig/Update';
sendData = { id: id, name: name, avatar: avatar };
}
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: url,
data: JSON.stringify(sendData),
done: function (res) {
if (res.code === 0) {
layer.close(idx);
layer.msg(isAdd ? '添加成功' : '更新成功');
loadData();
} else {
layer.msg(res.info || '操作失败');
}
}
});
}
});
}
});
</script>
@@ -0,0 +1,220 @@
<title>游戏房间配置</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏管理</cite></a>
<a><cite>游戏房间配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header" style="font-weight:bold;">中国象棋 - 开房权限</div>
<div class="layui-card-body" style="padding-top:10px;">
<form class="layui-form" lay-filter="chess-form">
<div class="layui-form-item">
<label class="layui-form-label">开房权限</label>
<div class="layui-input-block" id="createRoomLimit">
<input type="checkbox" name="createRoomLimit" value="1" title="VIP" lay-filter="createRoomLimitVip">
<input type="checkbox" name="createRoomLimit" value="2" title="代理商">
<input type="checkbox" name="createRoomLimit" value="3" title="普通用户">
</div>
</div>
<div class="layui-form-item" id="createRoomVipLevelsRow" style="display:none;">
<label class="layui-form-label">VIP等级范围</label>
<div class="layui-input-block" id="createRoomVipLevels">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">最小下注</label>
<div class="layui-input-block">
<input type="number" name="minBetGold" class="layui-input" style="width:300px">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">下注档位</label>
<div class="layui-input-block">
<input type="text" name="betGoldLimits" placeholder="多个值用逗号分隔" class="layui-input" style="width:300px">
</div>
</div>
</form>
</div>
</div>
<div class="layui-card">
<div class="layui-card-header" style="font-weight:bold;">捕鱼 - 手动选座权限</div>
<div class="layui-card-body" style="padding-top:10px;">
<form class="layui-form" lay-filter="fish-form">
<div class="layui-form-item">
<label class="layui-form-label">手动选座</label>
<div class="layui-input-block" id="seatSelectLimit">
<input type="checkbox" name="seatSelectLimit" value="1" title="VIP" lay-filter="seatSelectLimitVip">
<input type="checkbox" name="seatSelectLimit" value="2" title="代理商">
<input type="checkbox" name="seatSelectLimit" value="3" title="普通用户">
</div>
</div>
<div class="layui-form-item" id="seatSelectVipLevelsRow" style="display:none;">
<label class="layui-form-label">VIP等级范围</label>
<div class="layui-input-block" id="seatSelectVipLevels">
</div>
</div>
</form>
</div>
</div>
<div class="layui-card">
<div class="layui-card-body" style="padding-top:10px;">
<button class="layui-btn" lay-submit lay-filter="save-all"><i class="layui-icon layui-icon-ok"></i> 保存配置</button>
<button class="layui-btn layui-btn-warm" type="button" lay-filter="reload-all"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" onclick="saveSection('ChessConfig')"><i class="layui-icon layui-icon-export"></i> 保存到初始化文件</button>
</div>
</div>
</div>
<script>
layui.use(['admin', 'form'], function () {
var $ = layui.$,
admin = layui.admin,
form = layui.form,
setter = layui.setter;
setTimeout(function() {
$('.layui-layer-shade').remove();
layer.closeAll();
}, 100);
function buildVipCheckboxHtml(selectedVipLevels) {
var html = '';
for (var i = 1; i <= 10; i++) {
var checked = selectedVipLevels && selectedVipLevels.indexOf(i) !== -1 ? 'checked' : '';
html += '<input type="checkbox" name="vipLevels" value="' + i + '" title="VIP' + i + '" ' + checked + ' lay-skin="primary">';
}
return html;
}
function toggleVipLevelsRow() {
var createVipChecked = $('input[name="createRoomLimit"][value="1"]').is(':checked');
$('#createRoomVipLevelsRow').toggle(createVipChecked);
var seatVipChecked = $('input[name="seatSelectLimit"][value="1"]').is(':checked');
$('#seatSelectVipLevelsRow').toggle(seatVipChecked);
}
form.on('checkbox(createRoomLimitVip)', function() {
toggleVipLevelsRow();
});
form.on('checkbox(seatSelectLimitVip)', function() {
toggleVipLevelsRow();
});
function loadConfig() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig",
done: function (res) {
if (res.code === 0 && res.data) {
var cc = res.data.chessConfig || {};
var fc = res.data.fishRoomConfig || {};
$('#createRoomVipLevels').html(buildVipCheckboxHtml(cc.createRoomVipLevels || []));
$('#seatSelectVipLevels').html(buildVipCheckboxHtml(fc.seatSelectVipLevels || []));
$('input[name="createRoomLimit"]').prop('checked', false);
if (cc.createRoomLimit && Array.isArray(cc.createRoomLimit)) {
cc.createRoomLimit.forEach(function(v) {
$('input[name="createRoomLimit"][value="' + v + '"]').prop('checked', true);
});
}
$('input[name="seatSelectLimit"]').prop('checked', false);
if (fc.seatSelectLimit && Array.isArray(fc.seatSelectLimit)) {
fc.seatSelectLimit.forEach(function(v) {
$('input[name="seatSelectLimit"][value="' + v + '"]').prop('checked', true);
});
}
$('input[name="minBetGold"]').val(cc.minBetGold || 0);
var betStr = '';
if (cc.betGoldLimits && Array.isArray(cc.betGoldLimits)) {
betStr = cc.betGoldLimits.join(',');
}
$('input[name="betGoldLimits"]').val(betStr);
form.render('checkbox');
toggleVipLevelsRow();
}
}
});
}
function collectVipLevels(containerId) {
var levels = [];
$('#' + containerId + ' input[name="vipLevels"]:checked').each(function() {
levels.push(parseInt($(this).val()));
});
return levels;
}
$(document).on('click', '[lay-filter="save-all"]', function() {
var createRoomChecked = [];
$('input[name="createRoomLimit"]:checked').each(function() {
createRoomChecked.push(parseInt($(this).val()));
});
var seatChecked = [];
$('input[name="seatSelectLimit"]:checked').each(function() {
seatChecked.push(parseInt($(this).val()));
});
var betStr = $('input[name="betGoldLimits"]').val() || '';
var betList = [];
if (betStr.trim()) {
betList = betStr.split(',').map(function(x) { return parseInt(x.trim()); }).filter(function(x) { return !isNaN(x); });
}
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig",
done: function (res) {
if (res.code === 0 && res.data) {
res.data.chessConfig = {
createRoomLimit: createRoomChecked,
createRoomVipLevels: collectVipLevels('createRoomVipLevels'),
minBetGold: parseInt($('input[name="minBetGold"]').val()) || 0,
betGoldLimits: betList
};
res.data.fishRoomConfig = {
seatSelectLimit: seatChecked,
seatSelectVipLevels: collectVipLevels('seatSelectVipLevels')
};
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/GameConfig/Update",
data: JSON.stringify(res.data),
done: function (r) {
if (r.code === 0 || r.code === 200) {
layer.msg('保存成功');
} else {
layer.msg(r.info || '保存失败');
}
}
});
}
}
});
return false;
});
$(document).on('click', '[lay-filter="reload-all"]', function() {
loadConfig();
layer.msg('刷新成功');
});
window.saveSection=function(n){layui.admin.req({method:'get',url:(layui.setter.baseurl||'/')+'adminapi/GameConfig/SaveSection/'+n,done:function(r){layui.layer.msg(r.info||(r.code===0?'保存成功':'保存失败'))}})}
loadConfig();
});
</script>
@@ -0,0 +1,111 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>象棋 Hub 流程测试</title>
<style>
body { font-family: Consolas, monospace; font-size: 13px; background: #111; color: #ddd; padding: 12px; }
button { cursor: pointer; padding: 6px 12px; margin: 3px; }
.log { background: #000; border: 1px solid #333; height: 50vh; overflow-y: auto; margin-top: 10px; padding: 8px; white-space: pre-wrap; font-size: 12px; }
.ok { color: #0f0; } .bad { color: #f55; } .info { color: #9ad; }
</style>
</head>
<body>
<h3>象棋 Hub 流程测试</h3>
<div>
<label>Hub地址: <input id="hub" value="" placeholder="请输入测试环境 Hub 地址" style="width:260px"></label>
<button onclick="connAll()">1. 连接 L1/A/B</button>
<button onclick="createRoom()">2. A 建房</button>
<button onclick="joinRoom()">3. B 加入</button>
<button onclick="readyAll()">4. 双方Ready</button>
<button onclick="bExit()">5. B退出</button>
<button onclick="bJoin()">6. B重新加入</button>
<button onclick="afkA()">7. A挂起</button>
<button onclick="afkB()">8. B挂起(等5s)</button>
<br>
<label>RoomID: <input id="room" name="room" value="" style="width:200px"></label>
<label>密码: <input id="password" name="password" value="" placeholder="请输入测试房间密码" style="width:180px"></label>
<label>UID-A: <input id="a" name="a" value="" style="width:280px"></label>
<label>UID-B: <input id="b" name="b" value="" style="width:280px"></label>
<button onclick="clearLog()">清空日志</button>
</div>
<div class="log" id="log"></div>
<script src="https://cdn.jsdelivr.net/npm/@microsoft/signalr@8.0.7/dist/browser/signalr.min.js"></script>
<script>
const logEl = document.getElementById('log');
function log(msg) { logEl.innerHTML += msg + '\n'; logEl.scrollTop = logEl.scrollHeight; }
function ok(m) { log('<span class="ok">[OK]</span> ' + m); }
function bad(m) { log('<span class="bad">[FAIL]</span> ' + m); }
function info(m) { log('<span class="info">[INFO]</span> ' + m); }
let L1, A, B;
const roomQ = () => document.getElementById('room').value;
const passwordQ = () => document.getElementById('password').value;
const uidA = () => document.getElementById('a').value;
const uidB = () => document.getElementById('b').value;
const hubUrl = () => document.getElementById('hub').value;
function newTestId(prefix) {
const value = window.crypto && crypto.randomUUID ? crypto.randomUUID().replace(/-/g, '') : Date.now() + Math.random().toString(16).slice(2);
return prefix + value;
}
document.getElementById('hub').value = /^https?:$/.test(location.protocol) ? location.origin + '/chessgamehub' : '';
document.getElementById('room').value = 'TEST_' + Date.now();
document.getElementById('a').value = newTestId('A_');
document.getElementById('b').value = newTestId('B_');
function evt(conn, name, cb) { conn.on(name, cb); }
function make(id, uid, tag) {
const c = new signalR.HubConnectionBuilder()
.withUrl(hubUrl() + '?userId=' + uid)
.withAutomaticReconnect()
.build();
const events = ['GameError','BeginGame','CreateRoomNotice','JoinRoomNotice','ExitRoomNotice',
'JoinRoom','Reconnect','ReadyNotice','AllBeginTime','GoldConfigRequired','ExitRoomSuccess',
'GameEnd','Disconnect','PlayerAfkNotice','PlayerCancelAfkNotice','OpponentRequestEnd','EndGameRequestSent','RestartRoomNotice'];
events.forEach(e => c.on(e, (arg) => {
let s = typeof arg === 'object' ? JSON.stringify(arg) : arg;
if (s && s.length > 140) s = s.substr(0, 140) + '…';
log(`<span class="info">[${tag}]</span> 推送 <b>${e}</b>: ${s}`);
}));
c.onreconnected(() => log(`<span class="bad">[${tag}] 重连</span>`));
return c;
}
async function connAll() {
L1 = make('L1', 'L1_' + uidA().substr(0,8), 'L1'); A = make('A', uidA(), 'A'); B = make('B', uidB(), 'B');
for (const [tag, c] of [['L1', L1], ['A', A], ['B', B]]) {
try { await c.start(); ok(`${tag} 连接成功 ${c.connectionId}`); }
catch (e) { bad(`${tag} 连接失败: ${e}`); log(e.stack); }
}
}
async function createRoom() {
try {
await A.invoke('CreateRoom', { roomId: roomQ(), userId: uidA(), gameType: 1, password: passwordQ(), gold: 100 });
ok('A invoke CreateRoom 已发送');
} catch (e) { bad('CreateRoom 异常: ' + e); }
}
async function joinRoom() {
try { await B.invoke('JoinGame', roomQ(), passwordQ(), uidB()); ok('B invoke JoinGame 已发送'); }
catch (e) { bad('JoinGame 异常: ' + e); }
}
async function readyAll() {
try { await A.invoke('GameReady', roomQ(), uidA()); await B.invoke('GameReady', roomQ(), uidB()); ok('双方 Ready 已发送'); }
catch (e) { bad('Ready 异常: ' + e); }
}
async function bExit() { try { await B.invoke('ExitRoom', roomQ(), uidB()); ok('B ExitRoom 已发送'); } catch (e) { bad('ExitRoom: ' + e); } }
async function bJoin() { try { await B.invoke('JoinGame', roomQ(), passwordQ(), uidB()); ok('B 重新加入已发送'); } catch (e) { bad(e); } }
async function afkA() { try { await A.invoke('SetPlayerAfk', roomQ(), uidA()); ok('A SetPlayerAfk 已发送'); } catch (e) { bad('afkA: ' + e); } }
async function afkB() { try { await B.invoke('SetPlayerAfk', roomQ(), uidB()); ok('B SetPlayerAfk 已发送'); } catch (e) { bad('afkB: ' + e); } }
function clearLog(){ logEl.innerHTML=''; }
</script>
</body>
</html>
@@ -0,0 +1,359 @@
<title>钻石商城配置</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏管理</cite></a>
<a><cite>钻石商城配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-body">
<table id="table-diamondshop" lay-filter="table-diamondshop"></table>
</div>
</div>
</div>
<script type="text/html" id="toolbar-diamondshop">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-filter="init-diamondshop"><i class="layui-icon layui-icon-refresh"></i> 初始化配置</button>
<button class="layui-btn layui-btn-sm" lay-filter="save-diamondshop"><i class="layui-icon layui-icon-ok"></i> 保存配置</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-filter="reload-diamondshop"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
<button class="layui-btn layui-btn-sm" lay-filter="add-diamondshop" lay-event="add">添加</button>
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-filter="batchdel-diamondshop" lay-event="batchdel">批量删除</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" onclick="saveAct('DiamondShopConfigs')"><i class="layui-icon layui-icon-export"></i> 保存到初始化文件</button>
</div>
</script>
<script type="text/html" id="tpl-icon">
{{# if(d.icon){ }}
<img src="{{d.icon}}" style="width:40px;height:40px;object-fit:cover;cursor:pointer;border-radius:4px;" onclick="previewImage('{{d.icon}}')" alt="图标">
{{# } else { }}
<span style="color:#999;">无</span>
{{# } }}
</script>
<script type="text/html" id="table-operate">
<a class="layui-btn layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
<style>
.icon-upload-box { position: relative; display: inline-block; }
.icon-upload-box .upload-btn { position: absolute; top: 0; left: 0; width: 80px; height: 80px; opacity: 0; cursor: pointer; }
.icon-upload-preview { width: 80px; height: 80px; border: 1px dashed #ddd; border-radius: 4px; display: flex; align-items: center; justify-content: center; background: #fafafa; cursor: pointer; }
.icon-upload-preview:hover { border-color: #009688; }
.icon-upload-preview img { max-width: 100%; max-height: 100%; }
.icon-upload-preview .placeholder { color: #999; font-size: 12px; text-align: center; }
</style>
<script>
layui.use(['admin', 'table', 'form', 'layer'], function () {
var $ = layui.$,
admin = layui.admin,
table = layui.table,
form = layui.form,
layer = layui.layer,
setter = layui.setter;
var activityConfig = null;
setTimeout(function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
$('.layui-layer-shade').remove();
layer.closeAll();
}, 100);
function loadConfig() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/ActivityConfig/Get",
done: function (res) {
if (res.code === 0 && res.data) {
activityConfig = res.data;
renderTable();
}
}
});
}
function renderTable() {
table.render({
elem: '#table-diamondshop',
data: activityConfig ? activityConfig.diamondShopConfigs || [] : [],
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ field: "number", title: "商品编号", width: 100 },
{ field: "name", title: "商品名称", width: 120 },
{ field: "icon", title: "图标", width: 100, templet: '#tpl-icon' },
{ field: "diamond", title: "钻石数量", width: 100 },
{ field: "money", title: "现金价格", width: 100 },
{ field: "dealerMoney", title: "经销商价格", width: 120 },
{ title: "操作", width: 180, fixed: 'right', toolbar: '#table-operate' }
]],
toolbar: '#toolbar-diamondshop',
defaultToolbar: ['filter', 'exports', 'print']
});
}
window.previewImage = function(url) {
layer.open({
type: 1,
title: '图片预览',
area: ['auto', 'auto'],
shadeClose: true,
content: '<div style="padding:20px;text-align:center;"><img src="' + url + '" style="max-width:400px;max-height:400px;border-radius:8px;"></div>'
});
};
function initConfig() {
layer.confirm('确定要从配置文件还原钻石商城配置吗?', function(idx) {
layer.close(idx);
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig/InitActivitySection/DiamondShopConfigs",
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('初始化成功');
loadConfig();
} else {
layer.msg(res.info || '初始化失败');
}
},
fail: function(res) {
layer.msg(res.info || '初始化失败');
}
});
});
}
$(document).on('click', '[lay-filter="init-diamondshop"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
initConfig();
});
$(document).on('click', '[lay-filter="reload-diamondshop"]', function() {
loadConfig();
layer.msg('刷新成功');
});
$(document).on('click', '[lay-filter="save-diamondshop"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/ActivityConfig/UpdateDiamondShop",
data: JSON.stringify(activityConfig.diamondShopConfigs || []),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('保存成功');
loadConfig();
} else {
layer.msg(res.info || '保存失败');
}
}
});
});
table.on('tool(table-diamondshop)', function(obj) {
var data = obj.data;
var trIndex = activityConfig.diamondShopConfigs.findIndex(function(x) { return x.number === data.number; });
if (obj.event === 'edit') {
showEditDialog(trIndex > -1 ? activityConfig.diamondShopConfigs[trIndex] : data, false, trIndex);
} else if (obj.event === 'del') {
layer.confirm('确定删除该配置吗?', function(idx) {
if (trIndex > -1) activityConfig.diamondShopConfigs.splice(trIndex, 1);
obj.del();
layer.close(idx);
});
}
});
table.on('toolbar(table-diamondshop)', function(obj) {
if (obj.event === 'add') {
showAddDialog();
} else if (obj.event === 'batchdel') {
var checkStatus = table.checkStatus('table-diamondshop');
var checkData = checkStatus.data;
if (checkData.length === 0) {
return layer.msg('请选择要删除的数据');
}
layer.confirm('确定删除选中的 ' + checkData.length + ' 条数据吗?', function(idx) {
var ids = checkData.map(function(item) { return item.number; });
activityConfig.diamondShopConfigs = activityConfig.diamondShopConfigs.filter(function(item) {
return !ids.includes(item.number);
});
renderTable();
layer.close(idx);
});
}
});
function showAddDialog() {
showEditDialog({
number: '',
name: '',
icon: '',
diamond: 0,
money: 0,
dealerMoney: 0
}, true);
}
$(document).on('click', '[lay-filter="add-diamondshop"]', function() {
showAddDialog();
});
function showEditDialog(data, isAdd, rowIndex) {
var isNewAdd = isAdd === true;
var dialogTitle = isNewAdd ? '添加商品' : '编辑商品';
var imagePreviewHtml = data.icon
? '<img src="' + data.icon + '" style="max-width:100%;max-height:100%;">'
: '<div class="placeholder">点击上传</div>';
var html = '<div class="edit-dialog" style="padding:20px;">' +
'<form class="layui-form">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">商品编号</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="number" value="' + (data.number || '') + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">商品名称</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="name" value="' + (data.name || '') + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">图标</label>' +
'<div class="layui-input-block">' +
'<div class="icon-upload-box">' +
'<div class="icon-upload-preview" id="iconPreview">' + imagePreviewHtml + '</div>' +
'<input type="file" class="upload-btn" accept="image/png" id="iconUpload">' +
'</div>' +
'<input type="hidden" name="icon" id="iconInput" value="' + (data.icon || '') + '">' +
'<div style="margin-top:8px;color:#999;font-size:12px;">点击图片可预览,仅支持 PNG 格式</div>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">钻石数量</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="diamond" value="' + (data.diamond || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">现金价格</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="money" value="' + (data.money || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">经销商价格</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="dealerMoney" value="' + (data.dealerMoney || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'</form>' +
'</div>';
layer.open({
type: 1,
title: dialogTitle,
area: ['600px', '600px'],
content: html,
btn: ['保存', '取消'],
shadeClose: true,
success: function(layero) {
var $preview = layero.find('#iconPreview');
var $input = layero.find('#iconInput');
var $fileInput = layero.find('#iconUpload');
$preview.on('click', function() {
var url = $input.val();
if (url) previewImage(url);
});
$fileInput.on('change', function() {
var file = this.files[0];
if (file) {
var number = layero.find('input[name="number"]').val();
if (!number) {
layer.msg('请先填写商品编号');
$(this).val('');
return;
}
var formData = new FormData();
formData.append('file', file);
formData.append('number', number);
$.ajax({
url: setter.baseurl + 'adminapi/ImageUpload/upload/config',
type: 'POST',
data: formData,
processData: false,
contentType: false,
headers: { 'token': layui.data(setter.tableName)[setter.request.tokenName] },
success: function(res) {
if (res.code === 0 || res.code === 200) {
var path = res.data.path;
$input.val(path);
$preview.html('<img src="' + path + '" style="max-width:100%;max-height:100%;">');
layer.msg('上传成功');
} else {
layer.msg(res.info || '上传失败');
}
},
error: function() {
layer.msg('上传失败');
}
});
}
});
},
yes: function(index, layero) {
if (isAdd) {
var newItem = {
number: layero.find('input[name="number"]').val(),
name: layero.find('input[name="name"]').val(),
icon: layero.find('#iconInput').val(),
diamond: parseInt(layero.find('input[name="diamond"]').val()) || 0,
money: parseFloat(layero.find('input[name="money"]').val()) || 0,
dealerMoney: parseFloat(layero.find('input[name="dealerMoney"]').val()) || 0
};
activityConfig.diamondShopConfigs.unshift(newItem);
} else if (rowIndex > -1) {
var original = activityConfig.diamondShopConfigs[rowIndex];
original.number = layero.find('input[name="number"]').val();
original.name = layero.find('input[name="name"]').val();
original.icon = layero.find('#iconInput').val();
original.diamond = parseInt(layero.find('input[name="diamond"]').val()) || 0;
original.money = parseFloat(layero.find('input[name="money"]').val()) || 0;
original.dealerMoney = parseFloat(layero.find('input[name="dealerMoney"]').val()) || 0;
}
layer.close(index);
table.reload('table-diamondshop', {
data: activityConfig.diamondShopConfigs
});
}
});
}
window.saveAct=function(n){layui.admin.req({method:'get',url:(layui.setter.baseurl||'/')+'adminapi/ActivityConfig/SaveSection/'+n,done:function(r){layui.layer.msg(r.info||(r.code===0?'保存成功':'保存失败'))}})}
loadConfig();
});
</script>
@@ -0,0 +1,211 @@
<title>空房率配置</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏管理</cite></a>
<a><cite>空房率配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-body" style="padding-top:10px;">
<table id="table-rate" lay-filter="table-rate"></table>
</div>
</div>
</div>
<script type="text/html" id="toolbar-rate">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-filter="add-rate"><i class="layui-icon layui-icon-add-1"></i> 添加配置</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-filter="reload-rate"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
</div>
</script>
<script type="text/html" id="table-operate-rate">
<a class="layui-btn layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
<script>
layui.use(['admin', 'table', 'form'], function () {
var $ = layui.$,
admin = layui.admin,
table = layui.table,
form = layui.form,
layer = layui.layer,
setter = layui.setter;
// 页面加载时清理可能残留的遮罩
setTimeout(function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
$('.layui-layer-shade').remove();
layer.closeAll();
}, 100);
var typeMap = {0:'无', 1:'风雷之渊', 2:'永夜尸城', 3:'中国象棋', 4:'星际迷航', 5:'黑神话悟空'};
function loadData() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/EmptyRoomRateConfig/list?page=1&size=100",
done: function (res) {
if (res.code === 0) {
table.reload('table-rate', { data: res.data || [] });
} else {
layer.msg(res.info || '加载失败');
}
}
});
}
table.render({
elem: '#table-rate',
data: [],
page: true,
limit: 20,
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ field: "gameTypeName", title: "游戏类型", width: 130 },
{ field: "gameType", title: "游戏类型值", width: 100 },
{ field: "roomLevel", title: "房间等级", width: 100, templet: function(d) { return d.gameType === 3 ? '-' : d.roomLevel; } },
{ field: "emptyRoomRate", title: "空房率(%)", width: 120 },
{ title: "操作", width: 150, fixed: 'right', toolbar: '#table-operate-rate' }
]],
toolbar: '#toolbar-rate',
defaultToolbar: ['filter', 'exports', 'print']
});
$(document).on('click', '[lay-filter="add-rate"]', function () {
showEditDialog(null, true);
});
$(document).on('click', '[lay-filter="reload-rate"]', function () {
loadData();
layer.msg('刷新成功');
});
table.on('tool(table-rate)', function (obj) {
var data = obj.data;
if (obj.event === 'edit') {
showEditDialog(data, false);
} else if (obj.event === 'del') {
layer.confirm('确定删除「' + data.gameTypeName + ' Lv.' + data.roomLevel + '」的空房率配置吗?', function (idx) {
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/EmptyRoomRateConfig/delete",
data: JSON.stringify({ id: data.id }),
done: function (res) {
if (res.code === 0) {
obj.del();
layer.msg('删除成功');
} else {
layer.msg(res.info || '删除失败');
}
}
});
layer.close(idx);
});
}
});
function showEditDialog(data, isAdd) {
var dialogTitle = isAdd ? '添加空房率配置' : '编辑空房率配置';
var typeHtml = '';
for (var key in typeMap) {
var val = parseInt(key);
typeHtml += '<option value="' + val + '"' + (data && data.gameType === val ? ' selected' : '') + '>' + typeMap[key] + '</option>';
}
var html = '<div style="padding:20px;">' +
'<form class="layui-form">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">游戏类型</label>' +
'<div class="layui-input-block">' +
'<select name="gameType"' + (isAdd ? '' : ' disabled') + '>' + typeHtml + '</select>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">房间等级</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="roomLevel" value="' + (data ? data.roomLevel : 0) + '" class="layui-input"' + (isAdd ? '' : ' readonly') + '>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">空房率(%)</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="emptyRoomRate" value="' + (data ? data.emptyRoomRate : 30) + '" class="layui-input" placeholder="0-100,0或负数=不启用">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label"></label>' +
'<div class="layui-input-block" style="color:#999;font-size:12px;">空房率低于该值时暂停补充机器人;0或负数表示不启用</div>' +
'</div>' +
'</form>' +
'</div>';
layer.open({
type: 1,
title: dialogTitle,
area: ['480px', '360px'],
content: html,
btn: ['保存', '取消'],
shadeClose: true,
success: function (layero) {
form.render('select');
},
yes: function (index, layero) {
var gameType = parseInt(layero.find('select[name="gameType"]').val());
var roomLevel = parseInt(layero.find('input[name="roomLevel"]').val()) || 0;
if (gameType === 3) roomLevel = 0; // 象棋无房间等级
var emptyRoomRate = parseInt(layero.find('input[name="emptyRoomRate"]').val());
if (isNaN(emptyRoomRate)) {
layer.msg('请输入有效的空房率');
return;
}
if (!isAdd) {
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/EmptyRoomRateConfig/update",
data: JSON.stringify({ id: data.id, emptyRoomRate: emptyRoomRate }),
done: function (res) {
if (res.code === 0) {
layer.close(index);
layer.msg('更新成功');
loadData();
} else {
layer.msg(res.info || '更新失败');
}
}
});
} else {
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/EmptyRoomRateConfig/add",
data: JSON.stringify({ gameType: gameType, roomLevel: roomLevel, emptyRoomRate: emptyRoomRate }),
done: function (res) {
if (res.code === 0) {
layer.close(index);
layer.msg('添加成功');
loadData();
} else {
layer.msg(res.info || '添加失败');
}
}
});
}
}
});
}
loadData();
});
</script>
@@ -0,0 +1,458 @@
<title>鱼种配置</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏管理</cite></a>
<a><cite>鱼种配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-body" style="padding:10px 10px 0 10px;">
<div class="layui-btn-container" id="game-type-btns">
<button class="layui-btn layui-btn-sm layui-btn-primary" data-type="0">全部游戏</button>
<button class="layui-btn layui-btn-sm" data-type="1">风雷之渊</button>
<button class="layui-btn layui-btn-sm" data-type="2">永夜尸城</button>
<button class="layui-btn layui-btn-sm" data-type="3">黑化悟空</button>
<button class="layui-btn layui-btn-sm" data-type="4">星际迷航</button>
</div>
</div>
<div class="layui-card-body" style="padding-top:10px;">
<table id="table-fish" lay-filter="table-fish"></table>
</div>
</div>
</div>
<script type="text/html" id="toolbar-fish">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-filter="init-fish"><i class="layui-icon layui-icon-refresh"></i> 初始化配置</button>
<button class="layui-btn layui-btn-sm" lay-filter="save-fish"><i class="layui-icon layui-icon-ok"></i> 保存配置</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-filter="reload-fish"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
<button class="layui-btn layui-btn-sm" lay-filter="add-fish">添加</button>
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-filter="batchdel-fish">批量删除</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" onclick="saveSection('FishTypeConfigs')"><i class="layui-icon layui-icon-export"></i> 保存到初始化文件</button>
<select name="filterType" lay-filter="filter-type" lay-search="true" style="width:120px;">
<option value="">全部类型</option>
<option value="1">普通鱼</option>
<option value="2">Boss鱼</option>
<option value="3">黄金鱼</option>
<option value="4">特殊鱼</option>
<option value="5">特殊Boss</option>
</select>
</div>
</script>
<script type="text/html" id="table-operate">
<a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>
<script>
layui.use(['admin', 'table', 'form'], function () {
if (window._fishInitialized) return;
window._fishInitialized = true;
var $ = layui.$,
admin = layui.admin,
table = layui.table,
form = layui.form,
setter = layui.setter;
var gameConfig = null;
var currentGameType = 0;
var currentFilterType = '';
function loadConfig() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig",
done: function (res) {
if (res.code === 0 || res.data) {
gameConfig = res.data;
renderTable();
}
}
});
}
function getFishForGameType(gameType) {
var allFish = (gameConfig ? gameConfig.fishTypeConfigs : []) || [];
if (gameType === 0) return allFish;
var minNum = 0, maxNum = 0;
switch (gameType) {
case 1: minNum = 11000; maxNum = 11999; break; // 风雷之渊
case 2: minNum = 15000; maxNum = 15999; break; // 丧尸/永夜尸城
case 3: minNum = 13000; maxNum = 13999; break; // 黑化悟空
case 4: minNum = 12000; maxNum = 12999; break; // 星际迷航
case 5: minNum = 14000; maxNum = 14999; break; // 黑神话悟空 (备用)
default: return allFish;
}
return allFish.filter(function(f) {
var num = parseInt(f.fishNumber) || 0;
return num >= minNum && num <= maxNum;
});
}
function switchGameType(gameType) {
currentGameType = gameType;
$('#game-type-btns .layui-btn').removeClass('layui-btn-primary');
$('#game-type-btns .layui-btn[data-type="' + gameType + '"]').addClass('layui-btn-primary');
renderTable();
}
function getFilteredTableData() {
var data = getFishForGameType(currentGameType);
if (currentFilterType && currentFilterType !== '') {
data = data.filter(function(item) { return item.fishType === parseInt(currentFilterType); });
}
return data;
}
function reloadFilteredTable() {
table.reload('table-fish', {
data: getFilteredTableData()
});
}
// 删除鱼种时同步清理所有精确引用。Boss 概率配置允许多个鱼共用,
// 因此只移除目标编号,不能因为命中一个编号就删除整条配置。
function removeFishReferences(fishNumbers) {
var deletedNumbers = (fishNumbers || []).map(function(n) { return String(n); });
var isDeleted = function(n) { return deletedNumbers.includes(String(n)); };
var roomConfigs = gameConfig.roomFishConfigs || [];
if (!Array.isArray(roomConfigs)) roomConfigs = [roomConfigs];
roomConfigs.forEach(function(roomConfig) {
if (Array.isArray(roomConfig.roomFishLevelList)) {
roomConfig.roomFishLevelList = roomConfig.roomFishLevelList.filter(function(x) {
return !isDeleted(x.fishNumber);
});
}
if (Array.isArray(roomConfig.normalFishList)) {
roomConfig.normalFishList = roomConfig.normalFishList.filter(function(x) {
return !isDeleted(x.fishNumber);
});
}
});
var probability = gameConfig.bossConfigs && gameConfig.bossConfigs.probability;
if (Array.isArray(probability)) {
gameConfig.bossConfigs.probability = probability.filter(function(p) {
if (!Array.isArray(p.fishNumber)) return true;
p.fishNumber = p.fishNumber.filter(function(n) { return !isDeleted(n); });
return p.fishNumber.length > 0;
});
}
}
var tableInitialized = false;
function renderTable() {
var data = getFilteredTableData();
if (tableInitialized) {
table.reload('table-fish', { data: data });
return;
}
tableInitialized = true;
table.render({
elem: '#table-fish',
data: data,
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ field: "fishNumber", title: "鱼编号", width: 80 },
{ field: "fishName", title: "名称", width: 100 },
{ field: "fishBrief", title: "简介", width: 150 },
{ field: "fishType", title: "类型", width: 100, templet: function(d) { return getFishTypeName(d.fishType); } },
{ field: "fishImage", title: "图标", width: 100, templet: function(d) { return d.fishImage ? '<img src="' + d.fishImage + '" style="width:36px;height:36px;object-fit:cover;border-radius:4px;">' : '-'; } },
{ field: "fishScore", title: "击杀金币倍数", width: 140 },
{ field: "fishMultipleDesc", title: "倍数描述", width: 120 },
{ field: "killWeight", title: "击杀概率(1/10000)", width: 150 },
{ field: "pathList", title: "路径列表", minWidth: 200, templet: function(d) { return formatPathList(d.pathList); } },
{ title: "操作", width: 150, fixed: 'right', toolbar: '#table-operate' }
]],
toolbar: '#toolbar-fish',
defaultToolbar: ['filter', 'exports', 'print'],
done: function() {
form.render('select');
}
});
}
function getFishTypeName(type) {
var map = {1: '普通鱼', 2: 'Boss鱼', 3: '黄金鱼', 4: '特殊鱼', 5: '特殊Boss'};
return map[type] || '普通鱼';
}
function formatPathList(pathList) {
if (!pathList) return '';
if (Array.isArray(pathList)) {
return pathList.join(',');
}
return String(pathList);
}
function parsePathList(pathStr) {
if (!pathStr) return [];
if (Array.isArray(pathStr)) return pathStr;
if (typeof pathStr === 'string') {
return pathStr.split(',').map(function(x) { return parseInt(x.trim()); }).filter(function(x) { return !isNaN(x); });
}
return [];
}
function saveConfig() {
var saveData = JSON.stringify(gameConfig);
console.log('保存数据 pathList:', gameConfig.fishTypeConfigs.map(function(f) { return { fishNumber: f.fishNumber, pathList: f.pathList }; }));
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/GameConfig/Update",
data: saveData,
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('保存成功');
loadConfig();
} else {
layer.msg(res.info || '保存失败');
}
},
fail: function(res) {
layer.msg(res.info || '保存失败');
}
});
}
function initConfig() {
layer.confirm('确定要初始化全部配置数据吗?此操作将清空现有数据!', function(idx) {
layer.close(idx);
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig/InitSection/FishTypeConfigs",
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('初始化成功');
loadConfig();
} else {
layer.msg(res.info || '初始化失败');
}
},
fail: function(res) {
layer.msg(res.info || '初始化失败');
}
});
});
}
// 游戏类型切换
$('#game-type-btns').off().on('click', '.layui-btn', function() {
var gameType = parseInt($(this).data('type'));
switchGameType(gameType);
});
// 类型筛选事件
form.on('select(filter-type)', function(data) {
currentFilterType = data.value;
renderTable();
});
$(document).on('click', '[lay-filter="init-fish"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
initConfig();
});
$(document).on('click', '[lay-filter="reload-fish"]', function() {
loadConfig();
layer.msg('刷新成功');
});
$(document).on('click', '[lay-filter="save-fish"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
saveConfig();
});
table.on('tool(table-fish)', function(obj) {
var data = obj.data;
var trIndex = gameConfig.fishTypeConfigs.findIndex(function(x) { return x.fishNumber === data.fishNumber; });
console.log('编辑行 index:', trIndex, '数据:', data);
if (obj.event === 'edit') {
showEditDialog(data, false, trIndex);
} else if (obj.event === 'del') {
layer.confirm('确定删除该配置吗?', function(idx) {
var delNum = data.fishNumber;
gameConfig.fishTypeConfigs.splice(trIndex, 1);
removeFishReferences([delNum]);
obj.del();
reloadFilteredTable();
layer.close(idx);
});
}
});
function showAddDialog() {
var newData = {
fishNumber: '',
fishName: '',
fishBrief: '',
fishType: 1,
fishScore: 0,
killWeight: 0,
fishImage: '',
fishMultipleDesc: '',
pathList: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
};
if (!gameConfig.fishTypeConfigs) gameConfig.fishTypeConfigs = [];
showEditDialog(newData, true, -1);
}
$(document).on('click', '[lay-filter="add-fish"]', function() {
showAddDialog();
});
$(document).on('click', '[lay-filter="batchdel-fish"]', function() {
var checkStatus = table.checkStatus('table-fish');
var checkData = checkStatus.data;
if (checkData.length === 0) {
return layer.msg('请选择要删除的数据');
}
layer.confirm('确定删除选中的 ' + checkData.length + ' 条数据吗?', function(idx) {
var ids = checkData.map(function(item) { return item.fishNumber; });
gameConfig.fishTypeConfigs = gameConfig.fishTypeConfigs.filter(function(item) {
return !ids.includes(item.fishNumber);
});
removeFishReferences(ids);
reloadFilteredTable();
layer.close(idx);
});
});
function showEditDialog(data, isAdd, itemIndex) {
var dialogTitle = isAdd ? '添加鱼种' : '编辑鱼种';
var typeOptions = [
{value: 1, text: '普通鱼'},
{value: 2, text: 'Boss鱼'},
{value: 3, text: '黄金鱼'},
{value: 4, text: '特殊鱼'},
{value: 5, text: '特殊Boss'}
];
var typeHtml = '';
typeOptions.forEach(function(opt) {
typeHtml += '<option value="' + opt.value + '"' + (data.fishType === opt.value ? ' selected' : '') + '>' + opt.text + '</option>';
});
var currentPathStr = formatPathList(data.pathList);
var html = '<div style="padding:20px;">' +
'<form class="layui-form">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">鱼号</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="fishNumber" value="' + (data.fishNumber || '') + '" 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="fishName" value="' + (data.fishName || '') + '" 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="fishBrief" value="' + (data.fishBrief || '') + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">类型</label>' +
'<div class="layui-input-block">' +
'<select name="fishType">' + typeHtml + '</select>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">击杀金币倍数</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="fishScore" value="' + (data.fishScore || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">击杀概率(1/10000)</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="killWeight" value="' + (data.killWeight || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">图标URL</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="fishImage" value="' + (data.fishImage || '') + '" placeholder="图片地址" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">倍数描述</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="fishMultipleDesc" value="' + (data.fishMultipleDesc || '') + '" placeholder="如: 最高500倍" 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="pathList" value="' + currentPathStr + '" placeholder="多个值用逗号分隔" class="layui-input">' +
'</div>' +
'</div>' +
'</form>' +
'</div>';
layer.open({
type: 1,
title: dialogTitle,
area: ['800px', '750px'],
content: html,
btn: ['保存', '取消'],
shadeClose: true,
success: function(layero) {
layui.form.render('select');
},
yes: function(index, layero) {
console.log('保存 isAdd:', isAdd, 'itemIndex:', itemIndex);
var pathStr = layero.find('input[name="pathList"]').val();
var pathList = parsePathList(pathStr);
var updatedItem = {
fishNumber: layero.find('input[name="fishNumber"]').val(),
fishName: layero.find('input[name="fishName"]').val(),
fishBrief: layero.find('input[name="fishBrief"]').val(),
fishType: parseInt(layero.find('select[name="fishType"]').val()),
fishScore: parseFloat(layero.find('input[name="fishScore"]').val()) || 0,
killWeight: parseFloat(layero.find('input[name="killWeight"]').val()) || 0,
fishImage: layero.find('input[name="fishImage"]').val(),
fishMultipleDesc: layero.find('input[name="fishMultipleDesc"]').val(),
pathList: pathList
};
if (isAdd) {
gameConfig.fishTypeConfigs.unshift(updatedItem);
} else {
if (itemIndex >= 0 && itemIndex < gameConfig.fishTypeConfigs.length) {
gameConfig.fishTypeConfigs[itemIndex] = updatedItem;
console.log('更新后 gameConfig:', JSON.parse(JSON.stringify(gameConfig.fishTypeConfigs)));
} else {
console.log('条件不满足: itemIndex=', itemIndex, 'length=', gameConfig.fishTypeConfigs.length);
}
}
layer.close(index);
reloadFilteredTable();
}
});
}
window.saveSection=function(n){layui.admin.req({method:'get',url:(layui.setter.baseurl||'/')+'adminapi/GameConfig/SaveSection/'+n,done:function(r){layui.layer.msg(r.info||(r.code===0?'保存成功':'保存失败'))}})}
loadConfig();
});
</script>
@@ -0,0 +1,384 @@

<title>鱼阵配置</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏管理</cite></a>
<a><cite>鱼阵配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-body" style="padding:10px 10px 0 10px;">
<div class="layui-btn-container" id="game-type-btns">
<button class="layui-btn layui-btn-sm layui-btn-primary" data-type="0">全部游戏</button>
<button class="layui-btn layui-btn-sm" data-type="1">风雷之渊</button>
<button class="layui-btn layui-btn-sm" data-type="2">永夜尸城</button>
<button class="layui-btn layui-btn-sm" data-type="3">黑化悟空</button>
<button class="layui-btn layui-btn-sm" data-type="4">星际迷航</button>
</div>
</div>
<div class="layui-card-body" style="padding-top:10px;">
<table id="table-fisharray" lay-filter="table-fisharray"></table>
</div>
</div>
</div>
<script type="text/html" id="toolbar-fisharray">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-filter="init-fisharray"><i class="layui-icon layui-icon-refresh"></i> 初始化配置</button>
<button class="layui-btn layui-btn-sm" lay-filter="save-fisharray"><i class="layui-icon layui-icon-ok"></i> 保存配置</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-filter="reload-fisharray"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
<button class="layui-btn layui-btn-sm" lay-filter="add-fisharray">添加</button>
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-filter="batchdel-fisharray">批量删除</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" onclick="saveSection('FishArrayConfigs')"><i class="layui-icon layui-icon-export"></i> 保存到初始化文件</button>
</div>
</script>
<script type="text/html" id="table-operate">
<a class="layui-btn layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
<script>
layui.use(['admin', 'table', 'form'], function () {
var $ = layui.$,
admin = layui.admin,
table = layui.table,
form = layui.form,
setter = layui.setter;
var gameConfig = null;
var currentGameType = 0;
var fishList = [];
var bossFishList = [];
function loadConfig() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig",
done: function (res) {
if (res.code === 0 && res.data) {
gameConfig = res.data;
var fishConfigs = gameConfig.fishTypeConfigs || [];
fishList = fishConfigs.filter(function(f) { return f.fishType === 1 || f.fishType === 3; });
bossFishList = fishConfigs.filter(function(f) { return f.fishType === 2 || f.fishType === 5; });
renderTable();
}
}
});
}
function getArrayForGameType(gameType) {
var allArrays = (gameConfig ? gameConfig.fishArrayConfigs : []) || [];
if (gameType === 0) return allArrays;
var prefix = '';
switch (gameType) {
case 1: prefix = '32'; break; // 风雷之渊
case 2: prefix = '33'; break; // 永夜尸城
case 3: prefix = '31'; break; // 黑化悟空
case 4: prefix = '30'; break; // 星际迷航
default: return allArrays;
}
return allArrays.filter(function(f) {
var numStr = String(f.fishArrayNumber || '');
return numStr.indexOf(prefix) === 0;
});
}
function switchGameType(gameType) {
currentGameType = gameType;
$('#game-type-btns .layui-btn').removeClass('layui-btn-primary');
$('#game-type-btns .layui-btn[data-type="' + gameType + '"]').addClass('layui-btn-primary');
renderTable();
}
function formatList(list) {
if (!list) return '';
if (Array.isArray(list)) return list.join(',');
return String(list);
}
function parseList(str) {
if (!str) return [];
if (Array.isArray(str)) return str;
if (typeof str === 'string') {
return str.split(',').map(function(x) { return parseInt(x.trim()); }).filter(function(x) { return !isNaN(x); });
}
return [];
}
// 删除鱼阵时同步移除房间出鱼配置中的鱼阵引用(type=2)。
function removeFishArrayReferences(arrayNumbers) {
var deleted = new Set((arrayNumbers || []).map(function(x) { return String(x); }));
var roomConfigs = gameConfig && gameConfig.roomFishConfigs;
if (!deleted.size || !roomConfigs) return;
var configs = Array.isArray(roomConfigs) ? roomConfigs : [roomConfigs];
configs.forEach(function(roomConfig) {
if (!roomConfig) return;
['normalFishList', 'roomFishLevelList'].forEach(function(field) {
if (!Array.isArray(roomConfig[field])) return;
roomConfig[field] = roomConfig[field].filter(function(item) {
return !item || Number(item.type) !== 2 || !deleted.has(String(item.fishNumber));
});
});
});
}
var tableInitialized = false;
function renderTable() {
var tableData = getArrayForGameType(currentGameType);
if (tableInitialized) {
table.reload('table-fisharray', { data: tableData });
return;
}
tableInitialized = true;
table.render({
elem: '#table-fisharray',
data: tableData,
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ field: "fishArrayNumber", title: "鱼阵编号", width: 100 },
{ field: "name", title: "名称", width: 120 },
{ field: "fishNumber", title: "普通鱼编号", width: 100 },
{ field: "fishCount", title: "普通鱼数量", width: 100 },
{ field: "bossFishNumber", title: "Boss鱼编号", width: 100 },
{ field: "boosCount", title: "Boss数量", width: 90 },
{ field: "arrayNumber", title: "阵型编号", width: 100 },
{
field: "generateFrequency", title: "生成频率(影响阵容间距)", width: 100, templet: function(d) {
return d.generateFrequency || 0.2;
}},
{ title: "操作", width: 150, fixed: 'right', toolbar: '#table-operate' }
]],
toolbar: '#toolbar-fisharray',
defaultToolbar: ['filter', 'exports', 'print']
});
}
// 游戏类型切换
$('#game-type-btns').off().on('click', '.layui-btn', function() {
var gameType = parseInt($(this).data('type'));
switchGameType(gameType);
});
function initConfig() {
layer.confirm('确定要初始化全部配置数据吗?此操作将清空现有数据!', function(idx) {
layer.close(idx);
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig/InitSection/FishArrayConfigs",
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('初始化成功');
loadConfig();
} else {
layer.msg(res.info || '初始化失败');
}
},
fail: function(res) {
layer.msg(res.info || '初始化失败');
}
});
});
}
$(document).on('click', '[lay-filter="init-fisharray"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
initConfig();
});
$(document).on('click', '[lay-filter="reload-fisharray"]', function() {
loadConfig();
layer.msg('刷新成功');
});
$(document).on('click', '[lay-filter="save-fisharray"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/GameConfig/Update",
data: JSON.stringify(gameConfig),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('保存成功');
loadConfig();
} else {
layer.msg(res.info || '保存失败');
}
}
});
});
table.on('tool(table-fisharray)', function(obj) {
var data = obj.data;
var trIndex = gameConfig.fishArrayConfigs.findIndex(function(x) { return x.fishArrayNumber === data.fishArrayNumber; });
if (obj.event === 'edit') {
showEditDialog(data, false, trIndex);
} else if (obj.event === 'del') {
layer.confirm('确定删除该配置吗?', function(idx) {
if (trIndex > -1) {
gameConfig.fishArrayConfigs.splice(trIndex, 1);
removeFishArrayReferences([data.fishArrayNumber]);
}
obj.del();
layer.close(idx);
});
}
});
function showAddDialog() {
if (!gameConfig.fishArrayConfigs) gameConfig.fishArrayConfigs = [];
var newData = {
fishArrayNumber: '',
name: '',
fishNumber: '',
fishCount: 0,
bossFishNumber: '',
boosCount: 0,
arrayNumber: '',
generateFrequency: 0.2
};
showEditDialog(newData, true, -1);
}
$(document).on('click', '[lay-filter="add-fisharray"]', function() {
showAddDialog();
});
$(document).on('click', '[lay-filter="batchdel-fisharray"]', function() {
var checkStatus = table.checkStatus('table-fisharray');
var checkData = checkStatus.data;
if (checkData.length === 0) {
return layer.msg('请选择要删除的数据');
}
layer.confirm('确定删除选中的 ' + checkData.length + ' 条数据吗?', function(idx) {
var ids = checkData.map(function(item) { return item.fishArrayNumber; });
gameConfig.fishArrayConfigs = gameConfig.fishArrayConfigs.filter(function(item) {
return !ids.includes(item.fishArrayNumber);
});
removeFishArrayReferences(ids);
renderTable();
layer.close(idx);
});
});
function showEditDialog(data, isAdd, itemIndex) {
var dialogTitle = isAdd ? '添加鱼阵' : '编辑鱼阵';
var fishOptions = '<option value="">请选择</option>';
fishList.forEach(function(f) {
fishOptions += '<option value="' + f.fishNumber + '"' + (data.fishNumber === f.fishNumber ? ' selected' : '') + '>' + f.fishNumber + ' - ' + f.fishName + '</option>';
});
var bossOptions = '<option value="">请选择</option>';
bossFishList.forEach(function(f) {
bossOptions += '<option value="' + f.fishNumber + '"' + (data.bossFishNumber === f.fishNumber ? ' selected' : '') + '>' + f.fishNumber + ' - ' + f.fishName + '</option>';
});
var html = '<div class="edit-dialog" style="padding:20px;">' +
'<form class="layui-form">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">鱼阵编号</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="fishArrayNumber" value="' + (data.fishArrayNumber || '') + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">名称</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="name" value="' + (data.name || '') + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">普通鱼编号</label>' +
'<div class="layui-input-block">' +
'<select name="fishNumber" lay-search="true">' + fishOptions + '</select>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">普通鱼数量</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="fishCount" value="' + (data.fishCount || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">Boss鱼编号</label>' +
'<div class="layui-input-block">' +
'<select name="bossFishNumber" lay-search="true">' + bossOptions + '</select>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">Boss数量</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="boosCount" value="' + (data.boosCount || 0) + '" 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="arrayNumber" value="' + (data.arrayNumber || '') + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">生成频率</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="generateFrequency" value="' + (data.generateFrequency || 0.2) + '" step="0.01" class="layui-input" placeholder="如: 0.2">' +
'</div>' +
'</div>' +
'</form>' +
'</div>';
layer.open({
type: 1,
title: dialogTitle,
area: ['600px', '600px'],
content: html,
btn: ['保存', '取消'],
shadeClose: true,
success: function(layero) {
form.render('select');
},
yes: function(index, layero) {
var updatedItem = {
fishArrayNumber: layero.find('input[name="fishArrayNumber"]').val(),
name: layero.find('input[name="name"]').val(),
fishNumber: layero.find('select[name="fishNumber"]').val(),
fishCount: parseInt(layero.find('input[name="fishCount"]').val()) || 0,
bossFishNumber: layero.find('select[name="bossFishNumber"]').val(),
boosCount: parseInt(layero.find('input[name="boosCount"]').val()) || 0,
arrayNumber: layero.find('input[name="arrayNumber"]').val(),
generateFrequency: parseFloat(layero.find('input[name="generateFrequency"]').val()) || 0.2
};
if (isAdd) {
gameConfig.fishArrayConfigs.unshift(updatedItem);
} else {
if (itemIndex >= 0 && itemIndex < gameConfig.fishArrayConfigs.length) {
gameConfig.fishArrayConfigs[itemIndex] = updatedItem;
}
}
layer.close(index);
table.reload('table-fisharray', {
data: getArrayForGameType(currentGameType)
});
}
});
}
window.saveSection=function(n){layui.admin.req({method:'get',url:(layui.setter.baseurl||'/')+'adminapi/GameConfig/SaveSection/'+n,done:function(r){layui.layer.msg(r.info||(r.code===0?'保存成功':'保存失败'))}})}
loadConfig();
});
</script>
@@ -0,0 +1,164 @@

<title>捕鱼返金币活动配置</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏管理</cite></a>
<a><cite>捕鱼返金币活动</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-filter="reload-fishing-return"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
<button class="layui-btn layui-btn-sm" lay-filter="save-fishing-return"><i class="layui-icon layui-icon-ok"></i> 保存配置</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" onclick="saveAct('FishingReturnConfig')"><i class="layui-icon layui-icon-export"></i> 保存到初始化文件</button>
</div>
</div>
<div class="layui-card-body">
<blockquote class="layui-elem-quote layui-quote-nm" style="margin-bottom: 15px;">
<p><strong>活动说明:</strong></p>
<p>1. 用户每天捕鱼消耗金币的千分之一,第二天可领取返还</p>
<p>2. 例如:第一天捕鱼消耗 1000 万金币,第二天可领取 1 万金币</p>
<p>3. 每日返利上限可配置,0 表示无上限</p>
</blockquote>
<form class="layui-form" id="form-fishing-return" style="padding: 20px;">
<div class="layui-form-item">
<label class="layui-form-label">是否开启</label>
<div class="layui-input-block">
<input type="checkbox" name="isEnabled" lay-skin="switch" lay-text="开启|关闭">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">返利比例</label>
<div class="layui-input-inline" style="width: 200px;">
<input type="number" name="returnRate" class="layui-input" placeholder="返利比例" step="0.0001" min="0" max="1">
</div>
<div class="layui-form-mid layui-word-aux">千分之一 = 0.001(例如:消耗1000金币,返还1金币)</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">每日上限</label>
<div class="layui-input-inline" style="width: 200px;">
<input type="number" name="dailyMaxReturn" class="layui-input" placeholder="每日返利上限" min="0">
</div>
<div class="layui-form-mid layui-word-aux">0 = 无上限</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">计算示例</label>
<div class="layui-input-block">
<table class="layui-table" style="width: 400px;">
<thead>
<tr>
<th>昨日消耗金币</th>
<th>返利比例</th>
<th>可领取金币</th>
</tr>
</thead>
<tbody id="calc-example">
<tr><td colspan="3" style="text-align: center; color: #999;">请输入返利比例查看示例</td></tr>
</tbody>
</table>
</div>
</div>
</form>
</div>
</div>
</div>
<script>
layui.use(['admin', 'form', 'layer'], function () {
var $ = layui.$,
admin = layui.admin,
form = layui.form,
layer = layui.layer,
setter = layui.setter;
var fishingReturnConfig = null;
function loadConfig() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/ActivityConfig/GetFishingReturn",
done: function (res) {
if (res.code === 0 && res.data) {
fishingReturnConfig = res.data;
fillForms();
}
}
});
}
function fillForms() {
if (fishingReturnConfig) {
var r = fishingReturnConfig;
$('#form-fishing-return input[name="isEnabled"]').prop('checked', r.isEnabled);
$('#form-fishing-return input[name="returnRate"]').val(r.returnRate || 0.001);
$('#form-fishing-return input[name="dailyMaxReturn"]').val(r.dailyMaxReturn || 0);
}
form.render();
updateCalcExample();
}
function updateCalcExample() {
var rate = parseFloat($('#form-fishing-return input[name="returnRate"]').val()) || 0.001;
var max = parseInt($('#form-fishing-return input[name="dailyMaxReturn"]').val()) || 0;
var examples = [100000, 1000000, 10000000, 100000000];
var html = '';
examples.forEach(function(consume) {
var gold = Math.floor(consume * rate);
if (max > 0 && gold > max) {
gold = max;
}
html += '<tr><td>' + consume.toLocaleString() + '</td><td>' + rate + '</td><td>' + gold.toLocaleString() + '</td></tr>';
});
$('#calc-example').html(html);
}
$(document).on('input', '#form-fishing-return input[name="returnRate"], #form-fishing-return input[name="dailyMaxReturn"]', function() {
updateCalcExample();
});
$(document).on('click', '[lay-filter="reload-fishing-return"]', function() {
loadConfig();
layer.msg('刷新成功');
});
$(document).on('click', '[lay-filter="save-fishing-return"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
var config = {
isEnabled: $('#form-fishing-return input[name="isEnabled"]').prop('checked'),
returnRate: parseFloat($('#form-fishing-return input[name="returnRate"]').val()) || 0.001,
dailyMaxReturn: parseInt($('#form-fishing-return input[name="dailyMaxReturn"]').val()) || 0
};
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/ActivityConfig/UpdateFishingReturn",
data: JSON.stringify(config),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('保存成功');
loadConfig();
} else {
layer.msg(res.info || '保存失败');
}
},
fail: function(res) {
layer.msg(res.info || '保存失败');
}
});
});
window.saveAct=function(n){layui.admin.req({method:'get',url:(layui.setter.baseurl||'/')+'adminapi/ActivityConfig/SaveSection/'+n,done:function(r){layui.layer.msg(r.info||(r.code===0?'保存成功':'保存失败'))}})}
loadConfig();
});
</script>
@@ -0,0 +1,354 @@

<title>金币商城配置</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏管理</cite></a>
<a><cite>金币商城配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-body">
<table id="table-goldshop" lay-filter="table-goldshop"></table>
</div>
</div>
</div>
<script type="text/html" id="toolbar-goldshop">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-filter="init-goldshop"><i class="layui-icon layui-icon-refresh"></i> 初始化配置</button>
<button class="layui-btn layui-btn-sm" lay-filter="save-goldshop"><i class="layui-icon layui-icon-ok"></i> 保存配置</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-filter="reload-goldshop"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
<button class="layui-btn layui-btn-sm" lay-filter="add-goldshop">添加</button>
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-filter="batchdel-goldshop">批量删除</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" onclick="saveAct('GoldShopConfigs')"><i class="layui-icon layui-icon-export"></i> 保存到初始化文件</button>
</div>
</script>
<script type="text/html" id="tpl-icon">
{{# if(d.icon){ }}
<img src="{{d.icon}}" style="width:40px;height:40px;object-fit:cover;cursor:pointer;border-radius:4px;" onclick="previewImage('{{d.icon}}')" alt="图标">
{{# } else { }}
<span style="color:#999;">无</span>
{{# } }}
</script>
<script type="text/html" id="table-operate">
<a class="layui-btn layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
<style>
.icon-upload-box { position: relative; display: inline-block; }
.icon-upload-box .upload-btn { position: absolute; top: 0; left: 0; width: 80px; height: 80px; opacity: 0; cursor: pointer; }
.icon-upload-preview { width: 80px; height: 80px; border: 1px dashed #ddd; border-radius: 4px; display: flex; align-items: center; justify-content: center; background: #fafafa; cursor: pointer; }
.icon-upload-preview:hover { border-color: #009688; }
.icon-upload-preview img { max-width: 100%; max-height: 100%; }
.icon-upload-preview .placeholder { color: #999; font-size: 12px; text-align: center; }
</style>
<script>
layui.use(['admin', 'table', 'form', 'layer'], function () {
var $ = layui.$,
admin = layui.admin,
table = layui.table,
form = layui.form,
layer = layui.layer,
setter = layui.setter;
var activityConfig = null;
setTimeout(function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
$('.layui-layer-shade').remove();
layer.closeAll();
}, 100);
function loadConfig() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/ActivityConfig/Get",
done: function (res) {
if (res.code === 0 && res.data) {
activityConfig = res.data;
renderTable();
}
}
});
}
function renderTable() {
table.render({
elem: '#table-goldshop',
data: activityConfig ? activityConfig.goldShopConfigs || [] : [],
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ field: "number", title: "商品编号", width: 100 },
{ field: "name", title: "商品名称", width: 120 },
{ field: "icon", title: "图标", width: 100, templet: '#tpl-icon' },
{ field: "gold", title: "金币数量", width: 100 },
{ field: "diamond", title: "钻石数量", width: 100 },
{ field: "dealerDiamond", title: "经销商钻石", width: 120 },
{ title: "操作", width: 180, fixed: 'right', toolbar: '#table-operate' }
]],
toolbar: '#toolbar-goldshop',
defaultToolbar: ['filter', 'exports', 'print']
});
}
window.previewImage = function(url) {
layer.open({
type: 1,
title: '图片预览',
area: ['auto', 'auto'],
shadeClose: true,
content: '<div style="padding:20px;text-align:center;"><img src="' + url + '" style="max-width:400px;max-height:400px;border-radius:8px;"></div>'
});
};
function initConfig() {
layer.confirm('确定要从配置文件还原金币商城配置吗?', function(idx) {
layer.close(idx);
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig/InitActivitySection/GoldShopConfigs",
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('初始化成功');
loadConfig();
} else {
layer.msg(res.info || '初始化失败');
}
},
fail: function(res) {
layer.msg(res.info || '初始化失败');
}
});
});
}
$(document).on('click', '[lay-filter="init-goldshop"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
initConfig();
});
$(document).on('click', '[lay-filter="reload-goldshop"]', function() {
loadConfig();
layer.msg('刷新成功');
});
$(document).on('click', '[lay-filter="save-goldshop"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/ActivityConfig/UpdateGoldShop",
data: JSON.stringify(activityConfig.goldShopConfigs || []),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('保存成功');
loadConfig();
} else {
layer.msg(res.info || '保存失败');
}
}
});
});
table.on('tool(table-goldshop)', function(obj) {
var data = obj.data;
var trIndex = activityConfig.goldShopConfigs.findIndex(function(x) { return x.number === data.number; });
if (obj.event === 'edit') {
showEditDialog(trIndex > -1 ? activityConfig.goldShopConfigs[trIndex] : data, false, trIndex);
} else if (obj.event === 'del') {
layer.confirm('确定删除该配置吗?', function(idx) {
if (trIndex > -1) activityConfig.goldShopConfigs.splice(trIndex, 1);
obj.del();
layer.close(idx);
});
}
});
function showAddDialog() {
var newData = {
number: '',
name: '',
icon: '',
gold: 0,
diamond: 0,
dealerDiamond: 0
};
showEditDialog(newData, true);
}
$(document).on('click', '[lay-filter="add-goldshop"]', function() {
showAddDialog();
});
$(document).on('click', '[lay-filter="batchdel-goldshop"]', function() {
var checkStatus = table.checkStatus('table-goldshop');
var checkData = checkStatus.data;
if (checkData.length === 0) {
return layer.msg('请选择要删除的数据');
}
layer.confirm('确定删除选中的 ' + checkData.length + ' 条数据吗?', function(idx) {
var ids = checkData.map(function(item) { return item.number; });
activityConfig.goldShopConfigs = activityConfig.goldShopConfigs.filter(function(item) {
return !ids.includes(item.number);
});
renderTable();
layer.close(idx);
});
});
function showEditDialog(data, isAdd, rowIndex) {
var dialogTitle = isAdd ? '添加金币商城' : '编辑金币商城';
var imagePreviewHtml = data.icon
? '<img src="' + data.icon + '" style="max-width:100%;max-height:100%;">'
: '<div class="placeholder">点击上传</div>';
var html = '<div class="edit-dialog" style="padding:20px;">' +
'<form class="layui-form">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">商品编号</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="number" value="' + (data.number || '') + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">商品名称</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="name" value="' + (data.name || '') + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">图标</label>' +
'<div class="layui-input-block">' +
'<div class="icon-upload-box">' +
'<div class="icon-upload-preview" id="iconPreview">' + imagePreviewHtml + '</div>' +
'<input type="file" class="upload-btn" accept="image/png" id="iconUpload">' +
'</div>' +
'<input type="hidden" name="icon" id="iconInput" value="' + (data.icon || '') + '">' +
'<div style="margin-top:8px;color:#999;font-size:12px;">点击图片可预览,仅支持 PNG 格式</div>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">金币数量</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="gold" value="' + (data.gold || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">钻石数量</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="diamond" value="' + (data.diamond || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">经销商钻石</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="dealerDiamond" value="' + (data.dealerDiamond || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'</form>' +
'</div>';
layer.open({
type: 1,
title: dialogTitle,
area: ['600px', '600px'],
content: html,
btn: ['保存', '取消'],
shadeClose: true,
success: function(layero) {
var $preview = layero.find('#iconPreview');
var $input = layero.find('#iconInput');
var $fileInput = layero.find('#iconUpload');
$preview.on('click', function() {
var url = $input.val();
if (url) previewImage(url);
});
$fileInput.on('change', function() {
var file = this.files[0];
if (file) {
var number = layero.find('input[name="number"]').val();
if (!number) {
layer.msg('请先填写商品编号');
$(this).val('');
return;
}
var formData = new FormData();
formData.append('file', file);
formData.append('number', number);
$.ajax({
url: setter.baseurl + 'adminapi/ImageUpload/upload/config',
type: 'POST',
data: formData,
processData: false,
contentType: false,
headers: { 'token': layui.data(setter.tableName)[setter.request.tokenName] },
success: function(res) {
if (res.code === 0 || res.code === 200) {
var path = res.data.path;
$input.val(path);
$preview.html('<img src="' + path + '" style="max-width:100%;max-height:100%;">');
layer.msg('上传成功');
} else {
layer.msg(res.info || '上传失败');
}
},
error: function() {
layer.msg('上传失败');
}
});
}
});
},
yes: function(index, layero) {
if (isAdd) {
var newItem = {
number: layero.find('input[name="number"]').val(),
name: layero.find('input[name="name"]').val(),
icon: layero.find('#iconInput').val(),
gold: parseInt(layero.find('input[name="gold"]').val()) || 0,
diamond: parseInt(layero.find('input[name="diamond"]').val()) || 0,
dealerDiamond: parseInt(layero.find('input[name="dealerDiamond"]').val()) || 0
};
activityConfig.goldShopConfigs.unshift(newItem);
} else if (rowIndex > -1) {
var original = activityConfig.goldShopConfigs[rowIndex];
original.number = layero.find('input[name="number"]').val();
original.name = layero.find('input[name="name"]').val();
original.icon = layero.find('#iconInput').val();
original.gold = parseInt(layero.find('input[name="gold"]').val()) || 0;
original.diamond = parseInt(layero.find('input[name="diamond"]').val()) || 0;
original.dealerDiamond = parseInt(layero.find('input[name="dealerDiamond"]').val()) || 0;
}
layer.close(index);
table.reload('table-goldshop', {
data: activityConfig.goldShopConfigs
});
}
});
}
window.saveAct=function(n){layui.admin.req({method:'get',url:(layui.setter.baseurl||'/')+'adminapi/ActivityConfig/SaveSection/'+n,done:function(r){layui.layer.msg(r.info||(r.code===0?'保存成功':'保存失败'))}})}
loadConfig();
});
</script>
@@ -0,0 +1,335 @@

<title>击杀配置</title>
<style>
.layui-form-label { width: 400px; }
.kill-form legend { font-size: 14px; font-weight: normal; }
.kill-form .layui-form-mid { font-size: 12px; color: #999; }
.layui-elem-field { border-color: #666; }
.kill-row { display: flex; flex-wrap: wrap; gap: 15px; margin: 10px 0; }
.kill-row .layui-form-item { display: flex; align-items: center; margin: 0; white-space: nowrap; }
.kill-row .layui-form-label { width: auto; text-align: right; padding-right: 8px; font-size: 14px; }
.kill-row .layui-input-inline { width: 80px; }
</style>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏管理</cite></a>
<a><cite>击杀配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-filter="init-kill"><i class="layui-icon layui-icon-refresh"></i> 初始化配置</button>
<button class="layui-btn layui-btn-sm" lay-filter="save-kill"><i class="layui-icon layui-icon-ok"></i> 保存配置</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-filter="reload-kill"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" onclick="saveSection('KillConfig')"><i class="layui-icon layui-icon-export"></i> 保存到初始化文件</button>
</div>
</div>
<div class="layui-card-body">
<form class="layui-form kill-form" id="form-kill" style="padding: 20px;">
<fieldset class="layui-elem-field layui-field-title">
<legend>用户设置</legend>
</fieldset>
<div class="layui-form-item">
<label class="layui-form-label">用户盈亏调节</label>
<div class="layui-input-inline">
<input type="number" name="normalizationFactorPlayer" class="layui-input" placeholder="用户盈亏调节灵敏度">
</div>
<div class="layui-form-mid">公式: 原始玩家值 = -(当前余额-累计充值-注册赠送) / 此值, 玩家因子 = 1+原始玩家值。值越大调节越平缓。</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">击杀率下限</label>
<div class="layui-input-inline">
<input type="number" name="minPlayerModifier" class="layui-input" placeholder="击杀率下限倍数">
</div>
<div class="layui-form-mid">玩家因子最小值(下限)。<br>默认0.3=盈利再多也保持30%击杀率; 改为0=可能完全打不死鱼; 改为1=盈利再高也不压鱼</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">击杀率上限</label>
<div class="layui-input-inline">
<input type="number" name="maxPlayerModifier" class="layui-input" placeholder="击杀率上限倍数">
</div>
<div class="layui-form-mid">玩家因子最大值(上限)。<br>默认2.0=亏损再多最高200%击杀率; 改为1=亏损再多也不补偿; 改为5=亏损玩家可获5倍击杀率(易刷钱)</div>
</div>
<fieldset class="layui-elem-field layui-field-title">
<legend>系统设置</legend>
</fieldset>
<div class="layui-form-item">
<label class="layui-form-label">系统盈利目标</label>
<div class="layui-input-inline">
<input type="number" name="targetSystemProfit" class="layui-input" placeholder="系统期望累计盈利目标">
</div>
<div class="layui-form-mid">已弃用,实际由下方系统日收益替代。保留仅作参考</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">系统平滑系数</label>
<div class="layui-input-inline">
<input type="number" name="normalizationFactorSystem" class="layui-input" placeholder="系统调节平滑系数">
</div>
<div class="layui-form-mid">公式: 系统差值=(当日盈利-日目标)/此值, 系统因子=1+系统差值。值越大调节越平缓。</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">系统击杀下限</label>
<div class="layui-input-inline">
<input type="number" name="minSystemModifier" class="layui-input" placeholder="系统整体击杀率下限">
</div>
<div class="layui-form-mid">系统因子最小值(下限)。<br>默认0.95=系统亏损再大最低95%击杀率; 改为0.5=系统亏损时可以压到50%; 改为0=系统亏损时可完全禁止击杀(危险)</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">系统击杀上限</label>
<div class="layui-input-inline">
<input type="number" name="maxSystemModifier" class="layui-input" placeholder="系统整体击杀率上限">
</div>
<div class="layui-form-mid">系统因子最大值(上限)。<br>默认1.05=盈利再多最高105%; 改为1.2=系统可上调20%击杀率刺激消费; 改为1=关闭系统调节(永远固定1.0)</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">系统日收益</label>
<div class="layui-input-inline">
<input type="number" name="daySystemGold" class="layui-input" placeholder="系统每日收益金额">
</div>
<div class="layui-form-mid">当日系统净盈利的锚点值。实际盈利高于此→调高击杀率鼓励消费;低于此→调低击杀率回收金币。</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">全局播报最低金币</label>
<div class="layui-input-inline">
<input type="number" name="globalAnnounceMinGold" class="layui-input" placeholder="全局击杀播报最低金币">
</div>
<div class="layui-form-mid">击杀获得金币 ≥ 此值时才触发全局播报,0=全部播报</div>
</div>
<fieldset class="layui-elem-field layui-field-title">
<legend>限制设置</legend>
</fieldset>
<div class="layui-form-item">
<label class="layui-form-label">IP限制</label>
<div class="layui-input-inline">
<input type="number" name="ipCount" class="layui-input" placeholder="IP关联用户数限制">
</div>
<div class="layui-form-mid">相同IP关联超过此数量的用户会被屏蔽</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">违规值限制</label>
<div class="layui-input-inline">
<input type="number" name="violateValue" class="layui-input" placeholder="违规值限制" step="0.01">
</div>
<div class="layui-form-mid">公式: 被屏蔽用户直接使用此值作为玩家因子(跳过盈亏调节)。正常用户使用各自因子配置中的系统因子(默认1=不调节)。默认0.2=黑名单击杀率16%左右(KillWeight=8000时)。值越大击杀率越高(无限制范围,注意风险)</div>
</div>
<fieldset class="layui-elem-field layui-field-title">
<legend>补偿系数 (CompensateQuota > 0 时生效)</legend>
</fieldset>
<div class="kill-row">
<div class="layui-form-item">
<label class="layui-form-label">Boss鱼</label>
<div class="layui-input-inline">
<input type="number" name="compensateBossRate" class="layui-input" placeholder="Boss鱼补偿系数" step="0.01">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">黄金鱼</label>
<div class="layui-input-inline">
<input type="number" name="compensateGoldRate" class="layui-input" placeholder="黄金鱼补偿系数" step="0.01">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">特殊鱼</label>
<div class="layui-input-inline">
<input type="number" name="compensateSpecialRate" class="layui-input" placeholder="特殊鱼补偿系数" step="0.01">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">普通鱼</label>
<div class="layui-input-inline">
<input type="number" name="compensateOrdinary" class="layui-input" placeholder="普通鱼补偿系数" step="0.01">
</div>
</div>
<div class="layui-form-mid">在用户因子基础上按鱼种额外乘以系数(提升击杀率)。公式: 最终用户因子 = 限制范围(玩家因子 × 鱼种系数, 下限, 上限)</div>
</div>
<fieldset class="layui-elem-field layui-field-title">
<legend>罚款系数 (PenaltyQuota > 0 时生效)</legend>
</fieldset>
<div class="kill-row">
<div class="layui-form-item">
<label class="layui-form-label">Boss鱼</label>
<div class="layui-input-inline">
<input type="number" name="penaltyBossRate" class="layui-input" placeholder="Boss鱼罚款系数" step="0.01">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">黄金鱼</label>
<div class="layui-input-inline">
<input type="number" name="penaltyGoldRate" class="layui-input" placeholder="黄金鱼罚款系数" step="0.01">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">特殊鱼</label>
<div class="layui-input-inline">
<input type="number" name="penaltySpecialRate" class="layui-input" placeholder="特殊鱼罚款系数" step="0.01">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">普通鱼</label>
<div class="layui-input-inline">
<input type="number" name="penaltyOrdinary" class="layui-input" placeholder="普通鱼罚款系数" step="0.01">
</div>
</div>
<div class="layui-form-mid">在用户因子基础上按鱼种额外乘以系数(降低击杀率)。公式: 最终用户因子 = 限制范围(玩家因子 × 鱼种系数, 下限, 上限)</div>
</div>
</form>
</div>
</div>
</div>
<script>
layui.use(['admin', 'form', 'layer'], function () {
var $ = layui.$,
admin = layui.admin,
form = layui.form,
layer = layui.layer,
setter = layui.setter;
var gameConfig = null;
setTimeout(function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
$('.layui-layer-shade').remove();
layer.closeAll();
}, 100);
function loadConfig() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig",
done: function (res) {
if (res.code === 0 && res.data) {
gameConfig = res.data;
fillForms();
}
}
});
}
function fillForms() {
if (gameConfig.killConfig) {
var k = gameConfig.killConfig;
$('#form-kill input[name="normalizationFactorPlayer"]').val(k.normalizationFactorPlayer || 50000);
$('#form-kill input[name="minPlayerModifier"]').val(k.minPlayerModifier || 0.3);
$('#form-kill input[name="maxPlayerModifier"]').val(k.maxPlayerModifier || 2.0);
$('#form-kill input[name="targetSystemProfit"]').val(k.targetSystemProfit || 1000000);
$('#form-kill input[name="normalizationFactorSystem"]').val(k.normalizationFactorSystem || 10000000);
$('#form-kill input[name="minSystemModifier"]').val(k.minSystemModifier || 0.95);
$('#form-kill input[name="maxSystemModifier"]').val(k.maxSystemModifier || 1.05);
$('#form-kill input[name="daySystemGold"]').val(k.daySystemGold || 100000);
$('#form-kill input[name="globalAnnounceMinGold"]').val(k.globalAnnounceMinGold || 0);
$('#form-kill input[name="ipCount"]').val(k.ipCount || 5);
$('#form-kill input[name="violateValue"]').val(k.violateValue || 0.2);
if (k.compensateRate) {
$('#form-kill input[name="compensateBossRate"]').val(k.compensateRate.bossRate || 1);
$('#form-kill input[name="compensateGoldRate"]').val(k.compensateRate.goldRate || 1);
$('#form-kill input[name="compensateSpecialRate"]').val(k.compensateRate.specialRate || 1);
$('#form-kill input[name="compensateOrdinary"]').val(k.compensateRate.ordinary || 1);
}
if (k.penaltyRate) {
$('#form-kill input[name="penaltyBossRate"]').val(k.penaltyRate.bossRate || 1);
$('#form-kill input[name="penaltyGoldRate"]').val(k.penaltyRate.goldRate || 1);
$('#form-kill input[name="penaltySpecialRate"]').val(k.penaltyRate.specialRate || 1);
$('#form-kill input[name="penaltyOrdinary"]').val(k.penaltyRate.ordinary || 1);
}
}
form.render();
}
function initConfig() {
layer.confirm('确定要初始化全部配置数据吗?此操作将清空现有数据!', function(idx) {
layer.close(idx);
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig/InitSection/KillConfig",
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('初始化成功');
loadConfig();
} else {
layer.msg(res.info || '初始化失败');
}
},
fail: function(res) {
layer.msg(res.info || '初始化失败');
}
});
});
}
$(document).on('click', '[lay-filter="init-kill"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
initConfig();
});
$(document).on('click', '[lay-filter="reload-kill"]', function() {
loadConfig();
layer.msg('刷新成功');
});
$(document).on('click', '[lay-filter="save-kill"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
if (!gameConfig.killConfig) {
gameConfig.killConfig = {};
}
var k = gameConfig.killConfig;
k.normalizationFactorPlayer = parseInt($('#form-kill input[name="normalizationFactorPlayer"]').val()) || 50000;
k.minPlayerModifier = parseFloat($('#form-kill input[name="minPlayerModifier"]').val()) || 0.3;
k.maxPlayerModifier = parseFloat($('#form-kill input[name="maxPlayerModifier"]').val()) || 2.0;
k.targetSystemProfit = parseInt($('#form-kill input[name="targetSystemProfit"]').val()) || 1000000;
k.normalizationFactorSystem = parseInt($('#form-kill input[name="normalizationFactorSystem"]').val()) || 10000000;
k.minSystemModifier = parseFloat($('#form-kill input[name="minSystemModifier"]').val()) || 0.95;
k.maxSystemModifier = parseFloat($('#form-kill input[name="maxSystemModifier"]').val()) || 1.05;
k.daySystemGold = parseInt($('#form-kill input[name="daySystemGold"]').val()) || 100000;
k.globalAnnounceMinGold = parseInt($('#form-kill input[name="globalAnnounceMinGold"]').val()) || 0;
k.ipCount = parseInt($('#form-kill input[name="ipCount"]').val()) || 5;
k.violateValue = parseFloat($('#form-kill input[name="violateValue"]').val()) || 0.2;
k.compensateRate = {
bossRate: parseFloat($('#form-kill input[name="compensateBossRate"]').val()) || 1,
goldRate: parseFloat($('#form-kill input[name="compensateGoldRate"]').val()) || 1,
specialRate: parseFloat($('#form-kill input[name="compensateSpecialRate"]').val()) || 1,
ordinary: parseFloat($('#form-kill input[name="compensateOrdinary"]').val()) || 1
};
k.penaltyRate = {
bossRate: parseFloat($('#form-kill input[name="penaltyBossRate"]').val()) || 1,
goldRate: parseFloat($('#form-kill input[name="penaltyGoldRate"]').val()) || 1,
specialRate: parseFloat($('#form-kill input[name="penaltySpecialRate"]').val()) || 1,
ordinary: parseFloat($('#form-kill input[name="penaltyOrdinary"]').val()) || 1
};
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/GameConfig/Update",
data: JSON.stringify(gameConfig),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('保存成功');
loadConfig();
} else {
layer.msg(res.info || '保存失败');
}
}
});
});
window.saveSection=function(n){layui.admin.req({method:'get',url:(layui.setter.baseurl||'/')+'adminapi/GameConfig/SaveSection/'+n,done:function(r){layui.layer.msg(r.info||(r.code===0?'保存成功':'保存失败'))}})}
loadConfig();
});
</script>
@@ -0,0 +1,574 @@

<title>月卡配置</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏管理</cite></a>
<a><cite>月卡配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-body">
<table id="table-monthcard" lay-filter="table-monthcard"></table>
</div>
</div>
</div>
<script type="text/html" id="toolbar-monthcard">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-filter="init-monthcard"><i class="layui-icon layui-icon-refresh"></i> 初始化配置</button>
<button class="layui-btn layui-btn-sm" lay-filter="save-monthcard"><i class="layui-icon layui-icon-ok"></i> 保存配置</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-filter="reload-monthcard"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
<button class="layui-btn layui-btn-sm" lay-filter="add-monthcard">添加月卡</button>
</div>
</script>
<script type="text/html" id="monthcard-bar">
<a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a>
<a class="layui-btn layui-btn-xs" lay-event="gift">礼包</a>
<a class="layui-btn layui-btn-xs" lay-event="daily">每日</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>
<script type="text/html" id="gift-dialog-tpl">
<div class="gift-container">
<div class="gift-left">
<div class="gift-toolbar">
<button class="layui-btn layui-btn-sm" id="btn-add-gift"><i class="layui-icon layui-icon-add-1"></i> 新增</button>
<button class="layui-btn layui-btn-sm layui-btn-danger" id="btn-delete-gift"><i class="layui-icon layui-icon-delete"></i> 删除</button>
</div>
<div class="gift-table-wrap">
<table id="table-gift-list"></table>
</div>
</div>
<div class="gift-right">
<h4>礼包项详情</h4>
<div class="layui-form" id="gift-form">
<div class="layui-form-item">
<label class="layui-form-label">类型</label>
<div class="layui-input-block">
<select name="giftType" id="giftType" lay-filter="giftType">
<option value="1">金币</option>
<option value="2">钻石</option>
<option value="3">道具</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">数量</label>
<div class="layui-input-block">
<input type="number" name="giftCount" id="giftCount" class="layui-input" placeholder="请输入数量">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">随机</label>
<div class="layui-input-block">
<input type="checkbox" name="giftIsRandom" lay-skin="switch" lay-text="是|否" id="giftIsRandom">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">最小值</label>
<div class="layui-input-block">
<input type="number" name="giftMinCount" id="giftMinCount" class="layui-input" placeholder="最小值">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">最大值</label>
<div class="layui-input-block">
<input type="number" name="giftMaxCount" id="giftMaxCount" class="layui-input" placeholder="最大值">
</div>
</div>
<div class="layui-form-item gift-prop-select" style="display:none;">
<label class="layui-form-label">道具</label>
<div class="layui-input-block">
<select name="giftProductNumber" id="giftProductNumber" lay-search>
<option value="">请选择道具</option>
</select>
</div>
</div>
<div class="layui-form-item" style="margin-top:20px;">
<button class="layui-btn layui-btn-normal" id="btn-save-gift" style="width:100%;">保存</button>
</div>
</div>
</div>
</div>
</script>
<script>
layui.use(['admin', 'table', 'form', 'layer'], function () {
var $ = layui.$,
admin = layui.admin,
table = layui.table,
form = layui.form,
layer = layui.layer,
setter = layui.setter;
var gameConfig = null;
var currentMcIndex = -1;
var currentGiftIndex = -1;
var currentGiftType = 'gift';
var propList = [];
var currentDialogData = null;
setTimeout(function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
$('.layui-layer-shade').remove();
layer.closeAll();
}, 100);
function loadConfig() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig",
done: function (res) {
if (res.code === 0 && res.data) {
gameConfig = res.data;
propList = (gameConfig.propConfig || []).map(function(p) {
return { number: p.number, name: p.name, type: p.type };
});
renderTable();
}
}
});
}
function renderTable() {
table.render({
elem: '#table-monthcard',
data: gameConfig ? (gameConfig.monthCardConfigs || []) : [],
cols: [[
{ type: 'numbers', title: '序号', width: 60 },
{ field: "type", title: "类型", width: 80, templet: function(d) {
var map = {1: '周卡', 2: '月卡', 3: '年卡'};
return map[d.type] || '月卡';
}},
{ field: "name", title: "名称", width: 120 },
{ field: "remark", title: "备注", minWidth: 150 },
{ field: "signInAdd", title: "签到加成", width: 100 },
{ field: "gold", title: "金币", width: 100 },
{ field: "money", title: "现金价格", width: 100 },
{ field: "giftList", title: "礼包", width: 80, templet: function(d) {
return (d.giftList && d.giftList.length) ? d.giftList.length + '项' : '无';
}},
{ field: "dailyGift", title: "每日", width: 80, templet: function(d) {
return (d.dailyGift && d.dailyGift.length) ? d.dailyGift.length + '项' : '无';
}},
{ title: "操作", width: 250, fixed: 'right', toolbar: '#monthcard-bar' }
]],
toolbar: '#toolbar-monthcard',
defaultToolbar: ['filter', 'exports', 'print']
});
}
function initConfig() {
layer.confirm('确定要初始化全部配置数据吗?此操作将清空现有数据!', function(idx) {
layer.close(idx);
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig/init-game",
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('初始化成功');
loadConfig();
} else {
layer.msg(res.info || '初始化失败');
}
},
fail: function(res) {
layer.msg(res.info || '初始化失败');
}
});
});
}
$(document).on('click', '[lay-filter="init-monthcard"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
initConfig();
});
$(document).on('click', '[lay-filter="reload-monthcard"]', function() {
loadConfig();
layer.msg('刷新成功');
});
$(document).on('click', '[lay-filter="save-monthcard"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/GameConfig/Update",
data: JSON.stringify(gameConfig),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('保存成功');
loadConfig();
} else {
layer.msg(res.info || '保存失败');
}
}
});
});
table.on('tool(table-monthcard)', function(obj) {
var data = obj.data;
var trIndex = obj.tr.index();
if (trIndex < 0) trIndex = gameConfig.monthCardConfigs.findIndex(function(x) { return x.cardId === data.cardId; });
if (obj.event === 'del') {
layer.confirm('确定删除该配置吗?', function(idx) {
if (trIndex > -1) gameConfig.monthCardConfigs.splice(trIndex, 1);
obj.del();
layer.close(idx);
});
} else if (obj.event === 'edit') {
showEditDialog(data, false, trIndex);
} else if (obj.event === 'gift') {
currentMcIndex = trIndex;
currentGiftType = 'gift';
showGiftDialog(data, '月卡礼包');
} else if (obj.event === 'daily') {
currentMcIndex = trIndex;
currentGiftType = 'daily';
showGiftDialog(data, '月卡每日');
}
});
function showAddDialog() {
if (!gameConfig.monthCardConfigs) gameConfig.monthCardConfigs = [];
var newData = {
type: 2,
name: '',
remark: '',
signInAdd: 0,
gold: 0,
money: 0,
giftList: [],
dailyGift: []
};
showEditDialog(newData, true);
}
$(document).on('click', '[lay-filter="add-monthcard"]', function() {
showAddDialog();
});
function showEditDialog(data, isAdd, rowIndex) {
var dialogTitle = isAdd ? '添加月卡' : '编辑月卡';
var html = '<div style="padding:20px;">' +
'<form class="layui-form">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">类型</label>' +
'<div class="layui-input-block">' +
'<select name="type">' +
'<option value="1"' + (data.type === 1 ? ' selected' : '') + '>周卡</option>' +
'<option value="2"' + (data.type === 2 ? ' selected' : '') + '>月卡</option>' +
'<option value="3"' + (data.type === 3 ? ' selected' : '') + '>年卡</option>' +
'</select>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">名称</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="name" value="' + (data.name || '') + '" 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="remark" value="' + (data.remark || '') + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">签到加成</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="signInAdd" value="' + (data.signInAdd || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">金币</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="gold" value="' + (data.gold || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">现金价格</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="money" value="' + (data.money || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'</form>' +
'</div>';
layer.open({
type: 1,
title: dialogTitle,
area: ['500px', '450px'],
content: html,
btn: ['保存', '取消'],
shadeClose: true,
success: function(layero) {
form.render('select');
},
yes: function(idx, layero) {
if (isAdd) {
var newItem = {
type: parseInt(layero.find('select[name="type"]').val()),
name: layero.find('input[name="name"]').val(),
remark: layero.find('input[name="remark"]').val(),
signInAdd: parseFloat(layero.find('input[name="signInAdd"]').val()) || 0,
gold: parseInt(layero.find('input[name="gold"]').val()) || 0,
money: parseFloat(layero.find('input[name="money"]').val()) || 0,
giftList: [],
dailyGift: []
};
gameConfig.monthCardConfigs.unshift(newItem);
} else {
var original = gameConfig.monthCardConfigs[rowIndex];
if (!original) {
original = gameConfig.monthCardConfigs.find(function(x) { return x.cardId === data.cardId; });
}
if (!original) { layer.msg('数据异常,请刷新后重试'); return; }
original.type = parseInt(layero.find('select[name="type"]').val());
original.name = layero.find('input[name="name"]').val();
original.remark = layero.find('input[name="remark"]').val();
original.signInAdd = parseFloat(layero.find('input[name="signInAdd"]').val()) || 0;
original.gold = parseInt(layero.find('input[name="gold"]').val()) || 0;
original.money = parseFloat(layero.find('input[name="money"]').val()) || 0;
}
layer.close(idx);
table.reload('table-monthcard', {
data: gameConfig.monthCardConfigs
});
}
});
}
function showGiftDialog(data, title) {
currentDialogData = data;
currentGiftIndex = -1;
var dialogContent = $('#gift-dialog-tpl').html();
layer.open({
type: 1,
title: title + ' - ' + data.name,
area: ['1000px', '620px'],
content: dialogContent,
shadeClose: true,
success: function(layero) {
var propOptions = '<option value="">请选择道具</option>';
propList.forEach(function(p) {
propOptions += '<option value="' + p.number + '">' + p.number + ' - ' + p.name + '</option>';
});
layero.find('#giftProductNumber').html(propOptions);
form.render();
renderGiftTable(layero);
layero.find('#btn-add-gift').on('click', function() {
addGiftItem(layero);
});
layero.find('#btn-delete-gift').on('click', function() {
deleteGiftItem(layero);
});
layero.find('#btn-save-gift').on('click', function() {
saveGiftItem(layero);
});
form.on('select(giftType)', function(obj) {
if (obj.value === '3') {
layero.find('.gift-prop-select').show();
} else {
layero.find('.gift-prop-select').hide();
}
layero.find('#giftCount').val(0);
layero.find('#giftIsRandom')[0].checked = false;
form.render('checkbox');
});
layero.find('#giftCount').on('input', function() {
layero.find('#giftIsRandom')[0].checked = false;
layero.find('#giftIsRandom').prop('checked', false);
form.render('checkbox');
});
layero.on('click', '#giftIsRandom + .layui-form-switch', function() {
var checkbox = layero.find('#giftIsRandom')[0];
setTimeout(function() {
if (checkbox.checked) {
layero.find('#giftCount').val(0);
}
}, 10);
});
form.on('switch(giftIsRandom)', function(data) {
if (data.elem.checked) {
layero.find('#giftCount').val(0);
}
});
}
});
}
function getGiftList() {
if (!currentDialogData) return [];
if (currentGiftType === 'gift') {
return currentDialogData.giftList || [];
} else {
return currentDialogData.dailyGift || [];
}
}
function addGiftItem(layero) {
var list = getGiftList();
list.push({ type: 1, count: 100, isRandom: false, minCount: 0, maxCount: 0, otherId: '' });
currentGiftIndex = list.length - 1;
renderGiftTable(layero, currentGiftIndex);
loadFormData(list[list.length - 1], layero);
}
function deleteGiftItem(layero) {
if (currentGiftIndex < 0) {
return layer.msg('请先选择要删除的项');
}
var list = getGiftList();
if (list.length > currentGiftIndex) {
list.splice(currentGiftIndex, 1);
currentGiftIndex = -1;
clearForm(layero);
renderGiftTable(layero, -1);
}
}
function saveGiftItem(layero) {
if (currentGiftIndex < 0) {
return layer.msg('请先选择要编辑的项');
}
var list = getGiftList();
var giftType = parseInt(layero.find('#giftType').val()) || 1;
list[currentGiftIndex] = {
type: giftType,
count: parseInt(layero.find('#giftCount').val()) || 0,
isRandom: layero.find('#giftIsRandom')[0].checked,
minCount: parseInt(layero.find('#giftMinCount').val()) || 0,
maxCount: parseInt(layero.find('#giftMaxCount').val()) || 0,
otherId: giftType === 3 ? layero.find('#giftProductNumber').val() : ''
};
renderGiftTable(layero, currentGiftIndex);
layer.msg('保存成功');
}
function clearForm(layero) {
layero.find('#giftType').val('1');
layero.find('#giftCount').val('0');
layero.find('#giftIsRandom')[0].checked = false;
layero.find('#giftMinCount').val('0');
layero.find('#giftMaxCount').val('0');
layero.find('#giftProductNumber').val('');
layero.find('.gift-prop-select').hide();
form.render('checkbox');
form.render('select');
}
function loadFormData(data, layero) {
layero.find('#giftType').val(data.type || 1);
layero.find('#giftCount').val(data.count || 0);
layero.find('#giftIsRandom')[0].checked = data.isRandom || false;
layero.find('#giftMinCount').val(data.minCount || 0);
layero.find('#giftMaxCount').val(data.maxCount || 0);
layero.find('#giftProductNumber').val(data.otherId || '');
if (data.type === 3) {
layero.find('.gift-prop-select').show();
} else {
layero.find('.gift-prop-select').hide();
}
form.render('checkbox');
form.render('select');
}
function getPropName(number) {
var prop = propList.find(function(p) { return p.number === number; });
return prop ? prop.number + ' - ' + prop.name : number || '';
}
function renderGiftTable(layero, selectIndex) {
var list = getGiftList();
table.render({
elem: layero.find('#table-gift-list'),
data: list,
page: false,
height: 380,
cols: [[
{ type: 'numbers', title: '序号', width: 60 },
{ field: "type", title: "类型", width: 80, templet: function(d) {
var map = {1: '金币', 2: '钻石', 3: '道具'};
return map[d.type] || '金币';
}},
{ field: "count", title: "数量", width: 80 },
{ field: "minCount", title: "最小值", width: 80 },
{ field: "maxCount", title: "最大值", width: 80 },
{ field: "isRandom", title: "随机", width: 70, templet: function(d) {
return d.isRandom ? '是' : '否';
}},
{ field: "otherId", title: "道具编号", minWidth: 150, templet: function(d) {
return d.type === 3 ? getPropName(d.otherId || '') : '';
}}
]],
done: function() {
if (list.length > 0 && selectIndex < 0) {
selectIndex = 0;
}
if (selectIndex >= 0 && selectIndex < list.length) {
currentGiftIndex = selectIndex;
var $tr = layero.find('#table-gift-list + .layui-table-view tr[data-index="' + selectIndex + '"]');
if ($tr.length) {
$tr.addClass('layui-table-click');
loadFormData(list[selectIndex], layero);
}
}
}
});
layero.find('#table-gift-list + .layui-table-view').on('click', 'tr[data-index]', function() {
var index = parseInt($(this).attr('data-index'));
if (index >= 0 && index < list.length) {
currentGiftIndex = index;
layero.find('tr[data-index]').removeClass('layui-table-click');
$(this).addClass('layui-table-click');
loadFormData(list[index], layero);
}
});
}
loadConfig();
});
</script>
<style>
.gift-container { display: flex; height: 480px; padding: 10px; }
.gift-left { flex: 1; margin-right: 15px; }
.gift-table-wrap { height: 400px; overflow: auto; }
.gift-right { width: 280px; border-left: 1px solid #e6e6e6; padding-left: 15px; }
.gift-right h4 { margin-bottom: 15px; color: #333; font-size: 16px; padding-bottom: 10px; border-bottom: 1px solid #eee; }
.gift-toolbar { margin-bottom: 10px; }
.gift-toolbar .layui-btn { margin-right: 5px; }
.gift-right .layui-form-item { margin-bottom: 12px; }
.gift-right .layui-form-label { padding: 9px 0; width: 60px; }
.gift-right .layui-input-block { margin-left: 70px; }
</style>
@@ -0,0 +1,532 @@

<title>道具配置</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏管理</cite></a>
<a><cite>道具配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-body">
<table id="table-prop" lay-filter="table-prop"></table>
</div>
</div>
</div>
<script type="text/html" id="toolbar-prop">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-filter="init-prop"><i class="layui-icon layui-icon-refresh"></i> 初始化配置</button>
<button class="layui-btn layui-btn-sm" lay-filter="save-prop"><i class="layui-icon layui-icon-ok"></i> 保存配置</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-filter="reload-prop"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
<button class="layui-btn layui-btn-sm" lay-filter="add-prop">添加</button>
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-filter="batchdel-prop">批量删除</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" onclick="saveSection('PropConfig')"><i class="layui-icon layui-icon-export"></i> 保存到初始化文件</button>
</div>
</script>
<script type="text/html" id="tpl-type">
{{# var typeMap = {1:'炮台',2:'翅膀',3:'炮塔碎片',4:'翅膀碎片',5:'战神',6:'战神碎片',7:'刮刮卡',8:'改名卡',9:'大喇叭',10:'技能',11:'召唤卡',12:'VIP经验',13:'鱼雷'}; }}
{{= typeMap[d.type] || '未知' }}
</script>
<script type="text/html" id="tpl-saleType">
{{# var saleTypeMap = {1:'金币',2:'钻石'}; }}
{{= saleTypeMap[d.saleType] || '金币' }}
</script>
<script type="text/html" id="tpl-isEnable">
<input type="checkbox" name="isEnable" value="{{d.isEnable}}" lay-skin="switch" lay-text="启用|禁用" {{ d.isEnable ? 'checked' : '' }} lay-filter="isEnable-switch" data-field="isEnable">
</script>
<script type="text/html" id="tpl-isSale">
<input type="checkbox" name="isSale" value="{{d.isSale}}" lay-skin="switch" lay-text="销售|停售" {{ d.isSale ? 'checked' : '' }} lay-filter="isSale-switch" data-field="isSale">
</script>
<script type="text/html" id="tpl-image">
{{# if(d.imageUrl){ }}
<img src="{{d.imageUrl}}" style="width:40px;height:40px;object-fit:cover;cursor:pointer;border-radius:4px;" onclick="previewImage('{{d.imageUrl}}')" alt="图标">
{{# } else { }}
<span style="color:#999;">无</span>
{{# } }}
</script>
<script type="text/html" id="table-operate">
<a class="layui-btn layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑</a>
<a class="layui-btn layui-btn-xs" lay-event="preview"><i class="layui-icon layui-icon-search"></i>预览</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
<style>
.icon-upload-box { position: relative; display: inline-block; }
.icon-upload-box .upload-btn { position: absolute; top: 0; left: 0; width: 80px; height: 80px; opacity: 0; cursor: pointer; }
.icon-upload-preview { width: 80px; height: 80px; border: 1px dashed #ddd; border-radius: 4px; display: flex; align-items: center; justify-content: center; background: #fafafa; cursor: pointer; }
.icon-upload-preview:hover { border-color: #009688; }
.icon-upload-preview img { max-width: 100%; max-height: 100%; }
.icon-upload-preview .placeholder { color: #999; font-size: 12px; text-align: center; }
</style>
<script>
layui.use(['admin', 'table', 'form', 'layer'], function () {
var $ = layui.$,
admin = layui.admin,
table = layui.table,
form = layui.form,
layer = layui.layer,
setter = layui.setter;
var gameConfig = null;
var currentEditIndex = -1;
setTimeout(function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
$('.layui-layer-shade').remove();
layer.closeAll();
}, 100);
function loadConfig() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig",
done: function (res) {
if (res.code === 0 && res.data) {
gameConfig = res.data;
renderTable();
}
}
});
}
function renderTable() {
table.render({
elem: '#table-prop',
data: gameConfig ? gameConfig.propConfig || [] : [],
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ field: "number", title: "编号", width: 80 },
{ field: "name", title: "名称", width: 100 },
{ field: "type", title: "类型", width: 90, templet: '#tpl-type' },
{ field: "desc", title: "描述", width: 150 },
{ field: "imageUrl", title: "图标", width: 100, templet: '#tpl-image' },
{ field: "saleType", title: "售卖方式", width: 90, templet: '#tpl-saleType' },
{ field: "price", title: "价格", width: 80 },
{ field: "vipPrice", title: "VIP价格", width: 90 },
{ field: "dealerPrice", title: "经销商价", width: 100 },
{ field: "saleCount", title: "售卖数量", width: 90 },
{ field: "day", title: "有效期(天)", width: 100 },
{ field: "isEnable", title: "启用", width: 80, templet: '#tpl-isEnable' },
{ field: "isSale", title: "销售", width: 80, templet: '#tpl-isSale' },
{ title: "操作", width: 180, fixed: 'right', toolbar: '#table-operate' }
]],
toolbar: '#toolbar-prop',
defaultToolbar: ['filter', 'exports', 'print']
});
}
window.previewImage = function(url) {
layer.open({
type: 1,
title: '图片预览',
area: ['auto', 'auto'],
shadeClose: true,
content: '<div style="padding:20px;text-align:center;"><img src="' + url + '" style="max-width:400px;max-height:400px;border-radius:8px;"></div>'
});
};
function initConfig() {
layer.confirm('确定要初始化全部配置数据吗?此操作将清空现有数据!', function(idx) {
layer.close(idx);
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig/InitSection/PropConfig",
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('初始化成功');
loadConfig();
} else {
layer.msg(res.info || '初始化失败');
}
},
fail: function(res) {
layer.msg(res.info || '初始化失败');
}
});
});
}
$(document).on('click', '[lay-filter="init-prop"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
initConfig();
});
$(document).on('click', '[lay-filter="reload-prop"]', function() {
loadConfig();
layer.msg('刷新成功');
});
$(document).on('click', '[lay-filter="save-prop"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/GameConfig/Update",
data: JSON.stringify(gameConfig),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('保存成功');
loadConfig();
} else {
layer.msg(res.info || '保存失败');
}
}
});
});
// 启用开关
form.on('switch(isEnable-switch)', function(obj) {
var tr = $(obj.elem).closest('tr');
var dataIndex = tr.attr('data-index');
var tableData = gameConfig.propConfig;
if (tableData && tableData[dataIndex]) {
tableData[dataIndex].isEnable = obj.elem.checked;
}
});
// 销售开关
form.on('switch(isSale-switch)', function(obj) {
var tr = $(obj.elem).closest('tr');
var dataIndex = tr.attr('data-index');
var tableData = gameConfig.propConfig;
if (tableData && tableData[dataIndex]) {
tableData[dataIndex].isSale = obj.elem.checked;
}
});
// 表格行工具栏
table.on('tool(table-prop)', function(obj) {
var data = obj.data;
var trIndex = obj.tr.index();
if (trIndex < 0) trIndex = gameConfig.propConfig.findIndex(function(x) { return x.propId === data.propId; });
if (obj.event === 'edit') {
showEditDialog(data, false, trIndex);
} else if (obj.event === 'del') {
layer.confirm('确定删除该配置吗?', function(idx) {
if (trIndex > -1) gameConfig.propConfig.splice(trIndex, 1);
obj.del();
layer.close(idx);
});
} else if (obj.event === 'preview') {
if (data.imageUrl) {
previewImage(data.imageUrl);
} else {
layer.msg('暂无图片');
}
}
});
// 使用按钮点击事件
$(document).on('click', '[lay-filter="add-prop"]', function(e) {
e.preventDefault();
e.stopPropagation();
showAddDialog();
});
$(document).on('click', '[lay-filter="batchdel-prop"]', function(e) {
e.preventDefault();
e.stopPropagation();
var checkStatus = table.checkStatus('table-prop');
var checkData = checkStatus.data;
if (checkData.length === 0) {
return layer.msg('请选择要删除的数据');
}
layer.confirm('确定删除选中的 ' + checkData.length + ' 条数据吗?', function(idx) {
var ids = checkData.map(function(item) { return item.number; });
gameConfig.propConfig = gameConfig.propConfig.filter(function(item) {
return !ids.includes(item.number);
});
renderTable();
layer.close(idx);
});
});
$(document).on('click', '[lay-filter="batchdel-prop"]', function() {
var checkStatus = table.checkStatus('table-prop');
var checkData = checkStatus.data;
if (checkData.length === 0) {
return layer.msg('请选择要删除的数据');
}
layer.confirm('确定删除选中的 ' + checkData.length + ' 条数据吗?', function(idx) {
var ids = checkData.map(function(item) { return item.number; });
gameConfig.propConfig = gameConfig.propConfig.filter(function(item) {
return !ids.includes(item.number);
});
renderTable();
layer.close(idx);
});
});
function showAddDialog() {
var newData = {
number: '',
name: '',
type: 1,
desc: '',
imageUrl: '',
saleType: 1,
price: 0,
saleCount: 1,
vipPrice: 0,
dealerPrice: 0,
isEnable: true,
isSale: true,
gameNumber: [],
day: 0
};
showEditDialog(newData, true);
}
function showEditDialog(data, isAdd, rowIndex) {
var dialogTitle = isAdd ? '添加道具' : '编辑道具';
var typeOptions = [
{value: 1, text: '炮台'},
{value: 2, text: '翅膀'},
{value: 3, text: '炮塔碎片'},
{value: 4, text: '翅膀碎片'},
{value: 5, text: '战神'},
{value: 6, text: '战神碎片'},
{value: 7, text: '刮刮卡'},
{value: 8, text: '改名卡'},
{value: 9, text: '大喇叭'},
{value: 10, text: '技能'},
{value: 11, text: '召唤卡'},
{value: 12, text: 'VIP经验'},
{value: 13, text: '鱼雷'}
];
var saleTypeOptions = [
{value: 1, text: '金币'},
{value: 2, text: '钻石'}
];
var typeHtml = '';
typeOptions.forEach(function(opt) {
typeHtml += '<option value="' + opt.value + '"' + (data.type === opt.value ? ' selected' : '') + '>' + opt.text + '</option>';
});
var saleTypeHtml = '';
saleTypeOptions.forEach(function(opt) {
saleTypeHtml += '<option value="' + opt.value + '"' + (data.saleType === opt.value ? ' selected' : '') + '>' + opt.text + '</option>';
});
var imagePreviewHtml = data.imageUrl
? '<img src="' + data.imageUrl + '" style="max-width:100%;max-height:100%;">'
: '<div class="placeholder">点击上传图片</div>';
var uploadBtnHtml = '<input type="file" name="file" class="upload-btn" accept="image/png" id="propIconUpload">';
var html = '<div class="edit-dialog" style="padding:20px;">' +
'<form class="layui-form">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">编号</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="number" value="' + (data.number || '') + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">名称</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="name" value="' + (data.name || '') + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">类型</label>' +
'<div class="layui-input-block">' +
'<select name="type">' + typeHtml + '</select>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">描述</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="desc" value="' + (data.desc || '') + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">图标</label>' +
'<div class="layui-input-block">' +
'<div class="icon-upload-box">' +
'<div class="icon-upload-preview" id="iconPreview">' + imagePreviewHtml + '</div>' +
uploadBtnHtml +
'</div>' +
'<input type="hidden" name="imageUrl" id="imageUrlInput" value="' + (data.imageUrl || '') + '">' +
'<div style="margin-top:8px;color:#999;font-size:12px;">点击图片可预览,支持jpg、png、gif格式</div>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">售卖方式</label>' +
'<div class="layui-input-block">' +
'<select name="saleType">' + saleTypeHtml + '</select>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">价格</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="price" value="' + (data.price || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">VIP价格</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="vipPrice" value="' + (data.vipPrice || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">经销商价</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="dealerPrice" value="' + (data.dealerPrice || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">售卖数量</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="saleCount" value="' + (data.saleCount || 1) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">有效期(天)</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="day" value="' + (data.day || 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="isEnable" lay-skin="switch" lay-text="启用|禁用" ' + (data.isEnable ? 'checked' : '') + '>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">销售</label>' +
'<div class="layui-input-block">' +
'<input type="checkbox" name="isSale" lay-skin="switch" lay-text="销售|停售" ' + (data.isSale ? 'checked' : '') + '>' +
'</div>' +
'</div>' +
'</form>' +
'</div>';
layer.open({
type: 1,
title: dialogTitle,
area: ['650px', '700px'],
content: html,
btn: ['保存', '取消'],
shadeClose: true,
success: function(layero) {
form.render('select');
var $preview = layero.find('#iconPreview');
var $input = layero.find('#imageUrlInput');
var $fileInput = layero.find('#propIconUpload');
$preview.on('click', function() {
var url = $input.val();
if (url) {
previewImage(url);
}
});
$fileInput.on('change', function() {
var file = this.files[0];
if (file) {
var number = layero.find('input[name="number"]').val();
if (!number) {
layer.msg('请先填写道具编号');
$(this).val('');
return;
}
var formData = new FormData();
formData.append('file', file);
formData.append('number', number);
$.ajax({
// 配置图片按道具编号覆盖,并通过 timespan 参数刷新浏览器缓存。
url: setter.baseurl + 'adminapi/ImageUpload/upload/config',
type: 'POST',
data: formData,
processData: false,
contentType: false,
headers: { 'token': layui.data(setter.tableName)[setter.request.tokenName] },
success: function(res) {
if (res.code === 0 || res.code === 200) {
var path = res.data.path;
$input.val(path);
$preview.html('<img src="' + path + '" style="max-width:100%;max-height:100%;">');
layer.msg('上传成功');
} else {
layer.msg(res.info || '上传失败');
}
},
error: function() {
layer.msg('上传失败');
}
});
}
});
},
yes: function(index, layero) {
if (isAdd) {
var newItem = {
number: layero.find('input[name="number"]').val(),
name: layero.find('input[name="name"]').val(),
type: parseInt(layero.find('select[name="type"]').val()),
desc: layero.find('input[name="desc"]').val(),
imageUrl: layero.find('input[name="imageUrl"]').val(),
saleType: parseInt(layero.find('select[name="saleType"]').val()),
price: parseFloat(layero.find('input[name="price"]').val()) || 0,
vipPrice: parseFloat(layero.find('input[name="vipPrice"]').val()) || 0,
dealerPrice: parseFloat(layero.find('input[name="dealerPrice"]').val()) || 0,
saleCount: parseInt(layero.find('input[name="saleCount"]').val()) || 1,
day: parseInt(layero.find('input[name="day"]').val()) || 0,
isEnable: layero.find('input[name="isEnable"]').prop('checked'),
isSale: layero.find('input[name="isSale"]').prop('checked'),
gameNumber: [],
id: data.id
};
gameConfig.propConfig.unshift(newItem);
} else {
var number = layero.find('input[name="number"]').val();
var idx = gameConfig.propConfig.findIndex(function(x) { return x.number === data.number; });
if (idx < 0) idx = rowIndex;
if (idx < 0) { layer.msg('数据异常'); return; }
gameConfig.propConfig[idx].number = number;
gameConfig.propConfig[idx].name = layero.find('input[name="name"]').val();
gameConfig.propConfig[idx].type = parseInt(layero.find('select[name="type"]').val());
gameConfig.propConfig[idx].desc = layero.find('input[name="desc"]').val();
gameConfig.propConfig[idx].imageUrl = layero.find('input[name="imageUrl"]').val();
gameConfig.propConfig[idx].saleType = parseInt(layero.find('select[name="saleType"]').val());
gameConfig.propConfig[idx].price = parseFloat(layero.find('input[name="price"]').val()) || 0;
gameConfig.propConfig[idx].vipPrice = parseFloat(layero.find('input[name="vipPrice"]').val()) || 0;
gameConfig.propConfig[idx].dealerPrice = parseFloat(layero.find('input[name="dealerPrice"]').val()) || 0;
gameConfig.propConfig[idx].saleCount = parseInt(layero.find('input[name="saleCount"]').val()) || 1;
gameConfig.propConfig[idx].day = parseInt(layero.find('input[name="day"]').val()) || 0;
gameConfig.propConfig[idx].isEnable = layero.find('input[name="isEnable"]').prop('checked');
gameConfig.propConfig[idx].isSale = layero.find('input[name="isSale"]').prop('checked');
}
layer.close(index);
renderTable();
}
});
}
window.saveSection=function(n){layui.admin.req({method:'get',url:(layui.setter.baseurl||'/')+'adminapi/GameConfig/SaveSection/'+n,done:function(r){layui.layer.msg(r.info||(r.code===0?'保存成功':'保存失败'))}})}
loadConfig();
});
</script>
@@ -0,0 +1,526 @@

<title>道具碎片配置</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏管理</cite></a>
<a><cite>道具碎片配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-body">
<table id="table-propfragment" lay-filter="table-propfragment"></table>
</div>
</div>
</div>
<script type="text/html" id="toolbar-propfragment">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-filter="init-propfragment"><i class="layui-icon layui-icon-refresh"></i> 初始化配置</button>
<button class="layui-btn layui-btn-sm" lay-filter="save-propfragment"><i class="layui-icon layui-icon-ok"></i> 保存配置</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-filter="reload-propfragment"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
<button class="layui-btn layui-btn-sm" lay-filter="add-propfragment">添加</button>
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-filter="batchdel-propfragment">批量删除</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" onclick="saveSection('PropFragments')"><i class="layui-icon layui-icon-export"></i> 保存到初始化文件</button>
</div>
</script>
<script type="text/html" id="tpl-type">
{{# var t = d.type + ''; }}
{{# if(t === '3') { }}
炮塔碎片
{{# } else if(t === '4') { }}
翅膀碎片
{{# } else if(t === '6') { }}
战神碎片
{{# } else { }}
未知({{= t}})
{{# } }}
</script>
<script type="text/html" id="tpl-saleType">
{{# var saleTypeMap = {'1':'金币','2':'钻石'}; }}
{{= saleTypeMap[d.saleType] || '金币' }}
</script>
<script type="text/html" id="tpl-isEnable">
<input type="checkbox" name="isEnable" lay-skin="switch" lay-text="启用|禁用" {{ d.isEnable ? 'checked' : '' }} lay-filter="isEnable-switch">
</script>
<script type="text/html" id="tpl-isSale">
<input type="checkbox" name="isSale" lay-skin="switch" lay-text="销售|停售" {{ d.isSale ? 'checked' : '' }} lay-filter="isSale-switch">
</script>
<script type="text/html" id="tpl-image">
{{# if(d.imageUrl){ }}
<img src="{{d.imageUrl}}" style="width:40px;height:40px;object-fit:cover;cursor:pointer;border-radius:4px;" onclick="previewImage('{{d.imageUrl}}')" alt="图标">
{{# } else { }}
<span style="color:#999;">无</span>
{{# } }}
</script>
<script type="text/html" id="table-operate">
<a class="layui-btn layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
<style>
.icon-upload-box { position: relative; display: inline-block; }
.icon-upload-box .upload-btn { position: absolute; top: 0; left: 0; width: 80px; height: 80px; opacity: 0; cursor: pointer; }
.icon-upload-preview { width: 80px; height: 80px; border: 1px dashed #ddd; border-radius: 4px; display: flex; align-items: center; justify-content: center; background: #fafafa; cursor: pointer; }
.icon-upload-preview:hover { border-color: #009688; }
.icon-upload-preview img { max-width: 100%; max-height: 100%; }
.icon-upload-preview .placeholder { color: #999; font-size: 12px; text-align: center; }
</style>
<script>
layui.use(['admin', 'table', 'form', 'layer'], function () {
var $ = layui.$,
admin = layui.admin,
table = layui.table,
form = layui.form,
layer = layui.layer,
setter = layui.setter;
var gameConfig = null;
var propList = [];
function getProductName(number) {
if (!number) return '-';
var prop = propList.find(function(p) { return String(p.number) === String(number); });
return prop ? prop.name : number;
}
setTimeout(function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
$('.layui-layer-shade').remove();
layer.closeAll();
}, 100);
function loadConfig() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig",
done: function (res) {
if (res.code === 0 && res.data) {
gameConfig = res.data;
propList = gameConfig.propConfig || [];
renderTable();
}
}
});
}
function renderTable() {
table.render({
elem: '#table-propfragment',
data: gameConfig ? gameConfig.propFragments || [] : [],
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ field: "number", title: "编号", width: 80 },
{ field: "name", title: "名称", width: 100 },
{ field: "type", title: "类型", width: 100, templet: '#tpl-type' },
{ field: "desc", title: "描述", width: 150 },
{ field: "imageUrl", title: "图标", width: 100, templet: '#tpl-image' },
{ field: "saleType", title: "售卖方式", width: 100, templet: '#tpl-saleType' },
{ field: "price", title: "价格", width: 80 },
{ field: "vipPrice", title: "VIP价格", width: 90 },
{ field: "dealerPrice", title: "经销商价", width: 100 },
{ field: "saleCount", title: "每日限购", width: 90 },
{ field: "fragmentCount", title: "合成数量", width: 90 },
{ field: "productNumber", title: "成品编号", width: 120, templet: function(d) { return getProductName(d.productNumber); }},
{ field: "isEnable", title: "启用", width: 80, templet: '#tpl-isEnable' },
{ field: "isSale", title: "销售", width: 80, templet: '#tpl-isSale' },
{ title: "操作", width: 180, fixed: 'right', toolbar: '#table-operate' }
]],
toolbar: '#toolbar-propfragment',
defaultToolbar: ['filter', 'exports', 'print']
});
}
window.previewImage = function(url) {
layer.open({
type: 1,
title: '图片预览',
area: ['auto', 'auto'],
shadeClose: true,
content: '<div style="padding:20px;text-align:center;"><img src="' + url + '" style="max-width:400px;max-height:400px;border-radius:8px;"></div>'
});
};
function initConfig() {
layer.confirm('确定要初始化全部配置数据吗?此操作将清空现有数据!', function(idx) {
layer.close(idx);
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig/InitSection/PropFragments",
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('初始化成功');
loadConfig();
} else {
layer.msg(res.info || '初始化失败');
}
},
fail: function(res) {
layer.msg(res.info || '初始化失败');
}
});
});
}
$(document).on('click', '[lay-filter="init-propfragment"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
initConfig();
});
$(document).on('click', '[lay-filter="reload-propfragment"]', function() {
loadConfig();
layer.msg('刷新成功');
});
$(document).on('click', '[lay-filter="save-propfragment"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/GameConfig/Update",
data: JSON.stringify(gameConfig),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('保存成功');
loadConfig();
} else {
layer.msg(res.info || '保存失败');
}
}
});
});
form.on('switch(isEnable-switch)', function(obj) {
var tr = $(obj.elem).closest('tr');
var dataIndex = tr.attr('data-index');
var tableData = gameConfig.propFragments;
if (tableData && tableData[dataIndex]) {
tableData[dataIndex].isEnable = obj.elem.checked;
}
});
form.on('switch(isSale-switch)', function(obj) {
var tr = $(obj.elem).closest('tr');
var dataIndex = tr.attr('data-index');
var tableData = gameConfig.propFragments;
if (tableData && tableData[dataIndex]) {
tableData[dataIndex].isSale = obj.elem.checked;
}
});
table.on('tool(table-propfragment)', function(obj) {
var data = obj.data;
// Layui 返回的是行副本,按碎片编号定位原始配置。
var trIndex = gameConfig.propFragments.findIndex(function(x) {
return String(x.number) === String(data.number);
});
if (obj.event === 'edit') {
showEditDialog(trIndex > -1 ? gameConfig.propFragments[trIndex] : data, false, trIndex);
} else if (obj.event === 'del') {
layer.confirm('确定删除该配置吗?', function(idx) {
if (trIndex > -1) gameConfig.propFragments.splice(trIndex, 1);
obj.del();
layer.close(idx);
});
} else if (obj.event === 'preview') {
if (data.imageUrl) {
previewImage(data.imageUrl);
} else {
layer.msg('暂无图片');
}
}
});
function showAddDialog() {
if (!gameConfig.propFragments) gameConfig.propFragments = [];
var newData = {
number: '',
name: '',
type: 7,
desc: '',
imageUrl: '/uploads/',
saleType: 1,
price: 0,
saleCount: 1,
vipPrice: 0,
dealerPrice: 0,
fragmentCount: 60,
productNumber: '',
isEnable: true,
isSale: true
};
showEditDialog(newData, true);
}
$(document).on('click', '[lay-filter="add-propfragment"]', function() {
showAddDialog();
});
$(document).on('click', '[lay-filter="batchdel-propfragment"]', function() {
var checkStatus = table.checkStatus('table-propfragment');
var checkData = checkStatus.data;
if (checkData.length === 0) {
return layer.msg('请选择要删除的数据');
}
layer.confirm('确定删除选中的 ' + checkData.length + ' 条数据吗?', function(idx) {
var ids = checkData.map(function(item) { return item.number; });
gameConfig.propFragments = gameConfig.propFragments.filter(function(item) {
return !ids.includes(item.number);
});
renderTable();
layer.close(idx);
});
});
function showEditDialog(data, isAdd, rowIndex) {
var dialogTitle = isAdd ? '添加道具碎片' : '编辑道具碎片';
var typeOptions = [
{value: '3', text: '炮塔碎片'},
{value: '4', text: '翅膀碎片'},
{value: '6', text: '战神碎片'}
];
var saleTypeOptions = [
{value: '1', text: '金币'},
{value: '2', text: '钻石'}
];
var typeHtml = '';
typeOptions.forEach(function(opt) {
typeHtml += '<option value="' + opt.value + '"' + (String(data.type) === opt.value ? ' selected' : '') + '>' + opt.text + '</option>';
});
var saleTypeHtml = '';
saleTypeOptions.forEach(function(opt) {
saleTypeHtml += '<option value="' + opt.value + '"' + (String(data.saleType) === opt.value ? ' selected' : '') + '>' + opt.text + '</option>';
});
var productOptions = '<option value="">请选择成品</option>';
propList.forEach(function(p) {
productOptions += '<option value="' + p.number + '"' + (String(data.productNumber) === String(p.number) ? ' selected' : '') + '>' + p.number + ' - ' + p.name + '</option>';
});
var imagePreviewHtml = data.imageUrl
? '<img src="' + data.imageUrl + '" style="max-width:100%;max-height:100%;">'
: '<div class="placeholder">点击上传图片</div>';
var html = '<div class="edit-dialog" style="padding:20px;">' +
'<form class="layui-form">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">编号</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="number" value="' + (data.number || '') + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">名称</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="name" value="' + (data.name || '') + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">类型</label>' +
'<div class="layui-input-block">' +
'<select name="type">' + typeHtml + '</select>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">描述</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="desc" value="' + (data.desc || '') + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">图标</label>' +
'<div class="layui-input-block">' +
'<div class="icon-upload-box">' +
'<div class="icon-upload-preview" id="iconPreview">' + imagePreviewHtml + '</div>' +
'<input type="file" name="file" class="upload-btn" accept="image/png" id="propIconUpload">' +
'</div>' +
'<input type="hidden" name="imageUrl" id="imageUrlInput" value="' + (data.imageUrl || '') + '">' +
'<div style="margin-top:8px;color:#999;font-size:12px;">点击图片可预览,仅支持 PNG 格式</div>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">售卖方式</label>' +
'<div class="layui-input-block">' +
'<select name="saleType">' + saleTypeHtml + '</select>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">价格</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="price" value="' + (data.price || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">VIP价格</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="vipPrice" value="' + (data.vipPrice || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">经销商价</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="dealerPrice" value="' + (data.dealerPrice || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">每日限购</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="saleCount" value="' + (data.saleCount || 1) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">合成数量</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="fragmentCount" value="' + (data.fragmentCount || 60) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">成品编号</label>' +
'<div class="layui-input-block">' +
'<select name="productNumber">' + productOptions + '</select>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">启用</label>' +
'<div class="layui-input-block">' +
'<input type="checkbox" name="isEnable" lay-skin="switch" lay-text="启用|禁用" ' + (data.isEnable ? 'checked' : '') + '>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">销售</label>' +
'<div class="layui-input-block">' +
'<input type="checkbox" name="isSale" lay-skin="switch" lay-text="销售|停售" ' + (data.isSale ? 'checked' : '') + '>' +
'</div>' +
'</div>' +
'</form>' +
'</div>';
layer.open({
type: 1,
title: dialogTitle,
area: ['520px', '650px'],
content: html,
btn: ['保存', '取消'],
shadeClose: true,
success: function(layero) {
form.render('select');
var $preview = layero.find('#iconPreview');
var $input = layero.find('#imageUrlInput');
var $fileInput = layero.find('#propIconUpload');
$preview.on('click', function() {
var url = $input.val();
if (url) {
previewImage(url);
}
});
$fileInput.on('change', function() {
var file = this.files[0];
if (file) {
var number = layero.find('input[name="number"]').val();
if (!number) {
layer.msg('请先填写碎片编号');
$(this).val('');
return;
}
var formData = new FormData();
formData.append('file', file);
formData.append('number', number);
$.ajax({
// 配置图片按碎片编号覆盖,并通过 timespan 参数刷新浏览器缓存。
url: setter.baseurl + 'adminapi/ImageUpload/upload/config',
type: 'POST',
data: formData,
processData: false,
contentType: false,
headers: { 'token': layui.data(setter.tableName)[setter.request.tokenName] },
success: function(res) {
if (res.code === 0 || res.code === 200) {
var path = res.data.path;
$input.val(path);
$preview.html('<img src="' + path + '" style="max-width:100%;max-height:100%;">');
layer.msg('上传成功');
} else {
layer.msg(res.info || '上传失败');
}
},
error: function() {
layer.msg('上传失败');
}
});
}
});
},
yes: function(index, layero) {
if (isAdd) {
var newItem = {
number: layero.find('input[name="number"]').val(),
name: layero.find('input[name="name"]').val(),
type: parseInt(layero.find('select[name="type"]').val()),
desc: layero.find('input[name="desc"]').val(),
imageUrl: layero.find('input[name="imageUrl"]').val(),
saleType: parseInt(layero.find('select[name="saleType"]').val()),
price: parseFloat(layero.find('input[name="price"]').val()) || 0,
vipPrice: parseFloat(layero.find('input[name="vipPrice"]').val()) || 0,
dealerPrice: parseFloat(layero.find('input[name="dealerPrice"]').val()) || 0,
saleCount: parseInt(layero.find('input[name="saleCount"]').val()) || 1,
fragmentCount: parseInt(layero.find('input[name="fragmentCount"]').val()) || 60,
productNumber: layero.find('select[name="productNumber"]').val(),
isEnable: layero.find('input[name="isEnable"]').prop('checked'),
isSale: layero.find('input[name="isSale"]').prop('checked')
};
gameConfig.propFragments.unshift(newItem);
} else if (rowIndex > -1) {
// 上传后的 imageUrl 必须写回原数组,否则表格重载仍会使用旧图片。
var original = gameConfig.propFragments[rowIndex];
original.number = layero.find('input[name="number"]').val();
original.name = layero.find('input[name="name"]').val();
original.type = parseInt(layero.find('select[name="type"]').val());
original.desc = layero.find('input[name="desc"]').val();
original.imageUrl = layero.find('input[name="imageUrl"]').val();
original.saleType = parseInt(layero.find('select[name="saleType"]').val());
original.price = parseFloat(layero.find('input[name="price"]').val()) || 0;
original.vipPrice = parseFloat(layero.find('input[name="vipPrice"]').val()) || 0;
original.dealerPrice = parseFloat(layero.find('input[name="dealerPrice"]').val()) || 0;
original.saleCount = parseInt(layero.find('input[name="saleCount"]').val()) || 1;
original.fragmentCount = parseInt(layero.find('input[name="fragmentCount"]').val()) || 60;
original.productNumber = layero.find('select[name="productNumber"]').val();
original.isEnable = layero.find('input[name="isEnable"]').prop('checked');
original.isSale = layero.find('input[name="isSale"]').prop('checked');
}
layer.close(index);
table.reload('table-propfragment', {
data: gameConfig.propFragments
});
}
});
}
window.saveSection=function(n){layui.admin.req({method:'get',url:(layui.setter.baseurl||'/')+'adminapi/GameConfig/SaveSection/'+n,done:function(r){layui.layer.msg(r.info||(r.code===0?'保存成功':'保存失败'))}})}
loadConfig();
});
</script>
@@ -0,0 +1,432 @@

<title>排行榜奖励配置</title>
<style>
.layui-form-label { width: 200px; }
.rank-table { width: 100%; border-collapse: collapse; margin: 10px 0; }
.rank-table th, .rank-table td { border: 1px solid #e6e6e6; padding: 6px 8px; text-align: center; font-size: 13px; }
.rank-table th { background: #f2f2f2; font-weight: bold; }
.rank-table input { text-align: center; height: 32px; }
.rank-table .del-row { color: #ff5722; cursor: pointer; }
.item-btn { cursor: pointer; color: #1e9fff; text-decoration: underline; }
.prop-row { display: flex; align-items: center; gap: 6px; margin: 4px 0; }
.prop-row select { flex: 1; }
.prop-row input { width: 60px; }
</style>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏管理</cite></a>
<a><cite>排行榜奖励配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-filter="save-ranking"><i class="layui-icon layui-icon-ok"></i> 保存配置</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-filter="reload-ranking"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
<button class="layui-btn layui-btn-sm" lay-filter="init-ranking"><i class="layui-icon layui-icon-refresh-3"></i> 初始化</button>
</div>
</div>
<div class="layui-card-body">
<form class="layui-form" id="form-ranking" style="padding: 20px;">
<div class="layui-form-item">
<label class="layui-form-label">总开关</label>
<div class="layui-input-inline">
<input type="checkbox" name="isEnabled" lay-skin="switch" lay-text="开启|关闭">
</div>
<div class="layui-form-mid">关闭后所有排行榜奖励将不发放</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">维护中</label>
<div class="layui-input-inline">
<input type="checkbox" name="isMaintaining" lay-skin="switch" lay-text="开启|关闭">
</div>
<div class="layui-form-mid">开启后前端请求排行榜将提示维护中</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">维护提示文案</label>
<div class="layui-input-inline" style="width: 400px;">
<input type="text" name="maintainingMessage" class="layui-input" placeholder="当前排行榜正在维护中,请稍后再试">
</div>
</div>
<div id="groups-container"></div>
</form>
</div>
</div>
</div>
<script>
layui.use(['admin', 'form', 'layer'], function () {
var $ = layui.$,
admin = layui.admin,
form = layui.form,
layer = layui.layer,
setter = layui.setter;
var activityConfig = null;
var propList = [];
var RANKING_NAMES = { 1: '金币达人榜', 2: '等级达人榜', 3: '当日积分榜' };
var ITEMS_CTX = null; // { gi, ri } 当前编辑的单元格
function loadPropList() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig/PropList",
async: false,
done: function (res) {
if (res.code === 0 && res.data) propList = res.data;
}
});
}
function getPropInfo(otherId) {
return propList.find(function(p) { return p.number === otherId; });
}
function getTypeName(type) {
var typeNames = {
1: '炮台', 2: '翅膀', 3: '炮塔碎片', 4: '翅膀碎片', 5: '战神',
6: '战神碎片', 7: '刮刮卡', 8: '改名卡', 9: '大喇叭', 10: '技能',
11: '召唤卡', 12: 'VIP经验', 13: '鱼雷'
};
return typeNames[type] || '其他';
}
function loadConfig() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/ActivityConfig",
done: function (res) {
activityConfig = (res.code === 0 && res.data) ? res.data : {};
renderGroups();
},
fail: function () { activityConfig = {}; renderGroups(); }
});
}
function renderGroups() {
if (!activityConfig) activityConfig = {};
var cfg = activityConfig.rankingRewardConfig || { isEnabled: false, isMaintaining: false, maintainingMessage: '', groups: [] };
$('#form-ranking input[name="isEnabled"]').prop('checked', cfg.isEnabled);
$('#form-ranking input[name="isMaintaining"]').prop('checked', cfg.isMaintaining);
$('#form-ranking input[name="maintainingMessage"]').val(cfg.maintainingMessage || '当前排行榜正在维护中,请稍后再试');
form.render();
var html = '';
var groups = cfg.groups && cfg.groups.length > 0 ? cfg.groups : getDefaultGroups();
for (var gi = 0; gi < groups.length; gi++) {
var g = groups[gi];
html += '<fieldset class="layui-elem-field"><legend>' + (g.name || RANKING_NAMES[g.rankingType] || '排行榜') + '</legend>';
html += '<div class="layui-field-box">';
html += '<table class="rank-table"><thead><tr>';
html += '<th style="width:40px">启用</th><th>排名</th><th>金币</th><th>钻石</th><th>道具</th><th style="width:40px">操作</th>';
html += '</tr></thead><tbody id="tbody-' + gi + '">';
var rewards = g.rewards && g.rewards.length > 0 ? g.rewards : [];
for (var ri = 0; ri < rewards.length; ri++) {
html += renderRow(gi, ri, rewards[ri]);
}
html += '</tbody></table>';
html += '<button class="layui-btn layui-btn-xs layui-btn-normal" onclick="addRow(' + gi + ')"><i class="layui-icon layui-icon-add-1"></i> 新增排名</button>';
html += '</div></fieldset><br>';
}
$('#groups-container').html(html);
}
function renderRow(gi, ri, r) {
if (!r) r = { rankMin: ri + 1, rankMax: ri + 1, gold: 0, diamond: 0, items: [], enabled: true };
var rankLabel = r.rankMin === r.rankMax ? '第' + r.rankMin + '名' : '第' + r.rankMin + '-' + r.rankMax + '名';
var itemsStr = '';
if (r.items) {
itemsStr = r.items.map(function (it) {
var prop = getPropInfo(it.otherId);
var imgHtml = prop ? '<img src="' + (prop.imageUrl || '/uploads/' + prop.number + '.png') + '" style="width:20px;height:20px;vertical-align:middle;margin-right:3px;">' : '';
var name = prop ? prop.name : (it.otherId || '');
return imgHtml + name + 'x' + (it.count || 0);
}).join(' ');
}
var checked = r.enabled !== false ? 'checked' : '';
return '<tr>' +
'<td><input type="checkbox" lay-filter="row-switch" data-gi="' + gi + '" data-ri="' + ri + '" ' + checked + ' lay-skin="switch" lay-text="开|关"></td>' +
'<td><input type="text" class="layui-input" style="width:70px" data-gi="' + gi + '" data-ri="' + ri + '" data-field="rankLabel" value="' + rankLabel + '"></td>' +
'<td><input type="number" class="layui-input" style="width:80px" data-gi="' + gi + '" data-ri="' + ri + '" data-field="gold" value="' + (r.gold || 0) + '"></td>' +
'<td><input type="number" class="layui-input" style="width:80px" data-gi="' + gi + '" data-ri="' + ri + '" data-field="diamond" value="' + (r.diamond || 0) + '"></td>' +
'<td><span class="item-btn" onclick="openItemsDialog(' + gi + ',' + ri + ')">' + (itemsStr || '点击设置') + '</span></td>' +
'<td><span class="del-row" onclick="removeRow(' + gi + ',' + ri + ')"><i class="layui-icon layui-icon-delete"></i></span></td>' +
'</tr>';
}
// 每次操作前, 将 DOM 中的 gold/diamond/rankLabel 同步回 config 缓存
function syncDomToConfig() {
var cfg = activityConfig.rankingRewardConfig || { groups: [] };
$('#groups-container fieldset').each(function (gi) {
if (!cfg.groups[gi]) return;
$(this).find('table tbody tr').each(function (ri) {
if (!cfg.groups[gi].rewards[ri]) return;
var rankLabel = $(this).find('input[data-field="rankLabel"]').val() || ('第' + (ri + 1) + '名');
var parsed = parseRankLabel(rankLabel);
cfg.groups[gi].rewards[ri].rankMin = parsed.rankMin;
cfg.groups[gi].rewards[ri].rankMax = parsed.rankMax;
cfg.groups[gi].rewards[ri].gold = parseInt($(this).find('input[data-field="gold"]').val()) || 0;
cfg.groups[gi].rewards[ri].diamond = parseInt($(this).find('input[data-field="diamond"]').val()) || 0;
});
});
}
window.addRow = function (gi) {
syncDomToConfig();
var cfg = activityConfig.rankingRewardConfig || { groups: [] };
if (!cfg.groups[gi]) cfg.groups[gi] = { rankingType: gi + 1, name: RANKING_NAMES[gi + 1] || '', rewards: [] };
var ri = cfg.groups[gi].rewards.length;
cfg.groups[gi].rewards.push({ rankMin: ri + 1, rankMax: ri + 1, gold: 0, diamond: 0, items: [], enabled: true });
renderGroups();
};
window.removeRow = function (gi, ri) {
layer.confirm('确定删除该排名奖励?', function (idx) {
layer.close(idx);
syncDomToConfig();
var cfg = activityConfig.rankingRewardConfig || { groups: [] };
if (cfg.groups[gi] && cfg.groups[gi].rewards) {
cfg.groups[gi].rewards.splice(ri, 1);
renderGroups();
}
});
};
window.openItemsDialog = function (gi, ri) {
ITEMS_CTX = { gi: gi, ri: ri };
var tr = document.querySelector('#tbody-' + gi + ' tr:nth-child(' + (ri + 1) + ')');
var rewards = getGroupRewards(gi);
var items = (rewards[ri] && rewards[ri].items) ? JSON.parse(JSON.stringify(rewards[ri].items)) : [];
var html = '<div style="padding:15px;max-height:400px;overflow-y:auto;">';
html += '<div id="items-list">';
if (items.length === 0) {
html += '<div class="items-empty" style="color:#999;">暂无道具,点击下方按钮添加</div>';
} else {
for (var i = 0; i < items.length; i++) {
html += renderItemRow(i, items[i]);
}
}
html += '</div>';
html += '<button type="button" class="layui-btn layui-btn-xs layui-btn-normal btn-add-item-row" style="margin-top:10px;"><i class="layui-icon layui-icon-add-1"></i> 添加道具</button>';
html += '</div>';
layer.open({
type: 1,
title: '编辑道具',
area: ['500px', '450px'],
content: html,
btn: ['确定', '取消'],
success: function (layero) {
layero.on('click', '.btn-add-item-row', function () {
addItemRow(layero);
});
layero.on('click', '.btn-remove-item-row', function () {
$(this).closest('.prop-row').remove();
if (layero.find('#items-list .prop-row').length === 0) {
layero.find('#items-list').html('<div class="items-empty" style="color:#999;">暂无道具,点击下方按钮添加</div>');
}
});
form.render('select');
},
yes: function (idx, layero) {
var newItems = collectItems(layero);
saveItemsToReward(gi, ri, newItems);
layer.close(idx);
renderGroups();
}
});
};
function renderItemRow(i, item) {
var selectHtml = '<select class="layui-input item-prop" style="width:140px;height:32px;">';
selectHtml += '<option value="">无</option>';
var groups = {};
propList.forEach(function(p) {
var typeName = getTypeName(p.type);
if (!groups[typeName]) groups[typeName] = [];
groups[typeName].push(p);
});
for (var typeName in groups) {
selectHtml += '<optgroup label="' + typeName + '">';
groups[typeName].forEach(function(p) {
var selected = p.number === item.otherId ? ' selected' : '';
selectHtml += '<option value="' + p.number + '"' + selected + '>' + p.name + '</option>';
});
selectHtml += '</optgroup>';
}
selectHtml += '</select>';
var prop = getPropInfo(item.otherId);
var imgSrc = prop ? (prop.imageUrl || '/uploads/' + prop.number + '.png') : '';
var imgHtml = '<img class="prop-preview" src="' + imgSrc + '" style="width:32px;height:32px;vertical-align:middle;margin:0 4px;' + (prop ? '' : 'display:none;') + '">';
return '<div class="prop-row">' +
selectHtml +
imgHtml +
'<span>x</span>' +
'<input class="layui-input item-count" type="number" value="' + (item.count || 1) + '" style="width:60px;height:32px;">' +
'<span class="del-row btn-remove-item-row" style="margin-left:6px;"><i class="layui-icon layui-icon-delete"></i></span>' +
'</div>';
}
function addItemRow(layero) {
var list = layero.find('#items-list')[0];
layero.find('#items-list .items-empty').remove();
var i = layero.find('#items-list .prop-row').length;
var div = document.createElement('div');
div.innerHTML = renderItemRow(i, { otherId: '', count: 1 });
list.appendChild(div.firstElementChild);
form.render('select');
}
function collectItems(layero) {
var items = [];
layero.find('#items-list .prop-row').each(function () {
var row = this;
var otherId = row.querySelector('.item-prop').value.trim();
var count = parseInt(row.querySelector('.item-count').value) || 1;
if (otherId) items.push({ otherId: otherId, count: count });
});
return items;
}
function saveItemsToReward(gi, ri, items) {
var cfg = activityConfig.rankingRewardConfig || { groups: [] };
if (!cfg.groups[gi]) cfg.groups[gi] = { rankingType: gi + 1, name: RANKING_NAMES[gi + 1] || '', rewards: [] };
if (!cfg.groups[gi].rewards[ri]) cfg.groups[gi].rewards[ri] = { rankMin: ri + 1, rankMax: ri + 1, gold: 0, diamond: 0, items: [] };
cfg.groups[gi].rewards[ri].items = items;
}
// 道具选择变更时更新图片预览
$(document).on('change', '.item-prop', function() {
var val = $(this).val();
var $img = $(this).next('.prop-preview');
if (val) {
var prop = getPropInfo(val);
if (prop) {
$img.attr('src', prop.imageUrl || '/uploads/' + prop.number + '.png').show();
}
} else {
$img.hide();
}
});
function getGroupRewards(gi) {
var cfg = activityConfig.rankingRewardConfig || { groups: [] };
if (!cfg.groups[gi]) cfg.groups[gi] = { rankingType: gi + 1, name: RANKING_NAMES[gi + 1] || '', rewards: [] };
return cfg.groups[gi].rewards;
}
function parseRankLabel(str) {
// "第1名" / "第1-3名" / "第1名" → { rankMin, rankMax }
str = str.replace(/[第名\s]/g, '');
var parts = str.split('-');
return { rankMin: parseInt(parts[0]) || 1, rankMax: parseInt(parts[1]) || parseInt(parts[0]) || 1 };
}
function getDefaultGroups() {
return [
{ rankingType: 1, name: '金币达人榜', rewards: getDefaultRewards() },
{ rankingType: 2, name: '等级达人榜', rewards: getDefaultRewards() },
{ rankingType: 3, name: '当日积分榜', rewards: getDefaultRewards() }
];
}
function getDefaultRewards() {
return [
{ rankMin: 1, rankMax: 1, gold: 65000, diamond: 50, items: [{ otherId: '40004', count: 10 }, { otherId: '40003', count: 10 }], enabled: true },
{ rankMin: 2, rankMax: 2, gold: 45000, diamond: 50, items: [{ otherId: '40004', count: 10 }, { otherId: '40003', count: 10 }], enabled: true },
{ rankMin: 3, rankMax: 3, gold: 30000, diamond: 50, items: [{ otherId: '40004', count: 10 }, { otherId: '40003', count: 10 }], enabled: true },
{ rankMin: 4, rankMax: 4, gold: 20000, diamond: 50, items: [{ otherId: '40004', count: 8 }, { otherId: '40003', count: 8 }], enabled: true },
{ rankMin: 5, rankMax: 5, gold: 15000, diamond: 50, items: [{ otherId: '40004', count: 8 }, { otherId: '40003', count: 8 }], enabled: true },
{ rankMin: 6, rankMax: 6, gold: 12000, diamond: 30, items: [{ otherId: '40004', count: 8 }, { otherId: '40003', count: 8 }], enabled: true },
{ rankMin: 7, rankMax: 7, gold: 10000, diamond: 30, items: [{ otherId: '40004', count: 6 }, { otherId: '40003', count: 6 }], enabled: true },
{ rankMin: 8, rankMax: 8, gold: 8000, diamond: 30, items: [{ otherId: '40004', count: 6 }, { otherId: '40003', count: 6 }], enabled: true },
{ rankMin: 9, rankMax: 9, gold: 6000, diamond: 30, items: [{ otherId: '40004', count: 6 }, { otherId: '40003', count: 6 }], enabled: true },
{ rankMin: 10, rankMax: 10, gold: 5000, diamond: 30, items: [{ otherId: '40004', count: 5 }, { otherId: '40003', count: 5 }], enabled: true },
{ rankMin: 11, rankMax: 11, gold: 3000, diamond: 20, items: [{ otherId: '40004', count: 5 }, { otherId: '40003', count: 5 }], enabled: true },
{ rankMin: 12, rankMax: 12, gold: 2000, diamond: 20, items: [{ otherId: '40004', count: 5 }, { otherId: '40003', count: 5 }], enabled: true },
{ rankMin: 13, rankMax: 13, gold: 1500, diamond: 20, items: [{ otherId: '40004', count: 3 }, { otherId: '40003', count: 3 }], enabled: true },
{ rankMin: 14, rankMax: 14, gold: 1200, diamond: 20, items: [{ otherId: '40004', count: 3 }, { otherId: '40003', count: 3 }], enabled: true },
{ rankMin: 15, rankMax: 15, gold: 1000, diamond: 20, items: [{ otherId: '40004', count: 3 }, { otherId: '40003', count: 3 }], enabled: true }
];
}
$(document).on('click', '[lay-filter="reload-ranking"]', function () { loadConfig(); layer.msg('刷新成功'); });
$(document).on('click', '[lay-filter="init-ranking"]', function () {
layer.confirm('确定要初始化排行榜奖励配置吗?', function (idx) {
layer.close(idx);
if (!activityConfig.rankingRewardConfig) activityConfig.rankingRewardConfig = {};
var cfg = activityConfig.rankingRewardConfig;
cfg.isEnabled = true;
cfg.isMaintaining = false;
cfg.maintainingMessage = '当前排行榜正在维护中,请稍后再试';
cfg.groups = [
{ rankingType: 1, name: '金币达人榜', rewards: getDefaultRewards() },
{ rankingType: 2, name: '等级达人榜', rewards: getDefaultRewards() },
{ rankingType: 3, name: '当日积分榜', rewards: getDefaultRewards() }
];
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/ActivityConfig/Update",
data: JSON.stringify(activityConfig),
done: function (res) {
if (res.code === 0 || res.code === 200) { layer.msg('初始化成功'); loadConfig(); }
else { layer.msg(res.info || '初始化失败'); }
}
});
});
});
$(document).on('click', '[lay-filter="save-ranking"]', function () {
if (!activityConfig.rankingRewardConfig) activityConfig.rankingRewardConfig = {};
var cfg = activityConfig.rankingRewardConfig;
cfg.isEnabled = $('#form-ranking input[name="isEnabled"]').prop('checked');
cfg.isMaintaining = $('#form-ranking input[name="isMaintaining"]').prop('checked');
cfg.maintainingMessage = $('#form-ranking input[name="maintainingMessage"]').val() || '当前排行榜正在维护中,请稍后再试';
var groups = [];
$('#groups-container fieldset').each(function (gi) {
var name = $(this).find('legend').text();
var rewards = [];
$(this).find('table tbody tr').each(function (ri) {
var enabled = $(this).find('input[type="checkbox"]').first().prop('checked');
if (!enabled) return;
var rankLabel = $(this).find('input[data-field="rankLabel"]').val() || ('第' + (ri + 1) + '名');
var parsed = parseRankLabel(rankLabel);
var gold = parseInt($(this).find('input[data-field="gold"]').val()) || 0;
var diamond = parseInt($(this).find('input[data-field="diamond"]').val()) || 0;
// 从 config 缓存取道具数据
var items = [];
if (cfg.groups && cfg.groups[gi] && cfg.groups[gi].rewards && cfg.groups[gi].rewards[ri]) {
items = cfg.groups[gi].rewards[ri].items || [];
}
rewards.push({ rankMin: parsed.rankMin, rankMax: parsed.rankMax, gold: gold, diamond: diamond, items: items, enabled: true });
});
groups.push({ rankingType: gi + 1, name: name, rewards: rewards });
});
cfg.groups = groups;
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/ActivityConfig/Update",
data: JSON.stringify(activityConfig),
done: function (res) {
if (res.code === 0 || res.code === 200) { layer.msg('保存成功'); loadConfig(); }
else { layer.msg(res.info || '保存失败'); }
}
});
});
loadPropList();
loadConfig();
});
</script>
@@ -0,0 +1,431 @@

<title>注册配置</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏管理</cite></a>
<a><cite>注册配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-body">
<div class="layui-card-header">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-filter="init-register"><i class="layui-icon layui-icon-refresh"></i> 初始化配置</button>
<button class="layui-btn layui-btn-sm" lay-filter="save-register"><i class="layui-icon layui-icon-ok"></i> 保存配置</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-filter="reload-register"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" onclick="saveAct('RegisterConfig')"><i class="layui-icon layui-icon-export"></i> 保存到初始化文件</button>
</div>
</div>
<div class="layui-form" style="max-width: 800px; padding: 20px;">
<fieldset class="layui-elem-field">
<legend>基础配置</legend>
<div class="layui-form-item">
<label class="layui-form-label">初始金币</label>
<div class="layui-input-inline" style="width: 200px;">
<input type="number" name="initGold" class="layui-input" placeholder="初始金币">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">初始钻石</label>
<div class="layui-input-inline" style="width: 200px;">
<input type="number" name="initDiamond" class="layui-input" placeholder="初始钻石">
</div>
</div>
</fieldset>
<fieldset class="layui-elem-field">
<legend>初始道具</legend>
<div class="layui-form-item">
<button class="layui-btn layui-btn-sm" id="btn-edit-gifts">编辑初始道具</button>
<span id="gift-count" style="margin-left: 10px; color: #666;"></span>
</div>
<table id="table-init-gift" lay-filter="table-init-gift"></table>
</fieldset>
</div>
</div>
</div>
</div>
<script type="text/html" id="gift-toolbar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-filter="add-register">添加道具</button>
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-filter="batchdel-register">批量删除</button>
</div>
</script>
<script type="text/html" id="tpl-productType">
{{# var typeMap = {1:'炮台',2:'翅膀',3:'炮塔碎片',4:'翅膀碎片',5:'战神',6:'战神碎片',7:'刮刮卡',8:'改名卡',9:'大喇叭',10:'技能',11:'召唤卡',12:'VIP经验',13:'鱼雷'}; }}
{{= typeMap[d.type] || '道具' }}
</script>
<script type="text/html" id="gift-bar">
<a class="layui-btn layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
<style>
.gift-type-selector { margin-bottom: 15px; }
.gift-type-selector .layui-form-label { width: 80px; }
.gift-type-selector .layui-input-block { margin-left: 100px; }
.gift-type-row { display: flex; gap: 10px; align-items: center; }
.gift-type-row select { flex: 1; }
.gift-type-row input { flex: 1; }
</style>
<script>
layui.use(['admin', 'table', 'form', 'layer'], function () {
var $ = layui.$,
admin = layui.admin,
table = layui.table,
form = layui.form,
layer = layui.layer,
setter = layui.setter;
var activityConfig = null;
var propList = [];
setTimeout(function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
$('.layui-layer-shade').remove();
layer.closeAll();
}, 100);
function loadConfig() {
var loaded = { activity: false, props: false };
function tryFill() {
if (loaded.activity && loaded.props) {
fillForms();
}
}
admin.req({
method: "get",
url: setter.baseurl + "adminapi/ActivityConfig/Get",
done: function (res) {
if (res.code === 0 && res.data) {
activityConfig = res.data;
} else {
activityConfig = {};
}
if (!activityConfig.registerConfig) activityConfig.registerConfig = {};
loaded.activity = true;
tryFill();
}
});
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig",
done: function (res) {
if (res.code === 0 && res.data) {
propList = (res.data.propConfig || []).map(function(p) {
return { number: p.number, name: p.name, type: p.type, imageUrl: p.imageUrl };
});
}
loaded.props = true;
tryFill();
}
});
}
function getPropsByType(type) {
return propList.filter(function(p) { return p.type === type; });
}
function buildPropSelectOptions(type, currentNumber) {
var props = getPropsByType(type);
var html = '<option value="">请选择道具</option>';
props.forEach(function(p) {
var selected = p.number === currentNumber ? ' selected' : '';
html += '<option value="' + p.number + '"' + selected + '>' + p.number + ' - ' + p.name + '</option>';
});
return html;
}
function fillForms() {
var config = activityConfig.registerConfig || {};
$('input[name="initGold"]').val(config.initGold ?? 0);
$('input[name="initDiamond"]').val(config.initDiamond ?? 0);
renderGiftTable();
updateGiftCount();
form.render();
}
function updateGiftCount() {
var count = (activityConfig.registerConfig && activityConfig.registerConfig.initProductList)
? activityConfig.registerConfig.initProductList.length : 0;
$('#gift-count').text('共 ' + count + ' 个道具');
}
function renderGiftTable() {
var data = (activityConfig.registerConfig && activityConfig.registerConfig.initProductList)
? activityConfig.registerConfig.initProductList : [];
table.render({
elem: '#table-init-gift',
data: data,
cols: [[
{ type: 'checkbox' },
{ field: "number", title: "道具编号", width: 120 },
{ field: "name", title: "道具名称", width: 100 },
{ field: "count", title: "数量", width: 80 },
{ field: "image", title: "图标", width: 60, templet: function(d) {
var imgUrl = d.imageUrl || '';
if (!imgUrl && d.number) imgUrl = '/uploads/' + d.number + '.png';
return imgUrl ? '<img src="' + imgUrl + '" style="width:28px;height:28px;vertical-align:middle;">' : '';
}},
{ field: "type", title: "类型", width: 100, templet: '#tpl-productType' },
{ title: "操作", width: 150, toolbar: '#gift-bar' }
]],
toolbar: '#gift-toolbar',
page: false
});
}
function initConfig() {
layer.confirm('确定要从配置文件还原注册配置吗?', function(idx) {
layer.close(idx);
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig/InitActivitySection/RegisterConfig",
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('初始化成功');
loadConfig();
} else {
layer.msg(res.info || '初始化失败');
}
},
fail: function(res) {
layer.msg(res.info || '初始化失败');
}
});
});
}
$(document).on('click', '[lay-filter="init-register"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
initConfig();
});
$(document).on('click', '[lay-filter="reload-register"]', function() {
loadConfig();
layer.msg('刷新成功');
});
$(document).on('click', '[lay-filter="save-register"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
if (!activityConfig.registerConfig) {
activityConfig.registerConfig = {};
}
activityConfig.registerConfig.initGold = parseInt($('input[name="initGold"]').val()) || 0;
activityConfig.registerConfig.initDiamond = parseInt($('input[name="initDiamond"]').val()) || 0;
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/ActivityConfig/UpdateRegister",
data: JSON.stringify(activityConfig.registerConfig),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('保存成功');
loadConfig();
} else {
layer.msg(res.info || '保存失败');
}
}
});
});
table.on('tool(table-init-gift)', function(obj) {
var config = activityConfig.registerConfig;
var rowIndex = obj.tr.index();
if (rowIndex < 0 && config && config.initProductList) {
rowIndex = config.initProductList.findIndex(function(x) { return x.number === obj.data.number; });
}
if (obj.event === 'edit') {
showEditDialog(obj.data, false, rowIndex);
} else if (obj.event === 'del') {
layer.confirm('确定删除该道具吗?', function(idx) {
if (config && config.initProductList) {
if (rowIndex > -1) config.initProductList.splice(rowIndex, 1);
}
obj.del();
updateGiftCount();
layer.close(idx);
});
}
});
function showAddDialog() {
if (!activityConfig.registerConfig) activityConfig.registerConfig = {};
if (!activityConfig.registerConfig.initProductList) {
activityConfig.registerConfig.initProductList = [];
}
var newItem = { number: '', count: 1, name: '', type: 1, imageUrl: '' };
showEditDialog(newItem, true);
}
$(document).on('click', '[lay-filter="add-register"]', function() {
showAddDialog();
});
$(document).on('click', '[lay-filter="batchdel-register"]', function() {
var checkStatus = table.checkStatus('table-init-gift');
var checkData = checkStatus.data;
if (checkData.length === 0) {
return layer.msg('请选择要删除的数据');
}
layer.confirm('确定删除选中 ' + checkData.length + ' 条数据吗?', function(idx) {
var config = activityConfig.registerConfig;
if (config && config.initProductList) {
checkData.forEach(function(item) {
var index = config.initProductList.indexOf(item);
if (index > -1) config.initProductList.splice(index, 1);
});
}
renderGiftTable();
updateGiftCount();
layer.close(idx);
});
});
function showEditDialog(data, isAdd, rowIndex) {
var dialogTitle = isAdd ? '添加初始道具' : '编辑初始道具';
var typeOptions = [
{value: 1, text: '炮台'},
{value: 2, text: '翅膀'},
{value: 3, text: '炮塔碎片'},
{value: 4, text: '翅膀碎片'},
{value: 5, text: '战神'},
{value: 6, text: '战神碎片'},
{value: 7, text: '刮刮卡'},
{value: 8, text: '改名卡'},
{value: 9, text: '大喇叭'},
{value: 10, text: '技能'},
{value: 11, text: '召唤卡'},
{value: 12, text: 'VIP经验'},
{value: 13, text: '鱼雷'}
];
var currentType = data.type || 1;
var currentNumber = data.number || '';
var currentImg = data.imageUrl || '';
if (!currentImg && currentNumber) {
var cp = propList.find(function(p) { return p.number === currentNumber; });
if (cp) currentImg = cp.imageUrl || '/uploads/' + cp.number + '.png';
}
var typeHtml = '';
typeOptions.forEach(function(opt) {
typeHtml += '<option value="' + opt.value + '"' + (currentType === opt.value ? ' selected' : '') + '>' + opt.text + '</option>';
});
var propOptionsHtml = buildPropSelectOptions(currentType, currentNumber);
var imgStyle = currentImg ? '' : 'display:none;';
var html = '<div style="padding:20px;">' +
'<form class="layui-form">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">类型</label>' +
'<div class="layui-input-block">' +
'<select name="giftType" id="giftType" lay-filter="giftType">' + typeHtml + '</select>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">道具</label>' +
'<div class="layui-input-block">' +
'<select name="number" id="giftNumber" lay-search style="width:280px;">' + propOptionsHtml + '</select>' +
'<img class="prop-preview" src="' + currentImg + '" style="width:36px;height:36px;vertical-align:middle;margin-left:8px;' + imgStyle + '">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">数量</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="count" id="giftCount" value="' + (data.count || 1) + '" class="layui-input" style="width:200px;">' +
'</div>' +
'</div>' +
'</form>' +
'</div>';
layer.open({
type: 1,
title: dialogTitle,
area: ['600px', '400px'],
content: html,
btn: ['保存', '取消'],
shadeClose: true,
success: function(layero) {
form.render('select');
form.on('select(giftType)', function(obj) {
var type = parseInt(obj.value) || 1;
var propHtml = buildPropSelectOptions(type, '');
layero.find('#giftNumber').html(propHtml);
form.render('select');
layero.find('.prop-preview').hide();
});
form.on('select(giftNumber)', function(obj) {
var val = obj.value;
var $img = layero.find('.prop-preview');
if (val) {
var p = propList.find(function(x) { return x.number === val; });
if (p) {
$img.attr('src', p.imageUrl || '/uploads/' + p.number + '.png').show();
}
} else {
$img.hide();
}
});
},
yes: function(index, layero) {
var number = layero.find('#giftNumber').val();
var type = parseInt(layero.find('#giftType').val()) || 1;
var prop = propList.find(function(p) { return p.number === number; });
var name = prop ? prop.name : '';
var imgUrl = prop ? (prop.imageUrl || '/uploads/' + prop.number + '.png') : '';
var count = parseInt(layero.find('#giftCount').val()) || 1;
if (isAdd) {
var newItem = {
number: number,
name: name,
type: type,
count: count,
imageUrl: imgUrl
};
activityConfig.registerConfig.initProductList.unshift(newItem);
} else {
var original = activityConfig.registerConfig.initProductList[rowIndex];
if (!original) {
original = activityConfig.registerConfig.initProductList.find(function(x) { return x.number === data.number; });
}
if (!original) { layer.msg('数据异常,请刷新后重试'); return; }
original.number = number;
original.name = name;
original.type = type;
original.count = count;
original.imageUrl = imgUrl;
}
layer.close(index);
renderGiftTable();
updateGiftCount();
}
});
}
window.saveAct=function(n){layui.admin.req({method:'get',url:(layui.setter.baseurl||'/')+'adminapi/ActivityConfig/SaveSection/'+n,done:function(r){layui.layer.msg(r.info||(r.code===0?'保存成功':'保存失败'))}})}
loadConfig();
});
</script>
@@ -0,0 +1,161 @@

<title>救济金配置</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏管理</cite></a>
<a><cite>救济金配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-filter="init-relief"><i class="layui-icon layui-icon-refresh"></i> 初始化配置</button>
<button class="layui-btn layui-btn-sm" lay-filter="save-relief"><i class="layui-icon layui-icon-ok"></i> 保存配置</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-filter="reload-relief"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" onclick="saveAct('ReliefConfig')"><i class="layui-icon layui-icon-export"></i> 保存到初始化文件</button>
</div>
</div>
<div class="layui-card-body">
<form class="layui-form" id="form-relief" style="padding: 20px;">
<div class="layui-form-item">
<label class="layui-form-label">是否开启</label>
<div class="layui-input-block">
<input type="checkbox" name="isOpen" lay-skin="switch" lay-text="开启|关闭">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">金币数量</label>
<div class="layui-input-block">
<input type="number" name="gold" class="layui-input" placeholder="金币数量">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">每天次数</label>
<div class="layui-input-block">
<input type="number" name="count" class="layui-input" placeholder="每天可领取次数">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">金币限制</label>
<div class="layui-input-block">
<input type="number" name="limit" class="layui-input" placeholder="领取金币的限制">
</div>
</div>
</form>
</div>
</div>
</div>
<script>
layui.use(['admin', 'form', 'layer'], function () {
var $ = layui.$,
admin = layui.admin,
form = layui.form,
layer = layui.layer,
setter = layui.setter;
var activityConfig = null;
setTimeout(function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
$('.layui-layer-shade').remove();
layer.closeAll();
}, 100);
function loadConfig() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/ActivityConfig/Get",
done: function (res) {
if (res.code === 0 && res.data) {
activityConfig = res.data;
fillForms();
}
}
});
}
function fillForms() {
if (activityConfig.reliefConfig) {
var r = activityConfig.reliefConfig;
$('#form-relief input[name="isOpen"]').prop('checked', r.isOpen);
$('#form-relief input[name="gold"]').val(r.gold || 10000);
$('#form-relief input[name="count"]').val(r.count || 3);
$('#form-relief input[name="limit"]').val(r.limit || 10000);
}
form.render();
}
function initConfig() {
layer.confirm('确定要从配置文件还原救济金配置吗?', function(idx) {
layer.close(idx);
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig/InitActivitySection/ReliefConfig",
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('初始化成功');
loadConfig();
} else {
layer.msg(res.info || '初始化失败');
}
},
fail: function(res) {
layer.msg(res.info || '初始化失败');
}
});
});
}
$(document).on('click', '[lay-filter="init-relief"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
initConfig();
});
$(document).on('click', '[lay-filter="reload-relief"]', function() {
loadConfig();
layer.msg('刷新成功');
});
$(document).on('click', '[lay-filter="save-relief"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
var reliefData = {
isOpen: $('#form-relief input[name="isOpen"]').prop('checked'),
gold: parseInt($('#form-relief input[name="gold"]').val()) || 10000,
count: parseInt($('#form-relief input[name="count"]').val()) || 3,
limit: parseInt($('#form-relief input[name="limit"]').val()) || 10000
};
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/ActivityConfig/UpdateRelief",
data: JSON.stringify(reliefData),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('保存成功');
loadConfig();
} else {
layer.msg(res.info || '保存失败');
}
}
});
});
window.saveAct=function(n){layui.admin.req({method:'get',url:(layui.setter.baseurl||'/')+'adminapi/ActivityConfig/SaveSection/'+n,done:function(r){layui.layer.msg(r.info||(r.code===0?'保存成功':'保存失败'))}})}
loadConfig();
});
</script>
@@ -0,0 +1,317 @@
<title>机器人击杀播报配置</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏管理</cite></a>
<a><cite>机器人击杀播报配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-filter="save-robotkill"><i class="layui-icon layui-icon-ok"></i> 保存配置</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-filter="reload-robotkill"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-filter="add-robotkill"><i class="layui-icon layui-icon-add-1"></i> 添加</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-filter="init-robotkill"><i class="layui-icon layui-icon-refresh"></i> 初始化</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" onclick="saveSection('RobotKillConfig')"><i class="layui-icon layui-icon-export"></i> 保存到初始化文件</button>
</div>
</div>
<div class="layui-card-body">
<div class="layui-form" style="padding: 10px 0;">
<div class="layui-inline">
<label class="layui-form-label" style="width:80px;">总开关</label>
<div class="layui-input-inline" style="width:80px;">
<input type="checkbox" id="isEnabled" lay-skin="switch" lay-text="开启|关闭">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label" style="width:100px;">播报间隔(秒)</label>
<div class="layui-input-inline" style="width:80px;">
<input type="number" id="intervalSeconds" class="layui-input" value="5">
</div>
</div>
</div>
<table id="table-robotkill" lay-filter="table-robotkill"></table>
</div>
</div>
</div>
<script type="text/html" id="table-operate">
<a class="layui-btn layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑</a>
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
<script type="text/html" id="tpl-gameType">
{{# var map = {1:'风雷之渊',4:'星际迷航',5:'黑神话悟空'}; }}
{{= map[d.gameType] || d.gameName || '未知' }}
</script>
<script type="text/html" id="tpl-level">
{{# var map = {1:'初级场',2:'中级场',3:'高级场'}; }}
{{= map[d.roomLevel] || d.levelName || '未知' }}
</script>
<script>
layui.use(['admin', 'table', 'form', 'layer'], function () {
var $ = layui.$,
admin = layui.admin,
table = layui.table,
form = layui.form,
layer = layui.layer,
setter = layui.setter;
var gameConfig = null;
var GAME_TYPES = [
{ value: 1, name: '风雷之渊' },
{ value: 4, name: '星际迷航' },
{ value: 5, name: '黑神话悟空' }
];
var LEVELS = [
{ value: 1, name: '初级场' },
{ value: 2, name: '中级场' },
{ value: 3, name: '高级场' }
];
var fishOptions = ''; // 鱼种下拉选项
setTimeout(function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
$('.layui-layer-shade').remove();
layer.closeAll();
}, 100);
function loadConfig() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig",
done: function (res) {
if (res.code === 0 && res.data) {
gameConfig = res.data;
buildFishOptions();
fillForm();
}
}
});
}
function buildFishOptions() {
fishOptions = '<option value="">-- 选择鱼种 --</option>';
var list = gameConfig.fishTypeConfigs || [];
// 只保留 Boss鱼(2)、特殊鱼(4)、特殊Boss(5)
list = list.filter(function(f) {
var ft = f.fishType || f.fishType || 0;
return ft === 2 || ft === 4 || ft === 5;
});
list.sort(function(a, b) { return (a.fishNumber || '').localeCompare(b.fishNumber || ''); });
list.forEach(function(f) {
fishOptions += '<option value="' + (f.fishNumber || '') + '" data-name="' + (f.fishName || '') + '">' + (f.fishName || '') + ' (' + (f.fishNumber || '') + ')</option>';
});
}
function fillForm() {
if (!gameConfig.robotKillConfig) gameConfig.robotKillConfig = {};
var cfg = gameConfig.robotKillConfig;
$('#isEnabled').prop('checked', cfg.isEnabled === true);
$('#intervalSeconds').val(cfg.intervalSeconds || 5);
form.render();
renderTable();
}
function getScenes() {
if (!gameConfig.robotKillConfig) gameConfig.robotKillConfig = {};
return gameConfig.robotKillConfig.scenes || [];
}
function renderTable() {
var data = getScenes();
table.render({
elem: '#table-robotkill',
data: data,
page: { limit: 50 },
cols: [[
{ type: 'numbers', title: '序号', width: 60 },
{ field: 'gameName', title: '场次', width: 120, templet: '#tpl-gameType' },
{ field: 'levelName', title: '等级', width: 80, templet: '#tpl-level' },
{ field: 'weight', title: '权重', width: 70 },
{ field: 'fishName', title: '鱼种', width: 120, templet: function(d) { return d.fishName || d.fishNumber || '-'; } },
{ field: 'fishNumber', title: '鱼编号', width: 90 },
{ field: 'multiplierMin', title: '倍数Min', width: 90 },
{ field: 'multiplierMax', title: '倍数Max', width: 90 },
{ field: 'jackpotMin', title: '爆奖Min', width: 120 },
{ field: 'jackpotMax', title: '爆奖Max', width: 120 },
{ title: '操作', width: 120, fixed: 'right', toolbar: '#table-operate' }
]],
done: function() { form.render(); }
});
}
$(document).on('click', '[lay-filter="reload-robotkill"]', function () {
loadConfig();
layer.msg('刷新成功');
});
$(document).on('click', '[lay-filter="init-robotkill"]', function () {
layer.confirm('确定要从 gameconfig 初始化机器人击杀播报配置吗?', function (idx) {
layer.close(idx);
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig/InitSection/RobotKillConfig",
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('初始化成功');
loadConfig();
} else {
layer.msg(res.info || '初始化失败');
}
},
fail: function () { layer.msg('初始化失败'); }
});
});
});
$(document).on('click', '[lay-filter="add-robotkill"]', function () {
var scenes = getScenes();
scenes.push({
gameName: '', levelName: '', gameType: 1, roomLevel: 1, weight: 1,
fishName: '', fishNumber: '',
multiplierMin: 500, multiplierMax: 2000,
jackpotMin: 5000000, jackpotMax: 200000000
});
renderTable();
});
var currentEditIndex = -1;
function openEditDialog(data, idx) {
currentEditIndex = idx;
var gtOpts = '';
GAME_TYPES.forEach(function(g) {
gtOpts += '<option value="' + g.value + '"' + (data.gameType === g.value ? ' selected' : '') + '>' + g.name + '</option>';
});
var lvOpts = '';
LEVELS.forEach(function(l) {
lvOpts += '<option value="' + l.value + '"' + (data.roomLevel === l.value ? ' selected' : '') + '>' + l.name + '</option>';
});
var html = '<div style="padding:20px;"><form class="layui-form">' +
'<div class="layui-form-item"><label class="layui-form-label">场次</label><div class="layui-input-block"><select id="edit-gameType">' + gtOpts + '</select></div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">等级</label><div class="layui-input-block"><select id="edit-roomLevel">' + lvOpts + '</select></div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">权重</label><div class="layui-input-block"><input type="number" id="edit-weight" value="' + (data.weight||1) + '" class="layui-input"></div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">鱼种</label><div class="layui-input-block"><select id="edit-fishNumber" lay-search>' + fishOptions + '</select></div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">倍数Min</label><div class="layui-input-block"><input type="number" id="edit-multiplierMin" value="' + (data.multiplierMin||500) + '" class="layui-input"></div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">倍数Max</label><div class="layui-input-block"><input type="number" id="edit-multiplierMax" value="' + (data.multiplierMax||2000) + '" class="layui-input"></div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">爆奖Min</label><div class="layui-input-block"><input type="number" id="edit-jackpotMin" value="' + (data.jackpotMin||5000000) + '" class="layui-input"></div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">爆奖Max</label><div class="layui-input-block"><input type="number" id="edit-jackpotMax" value="' + (data.jackpotMax||200000000) + '" class="layui-input"></div></div>' +
'</form></div>';
layer.open({
type: 1, title: '编辑播报配置', area: ['500px', '500px'],
content: html,
btn: ['保存', '取消'],
success: function(layero) {
// 设置鱼种下拉的选中值
if (data.fishNumber) {
layero.find('#edit-fishNumber').val(data.fishNumber);
}
form.render();
},
yes: function (ei) {
var fishNum = $('#edit-fishNumber').val();
var fishName = '';
if (fishNum) {
var opt = $('#edit-fishNumber option[value="' + fishNum + '"]');
fishName = opt.attr('data-name') || opt.text().split(' (')[0] || '';
}
var update = {
gameType: parseInt($('#edit-gameType').val()) || 1,
roomLevel: parseInt($('#edit-roomLevel').val()) || 1,
weight: parseInt($('#edit-weight').val()) || 1,
fishNumber: fishNum || '',
fishName: fishName,
multiplierMin: parseInt($('#edit-multiplierMin').val()) || 500,
multiplierMax: parseInt($('#edit-multiplierMax').val()) || 2000,
jackpotMin: parseInt($('#edit-jackpotMin').val()) || 5000000,
jackpotMax: parseInt($('#edit-jackpotMax').val()) || 200000000
};
update.gameName = GAME_TYPES.find(function(g) { return g.value === update.gameType; })?.name || '';
update.levelName = LEVELS.find(function(l) { return l.value === update.roomLevel; })?.name || '';
// 写回原始数据
var scenes = getScenes();
if (currentEditIndex >= 0 && currentEditIndex < scenes.length) {
scenes[currentEditIndex] = update;
}
layer.close(ei);
renderTable();
}
});
}
table.on('tool(table-robotkill)', function (obj) {
var data = obj.data;
var scenes = getScenes();
// 通过对比所有字段在原始数组中找真实索引
var realIndex = -1;
for (var i = 0; i < scenes.length; i++) {
if (scenes[i].fishNumber === data.fishNumber &&
scenes[i].gameType === data.gameType &&
scenes[i].roomLevel === data.roomLevel) {
realIndex = i;
break;
}
}
// 如果找不到(新增行无 fishNumber),用行 data-index + 页码计算
if (realIndex < 0) {
var pageIndex = $('.layui-laypage-em').next().length > 0
? parseInt($('.layui-laypage-em').next().text()) || 1
: 1;
var trIdx = obj.tr ? $(obj.tr).data('index') : -1;
if (trIdx < 0) trIdx = $(obj.tr).index();
realIndex = (pageIndex - 1) * 50 + trIdx;
}
if (realIndex < 0 || realIndex >= scenes.length) realIndex = 0;
if (obj.event === 'del') {
layer.confirm('确定要删除该配置吗?', function (idx) {
if (realIndex >= 0 && realIndex < scenes.length) {
scenes.splice(realIndex, 1);
}
layer.close(idx);
renderTable();
});
}
if (obj.event === 'edit') {
openEditDialog(data, realIndex);
}
});
$(document).on('click', '[lay-filter="save-robotkill"]', function () {
if (!gameConfig.robotKillConfig) gameConfig.robotKillConfig = {};
var cfg = gameConfig.robotKillConfig;
cfg.isEnabled = $('#isEnabled').prop('checked');
cfg.intervalSeconds = parseInt($('#intervalSeconds').val()) || 5;
// 数据已直接存储在 gameConfig.robotKillConfig.scenes 中
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/GameConfig/Update",
data: JSON.stringify(gameConfig),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('保存成功');
loadConfig();
} else {
layer.msg(res.info || '保存失败');
}
}
});
});
loadConfig();
});
</script>
<script>window.saveSection=function(n){layui.admin.req({method:'get',url:(layui.setter.baseurl||'/')+'adminapi/GameConfig/SaveSection/'+n,done:function(r){layui.layer.msg(r.info||(r.code===0?'保存成功':'保存失败'))}})}</script>
@@ -0,0 +1,330 @@

<title>房间配置</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏管理</cite></a>
<a><cite>房间配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-body" style="padding-top:10px;">
<table id="table-room" lay-filter="table-room"></table>
</div>
</div>
</div>
<script type="text/html" id="toolbar-room">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-filter="init-room"><i class="layui-icon layui-icon-refresh"></i> 初始化配置</button>
<button class="layui-btn layui-btn-sm" lay-filter="save-room"><i class="layui-icon layui-icon-ok"></i> 保存配置</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-filter="reload-room"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
<button class="layui-btn layui-btn-sm" lay-filter="add-room">添加</button>
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-filter="batchdel-room">批量删除</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" onclick="saveSection('RoomConfigs')"><i class="layui-icon layui-icon-export"></i> 保存到初始化文件</button>
</div>
</script>
<script type="text/html" id="tpl-gameType">
{{# var typeMap = {0:'无',1:'风雷之渊',2:'永夜尸城',3:'中国象棋',4:'星际迷航',5:'黑神话悟空'}; }}
{{= typeMap[d.gameType] || '无' }}
</script>
<script type="text/html" id="table-operate">
<a class="layui-btn layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
<script>
layui.use(['admin', 'table', 'form'], function () {
var $ = layui.$,
admin = layui.admin,
table = layui.table,
form = layui.form,
setter = layui.setter;
var gameConfig = null;
// 页面加载时清理可能残留的遮罩
setTimeout(function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
$('.layui-layer-shade').remove();
layer.closeAll();
}, 100);
function loadConfig() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig",
done: function (res) {
if (res.code === 0 && res.data) {
gameConfig = res.data;
renderTable();
}
}
});
}
function formatList(list) {
if (!list) return '';
if (Array.isArray(list)) return list.join(',');
return String(list);
}
function parseList(str) {
if (!str) return [];
if (Array.isArray(str)) return str;
if (typeof str === 'string') {
return str.split(',').map(function(x) { return parseInt(x.trim()); }).filter(function(x) { return !isNaN(x); });
}
return [];
}
function renderTable() {
table.render({
elem: '#table-room',
data: gameConfig ? gameConfig.roomConfigs || [] : [],
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ field: "roomNumber", title: "编号", width: 80 },
{ field: "roomName", title: "名称", width: 120 },
{ field: "gameType", title: "游戏类型", width: 100, templet: '#tpl-gameType' },
{ field: "roomLevel", title: "等级", width: 70 },
{ field: "roomPartitionNum", title: "分区数", width: 80 },
{ field: "roomCount", title: "房间数", width: 80 },
{ field: "roomGoldLimit", title: "金币限制", width: 100 },
{ field: "cannonMultipleList", title: "炮倍列表", width: 150, templet: function(d) {
return formatList(d.cannonMultipleList);
}},
{ title: "操作", width: 150, fixed: 'right', toolbar: '#table-operate' }
]],
toolbar: '#toolbar-room',
defaultToolbar: ['filter', 'exports', 'print']
});
}
function initConfig() {
layer.confirm('确定要初始化全部配置数据吗?此操作将清空现有数据!', function(idx) {
layer.close(idx);
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig/InitSection/RoomConfigs",
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('初始化成功');
loadConfig();
} else {
layer.msg(res.info || '初始化失败');
}
},
fail: function(res) {
layer.msg(res.info || '初始化失败');
}
});
});
}
$(document).on('click', '[lay-filter="init-room"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
initConfig();
});
$(document).on('click', '[lay-filter="reload-room"]', function() {
loadConfig();
layer.msg('刷新成功');
});
$(document).on('click', '[lay-filter="save-room"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/GameConfig/Update",
data: JSON.stringify(gameConfig),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('保存成功');
loadConfig();
} else {
layer.msg(res.info || '保存失败');
}
}
});
});
table.on('tool(table-room)', function(obj) {
var data = obj.data;
var trIndex = obj.tr.index();
if (trIndex < 0) trIndex = gameConfig.roomConfigs.findIndex(function(x) { return x.roomNumber === data.roomNumber; });
if (obj.event === 'edit') {
showEditDialog(data, false, trIndex);
} else if (obj.event === 'del') {
layer.confirm('确定删除该配置吗?', function(idx) {
if (trIndex > -1) gameConfig.roomConfigs.splice(trIndex, 1);
obj.del();
layer.close(idx);
});
}
});
function showAddDialog() {
var newData = {
roomNumber: '',
roomName: '',
gameType: 0,
roomLevel: 0,
roomPartitionNum: 0,
roomCount: 0,
roomGoldLimit: 0,
cannonMultipleList: []
};
showEditDialog(newData, true, -1);
}
$(document).on('click', '[lay-filter="add-room"]', function() {
showAddDialog();
});
$(document).on('click', '[lay-filter="batchdel-room"]', function() {
var checkStatus = table.checkStatus('table-room');
var checkData = checkStatus.data;
if (checkData.length === 0) {
return layer.msg('请选择要删除的数据');
}
layer.confirm('确定删除选中的 ' + checkData.length + ' 条数据吗?', function(idx) {
var ids = checkData.map(function(item) { return item.roomNumber; });
gameConfig.roomConfigs = gameConfig.roomConfigs.filter(function(item) {
return !ids.includes(item.roomNumber);
});
renderTable();
layer.close(idx);
});
});
function showEditDialog(data, isAdd, itemIndex) {
var dialogTitle = isAdd ? '添加房间' : '编辑房间';
var typeOptions = [
{value: 0, text: '无'},
{value: 1, text: '风雷之渊'},
{value: 2, text: '永夜尸城'},
{value: 3, text: '中国象棋'},
{value: 4, text: '星际迷航'},
{value: 5, text: '黑神话悟空'}
];
var typeHtml = '';
typeOptions.forEach(function(opt) {
typeHtml += '<option value="' + opt.value + '"' + (data.gameType === opt.value ? ' selected' : '') + '>' + opt.text + '</option>';
});
var cannonStr = formatList(data.cannonMultipleList);
var html = '<div style="padding:20px;">' +
'<form class="layui-form">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">编号</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="roomNumber" value="' + (data.roomNumber || '') + '" 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="roomName" value="' + (data.roomName || '') + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">游戏类型</label>' +
'<div class="layui-input-block">' +
'<select name="gameType">' + typeHtml + '</select>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">等级</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="roomLevel" value="' + (data.roomLevel || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">分区数</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="roomPartitionNum" value="' + (data.roomPartitionNum || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">房间数</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="roomCount" value="' + (data.roomCount || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">金币限制</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="roomGoldLimit" value="' + (data.roomGoldLimit || 0) + '" 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="cannonMultipleList" value="' + cannonStr + '" placeholder="多个值用逗号分隔" class="layui-input">' +
'</div>' +
'</div>' +
'</form>' +
'</div>';
layer.open({
type: 1,
title: dialogTitle,
area: ['600px', '550px'],
content: html,
btn: ['保存', '取消'],
shadeClose: true,
success: function(layero) {
form.render('select');
},
yes: function(index, layero) {
var cannonStr = layero.find('input[name="cannonMultipleList"]').val();
var cannonList = parseList(cannonStr);
var updatedItem = {
roomNumber: layero.find('input[name="roomNumber"]').val(),
roomName: layero.find('input[name="roomName"]').val(),
gameType: parseInt(layero.find('select[name="gameType"]').val()),
roomLevel: parseInt(layero.find('input[name="roomLevel"]').val()) || 0,
roomPartitionNum: parseInt(layero.find('input[name="roomPartitionNum"]').val()) || 0,
roomCount: parseInt(layero.find('input[name="roomCount"]').val()) || 0,
roomGoldLimit: parseInt(layero.find('input[name="roomGoldLimit"]').val()) || 0,
cannonMultipleList: cannonList
};
if (isAdd) {
gameConfig.roomConfigs.unshift(updatedItem);
} else {
if (itemIndex >= 0 && itemIndex < gameConfig.roomConfigs.length) {
gameConfig.roomConfigs[itemIndex] = updatedItem;
}
}
layer.close(index);
table.reload('table-room', {
data: gameConfig.roomConfigs
});
}
});
}
window.saveSection=function(n){layui.admin.req({method:'get',url:(layui.setter.baseurl||'/')+'adminapi/GameConfig/SaveSection/'+n,done:function(r){layui.layer.msg(r.info||(r.code===0?'保存成功':'保存失败'))}})}
loadConfig();
});
</script>
@@ -0,0 +1,680 @@

<title>出鱼配置</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏管理</cite></a>
<a><cite>出鱼配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">
<div class="layui-tab layui-tab-brief" lay-filter="roomfish-tab">
<ul class="layui-tab-title">
<li class="layui-this" lay-id="normal">出鱼配置</li>
</ul>
<div class="layui-tab-content" style="padding: 10px 0 0 0;">
<div class="layui-tab-item layui-show">
<table id="table-roomfish" lay-filter="table-roomfish"></table>
</div>
<div class="layui-tab-item">
<div class="layui-row">
<div class="layui-col-md3">
<div class="layui-card">
<div class="layui-card-header">游戏类型选择</div>
<div class="layui-card-body">
<div class="layui-btn-container" id="game-type-btns">
<button class="layui-btn layui-btn-sm layui-btn-primary" data-type="1">风雷之渊</button>
<button class="layui-btn layui-btn-sm" data-type="2">永夜尸城</button>
<button class="layui-btn layui-btn-sm" data-type="5">黑神话悟空</button>
</div>
</div>
</div>
</div>
<div class="layui-col-md9">
<table id="table-probability" lay-filter="table-probability"></table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script type="text/html" id="toolbar-roomfish">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-filter="init-roomfish"><i class="layui-icon layui-icon-refresh"></i> 初始化配置</button>
<button class="layui-btn layui-btn-sm" lay-filter="save-roomfish"><i class="layui-icon layui-icon-ok"></i> 保存配置</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-filter="reload-roomfish"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" onclick="saveSection('RoomFishConfigs')"><i class="layui-icon layui-icon-export"></i> 保存到初始化文件</button>
</div>
</script>
<script type="text/html" id="table-operate">
<a class="layui-btn layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑</a>
</script>
<script type="text/html" id="tpl-gameType">
{{# var typeMap = {1:'风雷之渊',2:'永夜尸城',3:'中国象棋',4:'星际迷航',5:'黑神话悟空'}; }}
{{= typeMap[d.gameType] || '未知(' + d.gameType + ')' }}
</script>
<script>
layui.use(['admin', 'table', 'form'], function () {
var $ = layui.$,
admin = layui.admin,
table = layui.table,
setter = layui.setter;
var gameConfig = null;
setTimeout(function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
$('.layui-layer-shade').remove();
layer.closeAll();
}, 100);
function loadConfig() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig",
done: function (res) {
if (res.code === 0 && res.data) {
gameConfig = res.data;
buildFishNameMap();
renderTable();
}
}
});
}
var fishNameMap = {};
var fishArrayNameMap = {};
function buildFishNameMap() {
if (!gameConfig) return;
fishNameMap = {};
fishArrayNameMap = {};
var fishConfigs = gameConfig.fishTypeConfigs || [];
fishConfigs.forEach(function(f) {
fishNameMap[f.fishNumber] = f.fishName || f.fishNumber;
});
var fishArrayConfigs = gameConfig.fishArrayConfigs || [];
fishArrayConfigs.forEach(function(f) {
fishArrayNameMap[f.fishArrayNumber] = f.name || f.fishArrayNumber;
});
}
function buildRoomLevelNameMap(gameType) {
var map = {};
if (!gameConfig) return map;
var roomConfigs = gameConfig.roomConfigs || [];
roomConfigs.forEach(function(r) {
if (r.gameType === gameType) {
map[r.roomLevel] = r.roomLevel + ' ' + r.roomName;
}
});
return map;
}
function getRoomLevelDisplay(roomLevel, gameType) {
var map = buildRoomLevelNameMap(gameType);
return map[roomLevel] || roomLevel.toString();
}
function getFishDisplayName(fishNumber, type) {
if (type === 2) {
var name = fishArrayNameMap[fishNumber] || fishNumber;
return name + ' (' + fishNumber + ')';
} else {
var name = fishNameMap[fishNumber] || fishNumber;
return name + ' (' + fishNumber + ')';
}
}
function saveConfig() {
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/GameConfig/Update",
data: JSON.stringify(gameConfig),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('保存成功');
loadConfig();
} else {
layer.msg(res.info || '保存失败');
}
},
fail: function(res) {
layer.msg(res.info || '保存失败');
}
});
}
function initConfig() {
layer.confirm('确定要初始化全部配置数据吗?此操作将清空现有数据!', function(idx) {
layer.close(idx);
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig/InitSection/RoomFishConfigs",
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('初始化成功');
loadConfig();
} else {
layer.msg(res.info || '初始化失败');
}
},
fail: function(res) {
layer.msg(res.info || '初始化失败');
}
});
});
}
function renderTable() {
var data = (gameConfig ? gameConfig.roomFishConfigs : []) || [];
table.render({
elem: '#table-roomfish',
data: data,
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ field: "gameType", title: "游戏类型", width: 150, templet: '#tpl-gameType' },
{ field: "normalFishList", title: "普通鱼配置", width: 120, templet: function(d) { return (d.normalFishList || []).length + ' 条'; } },
{ field: "roomFishLevelList", title: "等级配置", width: 120, templet: function(d) { return (d.roomFishLevelList || []).length + ' 条'; } },
{ title: "操作", width: 100, fixed: 'right', toolbar: '#table-operate' }
]],
toolbar: '#toolbar-roomfish',
defaultToolbar: ['filter', 'exports', 'print']
});
}
$(document).on('click', '[lay-filter="init-roomfish"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
initConfig();
});
$(document).on('click', '[lay-filter="reload-roomfish"]', function() {
loadConfig();
layer.msg('刷新成功');
});
$(document).on('click', '[lay-filter="save-roomfish"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
saveConfig();
});
table.on('tool(table-roomfish)', function(obj) {
var data = obj.data;
var trIndex = obj.tr.index();
if (trIndex < 0) trIndex = gameConfig.roomFishConfigs.findIndex(function(x) { return x.gameType === data.gameType; });
if (obj.event === 'edit') {
showEditDialog(data, trIndex);
}
});
function showEditDialog(data, itemIndex) {
var typeMap = {1:'风雷之渊',2:'永夜尸城',3:'中国象棋',4:'星际迷航',5:'黑神话悟空'};
var gameTypeName = typeMap[data.gameType] || '未知';
var html = '<div style="padding:15px;">' +
'<div style="margin-bottom:10px;">' +
'<span style="font-weight:bold;font-size:14px;">游戏类型:</span>' +
'<span style="color:#1e88e5;">' + gameTypeName + '</span>' +
'</div>' +
'<div class="layui-tab layui-tab-brief" lay-filter="fish-config-tab">' +
'<ul class="layui-tab-title">' +
'<li class="layui-this" lay-id="normal">普通鱼配置 (' + ((data.normalFishList || []).length) + '条)</li>' +
'<li lay-id="level1">初级房间配置 (' + ((data.roomFishLevelList || []).filter(function(x){return x.roomLevel===1;}).length) + '条)</li>' +
'<li lay-id="level2">中级房间配置 (' + ((data.roomFishLevelList || []).filter(function(x){return x.roomLevel===2;}).length) + '条)</li>' +
'<li lay-id="level3">高级房间配置 (' + ((data.roomFishLevelList || []).filter(function(x){return x.roomLevel===3;}).length) + '条)</li>' +
'</ul>' +
'<div class="layui-tab-content" style="padding:10px 0 0 0;">' +
'<div class="layui-tab-item layui-show">' +
'<div style="margin-bottom:10px;">' +
'<button type="button" class="layui-btn layui-btn-sm" id="add-normal-fish-btn"><i class="layui-icon layui-icon-add-1"></i> 添加</button>' +
'<button type="button" class="layui-btn layui-btn-sm layui-btn-danger" id="del-normal-fish-btn"><i class="layui-icon layui-icon-delete"></i> 批量删除</button>' +
'</div>' +
'<table id="table-normal-detail" lay-filter="table-normal-detail"></table>' +
'</div>' +
'<div class="layui-tab-item">' +
'<div style="margin-bottom:10px;">' +
'<button type="button" class="layui-btn layui-btn-sm" id="add-level1-fish-btn"><i class="layui-icon layui-icon-add-1"></i> 添加</button>' +
'<button type="button" class="layui-btn layui-btn-sm layui-btn-danger" id="del-level1-fish-btn"><i class="layui-icon layui-icon-delete"></i> 批量删除</button>' +
'</div>' +
'<table id="table-level1-detail" lay-filter="table-level1-detail"></table>' +
'</div>' +
'<div class="layui-tab-item">' +
'<div style="margin-bottom:10px;">' +
'<button type="button" class="layui-btn layui-btn-sm" id="add-level2-fish-btn"><i class="layui-icon layui-icon-add-1"></i> 添加</button>' +
'<button type="button" class="layui-btn layui-btn-sm layui-btn-danger" id="del-level2-fish-btn"><i class="layui-icon layui-icon-delete"></i> 批量删除</button>' +
'</div>' +
'<table id="table-level2-detail" lay-filter="table-level2-detail"></table>' +
'</div>' +
'<div class="layui-tab-item">' +
'<div style="margin-bottom:10px;">' +
'<button type="button" class="layui-btn layui-btn-sm" id="add-level3-fish-btn"><i class="layui-icon layui-icon-add-1"></i> 添加</button>' +
'<button type="button" class="layui-btn layui-btn-sm layui-btn-danger" id="del-level3-fish-btn"><i class="layui-icon layui-icon-delete"></i> 批量删除</button>' +
'</div>' +
'<table id="table-level3-detail" lay-filter="table-level3-detail"></table>' +
'</div>' +
'</div>' +
'</div>' +
'</div>';
layer.open({
type: 1,
title: '编辑出鱼配置 - ' + gameTypeName,
area: ['1000px', '600px'],
content: html,
btn: ['保存', '取消'],
shadeClose: true,
success: function(layero) {
layui.element.init();
var normalTable = layui.table.render({
elem: '#table-normal-detail',
data: data.normalFishList || [],
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ field: "type", title: "类型", width: 80, templet: function(d) { return d.type === 1 ? '鱼' : '鱼阵'; } },
{ field: "fishNumber", title: "鱼/鱼阵", minWidth: 180, templet: function(d) { return getFishDisplayName(d.fishNumber, d.type); } },
{ field: "fishWeight", title: "权重", width: 80 },
{ field: "fishFrequency", title: "频率(秒)", width: 100 },
{ field: "maxCount", title: "最大数量", width: 100 },
{ field: "times", title: "出场秒数", width: 100 },
{ title: "操作", width: 80, fixed: 'right', toolbar: '#table-normal-operate' }
]]
});
var level1Data = (data.roomFishLevelList || []).filter(function(x){return x.roomLevel===1;});
var level2Data = (data.roomFishLevelList || []).filter(function(x){return x.roomLevel===2;});
var level3Data = (data.roomFishLevelList || []).filter(function(x){return x.roomLevel===3;});
layui.table.render({
elem: '#table-level1-detail',
data: level1Data,
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ field: "roomLevel", title: "房间等级", width: 120, templet: function(d) { return getRoomLevelDisplay(d.roomLevel, data.gameType); } },
{ field: "type", title: "类型", width: 80, templet: function(d) { return d.type === 1 ? '鱼' : '鱼阵'; } },
{ field: "fishNumber", title: "鱼/鱼阵", minWidth: 180, templet: function(d) { return getFishDisplayName(d.fishNumber, d.type); } },
{ field: "fishWeight", title: "权重", width: 80 },
{ field: "fishFrequency", title: "频率(秒)", width: 100 },
{ field: "maxCount", title: "最大数量", width: 100 },
{ field: "times", title: "出场秒数", width: 100 },
{ title: "操作", width: 80, fixed: 'right', toolbar: '#table-level-operate' }
]]
});
layui.table.render({
elem: '#table-level2-detail',
data: level2Data,
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ field: "roomLevel", title: "房间等级", width: 120, templet: function(d) { return getRoomLevelDisplay(d.roomLevel, data.gameType); } },
{ field: "type", title: "类型", width: 80, templet: function(d) { return d.type === 1 ? '鱼' : '鱼阵'; } },
{ field: "fishNumber", title: "鱼/鱼阵", minWidth: 180, templet: function(d) { return getFishDisplayName(d.fishNumber, d.type); } },
{ field: "fishWeight", title: "权重", width: 80 },
{ field: "fishFrequency", title: "频率(秒)", width: 100 },
{ field: "maxCount", title: "最大数量", width: 100 },
{ field: "times", title: "出场秒数", width: 100 },
{ title: "操作", width: 80, fixed: 'right', toolbar: '#table-level-operate' }
]]
});
layui.table.render({
elem: '#table-level3-detail',
data: level3Data,
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ field: "roomLevel", title: "房间等级", width: 120, templet: function(d) { return getRoomLevelDisplay(d.roomLevel, data.gameType); } },
{ field: "type", title: "类型", width: 80, templet: function(d) { return d.type === 1 ? '鱼' : '鱼阵'; } },
{ field: "fishNumber", title: "鱼/鱼阵", minWidth: 180, templet: function(d) { return getFishDisplayName(d.fishNumber, d.type); } },
{ field: "fishWeight", title: "权重", width: 80 },
{ field: "fishFrequency", title: "频率(秒)", width: 100 },
{ field: "maxCount", title: "最大数量", width: 100 },
{ field: "times", title: "出场秒数", width: 100 },
{ title: "操作", width: 80, fixed: 'right', toolbar: '#table-level-operate' }
]]
});
layui.table.on('tool(table-normal-detail)', function(obj) {
var d = obj.data;
var idx = obj.tr.index();
if (obj.event === 'edit') {
showFishEditDialog(d, 'normal', data, idx, function(updated) {
obj.update(updated);
});
}
});
layui.table.on('tool(table-level1-detail)', function(obj) {
var d = obj.data;
var idx = obj.tr.index();
if (obj.event === 'edit') {
showFishEditDialog(d, 'level', data, idx, 1, function(updated) {
obj.update(updated);
});
}
});
layui.table.on('tool(table-level2-detail)', function(obj) {
var d = obj.data;
var idx = obj.tr.index();
if (obj.event === 'edit') {
showFishEditDialog(d, 'level', data, idx, 2, function(updated) {
obj.update(updated);
});
}
});
layui.table.on('tool(table-level3-detail)', function(obj) {
var d = obj.data;
var idx = obj.tr.index();
if (obj.event === 'edit') {
showFishEditDialog(d, 'level', data, idx, 3, function(updated) {
obj.update(updated);
});
}
});
$('#add-normal-fish-btn').on('click', function() {
var newData = {type: 1, fishNumber: '', fishWeight: 0, fishFrequency: 5, maxCount: 1, times: 10};
showFishEditDialog(newData, 'normal', data, -1, function(updated) {
data.normalFishList = data.normalFishList || [];
data.normalFishList.unshift(updated);
layui.table.reload('table-normal-detail', { data: data.normalFishList });
$('.layui-tab-title li[lay-id="normal"]').text('普通鱼配置 (' + data.normalFishList.length + '条)');
});
});
$('#add-level1-fish-btn').on('click', function() {
var newData = {roomLevel: 1, type: 1, fishNumber: '', fishWeight: 0, fishFrequency: 5, maxCount: 1, times: 10};
showFishEditDialog(newData, 'level', data, -1, 1, function(updated) {
data.roomFishLevelList = data.roomFishLevelList || [];
data.roomFishLevelList.unshift(updated);
layui.table.reload('table-level1-detail', { data: (data.roomFishLevelList||[]).filter(function(x){return x.roomLevel===1;}) });
$('.layui-tab-title li[lay-id="level1"]').text('初级房间配置 (' + (data.roomFishLevelList||[]).filter(function(x){return x.roomLevel===1;}).length + '条)');
});
});
$('#add-level2-fish-btn').on('click', function() {
var newData = {roomLevel: 2, type: 1, fishNumber: '', fishWeight: 0, fishFrequency: 5, maxCount: 1, times: 10};
showFishEditDialog(newData, 'level', data, -1, 2, function(updated) {
data.roomFishLevelList = data.roomFishLevelList || [];
data.roomFishLevelList.unshift(updated);
layui.table.reload('table-level2-detail', { data: (data.roomFishLevelList||[]).filter(function(x){return x.roomLevel===2;}) });
$('.layui-tab-title li[lay-id="level2"]').text('中级房间配置 (' + (data.roomFishLevelList||[]).filter(function(x){return x.roomLevel===2;}).length + '条)');
});
});
$('#add-level3-fish-btn').on('click', function() {
var newData = {roomLevel: 3, type: 1, fishNumber: '', fishWeight: 0, fishFrequency: 5, maxCount: 1, times: 10};
showFishEditDialog(newData, 'level', data, -1, 3, function(updated) {
data.roomFishLevelList = data.roomFishLevelList || [];
data.roomFishLevelList.unshift(updated);
layui.table.reload('table-level3-detail', { data: (data.roomFishLevelList||[]).filter(function(x){return x.roomLevel===3;}) });
$('.layui-tab-title li[lay-id="level3"]').text('高级房间配置 (' + (data.roomFishLevelList||[]).filter(function(x){return x.roomLevel===3;}).length + '条)');
});
});
$('#del-normal-fish-btn').on('click', function() {
var checkStatus = layui.table.checkStatus('table-normal-detail');
var checkData = checkStatus.data;
if (checkData.length === 0) {
layer.msg('请选择要删除的数据');
return;
}
layer.confirm('确定删除选中的 ' + checkData.length + ' 条数据吗?', function(idx) {
var ids = checkData.map(function(item) { return item.fishNumber + '_' + item.type; });
data.normalFishList = (data.normalFishList || []).filter(function(item) {
return !ids.includes(item.fishNumber + '_' + item.type);
});
layui.table.reload('table-normal-detail', { data: data.normalFishList });
$('.layui-tab-title li[lay-id="normal"]').text('普通鱼配置 (' + data.normalFishList.length + '条)');
layer.close(idx);
});
});
$('#del-level1-fish-btn').on('click', function() {
var checkStatus = layui.table.checkStatus('table-level1-detail');
var checkData = checkStatus.data;
if (checkData.length === 0) {
layer.msg('请选择要删除的数据');
return;
}
layer.confirm('确定删除选中的 ' + checkData.length + ' 条数据吗?', function(idx) {
var ids = checkData.map(function(item) { return item.roomLevel + '_' + item.fishNumber + '_' + item.type; });
data.roomFishLevelList = (data.roomFishLevelList || []).filter(function(item) {
return !ids.includes(item.roomLevel + '_' + item.fishNumber + '_' + item.type);
});
layui.table.reload('table-level1-detail', { data: (data.roomFishLevelList||[]).filter(function(x){return x.roomLevel===1;}) });
$('.layui-tab-title li[lay-id="level1"]').text('初级房间配置 (' + (data.roomFishLevelList||[]).filter(function(x){return x.roomLevel===1;}).length + '条)');
layer.close(idx);
});
});
$('#del-level2-fish-btn').on('click', function() {
var checkStatus = layui.table.checkStatus('table-level2-detail');
var checkData = checkStatus.data;
if (checkData.length === 0) {
layer.msg('请选择要删除的数据');
return;
}
layer.confirm('确定删除选中的 ' + checkData.length + ' 条数据吗?', function(idx) {
var ids = checkData.map(function(item) { return item.roomLevel + '_' + item.fishNumber + '_' + item.type; });
data.roomFishLevelList = (data.roomFishLevelList || []).filter(function(item) {
return !ids.includes(item.roomLevel + '_' + item.fishNumber + '_' + item.type);
});
layui.table.reload('table-level2-detail', { data: (data.roomFishLevelList||[]).filter(function(x){return x.roomLevel===2;}) });
$('.layui-tab-title li[lay-id="level2"]').text('中级房间配置 (' + (data.roomFishLevelList||[]).filter(function(x){return x.roomLevel===2;}).length + '条)');
layer.close(idx);
});
});
$('#del-level3-fish-btn').on('click', function() {
var checkStatus = layui.table.checkStatus('table-level3-detail');
var checkData = checkStatus.data;
if (checkData.length === 0) {
layer.msg('请选择要删除的数据');
return;
}
layer.confirm('确定删除选中的 ' + checkData.length + ' 条数据吗?', function(idx) {
var ids = checkData.map(function(item) { return item.roomLevel + '_' + item.fishNumber + '_' + item.type; });
data.roomFishLevelList = (data.roomFishLevelList || []).filter(function(item) {
return !ids.includes(item.roomLevel + '_' + item.fishNumber + '_' + item.type);
});
layui.table.reload('table-level3-detail', { data: (data.roomFishLevelList||[]).filter(function(x){return x.roomLevel===3;}) });
$('.layui-tab-title li[lay-id="level3"]').text('高级房间配置 (' + (data.roomFishLevelList||[]).filter(function(x){return x.roomLevel===3;}).length + '条)');
layer.close(idx);
});
});
},
yes: function(idx, layero) {
if (itemIndex >= 0 && itemIndex < gameConfig.roomFishConfigs.length) {
gameConfig.roomFishConfigs[itemIndex] = data;
}
layer.close(idx);
renderTable();
}
});
}
function showFishEditDialog(data, type, configData, itemIndex, roomLevel, callback) {
// 兼容普通鱼调用(不传 roomLevel,callback 在第5位)
if (type !== 'level' && typeof roomLevel === 'function') {
callback = roomLevel;
roomLevel = undefined;
}
var isLevel = type === 'level';
var title = isLevel ? '编辑房间等级出鱼配置' : '编辑普通鱼出鱼配置';
var gameType = configData.gameType || data.gameType || 0;
var typeOptions = '<option value="1"' + (data.type === 1 ? ' selected' : '') + '>鱼</option>' +
'<option value="2"' + (data.type === 2 ? ' selected' : '') + '>鱼阵</option>';
var fishConfigs = gameConfig.fishTypeConfigs || [];
var fishArrayConfigs = gameConfig.fishArrayConfigs || [];
var roomConfigs = gameConfig.roomConfigs || [];
var roomLevelOptions = '<option value="">请选择</option>';
roomConfigs.forEach(function(r) {
if (r.gameType === gameType) {
roomLevelOptions += '<option value="' + r.roomLevel + '"' + (data.roomLevel === r.roomLevel ? ' selected' : '') + '>' + r.roomLevel + ' ' + r.roomName + '</option>';
}
});
var fishOptions = '<option value="">请选择</option>';
fishConfigs.forEach(function(f) {
fishOptions += '<option value="' + f.fishNumber + '"' + (data.fishNumber === f.fishNumber ? ' selected' : '') + '>' + (f.fishName || f.fishNumber) + ' (' + f.fishNumber + ')</option>';
});
var fishArrayOptions = '<option value="">请选择</option>';
fishArrayConfigs.forEach(function(f) {
fishArrayOptions += '<option value="' + f.fishArrayNumber + '"' + (data.fishNumber === f.fishArrayNumber ? ' selected' : '') + '>' + (f.name || f.fishArrayNumber) + ' (' + f.fishArrayNumber + ')</option>';
});
var html = '<div style="padding:15px 15px 5px 15px;">' +
'<form class="layui-form" style="margin:0;">' +
(isLevel ? '<div class="layui-form-item" style="margin-bottom:10px;">' +
'<label class="layui-form-label" style="width:80px;">房间等级</label>' +
'<div class="layui-input-inline" style="width:160px;">' +
'<select name="roomLevel" id="room-level-select" lay-search>' + roomLevelOptions + '</select>' +
'</div>' +
'</div>' : '') +
'<div class="layui-form-item" style="margin-bottom:10px;">' +
'<label class="layui-form-label" style="width:80px;">类型</label>' +
'<div class="layui-input-inline" style="width:100px;">' +
'<select name="type" id="fish-type-select" lay-filter="fish-type-select">' + typeOptions + '</select>' +
'</div>' +
'</div>' +
'<div class="layui-form-item" id="fish-select-item" style="margin-bottom:10px;">' +
'<label class="layui-form-label" style="width:80px;">选择鱼</label>' +
'<div class="layui-input-inline" style="width:280px;">' +
'<select name="fishSelect" id="fish-select" lay-filter="fish-select" lay-search>' + fishOptions + '</select>' +
'</div>' +
'</div>' +
'<div class="layui-form-item" id="fish-array-select-item" style="display:none;margin-bottom:10px;">' +
'<label class="layui-form-label" style="width:80px;">选择鱼阵</label>' +
'<div class="layui-input-inline" style="width:280px;">' +
'<select name="fishArraySelect" id="fish-array-select" lay-filter="fish-array-select" lay-search>' + fishArrayOptions + '</select>' +
'</div>' +
'</div>' +
'<input type="hidden" name="fishNumber" value="' + (data.fishNumber || '') + '">' +
'<div class="layui-form-item" style="margin-bottom:10px;">' +
'<label class="layui-form-label" style="width:80px;">权重</label>' +
'<div class="layui-input-inline" style="width:100px;">' +
'<input type="number" name="fishWeight" value="' + (data.fishWeight || 0) + '" class="layui-input">' +
'</div>' +
'<div class="layui-form-mid" style="color:#999;font-size:12px;">权重值越大,出鱼概率越高</div>' +
'</div>' +
'<div class="layui-form-item" style="margin-bottom:10px;">' +
'<label class="layui-form-label" style="width:80px;">频率(秒)</label>' +
'<div class="layui-input-inline" style="width:100px;">' +
'<input type="number" name="fishFrequency" value="' + (data.fishFrequency || 0) + '" class="layui-input">' +
'</div>' +
'<div class="layui-form-mid" style="color:#999;font-size:12px;">下次出鱼间隔时间</div>' +
'</div>' +
'<div class="layui-form-item" style="margin-bottom:10px;">' +
'<label class="layui-form-label" style="width:80px;">最大数量</label>' +
'<div class="layui-input-inline" style="width:100px;">' +
'<input type="number" name="maxCount" value="' + (data.maxCount || 0) + '" class="layui-input">' +
'</div>' +
'<div class="layui-form-mid" style="color:#999;font-size:12px;">屏幕中该鱼最大数量</div>' +
'</div>' +
'<div class="layui-form-item" style="margin-bottom:10px;">' +
'<label class="layui-form-label" style="width:80px;">出场秒数</label>' +
'<div class="layui-input-inline" style="width:100px;">' +
'<input type="number" name="times" value="' + (data.times || 0) + '" class="layui-input">' +
'</div>' +
'<div class="layui-form-mid" style="color:#999;font-size:12px;">出场时间(秒)</div>' +
'</div>' +
'</form>' +
'</div>';
layer.open({
type: 1,
title: title,
area: ['800px', '500px'],
content: html,
btn: ['保存', '取消'],
shadeClose: true,
success: function(layero) {
layui.form.render('select');
function updateFishSelect(type) {
if (type === 1) {
$('#fish-select-item', layero).show();
$('#fish-array-select-item', layero).hide();
} else {
$('#fish-select-item', layero).hide();
$('#fish-array-select-item', layero).show();
}
}
updateFishSelect(data.type);
layui.form.on('select(fish-type-select)', function(fdata) {
var t = parseInt(fdata.value);
updateFishSelect(t);
});
layui.form.on('select(fish-select)', function(fdata) {
$('input[name="fishNumber"]', layero).val(fdata.value);
});
layui.form.on('select(fish-array-select)', function(fdata) {
$('input[name="fishNumber"]', layero).val(fdata.value);
});
},
yes: function(index, layero) {
var updatedItem = {
type: parseInt(layero.find('select[name="type"]').val()) || 1,
fishNumber: layero.find('input[name="fishNumber"]').val(),
fishWeight: parseInt(layero.find('input[name="fishWeight"]').val()) || 0,
fishFrequency: parseInt(layero.find('input[name="fishFrequency"]').val()) || 0,
maxCount: parseInt(layero.find('input[name="maxCount"]').val()) || 0,
times: parseInt(layero.find('input[name="times"]').val()) || 0
};
if (isLevel) {
updatedItem.roomLevel = parseInt(layero.find('select[name="roomLevel"]').val()) || 0;
}
callback(updatedItem);
layer.close(index);
}
});
}
window.saveSection=function(n){layui.admin.req({method:'get',url:(layui.setter.baseurl||'/')+'adminapi/GameConfig/SaveSection/'+n,done:function(r){layui.layer.msg(r.info||(r.code===0?'保存成功':'保存失败'))}})}
loadConfig();
});
</script>
<script type="text/html" id="table-normal-operate">
<a class="layui-btn layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑</a>
</script>
<script type="text/html" id="table-level-operate">
<a class="layui-btn layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑</a>
</script>
@@ -0,0 +1,531 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>出鱼概率配置</title>
</head>
<body>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏管理</cite></a>
<a><cite>出鱼概率配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-filter="init-roomfishprobability"><i class="layui-icon layui-icon-refresh"></i> 初始化配置</button>
<button class="layui-btn layui-btn-sm" lay-filter="save-roomfishprobability"><i class="layui-icon layui-icon-ok"></i> 保存配置</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-filter="reload-roomfishprobability"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" onclick="saveSection('RoomFishProbabilityList')"><i class="layui-icon layui-icon-export"></i> 保存到初始化文件</button>
</div>
</div>
<div class="layui-card-body">
<div class="layui-row">
<div class="layui-col-md3">
<div class="layui-card">
<div class="layui-card-header">游戏类型选择</div>
<div class="layui-card-body">
<div class="layui-btn-container" id="game-type-btns">
<button class="layui-btn layui-btn-sm layui-btn-primary" data-type="1">风雷之渊</button>
<button class="layui-btn layui-btn-sm" data-type="2">永夜尸城</button>
<button class="layui-btn layui-btn-sm" data-type="4">星际迷航</button>
<button class="layui-btn layui-btn-sm" data-type="5">黑神话悟空</button>
</div>
</div>
</div>
<div class="layui-card" style="margin-top: 15px;">
<div class="layui-card-header">房间等级筛选</div>
<div class="layui-card-body">
<div class="layui-btn-container" id="room-level-btns">
<button class="layui-btn layui-btn-sm" data-level="all">全部房间</button>
<button class="layui-btn layui-btn-sm layui-btn-primary" data-level="1">初级房间</button>
<button class="layui-btn layui-btn-sm layui-btn-primary" data-level="2">中级房间</button>
<button class="layui-btn layui-btn-sm layui-btn-primary" data-level="3">高级房间</button>
</div>
</div>
</div>
</div>
<div class="layui-col-md9">
<table id="table-roomfishprobability" lay-filter="table-roomfishprobability"></table>
</div>
</div>
</div>
</div>
</div>
<script type="text/html" id="toolbar-roomfishprobability">
<div class="layui-btn-container">
</div>
</script>
<script type="text/html" id="table-operate">
<a class="layui-btn layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑</a>
</script>
<script type="text/html" id="tpl-type">
{{# var typeMap = {1:'鱼',2:'鱼阵'}; }}
{{= typeMap[d.type] || '未知(' + d.type + ')' }}
</script>
<script type="text/html" id="tpl-fishType">
{{# var typeMap = {0:'不限',1:'普通鱼',2:'Boss鱼',3:'黄金鱼',4:'特殊鱼',5:'特殊Boss'}; }}
{{= typeMap[d.fishType] || '未知(' + d.fishType + ')' }}
</script>
<script>
layui.use(['admin', 'table', 'form', 'layer'], function () {
if (window._roomFishProbabilityInitialized) return;
window._roomFishProbabilityInitialized = true;
var $ = layui.$,
admin = layui.admin,
table = layui.table,
form = layui.form,
layer = layui.layer,
setter = layui.setter;
var TABLE_ID = 'table-roomfishprobability';
var gameConfig = null;
var probabilityCache = [];
var currentGameType = 1;
var currentRoomLevel = 'all';
var tableRendered = false;
setTimeout(function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
$('.layui-layer-shade').remove();
layer.closeAll();
}, 100);
// 格式化 range 字段用于显示
function formatRange(val) {
if (Array.isArray(val)) return val.join('-');
if (val !== undefined && val !== null) return String(val);
return '';
}
// 从任意值解析为数组
function parseIntOrDefault(val, defaultValue) {
var parsed = parseInt(val);
return Number.isNaN(parsed) ? defaultValue : parsed;
}
function parseRange(val, defaultMin, defaultMax) {
if (Array.isArray(val) && val.length >= 2) {
return [parseIntOrDefault(val[0], defaultMin), parseIntOrDefault(val[1], defaultMax)];
}
if (typeof val === 'string' && val.indexOf('-') > -1) {
var parts = val.split('-');
return [parseIntOrDefault(parts[0], defaultMin), parseIntOrDefault(parts[1], defaultMax)];
}
return [defaultMin, defaultMax];
}
function loadConfig() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig",
done: function (res) {
if (res.code === 0 && res.data) {
gameConfig = res.data;
buildFishNameMap();
switchGameType(currentGameType);
}
}
});
}
var fishNameMap = {};
var fishArrayNameMap = {};
function buildFishNameMap() {
if (!gameConfig) return;
fishNameMap = {};
fishArrayNameMap = {};
var fishConfigs = gameConfig.fishTypeConfigs || [];
fishConfigs.forEach(function(f) {
fishNameMap[f.fishNumber] = f.fishName || f.fishNumber;
});
var fishArrayConfigs = gameConfig.fishArrayConfigs || [];
fishArrayConfigs.forEach(function(f) {
fishArrayNameMap[f.fishArrayNumber] = f.name || f.fishArrayNumber;
});
}
function switchGameType(gameType) {
currentGameType = gameType;
$('#game-type-btns .layui-btn').removeClass('layui-btn-primary');
$('#game-type-btns .layui-btn[data-type="' + gameType + '"]').addClass('layui-btn-primary');
var roomFishConfig = null;
if (gameConfig && gameConfig.roomFishConfigs) {
roomFishConfig = gameConfig.roomFishConfigs.find(function(r) {
var gt = r.gameType !== undefined ? r.gameType : r.GameType;
return gt === gameType;
});
}
// 深拷贝,切断与 gameConfig 的引用关系
probabilityCache = roomFishConfig
? JSON.parse(JSON.stringify(roomFishConfig.roomFishProbabilityList || roomFishConfig.RoomFishProbabilityList || []))
: [];
// 确保所有 range 字段都是数组,并给每条数据加唯一标识
probabilityCache.forEach(function(item, index) {
item.firstFishCountRange = parseRange(item.firstFishCountRange || item.FirstFishCountRange, 1, 5);
item.timeRange = parseRange(item.timeRange || item.TimeRange, 5, 10);
item.countRange = parseRange(item.countRange || item.CountRange, 5, 10);
item.maxCountRange = parseRange(item.maxCountRange || item.MaxCountRange, 10, 20);
// 给每条数据添加唯一标识(用于编辑时精确匹配)
if (!item._uid) {
item._uid = 'prob_' + Date.now() + '_' + index;
}
});
renderTable();
}
function renderTable() {
var filteredData = probabilityCache;
if (currentRoomLevel !== 'all') {
var levelNum = parseInt(currentRoomLevel);
filteredData = probabilityCache.filter(function(item) {
return item.roomLevel === levelNum;
});
}
var cols = [[
{ type: 'checkbox', fixed: 'left' },
{ field: "roomLevel", title: "房间等级", width: 100 },
{ field: "type", title: "类型", width: 80, templet: '#tpl-type' },
{ field: "fishType", title: "鱼类型", width: 100, templet: '#tpl-fishType' },
{ field: "firstFishCountRange", title: "首次出鱼数量", width: 140, templet: function(d) { return formatRange(d.firstFishCountRange); } },
{ field: "timeRange", title: "出鱼时间频率(秒)", width: 170, templet: function(d) { return formatRange(d.timeRange); } },
{ field: "countRange", title: "出鱼数量频率", width: 140, templet: function(d) { return formatRange(d.countRange); } },
{ field: "maxCountRange", title: "场内允许数量", width: 140, templet: function(d) { return formatRange(d.maxCountRange); } },
{ title: "操作", width: 150, fixed: 'right', toolbar: '#table-operate' }
]];
if (tableRendered) {
table.reload(TABLE_ID, {
data: JSON.parse(JSON.stringify(filteredData)),
cols: cols
});
} else {
table.render({
id: TABLE_ID,
elem: '#' + TABLE_ID,
data: JSON.parse(JSON.stringify(filteredData)),
cols: cols,
toolbar: '#toolbar-roomfishprobability',
defaultToolbar: ['filter', 'exports', 'print']
});
tableRendered = true;
}
}
function saveConfig() {
if (!gameConfig) return;
if (!gameConfig.roomFishConfigs) gameConfig.roomFishConfigs = [];
var roomFishConfig = gameConfig.roomFishConfigs.find(function(r) {
return r.gameType === currentGameType;
});
if (!roomFishConfig) {
roomFishConfig = { gameType: currentGameType, roomFishProbabilityList: [] };
gameConfig.roomFishConfigs.push(roomFishConfig);
}
// 深拷贝写入,防止引用污染
roomFishConfig.roomFishProbabilityList = JSON.parse(JSON.stringify(probabilityCache));
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/GameConfig/Update",
data: JSON.stringify(gameConfig),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('保存成功');
loadConfig();
} else {
layer.msg(res.info || '保存失败');
}
}
});
}
function initConfig() {
layer.confirm('确定要初始化全部配置数据吗?此操作将清空现有数据!', function(idx) {
layer.close(idx);
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig/InitSection/RoomFishProbabilityList",
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('初始化成功');
loadConfig();
} else {
layer.msg(res.info || '初始化失败');
}
}
});
});
}
$(document).on('click', '[lay-filter="init-roomfishprobability"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
initConfig();
});
$(document).on('click', '[lay-filter="reload-roomfishprobability"]', function() {
loadConfig();
layer.msg('刷新成功');
});
$(document).on('click', '[lay-filter="save-roomfishprobability"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
saveConfig();
});
$('#game-type-btns').off().on('click', '.layui-btn', function() {
var gameType = parseInt($(this).data('type'));
switchGameType(gameType);
});
$('#room-level-btns').off().on('click', '.layui-btn', function() {
var level = $(this).data('level');
switchRoomLevel(level);
});
function switchRoomLevel(level) {
currentRoomLevel = level;
$('#room-level-btns .layui-btn').removeClass('layui-btn-primary');
if (level === 'all') {
$('#room-level-btns .layui-btn[data-level="all"]').addClass('layui-btn-primary');
} else {
$('#room-level-btns .layui-btn[data-level="' + level + '"]').addClass('layui-btn-primary');
}
renderTable();
}
table.on('tool(' + TABLE_ID + ')', function(obj) {
if (obj.event === 'edit') {
// 使用唯一标识匹配,而不是行号(行号在分页/筛选后会不准)
var data = null;
if (obj.data._uid) {
// 优先通过唯一ID匹配
data = probabilityCache.find(function(item) { return item._uid === obj.data._uid; });
}
if (!data) {
// 兜底:通过字段组合匹配
var roomLevel = obj.data.roomLevel;
var type = obj.data.type;
var fishType = obj.data.fishType;
data = probabilityCache.find(function(item) {
return item.roomLevel === roomLevel && item.type === type && item.fishType === fishType;
});
}
if (!data) data = obj.data;
showEditDialog(data);
}
});
function showEditDialog(data) {
var d = JSON.parse(JSON.stringify(data));
// 统一解析 range 为数组
d.firstFishCountRange = parseRange(d.firstFishCountRange, 1, 5);
d.timeRange = parseRange(d.timeRange, 5, 10);
d.countRange = parseRange(d.countRange, 5, 10);
d.maxCountRange = parseRange(d.maxCountRange, 10, 20);
var html = '<form class="layui-form" lay-filter="probability-form" style="padding:20px;">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">房间等级</label>' +
'<div class="layui-input-inline">' +
'<input type="number" name="roomLevel" value="' + (d.roomLevel || 1) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">类型</label>' +
'<div class="layui-input-inline">' +
'<select name="type">' +
'<option value="1" ' + (d.type === 1 ? 'selected' : '') + '>鱼</option>' +
'<option value="2" ' + (d.type === 2 ? 'selected' : '') + '>鱼阵</option>' +
'</select>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">鱼类型</label>' +
'<div class="layui-input-inline">' +
'<select name="fishType">' +
'<option value="0" ' + (d.fishType === 0 ? 'selected' : '') + '>不限</option>' +
'<option value="1" ' + (d.fishType === 1 ? 'selected' : '') + '>普通鱼</option>' +
'<option value="2" ' + (d.fishType === 2 ? 'selected' : '') + '>Boss鱼</option>' +
'<option value="3" ' + (d.fishType === 3 ? 'selected' : '') + '>黄金鱼</option>' +
'<option value="4" ' + (d.fishType === 4 ? 'selected' : '') + '>特殊鱼</option>' +
'<option value="5" ' + (d.fishType === 5 ? 'selected' : '') + '>特殊Boss</option>' +
'</select>' +
'</div>' +
'<div class="layui-form-mid">鱼阵时此参数无效</div>' +
'</div>' +
'<fieldset class="layui-elem-field">' +
'<legend>首次出鱼数量区间</legend>' +
'<div class="layui-form-item">' +
'<div class="layui-inline">' +
'<label class="layui-form-label">最小值</label>' +
'<div class="layui-input-inline">' +
'<input type="number" name="firstFishCountMin" value="' + d.firstFishCountRange[0] + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-inline">' +
'<label class="layui-form-label">最大值</label>' +
'<div class="layui-input-inline">' +
'<input type="number" name="firstFishCountMax" value="' + d.firstFishCountRange[1] + '" class="layui-input">' +
'</div>' +
'</div>' +
'</div>' +
'</fieldset>' +
'<fieldset class="layui-elem-field">' +
'<legend>出鱼时间频率区间(秒)</legend>' +
'<div class="layui-form-item">' +
'<div class="layui-inline">' +
'<label class="layui-form-label">最小值</label>' +
'<div class="layui-input-inline">' +
'<input type="number" name="timeRangeMin" value="' + d.timeRange[0] + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-inline">' +
'<label class="layui-form-label">最大值</label>' +
'<div class="layui-input-inline">' +
'<input type="number" name="timeRangeMax" value="' + d.timeRange[1] + '" class="layui-input">' +
'</div>' +
'</div>' +
'</div>' +
'</fieldset>' +
'<fieldset class="layui-elem-field">' +
'<legend>出鱼数量频率区间</legend>' +
'<div class="layui-form-item">' +
'<div class="layui-inline">' +
'<label class="layui-form-label">最小值</label>' +
'<div class="layui-input-inline">' +
'<input type="number" name="countRangeMin" value="' + d.countRange[0] + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-inline">' +
'<label class="layui-form-label">最大值</label>' +
'<div class="layui-input-inline">' +
'<input type="number" name="countRangeMax" value="' + d.countRange[1] + '" class="layui-input">' +
'</div>' +
'</div>' +
'</div>' +
'</fieldset>' +
'<fieldset class="layui-elem-field">' +
'<legend>场内允许数量区间</legend>' +
'<div class="layui-form-item">' +
'<div class="layui-inline">' +
'<label class="layui-form-label">最小值</label>' +
'<div class="layui-input-inline">' +
'<input type="number" name="maxCountRangeMin" value="' + d.maxCountRange[0] + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-inline">' +
'<label class="layui-form-label">最大值</label>' +
'<div class="layui-input-inline">' +
'<input type="number" name="maxCountRangeMax" value="' + d.maxCountRange[1] + '" class="layui-input">' +
'</div>' +
'</div>' +
'</div>' +
'</fieldset>' +
'<div class="layui-form-item">' +
'<div class="layui-input-block">' +
'<button class="layui-btn" lay-submit lay-filter="probability-save">保存</button>' +
'<button type="button" class="layui-btn layui-btn-primary" id="probability-cancel">取消</button>' +
'</div>' +
'</div>' +
'</form>';
layer.open({
type: 1,
title: '编辑出鱼概率配置',
area: ['600px', '550px'],
content: html,
success: function(layero) {
form.render();
form.on('submit(probability-save)', function(formData) {
var newData = {
roomLevel: parseIntOrDefault(formData.field.roomLevel, 1),
type: parseIntOrDefault(formData.field.type, 1),
fishType: parseIntOrDefault(formData.field.fishType, 0),
firstFishCountRange: [
parseIntOrDefault(formData.field.firstFishCountMin, 1),
parseIntOrDefault(formData.field.firstFishCountMax, 5)
],
timeRange: [
parseIntOrDefault(formData.field.timeRangeMin, 5),
parseIntOrDefault(formData.field.timeRangeMax, 10)
],
countRange: [
parseIntOrDefault(formData.field.countRangeMin, 5),
parseIntOrDefault(formData.field.countRangeMax, 10)
],
maxCountRange: [
parseIntOrDefault(formData.field.maxCountRangeMin, 10),
parseIntOrDefault(formData.field.maxCountRangeMax, 20)
]
};
// 通过 roomLevel + type + fishType 匹配找到对应行并更新
var found = false;
for (var i = 0; i < probabilityCache.length; i++) {
if (probabilityCache[i].roomLevel === data.roomLevel &&
probabilityCache[i].type === data.type &&
probabilityCache[i].fishType === data.fishType) {
probabilityCache[i] = newData;
found = true;
break;
}
}
if (!found) {
probabilityCache.push(newData);
}
layer.closeAll();
renderTable();
layer.msg('保存成功');
return false;
});
$('#probability-cancel').off().on('click', function() {
layer.closeAll();
});
}
});
}
window.saveSection=function(n){layui.admin.req({method:'get',url:(layui.setter.baseurl||'/')+'adminapi/GameConfig/SaveSection/'+n,done:function(r){layui.layer.msg(r.info||(r.code===0?'保存成功':'保存失败'))}})}
loadConfig();
});
</script>
</body>
</html>
@@ -0,0 +1,349 @@

<title>刮刮卡配置</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏管理</cite></a>
<a><cite>刮刮卡配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-body">
<table id="table-scratchcard" lay-filter="table-scratchcard"></table>
</div>
</div>
</div>
<script type="text/html" id="toolbar-scratchcard">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-filter="init-scratchcard"><i class="layui-icon layui-icon-refresh"></i> 初始化配置</button>
<button class="layui-btn layui-btn-sm" lay-filter="save-scratchcard"><i class="layui-icon layui-icon-ok"></i> 保存配置</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-filter="reload-scratchcard"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
<button class="layui-btn layui-btn-sm" lay-filter="add-scratchcard">添加</button>
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-filter="batchdel-scratchcard">批量删除</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" onclick="saveSection('ScratchCardConfig')"><i class="layui-icon layui-icon-export"></i> 保存到初始化文件</button>
</div>
</script>
<script type="text/html" id="tpl-type">
{{# var typeMap = {1:'金币',2:'钻石',3:'道具'}; }}
{{= typeMap[d.type] || '金币' }}
</script>
<script type="text/html" id="table-operate">
<a class="layui-btn layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
<script>
layui.use(['admin', 'table', 'form', 'layer'], function () {
var $ = layui.$,
admin = layui.admin,
table = layui.table,
form = layui.form,
layer = layui.layer,
setter = layui.setter;
var gameConfig = null;
setTimeout(function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
$('.layui-layer-shade').remove();
layer.closeAll();
}, 100);
function loadConfig() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig",
done: function (res) {
if (res.code === 0 && res.data) {
gameConfig = res.data;
renderTable();
}
}
});
}
function renderTable() {
var data = gameConfig ? gameConfig.scratchCardConfig || [] : [];
table.render({
elem: '#table-scratchcard',
data: data,
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ field: "type", title: "奖励类型", width: 100, templet: '#tpl-type' },
{ field: "count", title: "奖励数量", width: 100 },
{ field: "weight", title: "权重", width: 100 },
{ field: "baseProduct", title: "道具编号", width: 120, templet: function(d) { return d.baseProduct ? d.baseProduct.number : ''; } },
{ field: "baseProduct", title: "道具名称", width: 120, templet: function(d) { return d.baseProduct ? d.baseProduct.name : ''; } },
{ title: "操作", width: 150, fixed: 'right', toolbar: '#table-operate' }
]],
toolbar: '#toolbar-scratchcard',
defaultToolbar: ['filter', 'exports', 'print']
});
}
function initConfig() {
layer.confirm('确定要初始化全部配置数据吗?此操作将清空现有数据!', function(idx) {
layer.close(idx);
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig/InitSection/ScratchCardConfig",
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('初始化成功');
loadConfig();
} else {
layer.msg(res.info || '初始化失败');
}
},
fail: function(res) {
layer.msg(res.info || '初始化失败');
}
});
});
}
$(document).on('click', '[lay-filter="init-scratchcard"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
initConfig();
});
$(document).on('click', '[lay-filter="reload-scratchcard"]', function() {
loadConfig();
layer.msg('刷新成功');
});
$(document).on('click', '[lay-filter="save-scratchcard"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
var configs = gameConfig.scratchCardConfig || [];
if (configs.length === 0) return layer.msg('至少保留一条刮刮卡配置');
for (var i = 0; i < configs.length; i++) {
if (configs[i].count <= 0) return layer.msg('第 ' + (i + 1) + ' 项奖励数量必须大于0');
if (configs[i].weight <= 0) return layer.msg('第 ' + (i + 1) + ' 项权重必须大于0');
if (configs[i].type === 3 && (!configs[i].baseProduct || !configs[i].baseProduct.number))
return layer.msg('第 ' + (i + 1) + ' 项道具编号不能为空');
}
if (configs.length > 0) {
configs.forEach(function(item) {
if (!item.baseProduct) {
item.baseProduct = { type: 1, number: '', name: '', desc: '', imageUrl: '', level: 0 };
}
});
}
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/GameConfig/UpdateScratchCard",
data: JSON.stringify(configs),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('保存成功');
loadConfig();
} else {
layer.msg(res.info || '保存失败');
}
}
});
});
table.on('tool(table-scratchcard)', function(obj) {
var data = obj.data;
var trIndex = gameConfig.scratchCardConfig.findIndex(function(x) {
return x.type === data.type && x.count === data.count && x.weight === data.weight;
});
if (obj.event === 'edit') {
showEditDialog(trIndex > -1 ? gameConfig.scratchCardConfig[trIndex] : data, false, trIndex);
} else if (obj.event === 'del') {
layer.confirm('确定删除该配置吗?', function(idx) {
if (trIndex > -1) gameConfig.scratchCardConfig.splice(trIndex, 1);
obj.del();
layer.close(idx);
});
}
});
function showAddDialog() {
if (!gameConfig.scratchCardConfig) {
gameConfig.scratchCardConfig = [];
}
var newData = {
type: 1,
count: 100,
weight: 10,
baseProduct: { type: 1, number: '', name: '', desc: '' },
propNumber: '',
propName: ''
};
showEditDialog(newData, true);
}
$(document).on('click', '[lay-filter="add-scratchcard"]', function() {
showAddDialog();
});
$(document).on('click', '[lay-filter="batchdel-scratchcard"]', function() {
var checkStatus = table.checkStatus('table-scratchcard');
var checkData = checkStatus.data;
if (checkData.length === 0) {
return layer.msg('请选择要删除的数据');
}
layer.confirm('确定删除选中的 ' + checkData.length + ' 条数据吗?', function(idx) {
var ids = checkData.map(function(item) { return item.baseProduct ? item.baseProduct.number : ''; });
gameConfig.scratchCardConfig = gameConfig.scratchCardConfig.filter(function(item) {
return !ids.includes(item.baseProduct ? item.baseProduct.number : '');
});
renderTable();
layer.close(idx);
});
});
function showEditDialog(data, isAdd, rowIndex) {
var dialogTitle = isAdd ? '添加刮刮卡' : '编辑刮刮卡';
if (!data.baseProduct) {
data.baseProduct = { type: 1, number: '', name: '', desc: '' };
}
var typeOptions = [
{value: 1, text: '金币'},
{value: 2, text: '钻石'},
{value: 3, text: '道具'}
];
var typeHtml = '';
typeOptions.forEach(function(opt) {
typeHtml += '<option value="' + opt.value + '"' + (data.type === opt.value ? ' selected' : '') + '>' + opt.text + '</option>';
});
var propTypeOptions = [
{value: 1, text: '道具'},
{value: 2, text: '技能'},
{value: 5, text: '炮台'},
{value: 6, text: '翅膀'},
{value: 7, text: '战神'}
];
var propTypeHtml = '';
var currentPropType = data.baseProduct ? data.baseProduct.type : 1;
propTypeOptions.forEach(function(opt) {
propTypeHtml += '<option value="' + opt.value + '"' + (currentPropType === opt.value ? ' selected' : '') + '>' + opt.text + '</option>';
});
var html = '<div style="padding:20px;">' +
'<form class="layui-form">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">奖励类型</label>' +
'<div class="layui-input-block">' +
'<select name="type">' + typeHtml + '</select>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">奖励数量</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="count" value="' + (data.count || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">权重</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="weight" value="' + (data.weight || 0) + '" 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="propNumber" value="' + ((data.baseProduct && data.baseProduct.number) || data.propNumber || '') + '" 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="propName" value="' + ((data.baseProduct && data.baseProduct.name) || data.propName || '') + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">道具类型</label>' +
'<div class="layui-input-block">' +
'<select name="propType">' + propTypeHtml + '</select>' +
'</div>' +
'</div>' +
'</form>' +
'</div>';
layer.open({
type: 1,
title: dialogTitle,
area: ['500px', '450px'],
content: html,
btn: ['保存', '取消'],
shadeClose: true,
success: function(layero) {
form.render('select');
},
yes: function(index, layero) {
var rewardType = parseInt(layero.find('select[name="type"]').val());
var rewardCount = parseInt(layero.find('input[name="count"]').val()) || 0;
var rewardWeight = parseInt(layero.find('input[name="weight"]').val()) || 0;
var propNumber = layero.find('input[name="propNumber"]').val();
var propName = layero.find('input[name="propName"]').val();
var propType = parseInt(layero.find('select[name="propType"]').val());
if (rewardCount <= 0) return layer.msg('奖励数量必须大于0');
if (rewardWeight <= 0) return layer.msg('权重必须大于0');
if (rewardType === 3 && !propNumber) return layer.msg('道具奖励必须填写道具编号');
if (isAdd) {
var newItem = {
type: rewardType,
count: rewardCount,
weight: rewardWeight,
propNumber: propNumber,
propName: propName,
baseProduct: {
type: propType,
number: propNumber,
name: propName,
desc: ''
}
};
gameConfig.scratchCardConfig.unshift(newItem);
} else if (rowIndex > -1) {
// Layui 行对象是副本,编辑结果必须写回原始数组。
var original = gameConfig.scratchCardConfig[rowIndex];
original.type = rewardType;
original.count = rewardCount;
original.weight = rewardWeight;
original.propNumber = propNumber;
original.propName = propName;
original.baseProduct = {
type: propType,
number: propNumber,
name: propName,
desc: original.baseProduct ? (original.baseProduct.desc || '') : ''
};
}
layer.close(index);
table.reload('table-scratchcard', {
data: gameConfig.scratchCardConfig
});
}
});
}
window.saveSection=function(n){layui.admin.req({method:'get',url:(layui.setter.baseurl||'/')+'adminapi/GameConfig/SaveSection/'+n,done:function(r){layui.layer.msg(r.info||(r.code===0?'保存成功':'保存失败'))}})}
loadConfig();
});
</script>
@@ -0,0 +1,378 @@

<title>签到配置</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏管理</cite></a>
<a><cite>签到配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-body">
<table id="table-sign" lay-filter="table-sign"></table>
</div>
</div>
</div>
<script type="text/html" id="toolbar-sign">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-filter="init-sign"><i class="layui-icon layui-icon-refresh"></i> 初始化配置</button>
<button class="layui-btn layui-btn-sm" lay-filter="save-sign"><i class="layui-icon layui-icon-ok"></i> 保存配置</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-filter="reload-sign"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
<button class="layui-btn layui-btn-sm" lay-filter="add-sign">添加</button>
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-filter="batchdel-sign">批量删除</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" onclick="saveAct('SignConfigs')"><i class="layui-icon layui-icon-export"></i> 保存到初始化文件</button>
</div>
</script>
<script type="text/html" id="table-operate">
<a class="layui-btn layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑</a>
<a class="layui-btn layui-btn-xs" lay-event="preview"><i class="layui-icon layui-icon-search"></i>预览</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
<script type="text/html" id="tpl-icon">
{{# if(d.icon){ }}
<img src="{{d.icon}}" style="width:40px;height:40px;object-fit:cover;cursor:pointer;border-radius:4px;" onclick="previewImage('{{d.icon}}')" alt="图标">
{{# } else { }}
<span style="color:#999;">无图标</span>
{{# } }}
</script>
<style>
.icon-upload-box { position: relative; display: inline-block; }
.icon-upload-box .upload-btn { position: absolute; top: 0; left: 0; width: 80px; height: 80px; opacity: 0; cursor: pointer; }
.icon-upload-preview { width: 80px; height: 80px; border: 1px dashed #ddd; border-radius: 4px; display: flex; align-items: center; justify-content: center; background: #fafafa; cursor: pointer; }
.icon-upload-preview:hover { border-color: #009688; }
.icon-upload-preview img { max-width: 100%; max-height: 100%; }
.icon-upload-preview .placeholder { color: #999; font-size: 12px; text-align: center; }
</style>
<script>
layui.use(['admin', 'table', 'form', 'layer'], function () {
var $ = layui.$,
admin = layui.admin,
table = layui.table,
form = layui.form,
layer = layui.layer,
setter = layui.setter;
var activityConfig = null;
setTimeout(function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
$('.layui-layer-shade').remove();
layer.closeAll();
}, 100);
function loadConfig() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/ActivityConfig/Get",
done: function (res) {
if (res.code === 0 && res.data) {
activityConfig = res.data;
renderTable();
}
}
});
}
function renderTable() {
table.render({
elem: '#table-sign',
data: activityConfig ? activityConfig.signConfigs || [] : [],
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ field: "order", title: "排序", width: 70, edit: 'text' },
{ field: "name", title: "名称", width: 100, edit: 'text' },
{ field: "gold", title: "金币", width: 100, edit: 'text' },
{ field: "probability", title: "概率", width: 100, edit: 'text' },
{ field: "icon", title: "图标", width: 100, templet: '#tpl-icon' },
{ title: "操作", width: 180, fixed: 'right', toolbar: '#table-operate' }
]],
toolbar: '#toolbar-sign',
defaultToolbar: ['filter', 'exports', 'print']
});
}
function initConfig() {
layer.confirm('确定要从配置文件还原签到配置吗?', function(idx) {
layer.close(idx);
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig/InitActivitySection/SignConfigs",
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('初始化成功');
loadConfig();
} else {
layer.msg(res.info || '初始化失败');
}
},
fail: function(res) {
layer.msg(res.info || '初始化失败');
}
});
});
}
$(document).on('click', '[lay-filter="init-sign"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
initConfig();
});
$(document).on('click', '[lay-filter="reload-sign"]', function() {
loadConfig();
layer.msg('刷新成功');
});
$(document).on('click', '[lay-filter="save-sign"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/ActivityConfig/UpdateSign",
data: JSON.stringify(activityConfig.signConfigs || []),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('保存成功');
loadConfig();
} else {
layer.msg(res.info || '保存失败');
}
}
});
});
window.previewImage = function(url) {
layer.open({
type: 1,
title: '图片预览',
area: ['auto', 'auto'],
shadeClose: true,
content: '<div style="padding:20px;text-align:center;"><img src="' + url + '" style="max-width:400px;max-height:400px;border-radius:8px;"></div>'
});
};
function updateDataByTable(obj) {
var field = obj.field;
var value = obj.value;
var data = obj.data;
if (field === 'gold') {
data[field] = parseInt(value) || 0;
} else if (field === 'probability') {
data[field] = parseFloat(value) || 0;
} else {
data[field] = value;
}
}
table.on('edit(table-sign)', function(obj){
updateDataByTable(obj);
});
table.on('tool(table-sign)', function(obj) {
var data = obj.data;
var trIndex = activityConfig && activityConfig.signConfigs
? activityConfig.signConfigs.findIndex(function(x) { return x.order === data.order; }) : -1;
if (obj.event === 'edit') {
showEditDialog(trIndex > -1 ? activityConfig.signConfigs[trIndex] : data, false, trIndex);
} else if (obj.event === 'del') {
layer.confirm('确定删除该配置吗?', function(idx) {
if (trIndex >= 0 && activityConfig && activityConfig.signConfigs) {
activityConfig.signConfigs.splice(trIndex, 1);
}
obj.del();
layer.close(idx);
});
} else if (obj.event === 'preview') {
if (data.icon) {
previewImage(data.icon);
} else {
layer.msg('暂无图片');
}
}
});
function showAddDialog() {
var newData = {
order: 0,
name: '',
gold: 0,
probability: 0,
icon: ''
};
showEditDialog(newData, true);
}
$(document).on('click', '[lay-filter="add-sign"]', function() {
showAddDialog();
});
function showEditDialog(data, isAdd, rowIndex) {
var dialogTitle = isAdd ? '添加签到配置' : '编辑签到配置';
var imagePreviewHtml = data.icon
? '<img src="' + data.icon + '" style="max-width:100%;max-height:100%;">'
: '<div class="placeholder">点击上传</div>';
var html = '<div style="padding:20px;">' +
'<form class="layui-form">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">排序</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="order" value="' + (data.order || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">名称</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="name" value="' + (data.name || '') + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">金币</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="gold" value="' + (data.gold || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">概率</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="probability" value="' + (data.probability || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">图标</label>' +
'<div class="layui-input-block">' +
'<div class="icon-upload-box">' +
'<div class="icon-upload-preview" id="iconPreview">' + imagePreviewHtml + '</div>' +
'<input type="file" class="upload-btn" accept="image/png" id="iconUpload">' +
'</div>' +
'<input type="hidden" name="icon" id="iconInput" value="' + (data.icon || '') + '">' +
'<div style="margin-top:8px;color:#999;font-size:12px;">点击图片可预览,仅支持 PNG 格式</div>' +
'</div>' +
'</div>' +
'</form>' +
'</div>';
layer.open({
type: 1,
title: dialogTitle,
area: ['600px', '500px'],
content: html,
btn: ['保存', '取消'],
shadeClose: true,
success: function(layero) {
var $preview = layero.find('#iconPreview');
var $input = layero.find('#iconInput');
var $fileInput = layero.find('#iconUpload');
$preview.on('click', function() {
var url = $input.val();
if (url) {
previewImage(url);
}
});
$fileInput.on('change', function() {
var file = this.files[0];
if (file) {
var order = layero.find('input[name="order"]').val();
if (!order && isAdd) {
layer.msg('请先填写排序编号');
$(this).val('');
return;
}
var formData = new FormData();
formData.append('file', file);
formData.append('number', order);
$.ajax({
url: setter.baseurl + 'adminapi/ImageUpload/upload/config',
type: 'POST',
data: formData,
processData: false,
contentType: false,
headers: { 'token': layui.data(setter.tableName)[setter.request.tokenName] },
success: function(res) {
if (res.code === 0 || res.code === 200) {
var path = res.data.path;
$input.val(path);
$preview.html('<img src="' + path + '" style="max-width:100%;max-height:100%;">');
layer.msg('上传成功');
} else {
layer.msg(res.info || '上传失败');
}
},
error: function() {
layer.msg('上传失败');
}
});
}
});
},
yes: function(index, layero) {
if (isAdd) {
var newItem = {
order: parseInt(layero.find('input[name="order"]').val()) || 0,
name: layero.find('input[name="name"]').val(),
gold: parseInt(layero.find('input[name="gold"]').val()) || 0,
probability: parseFloat(layero.find('input[name="probability"]').val()) || 0,
icon: layero.find('input[name="icon"]').val()
};
if (!activityConfig.signConfigs) activityConfig.signConfigs = [];
activityConfig.signConfigs.unshift(newItem);
} else if (rowIndex > -1) {
var original = activityConfig.signConfigs[rowIndex];
original.order = parseInt(layero.find('input[name="order"]').val()) || 0;
original.name = layero.find('input[name="name"]').val();
original.gold = parseInt(layero.find('input[name="gold"]').val()) || 0;
original.probability = parseFloat(layero.find('input[name="probability"]').val()) || 0;
original.icon = layero.find('input[name="icon"]').val();
}
layer.close(index);
table.reload('table-sign', {
data: activityConfig.signConfigs || []
});
}
});
}
$(document).on('click', '[lay-filter="batchdel-sign"]', function() {
var checkStatus = table.checkStatus('table-sign');
var checkData = checkStatus.data;
if (checkData.length === 0) {
return layer.msg('请选择要删除的数据');
}
layer.confirm('确定删除选中 ' + checkData.length + ' 条数据吗?', function(idx) {
var orders = checkData.map(function(item) { return item.order; });
if (activityConfig && activityConfig.signConfigs) {
activityConfig.signConfigs = activityConfig.signConfigs.filter(function(item) {
return !orders.includes(item.order);
});
}
renderTable();
layer.close(idx);
});
});
window.saveAct=function(n){layui.admin.req({method:'get',url:(layui.setter.baseurl||'/')+'adminapi/ActivityConfig/SaveSection/'+n,done:function(r){layui.layer.msg(r.info||(r.code===0?'保存成功':'保存失败'))}})}
loadConfig();
});
</script>
@@ -0,0 +1,111 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>象棋 Hub 流程测试</title>
<style>
body { font-family: Consolas, monospace; font-size: 13px; background: #111; color: #ddd; padding: 12px; }
button { cursor: pointer; padding: 6px 12px; margin: 3px; }
.log { background: #000; border: 1px solid #333; height: 50vh; overflow-y: auto; margin-top: 10px; padding: 8px; white-space: pre-wrap; font-size: 12px; }
.ok { color: #0f0; } .bad { color: #f55; } .info { color: #9ad; }
</style>
</head>
<body>
<h3>象棋 Hub 流程测试</h3>
<div>
<label>Hub地址: <input id="hub" value="" placeholder="请输入测试环境 Hub 地址" style="width:260px"></label>
<button onclick="connAll()">1. 连接 L1/A/B</button>
<button onclick="createRoom()">2. A 建房</button>
<button onclick="joinRoom()">3. B 加入</button>
<button onclick="readyAll()">4. 双方Ready</button>
<button onclick="bExit()">5. B退出</button>
<button onclick="bJoin()">6. B重新加入</button>
<button onclick="afkA()">7. A挂起</button>
<button onclick="afkB()">8. B挂起(等5s)</button>
<br>
<label>RoomID: <input id="room" name="room" value="" style="width:200px"></label>
<label>密码: <input id="password" name="password" value="" placeholder="请输入测试房间密码" style="width:180px"></label>
<label>UID-A: <input id="a" name="a" value="" style="width:280px"></label>
<label>UID-B: <input id="b" name="b" value="" style="width:280px"></label>
<button onclick="clearLog()">清空日志</button>
</div>
<div class="log" id="log"></div>
<script src="https://cdn.jsdelivr.net/npm/@microsoft/signalr@8.0.7/dist/browser/signalr.min.js"></script>
<script>
const logEl = document.getElementById('log');
function log(msg) { logEl.innerHTML += msg + '\n'; logEl.scrollTop = logEl.scrollHeight; }
function ok(m) { log('<span class="ok">[OK]</span> ' + m); }
function bad(m) { log('<span class="bad">[FAIL]</span> ' + m); }
function info(m) { log('<span class="info">[INFO]</span> ' + m); }
let L1, A, B;
const roomQ = () => document.getElementById('room').value;
const passwordQ = () => document.getElementById('password').value;
const uidA = () => document.getElementById('a').value;
const uidB = () => document.getElementById('b').value;
const hubUrl = () => document.getElementById('hub').value;
function newTestId(prefix) {
const value = window.crypto && crypto.randomUUID ? crypto.randomUUID().replace(/-/g, '') : Date.now() + Math.random().toString(16).slice(2);
return prefix + value;
}
document.getElementById('hub').value = /^https?:$/.test(location.protocol) ? location.origin + '/chessgamehub' : '';
document.getElementById('room').value = 'TEST_' + Date.now();
document.getElementById('a').value = newTestId('A_');
document.getElementById('b').value = newTestId('B_');
function evt(conn, name, cb) { conn.on(name, cb); }
function make(id, uid, tag) {
const c = new signalR.HubConnectionBuilder()
.withUrl(hubUrl() + '?userId=' + uid)
.withAutomaticReconnect()
.build();
const events = ['GameError','BeginGame','CreateRoomNotice','JoinRoomNotice','ExitRoomNotice',
'JoinRoom','Reconnect','ReadyNotice','AllBeginTime','GoldConfigRequired','ExitRoomSuccess',
'GameEnd','Disconnect','PlayerAfkNotice','PlayerCancelAfkNotice','OpponentRequestEnd','EndGameRequestSent','RestartRoomNotice'];
events.forEach(e => c.on(e, (arg) => {
let s = typeof arg === 'object' ? JSON.stringify(arg) : arg;
if (s && s.length > 140) s = s.substr(0, 140) + '…';
log(`<span class="info">[${tag}]</span> 推送 <b>${e}</b>: ${s}`);
}));
c.onreconnected(() => log(`<span class="bad">[${tag}] 重连</span>`));
return c;
}
async function connAll() {
L1 = make('L1', 'L1_' + uidA().substr(0,8), 'L1'); A = make('A', uidA(), 'A'); B = make('B', uidB(), 'B');
for (const [tag, c] of [['L1', L1], ['A', A], ['B', B]]) {
try { await c.start(); ok(`${tag} 连接成功 ${c.connectionId}`); }
catch (e) { bad(`${tag} 连接失败: ${e}`); log(e.stack); }
}
}
async function createRoom() {
try {
await A.invoke('CreateRoom', { roomId: roomQ(), userId: uidA(), gameType: 1, password: passwordQ(), gold: 100 });
ok('A invoke CreateRoom 已发送');
} catch (e) { bad('CreateRoom 异常: ' + e); }
}
async function joinRoom() {
try { await B.invoke('JoinGame', roomQ(), passwordQ(), uidB()); ok('B invoke JoinGame 已发送'); }
catch (e) { bad('JoinGame 异常: ' + e); }
}
async function readyAll() {
try { await A.invoke('GameReady', roomQ(), uidA()); await B.invoke('GameReady', roomQ(), uidB()); ok('双方 Ready 已发送'); }
catch (e) { bad('Ready 异常: ' + e); }
}
async function bExit() { try { await B.invoke('ExitRoom', roomQ(), uidB()); ok('B ExitRoom 已发送'); } catch (e) { bad('ExitRoom: ' + e); } }
async function bJoin() { try { await B.invoke('JoinGame', roomQ(), passwordQ(), uidB()); ok('B 重新加入已发送'); } catch (e) { bad(e); } }
async function afkA() { try { await A.invoke('SetPlayerAfk', roomQ(), uidA()); ok('A SetPlayerAfk 已发送'); } catch (e) { bad('afkA: ' + e); } }
async function afkB() { try { await B.invoke('SetPlayerAfk', roomQ(), uidB()); ok('B SetPlayerAfk 已发送'); } catch (e) { bad('afkB: ' + e); } }
function clearLog(){ logEl.innerHTML=''; }
</script>
</body>
</html>
@@ -0,0 +1,197 @@
<title>鱼雷配置</title>
<style>
.torpedo-item { border: 1px solid #ddd; padding: 15px; margin: 15px 0; border-radius: 4px; background: #fafafa; }
.torpedo-item .item-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
.torpedo-item .item-header .item-title { font-weight: bold; font-size: 14px; }
.torpedo-item table { width: 100%; border-collapse: collapse; margin-top: 10px; }
.torpedo-item table th, .torpedo-item table td { border: 1px solid #ddd; padding: 8px; text-align: center; }
.torpedo-item table th { background: #e6e6e6; font-weight: bold; }
.torpedo-item .param-row input { width: 100px; text-align: center; }
</style>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏管理</cite></a>
<a><cite>鱼雷配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-filter="init-torpedo"><i class="layui-icon layui-icon-refresh"></i> 初始化配置</button>
<button class="layui-btn layui-btn-sm" lay-filter="save-torpedo"><i class="layui-icon layui-icon-ok"></i> 保存配置</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-filter="add-torpedo"><i class="layui-icon layui-icon-addition"></i> 添加鱼雷</button>
</div>
</div>
<div class="layui-card-body">
<div id="torpedo-list"></div>
</div>
</div>
</div>
<script>
layui.use(['admin', 'form'], function() {
var $ = layui.$;
var admin = layui.admin;
var form = layui.form;
var layer = layui.layer;
var setter = layui.setter;
var gameConfig = {};
var torpedoListCache = [];
var propList = [];
function escapeHtml(value) {
return $('<div>').text(value == null ? '' : String(value)).html();
}
function loadConfig() {
admin.req({
method: 'get',
url: setter.baseurl + 'adminapi/GameConfig',
done: function(res) {
if ((res.code !== 0 && res.code !== 200) || !res.data) {
layer.msg(res.info || '鱼雷配置加载失败');
return;
}
gameConfig = res.data;
torpedoListCache = gameConfig.torpedoConfigs || [];
propList = (gameConfig.propConfig || []).filter(function(prop) {
return Number(prop.type) === 13;
});
renderTorpedoList();
}
});
}
function renderTorpedoList() {
var html = '';
torpedoListCache.forEach(function(item, index) {
item.rewardRanges = item.rewardRanges || [];
var options = '<option value="">请选择鱼雷道具</option>';
var found = false;
propList.forEach(function(prop) {
var selected = String(prop.number) === String(item.number);
if (selected) found = true;
options += '<option value="' + escapeHtml(prop.number) + '"' + (selected ? ' selected' : '') + '>' +
escapeHtml(prop.number + ' - ' + prop.name) + '</option>';
});
if (item.number && !found) {
options += '<option value="' + escapeHtml(item.number) + '" selected>' +
escapeHtml(item.number + ' - ' + (item.name || '道具表未找到')) + '</option>';
}
html += '<div class="torpedo-item" data-index="' + index + '">';
html += '<div class="item-header">';
html += '<span class="item-title">' + escapeHtml(item.name || '未选择鱼雷') + ' (' + escapeHtml(item.number || '') + ')</span>';
html += '<button type="button" class="layui-btn layui-btn-xs layui-btn-danger js-remove-torpedo" data-index="' + index + '"><i class="layui-icon layui-icon-delete"></i> 删除</button>';
html += '</div>';
html += '<div class="layui-form" style="margin-bottom:10px;max-width:460px">';
html += '<label style="display:inline-block;margin-right:10px">鱼雷道具:</label>';
html += '<div style="display:inline-block;width:320px;vertical-align:middle"><select class="js-torpedo-prop" data-index="' + index + '" lay-search>' + options + '</select></div>';
html += '</div>';
html += '<table><thead><tr><th>最小金币</th><th>最大金币</th><th>权重</th><th>操作</th></tr></thead><tbody>';
item.rewardRanges.forEach(function(range, ri) {
html += '<tr>';
html += '<td><input type="number" class="layui-input param-row js-reward-field" value="' + (range.minGold || 0) + '" data-index="' + index + '" data-ri="' + ri + '" data-field="minGold" /></td>';
html += '<td><input type="number" class="layui-input param-row js-reward-field" value="' + (range.maxGold || 0) + '" data-index="' + index + '" data-ri="' + ri + '" data-field="maxGold" /></td>';
html += '<td><input type="number" class="layui-input param-row js-reward-field" value="' + (range.weight || 0) + '" data-index="' + index + '" data-ri="' + ri + '" data-field="weight" /></td>';
html += '<td><button type="button" class="layui-btn layui-btn-xs layui-btn-danger js-remove-reward" data-index="' + index + '" data-ri="' + ri + '">删除</button></td>';
html += '</tr>';
});
html += '</tbody></table>';
html += '<button type="button" class="layui-btn layui-btn-xs layui-btn-warm js-add-reward" data-index="' + index + '" style="margin-top:8px"><i class="layui-icon layui-icon-addition"></i> 添加奖励区间</button>';
html += '</div>';
});
if (torpedoListCache.length === 0) {
html = '<div style="text-align:center;color:#999;padding:40px">暂无鱼雷配置,点击“添加鱼雷”开始配置</div>';
}
$('#torpedo-list').html(html);
form.render('select');
}
$(document).off('.torpedoConfig');
$(document).on('change.torpedoConfig', '.js-torpedo-prop', function() {
var index = Number($(this).data('index'));
var number = String($(this).val() || '');
var prop = propList.find(function(p) { return String(p.number) === number; });
torpedoListCache[index].number = number;
torpedoListCache[index].name = prop ? prop.name : '';
renderTorpedoList();
});
$(document).on('change.torpedoConfig', '.js-reward-field', function() {
var index = Number($(this).data('index'));
var rewardIndex = Number($(this).data('ri'));
var field = $(this).data('field');
torpedoListCache[index].rewardRanges[rewardIndex][field] = parseInt($(this).val(), 10) || 0;
});
$(document).on('click.torpedoConfig', '.js-add-reward', function() {
var index = Number($(this).data('index'));
torpedoListCache[index].rewardRanges.push({ minGold: 0, maxGold: 0, weight: 1 });
renderTorpedoList();
});
$(document).on('click.torpedoConfig', '.js-remove-reward', function() {
var index = Number($(this).data('index'));
var rewardIndex = Number($(this).data('ri'));
torpedoListCache[index].rewardRanges.splice(rewardIndex, 1);
renderTorpedoList();
});
$(document).on('click.torpedoConfig', '.js-remove-torpedo', function() {
torpedoListCache.splice(Number($(this).data('index')), 1);
renderTorpedoList();
});
loadConfig();
$(document).on('click.torpedoConfig', '[lay-filter="init-torpedo"]', function() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig/InitSection/TorpedoConfigs",
done: function(res) {
layer.msg(res.info || '初始化成功');
loadConfig();
}
});
});
$(document).on('click.torpedoConfig', '[lay-filter="save-torpedo"]', function() {
gameConfig.torpedoConfigs = torpedoListCache;
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/GameConfig/Update",
data: JSON.stringify(gameConfig),
done: function(res) {
if (res.code === 0 || res.code === 200) {
layer.msg('保存成功');
loadConfig();
} else {
layer.msg(res.info || '保存失败');
}
}
});
});
$(document).on('click.torpedoConfig', '[lay-filter="add-torpedo"]', function() {
torpedoListCache.push({
number: '',
name: '',
color: '',
rewardRanges: [
{ minGold: 10000, maxGold: 30000, weight: 100 },
{ minGold: 30001, maxGold: 100000, weight: 20 }
]
});
renderTorpedoList();
});
});
</script>
@@ -0,0 +1,443 @@
<title>用户等级配置</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏管理</cite></a>
<a><cite>用户等级配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-body">
<table id="table-userlevel" lay-filter="table-userlevel"></table>
</div>
</div>
</div>
<script type="text/html" id="toolbar-userlevel">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-filter="init-userlevel"><i class="layui-icon layui-icon-refresh"></i> 初始化配置</button>
<button class="layui-btn layui-btn-sm" lay-filter="save-userlevel"><i class="layui-icon layui-icon-ok"></i> 保存配置</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-filter="reload-userlevel"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
<button class="layui-btn layui-btn-sm" lay-filter="add-userlevel">添加</button>
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-filter="batchdel-userlevel">批量删除</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" onclick="saveSection('UserLevelConfigs')"><i class="layui-icon layui-icon-export"></i> 保存到初始化文件</button>
</div>
</script>
<script>window.saveSection=function(n){layui.admin.req({method:'get',url:(layui.setter.baseurl||'/')+'adminapi/GameConfig/SaveSection/'+n,done:function(r){layui.layer.msg(r.info||(r.code===0?'保存成功':'保存失败'))}})}</script>
<script type="text/html" id="table-operate">
<a class="layui-btn layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑</a>
<a class="layui-btn layui-btn-xs layui-btn-normal" lay-event="product"><i class="layui-icon layui-icon-gift"></i>道具</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
<script type="text/html" id="product-dialog-tpl">
<div style="padding:20px;">
<div class="layui-form">
<div class="layui-form-item">
<label class="layui-form-label">选择道具</label>
<div class="layui-input-block">
<select name="productNumber" id="productNumber" lay-search>
<option value="">请选择道具</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">数量</label>
<div class="layui-input-block">
<input type="number" name="productCount" id="productCount" class="layui-input" placeholder="请输入数量" value="1">
</div>
</div>
</div>
</div>
</script>
<script>
layui.use(['admin', 'table', 'form', 'layer'], function () {
var $ = layui.$,
admin = layui.admin,
table = layui.table,
form = layui.form,
layer = layui.layer,
setter = layui.setter;
var gameConfig = null;
var currentLevelIndex = -1;
var currentProductIndex = -1;
var propList = [];
var currentDialogData = null;
setTimeout(function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
$('.layui-layer-shade').remove();
layer.closeAll();
}, 100);
function loadConfig() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig",
done: function (res) {
if (res.code === 0 && res.data) {
gameConfig = res.data;
propList = (gameConfig.propConfig || []).map(function(p) {
return { number: p.number, name: p.name, type: p.type };
});
renderTable();
}
}
});
}
function renderTable() {
table.render({
elem: '#table-userlevel',
data: gameConfig ? gameConfig.userLevelConfigs || [] : [],
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ field: "level", title: "等级", width: 70, edit: 'text' },
{ field: "expMin", title: "经验下限", width: 100, edit: 'text' },
{ field: "expMax", title: "经验上限", width: 100, edit: 'text' },
{ field: "gold", title: "金币", width: 120, edit: 'text' },
{ field: "diamond", title: "钻石", width: 100, edit: 'text' },
{ field: "productList", title: "升级道具", width: 100, templet: function(d) {
return (d.productList && d.productList.length) ? d.productList.length + '项' : '无';
}},
{ title: "操作", width: 220, fixed: 'right', toolbar: '#table-operate' }
]],
toolbar: '#toolbar-userlevel',
defaultToolbar: ['filter', 'exports', 'print']
});
}
function initConfig() {
layer.confirm('确定要初始化全部配置数据吗?此操作将清空现有数据!', function(idx) {
layer.close(idx);
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig/InitSection/UserLevelConfigs",
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('初始化成功');
loadConfig();
} else {
layer.msg(res.info || '初始化失败');
}
},
fail: function(res) {
layer.msg(res.info || '初始化失败');
}
});
});
}
$(document).on('click', '[lay-filter="init-userlevel"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
initConfig();
});
$(document).on('click', '[lay-filter="reload-userlevel"]', function() {
loadConfig();
layer.msg('刷新成功');
});
$(document).on('click', '[lay-filter="save-userlevel"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/GameConfig/Update",
data: JSON.stringify(gameConfig),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('保存成功');
loadConfig();
} else {
layer.msg(res.info || '保存失败');
}
}
});
});
function updateDataByTable(obj) {
var field = obj.field;
var value = obj.value;
var data = obj.data;
if (!isNaN(value) && value !== '') {
var num = parseFloat(value);
data[field] = num;
}
}
table.on('edit(table-userlevel)', function(obj){
updateDataByTable(obj);
});
table.on('tool(table-userlevel)', function(obj) {
var data = obj.data;
var trIndex = obj.tr.index();
if (trIndex < 0) trIndex = gameConfig.userLevelConfigs.findIndex(function(x) { return x.level === data.level; });
if (obj.event === 'edit') {
showEditDialog(data, false, trIndex);
} else if (obj.event === 'del') {
layer.confirm('确定删除该配置吗?', function(idx) {
if (trIndex >= 0) gameConfig.userLevelConfigs.splice(trIndex, 1);
obj.del();
layer.close(idx);
});
} else if (obj.event === 'product') {
currentLevelIndex = trIndex;
showProductDialog(data);
}
});
function showAddDialog() {
var newData = {
level: 0,
expMin: 0,
expMax: 0,
gold: 0,
diamond: 0,
productList: []
};
showEditDialog(newData, true);
}
$(document).on('click', '[lay-filter="add-userlevel"]', function() {
showAddDialog();
});
function showEditDialog(data, isAdd, rowIndex) {
var dialogTitle = isAdd ? '添加用户等级' : '编辑用户等级';
var html = '<div style="padding:20px;">' +
'<form class="layui-form">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">等级</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="level" value="' + (data.level || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">经验下限</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="expMin" value="' + (data.expMin || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">经验上限</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="expMax" value="' + (data.expMax || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">金币</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="gold" value="' + (data.gold || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">钻石</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="diamond" value="' + (data.diamond || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'</form>' +
'</div>';
layer.open({
type: 1,
title: dialogTitle,
area: ['550px', '450px'],
content: html,
btn: ['保存', '取消'],
shadeClose: true,
yes: function(index, layero) {
if (isAdd) {
var newItem = {
level: parseInt(layero.find('input[name="level"]').val()) || 0,
expMin: parseInt(layero.find('input[name="expMin"]').val()) || 0,
expMax: parseInt(layero.find('input[name="expMax"]').val()) || 0,
gold: parseInt(layero.find('input[name="gold"]').val()) || 0,
diamond: parseInt(layero.find('input[name="diamond"]').val()) || 0,
productList: []
};
gameConfig.userLevelConfigs.unshift(newItem);
} else {
var original = gameConfig.userLevelConfigs[rowIndex];
if (!original) {
original = gameConfig.userLevelConfigs.find(function(x) { return x.level === data.level; });
}
if (!original) { layer.msg('数据异常,请刷新后重试'); return; }
original.level = parseInt(layero.find('input[name="level"]').val()) || 0;
original.expMin = parseInt(layero.find('input[name="expMin"]').val()) || 0;
original.expMax = parseInt(layero.find('input[name="expMax"]').val()) || 0;
original.gold = parseInt(layero.find('input[name="gold"]').val()) || 0;
original.diamond = parseInt(layero.find('input[name="diamond"]').val()) || 0;
}
layer.close(index);
table.reload('table-userlevel', {
data: gameConfig.userLevelConfigs
});
}
});
}
$(document).on('click', '[lay-filter="batchdel-userlevel"]', function() {
var checkStatus = table.checkStatus('table-userlevel');
var checkData = checkStatus.data;
if (checkData.length === 0) {
return layer.msg('请选择要删除的数据');
}
layer.confirm('确定删除选中的 ' + checkData.length + ' 条数据吗?', function(idx) {
var levels = checkData.map(function(item) { return item.level; });
gameConfig.userLevelConfigs = gameConfig.userLevelConfigs.filter(function(item) {
return !levels.includes(item.level);
});
table.reload('table-userlevel', {
data: gameConfig.userLevelConfigs
});
layer.close(idx);
});
});
function showProductDialog(data) {
currentDialogData = data;
currentProductIndex = -1;
var dialogContent = $('#product-dialog-tpl').html();
layer.open({
type: 1,
title: '等级' + data.level + ' - 升级道具',
area: ['600px', '400px'],
content: dialogContent,
shadeClose: true,
success: function(layero) {
var propOptions = '<option value="">请选择道具</option>';
propList.forEach(function(p) {
propOptions += '<option value="' + p.number + '">' + p.number + ' - ' + p.name + '</option>';
});
layero.find('#productNumber').html(propOptions);
form.render();
renderProductTable(layero);
layero.find('#productNumber').on('change', function() {
var selected = $(this).val();
if (selected) {
var prop = propList.find(function(p) { return p.number === selected; });
if (prop) {
layero.find('#productCount').val(1);
}
}
});
},
btn: ['添加', '关闭'],
yes: function(index, layero) {
var number = layero.find('#productNumber').val();
var count = parseInt(layero.find('#productCount').val()) || 1;
if (!number) {
layer.msg('请选择道具');
return;
}
var prop = propList.find(function(p) { return p.number === number; });
var list = getProductList();
var existing = list.find(function(p) { return p.number === number; });
if (existing) {
existing.count += count;
} else {
list.push({
number: number,
name: prop ? prop.name : '',
type: prop ? prop.type : 0,
count: count
});
}
renderProductTable(layero);
layer.msg('添加成功');
}
});
}
function getProductList() {
if (!currentDialogData) return [];
if (!currentDialogData.productList) {
currentDialogData.productList = [];
}
return currentDialogData.productList;
}
function getPropName(number) {
var prop = propList.find(function(p) { return p.number === number; });
return prop ? prop.number + ' - ' + prop.name : number || '';
}
function getPropType(number) {
var prop = propList.find(function(p) { return p.number === number; });
return prop ? prop.type : 0;
}
function renderProductTable(layero) {
var list = getProductList();
if (layero.find('#table-product-list').length === 0) {
layero.find('.layui-layer-content').append(
'<div style="padding:0 20px 20px;"><table id="table-product-list" lay-filter="table-product-list"></table></div>'
);
}
table.render({
elem: layero.find('#table-product-list'),
data: list,
page: false,
height: 260,
cols: [[
{ type: 'numbers', title: '序号', width: 60 },
{ field: "number", title: "道具编号", width: 120 },
{ field: "name", title: "道具名称", width: 120 },
{ field: "count", title: "数量", width: 80, edit: 'text' },
{ title: "操作", width: 100, templet: function(d) {
return '<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>';
}}
]]
});
layero.off('click', '.layui-btn-danger').on('click', '.layui-btn-danger', function() {
var tr = $(this).closest('tr[data-index]');
var index = parseInt(tr.attr('data-index'));
var list = getProductList();
if (index >= 0 && index < list.length) {
list.splice(index, 1);
renderProductTable(layero);
}
});
table.on('edit(table-product-list)', function(obj) {
var list = getProductList();
var index = layero.find('#table-product-list + .layui-table-view tr[data-index]').index(obj.tr);
if (index >= 0 && index < list.length) {
list[index].count = parseInt(obj.value) || 0;
}
});
}
loadConfig();
});
</script>
<style>
</style>
@@ -0,0 +1,604 @@

<title>VIP等级配置</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏管理</cite></a>
<a><cite>VIP等级配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-body">
<div class="layui-bg-gray" style="padding:12px 16px;margin-bottom:12px;line-height:24px;">
<strong>VIP经验计算规则:</strong>
VIP经验 = 实际现金充值金额 × 10 + VIP经验包经验。
金币、钻石余额、象棋或捕鱼输赢以及“累计充值金币”均不参与VIP经验计算。
</div>
<table id="table-vip" lay-filter="table-vip"></table>
</div>
</div>
</div>
<script type="text/html" id="toolbar-vip">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-filter="init-vip"><i class="layui-icon layui-icon-refresh"></i> 初始化配置</button>
<button class="layui-btn layui-btn-sm" lay-filter="save-vip"><i class="layui-icon layui-icon-ok"></i> 保存配置</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-filter="reload-vip"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
<button class="layui-btn layui-btn-sm" lay-filter="add-vip">添加VIP等级</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" onclick="saveSection('VipLevelConfigs')"><i class="layui-icon layui-icon-export"></i> 保存到初始化文件</button>
</div>
</script>
<script type="text/html" id="vip-bar">
<a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a>
<a class="layui-btn layui-btn-xs" lay-event="gift">礼包</a>
<a class="layui-btn layui-btn-xs" lay-event="daily">每日</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>
<script type="text/html" id="gift-dialog-tpl">
<div class="gift-container">
<div class="gift-left">
<div class="gift-toolbar">
<button class="layui-btn layui-btn-sm" id="btn-add-gift"><i class="layui-icon layui-icon-add-1"></i> 新增</button>
<button class="layui-btn layui-btn-sm layui-btn-danger" id="btn-delete-gift"><i class="layui-icon layui-icon-delete"></i> 删除</button>
</div>
<div class="gift-table-wrap">
<table id="table-gift-list"></table>
</div>
</div>
<div class="gift-right">
<h4>礼包项详情</h4>
<div class="layui-form" id="gift-form">
<div class="layui-form-item">
<label class="layui-form-label">类型</label>
<div class="layui-input-block">
<select name="giftType" id="giftType" lay-filter="giftType">
<option value="1">金币</option>
<option value="2">钻石</option>
<option value="3">道具</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">数量</label>
<div class="layui-input-block">
<input type="number" name="giftCount" id="giftCount" class="layui-input" placeholder="请输入数量">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">随机</label>
<div class="layui-input-block">
<input type="checkbox" name="giftIsRandom" lay-skin="switch" lay-text="是|否" id="giftIsRandom">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">最小值</label>
<div class="layui-input-block">
<input type="number" name="giftMinCount" id="giftMinCount" class="layui-input" placeholder="最小值">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">最大值</label>
<div class="layui-input-block">
<input type="number" name="giftMaxCount" id="giftMaxCount" class="layui-input" placeholder="最大值">
</div>
</div>
<div class="layui-form-item gift-prop-select" style="display:none;">
<label class="layui-form-label">道具编号</label>
<div class="layui-input-block">
<input type="text" name="giftProductNumber" id="giftProductNumber" class="layui-input" placeholder="输入道具编号">
</div>
</div>
<div class="layui-form-item" style="margin-top:20px;">
<button class="layui-btn layui-btn-normal" id="btn-save-gift" style="width:100%;">保存</button>
</div>
</div>
</div>
</div>
</script>
<script>
layui.use(['admin', 'table', 'form', 'layer'], function () {
var $ = layui.$,
admin = layui.admin,
table = layui.table,
form = layui.form,
layer = layui.layer,
setter = layui.setter;
var gameConfig = null;
var propList = [];
var currentVipIndex = -1;
var currentGiftIndex = -1;
var currentGiftType = 'gift';
var currentDialogData = null;
function getPropName(number) {
var prop = propList.find(function(p) { return p.number === number; });
return prop ? prop.number + ' - ' + prop.name : number || '';
}
function getOid(data) {
return data.otherId || data.OtherId || '';
}
function getType(data) {
return data.type || data.Type || 1;
}
function enrichProduct(gift) {
var oid = getOid(gift);
if (getType(gift) === 3 && oid) {
var prop = propList.find(function(p) { return p.number === oid; });
gift.product = prop ? { number: prop.number, name: prop.name } : { number: oid, name: '' };
} else {
gift.product = null;
}
return gift;
}
function enrichGiftList(list) {
if (list) list.forEach(enrichProduct);
}
setTimeout(function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
$('.layui-layer-shade').remove();
layer.closeAll();
}, 100);
function loadConfig() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig",
done: function (res) {
if (res.code === 0 && res.data) {
gameConfig = res.data;
propList = gameConfig.propConfig || [];
renderTable();
}
}
});
}
function renderTable() {
table.render({
elem: '#table-vip',
data: gameConfig ? (gameConfig.vipLevelConfigs || []) : [],
cols: [[
{ type: 'numbers', title: '序号', width: 60 },
{ field: "level", title: "等级", width: 80 },
{ field: "expMin", title: "经验下限", width: 100 },
{ field: "expMax", title: "经验上限", width: 100 },
{ field: "signInAdd", title: "签到加成", width: 100 },
{ field: "remark", title: "备注", minWidth: 150 },
{ field: "giftList", title: "礼包", width: 80, templet: function(d) {
return (d.giftList && d.giftList.length) ? d.giftList.length + '项' : '无';
}},
{ field: "dailyGift", title: "每日", width: 80, templet: function(d) {
return (d.dailyGift && d.dailyGift.length) ? d.dailyGift.length + '项' : '无';
}},
{ title: "操作", width: 250, fixed: 'right', toolbar: '#vip-bar' }
]],
toolbar: '#toolbar-vip',
defaultToolbar: ['filter', 'exports', 'print']
});
}
function initConfig() {
layer.confirm('确定要初始化全部配置数据吗?此操作将清空现有数据!', function(idx) {
layer.close(idx);
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig/InitSection/VipLevelConfigs",
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('初始化成功');
loadConfig();
} else {
layer.msg(res.info || '初始化失败');
}
},
fail: function(res) {
layer.msg(res.info || '初始化失败');
}
});
});
}
$(document).on('click', '[lay-filter="init-vip"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
initConfig();
});
$(document).on('click', '[lay-filter="reload-vip"]', function() {
loadConfig();
layer.msg('刷新成功');
});
$(document).on('click', '[lay-filter="save-vip"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
(gameConfig.vipLevelConfigs || []).forEach(function(v) {
enrichGiftList(v.giftList);
enrichGiftList(v.dailyGift);
});
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/GameConfig/UpdateVipLevel",
data: JSON.stringify(gameConfig.vipLevelConfigs || []),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('保存成功');
loadConfig();
} else {
layer.msg(res.info || '保存失败');
}
}
});
});
table.on('tool(table-vip)', function(obj) {
var data = obj.data;
var trIndex = gameConfig.vipLevelConfigs.findIndex(function(x) { return x.level === data.level; });
var originalData = trIndex > -1 ? gameConfig.vipLevelConfigs[trIndex] : data;
if (obj.event === 'del') {
layer.confirm('确定删除该配置吗?', function(idx) {
if (trIndex > -1) gameConfig.vipLevelConfigs.splice(trIndex, 1);
obj.del();
layer.close(idx);
});
} else if (obj.event === 'edit') {
showEditDialog(originalData, false, trIndex);
} else if (obj.event === 'gift') {
currentVipIndex = trIndex;
currentGiftType = 'gift';
showGiftDialog(originalData, '等级礼包');
} else if (obj.event === 'daily') {
currentVipIndex = trIndex;
currentGiftType = 'daily';
showGiftDialog(originalData, '每日礼包');
}
});
function showAddDialog() {
if (!gameConfig.vipLevelConfigs) gameConfig.vipLevelConfigs = [];
var newData = {
level: (gameConfig.vipLevelConfigs || []).length + 1,
expMin: 0,
expMax: 0,
signInAdd: 0,
remark: '',
giftList: [],
dailyGift: []
};
showEditDialog(newData, true);
}
$(document).on('click', '[lay-filter="add-vip"]', function() {
showAddDialog();
});
function showEditDialog(data, isAdd, rowIndex) {
var dialogTitle = isAdd ? '添加VIP等级' : '编辑VIP等级';
var index = isAdd ? -1 : rowIndex;
var html = '<div style="padding:20px;">' +
'<form class="layui-form">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">等级</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="level" value="' + (data.level || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">经验下限</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="expMin" value="' + (data.expMin || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">经验上限</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="expMax" value="' + (data.expMax || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">签到加成</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="signInAdd" value="' + (data.signInAdd || 0) + '" 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="remark" value="' + (data.remark || '') + '" class="layui-input">' +
'</div>' +
'</div>' +
'</form>' +
'</div>';
layer.open({
type: 1,
title: dialogTitle,
area: ['550px', '450px'],
content: html,
btn: ['保存', '取消'],
shadeClose: true,
success: function(layero) {
form.render();
},
yes: function(idx, layero) {
if (isAdd) {
var newItem = {
level: parseInt(layero.find('input[name="level"]').val()) || 0,
expMin: parseInt(layero.find('input[name="expMin"]').val()) || 0,
expMax: parseInt(layero.find('input[name="expMax"]').val()) || 0,
signInAdd: parseFloat(layero.find('input[name="signInAdd"]').val()) || 0,
remark: layero.find('input[name="remark"]').val(),
giftList: [],
dailyGift: []
};
gameConfig.vipLevelConfigs.unshift(newItem);
} else if (index > -1) {
// Layui 返回的是行数据副本,必须明确写回原配置数组。
var original = gameConfig.vipLevelConfigs[index];
original.level = parseInt(layero.find('input[name="level"]').val()) || 0;
original.expMin = parseInt(layero.find('input[name="expMin"]').val()) || 0;
original.expMax = parseInt(layero.find('input[name="expMax"]').val()) || 0;
original.signInAdd = parseFloat(layero.find('input[name="signInAdd"]').val()) || 0;
original.remark = layero.find('input[name="remark"]').val();
}
layer.close(idx);
table.reload('table-vip', {
data: gameConfig.vipLevelConfigs
});
}
});
}
function showGiftDialog(data, title) {
currentDialogData = data;
currentGiftIndex = -1;
var dialogContent = $('#gift-dialog-tpl').html();
layer.open({
type: 1,
title: 'VIP' + data.level + ' - ' + title,
area: ['1000px', '620px'],
content: dialogContent,
shadeClose: true,
success: function(layero) {
form.render();
renderGiftTable(layero);
layero.find('#btn-add-gift').on('click', function() {
addGiftItem(layero);
});
layero.find('#btn-delete-gift').on('click', function() {
deleteGiftItem(layero);
});
layero.find('#btn-save-gift').on('click', function() {
saveGiftItem(layero);
});
form.on('select(giftType)', function(obj) {
if (obj.value === '3') {
layero.find('.gift-prop-select').show();
} else {
layero.find('.gift-prop-select').hide();
}
layero.find('#giftCount').val(0);
layero.find('#giftIsRandom')[0].checked = false;
form.render('checkbox');
});
layero.find('#giftCount').on('input', function() {
layero.find('#giftIsRandom')[0].checked = false;
layero.find('#giftIsRandom').prop('checked', false);
form.render('checkbox');
});
layero.on('click', '#giftIsRandom + .layui-form-switch', function() {
var checkbox = layero.find('#giftIsRandom')[0];
setTimeout(function() {
if (checkbox.checked) {
layero.find('#giftCount').val(0);
}
}, 10);
});
form.on('switch(giftIsRandom)', function(data) {
if (data.elem.checked) {
layero.find('#giftCount').val(0);
}
});
}
});
}
function getGiftList() {
if (!currentDialogData) return [];
if (currentGiftType === 'gift') {
return currentDialogData.giftList || (currentDialogData.giftList = []);
} else {
return currentDialogData.dailyGift || (currentDialogData.dailyGift = []);
}
}
function addGiftItem(layero) {
var list = getGiftList();
list.push({ type: 1, count: 100, isRandom: false, minCount: 0, maxCount: 0, otherId: '' });
currentGiftIndex = list.length - 1;
renderGiftTable(layero, currentGiftIndex);
loadFormData(list[list.length - 1], layero);
}
function deleteGiftItem(layero) {
if (currentGiftIndex < 0) {
return layer.msg('请先选择要删除的项');
}
var list = getGiftList();
if (list.length > currentGiftIndex) {
list.splice(currentGiftIndex, 1);
currentGiftIndex = -1;
clearForm(layero);
renderGiftTable(layero, -1);
}
}
function saveGiftItem(layero) {
if (currentGiftIndex < 0) {
return layer.msg('请先选择要编辑的项');
}
var list = getGiftList();
var giftType = parseInt(layero.find('#giftType').val()) || 1;
var otherId = $.trim(layero.find('#giftProductNumber').val() || '');
if (giftType === 3 && !propList.some(function(p) { return String(p.number) === otherId; })) {
return layer.msg('请输入道具表中存在的道具编号');
}
list[currentGiftIndex] = {
type: giftType,
count: parseInt(layero.find('#giftCount').val()) || 0,
isRandom: layero.find('#giftIsRandom')[0].checked,
minCount: parseInt(layero.find('#giftMinCount').val()) || 0,
maxCount: parseInt(layero.find('#giftMaxCount').val()) || 0,
otherId: giftType === 3 ? otherId : ''
};
renderGiftTable(layero, currentGiftIndex);
(gameConfig.vipLevelConfigs || []).forEach(function(v) {
enrichGiftList(v.giftList);
enrichGiftList(v.dailyGift);
});
var saveButton = layero.find('#btn-save-gift');
if (saveButton.prop('disabled')) return;
saveButton.prop('disabled', true).addClass('layui-btn-disabled');
admin.req({
method: 'post',
contentType: 'application/json;charset=UTF-8',
url: setter.baseurl + 'adminapi/GameConfig/UpdateVipLevel',
data: JSON.stringify(gameConfig.vipLevelConfigs || []),
done: function(res) {
saveButton.prop('disabled', false).removeClass('layui-btn-disabled');
layer.msg(res.code === 0 || res.code === 200 ? '保存成功' : (res.info || '保存失败,请重试'));
},
error: function() {
saveButton.prop('disabled', false).removeClass('layui-btn-disabled');
layer.msg('保存请求失败,请重试');
}
});
}
function clearForm(layero) {
layero.find('#giftType').val('1');
layero.find('#giftCount').val('0');
layero.find('#giftIsRandom')[0].checked = false;
layero.find('#giftMinCount').val('0');
layero.find('#giftMaxCount').val('0');
layero.find('#giftProductNumber').val('');
layero.find('.gift-prop-select').hide();
form.render('checkbox');
form.render('select');
}
function loadFormData(data, layero) {
layero.find('#giftType').val(getType(data));
layero.find('#giftCount').val(data.count || data.Count || 0);
layero.find('#giftIsRandom')[0].checked = data.isRandom || data.IsRandom || false;
layero.find('#giftMinCount').val(data.minCount || data.MinCount || 0);
layero.find('#giftMaxCount').val(data.maxCount || data.MaxCount || 0);
layero.find('#giftProductNumber').val(getOid(data));
if (getType(data) === 3) {
layero.find('.gift-prop-select').show();
} else {
layero.find('.gift-prop-select').hide();
}
form.render('checkbox');
form.render();
}
function renderGiftTable(layero, selectIndex) {
var list = getGiftList();
if (typeof selectIndex !== 'number') selectIndex = list.length > 0 ? 0 : -1;
layero.find('.layui-table-view').remove();
table.render({
elem: layero.find('#table-gift-list'),
data: list,
page: false,
height: 380,
cols: [[
{ type: 'numbers', title: '序号', width: 60 },
{ field: "type", title: "类型", width: 80, templet: function(d) {
var map = {1: '金币', 2: '钻石', 3: '道具'};
return map[d.type] || '金币';
}},
{ field: "count", title: "数量", width: 80 },
{ field: "minCount", title: "最小值", width: 80 },
{ field: "maxCount", title: "最大值", width: 80 },
{ field: "isRandom", title: "随机", width: 70, templet: function(d) {
return d.isRandom ? '是' : '否';
}},
{ field: "otherId", title: "道具编号", minWidth: 150, templet: function(d) {
return getType(d) === 3 ? getPropName(getOid(d)) : '';
}}
]],
done: function() {
if (list.length > 0 && selectIndex < 0) {
selectIndex = 0;
}
if (selectIndex >= 0 && selectIndex < list.length) {
currentGiftIndex = selectIndex;
loadFormData(list[selectIndex], layero);
layero.find('.layui-table-view tr[data-index="' + selectIndex + '"]').addClass('layui-table-click');
}
layero.find('.layui-table-view').off('click', 'tr[data-index]').on('click', 'tr[data-index]', function() {
var index = parseInt($(this).attr('data-index'));
if (index >= 0 && index < list.length) {
currentGiftIndex = index;
layero.find('.layui-table-view tr[data-index]').removeClass('layui-table-click');
$(this).addClass('layui-table-click');
loadFormData(list[index], layero);
}
});
}
});
}
window.saveSection=function(n){layui.admin.req({method:'get',url:(layui.setter.baseurl||'/')+'adminapi/GameConfig/SaveSection/'+n,done:function(r){layui.layer.msg(r.info||(r.code===0?'保存成功':'保存失败'))}})}
loadConfig();
});
</script>
<style>
.gift-container { display: flex; height: 480px; padding: 10px; }
.gift-left { flex: 1; margin-right: 15px; }
.gift-table-wrap { height: 400px; overflow: auto; }
.gift-right { width: 280px; border-left: 1px solid #e6e6e6; padding-left: 15px; }
.gift-right h4 { margin-bottom: 15px; color: #333; font-size: 16px; padding-bottom: 10px; border-bottom: 1px solid #eee; }
.gift-toolbar { margin-bottom: 10px; }
.gift-toolbar .layui-btn { margin-right: 5px; }
.gift-right .layui-form-item { margin-bottom: 12px; }
.gift-right .layui-form-label { padding: 9px 0; width: 60px; }
.gift-right .layui-input-block { margin-left: 70px; }
</style>
+180
View File
@@ -0,0 +1,180 @@
<div class="layui-fluid">
<div class="layui-row layui-col-space15">
<div class="layui-col-md8">
<div class="layui-row layui-col-space15">
<div class="layui-col-md6">
<div class="layui-card">
<div class="layui-card-header">快捷方式111111111111</div>
<div class="layui-card-body">
<div class="layui-carousel layadmin-carousel layadmin-shortcut">
<div carousel-item>
<ul class="layui-row layui-col-space10">
<li class="layui-col-xs3">
<a lay-href="home/homepage1">
<i class="layui-icon layui-icon-console"></i>
<cite>主页一1111111111</cite>
</a>
</li>
<li class="layui-col-xs3">
<a lay-href="home/homepage2">
<i class="layui-icon layui-icon-chart"></i>
<cite>主页二</cite>
</a>
</li>
<li class="layui-col-xs3">
<a lay-href="component/layer/list">
<i class="layui-icon layui-icon-template-1"></i>
<cite>弹层</cite>
</a>
</li>
<li class="layui-col-xs3">
<a lay-href="/senior/im/">
<i class="layui-icon layui-icon-chat"></i>
<cite>聊天</cite>
</a>
</li>
<li class="layui-col-xs3">
<a lay-href="component/progress/index">
<i class="layui-icon layui-icon-find-fill"></i>
<cite>进度条</cite>
</a>
</li>
<li class="layui-col-xs3">
<a lay-href="app/workorder/list">
<i class="layui-icon layui-icon-survey"></i>
<cite>工单</cite>
</a>
</li>
<li class="layui-col-xs3">
<a lay-href="user/user/list">
<i class="layui-icon layui-icon-user"></i>
<cite>用户</cite>
</a>
</li>
<li class="layui-col-xs3">
<a lay-href="set/system/website">
<i class="layui-icon layui-icon-set"></i>
<cite>设置</cite>
</a>
</li>
</ul>
<ul class="layui-row layui-col-space10">
<li class="layui-col-xs3">
<a lay-href="set/user/info">
<i class="layui-icon layui-icon-set"></i>
<cite>我的资料</cite>
</a>
</li>
<li class="layui-col-xs3">
<a lay-href="set/user/info">
<i class="layui-icon layui-icon-set"></i>
<cite>我的资料</cite>
</a>
</li>
<li class="layui-col-xs3">
<a lay-href="set/user/info">
<i class="layui-icon layui-icon-set"></i>
<cite>我的资料</cite>
</a>
</li>
<li class="layui-col-xs3">
<a lay-href="set/user/info">
<i class="layui-icon layui-icon-set"></i>
<cite>我的资料</cite>
</a>
</li>
<li class="layui-col-xs3">
<a lay-href="set/user/info">
<i class="layui-icon layui-icon-set"></i>
<cite>我的资料</cite>
</a>
</li>
<li class="layui-col-xs3">
<a lay-href="set/user/info">
<i class="layui-icon layui-icon-set"></i>
<cite>我的资料</cite>
</a>
</li>
<li class="layui-col-xs3">
<a lay-href="set/user/info">
<i class="layui-icon layui-icon-set"></i>
<cite>我的资料</cite>
</a>
</li>
<li class="layui-col-xs3">
<a lay-href="set/user/info">
<i class="layui-icon layui-icon-set"></i>
<cite>我的资料</cite>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<div class="layui-col-md12">
<div class="layui-card">
<div class="layui-card-header">数据概览</div>
<div class="layui-card-body">
<div class="layui-carousel layadmin-carousel layadmin-dataview" data-anim="fade" lay-filter="LAY-index-dataview">
<div carousel-item id="LAY-index-dataview">
<div><i class="layui-icon layui-icon-loading1 layadmin-loading"></i></div>
<div></div>
<div></div>
</div>
</div>
</div>
</div>
<div class="layui-card">
<div class="layui-tab layui-tab-brief layadmin-latestData">
<ul class="layui-tab-title">
<li class="layui-this">今日热搜</li>
<li>今日热帖</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
<table id="LAY-index-topSearch"></table>
</div>
<div class="layui-tab-item">
<table id="LAY-index-topCard"></table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
//加载 controller 目录下的对应模块
/*
小贴士:
这里 console 模块对应 的 console.js 并不会重复加载,
然而该页面的视图则是重新插入到容器,那如何保证能重新来控制视图?有两种方式:
1): 借助 layui.factory 方法获取 console 模块的工厂(回调函数)给 layui.use
2): 直接在 layui.use 方法的回调中书写业务代码,即:
layui.use('console', function(){
//同 console.js 中的 layui.define 回调中的代码
});
这里我们采用的是方式1。其它很多视图中采用的其实都是方式2,因为更简单些,也减少了一个请求数。
*/
layui.use('console', layui.factory('console'));
</script>
+287
View File
@@ -0,0 +1,287 @@
<div class="layui-layout layui-layout-admin">
<div class="layui-header">
<!-- 头部区域 -->
<ul class="layui-nav layui-layout-left">
<li class="layui-nav-item layadmin-flexible" lay-unselect>
<a href="javascript:;" layadmin-event="flexible" title="侧边伸缩">
<i class="layui-icon layui-icon-shrink-right" id="LAY_app_flexible"></i>
</a>
</li>
<li class="layui-nav-item" lay-unselect>
<a href="javascript:;" layadmin-event="refresh" title="刷新">
<i class="layui-icon layui-icon-refresh-3"></i>
</a>
</li>
</ul>
<ul class="layui-nav layui-layout-right" lay-filter="layadmin-layout-right">
<li class="layui-nav-item layui-hide-xs" lay-unselect>
<a href="javascript:;" layadmin-event="theme">
<i class="layui-icon layui-icon-theme"></i>
</a>
</li>
<li class="layui-nav-item layui-hide-xs" lay-unselect>
<a href="javascript:;" layadmin-event="note">
<i class="layui-icon layui-icon-note"></i>
</a>
</li>
<li class="layui-nav-item layui-hide-xs" lay-unselect>
<a href="javascript:;" layadmin-event="fullscreen">
<i class="layui-icon layui-icon-screen-full"></i>
</a>
</li>
<li class="layui-nav-item" lay-unselect>
<script type="text/html" template lay-url="{{layui.setter.baseurl}}adminapi/system/user/getuser"
lay-done="layui.element.render('nav', 'layadmin-layout-right');">
<a href="javascript:;">
<cite>{{ (d.data && d.data.displayName) ? d.data.displayName : '用户' }}</cite>
</a>
<dl class="layui-nav-child">
<dd layadmin-event="logout" style="text-align: center;"><a>退出</a></dd>
</dl>
</script>
</li>
</ul>
</div>
<!-- 侧边菜单 -->
<div class="layui-side layui-side-menu">
<div class="layui-side-scroll">
<script type="text/html" template lay-url="{{layui.setter.baseurl}}adminapi/System/user/nav"
lay-done="layui.element.render('nav', 'layadmin-system-side-menu'); initMenuHighlight();" id="TPL_layout">
<div class="layui-logo" lay-href="">
<span>{{ layui.setter.name || 'layuiAdmin' }}</span>
</div>
<ul class="layui-nav layui-nav-tree" id="LAY-system-side-menu" lay-filter="layadmin-system-side-menu">
{{#
var path = layui.router().path
,pathURL = layui.admin.correctRouter(path.join('/'))
,dataName = layui.setter.response.dataName;
layui.each(d[dataName], function(index, item){
var hasChildren = typeof item.children === 'object' && item.children.length > 0
,url = (item.url && typeof item.url === 'string') ? item.url : item.name;
}}
<li data-name="{{ item.name || '' }}" data-jump="{{ item.url || '' }}" class="layui-nav-item">
<a href="javascript:;" {{ hasChildren ? '' : 'lay-href='+ url }} lay-tips="{{ item.name }}" lay-direction="2">
<i class="layui-icon {{ item.icon }}"></i>
<cite>{{ item.name }}</cite>
</a>
{{# if(hasChildren){ }}
<dl class="layui-nav-child">
{{# layui.each(item.children, function(index2, item2){
var hasChildren2 = typeof item2.children == 'object' && item2.children.length > 0
,url2 = (item2.url && typeof item2.url === 'string')
? item2.url
: [item.name, item2.name, ''].join('/');
}}
<dd data-name="{{ item2.name || '' }}" data-jump="{{ item2.url || '' }}" class="layui-nav-item">
<a href="javascript:;" {{ hasChildren2 ? '' : 'lay-href='+ url2 }}>{{ item2.name }}</a>
{{# if(hasChildren2){ }}
<dl class="layui-nav-child">
{{# layui.each(item2.children, function(index3, item3){
var url3 = (item3.url && typeof item3.url === 'string')
? item3.url
: [item.name, item2.name, item3.name].join('/')
}}
<dd data-name="{{ item3.name || '' }}" data-jump="{{ item3.url || '' }}">
<a href="javascript:;" lay-href="{{ url3 }}" {{ item3.iframe ? 'lay-iframe="true"' : '' }}>{{ item3.name }}</a>
</dd>
{{# }); }}
</dl>
{{# } }}
</dd>
{{# }); }}
</dl>
{{# } }}
</li>
{{# }); }}
</ul>
</script>
</div>
</div>
<!-- 页面标签 -->
<div class="layadmin-pagetabs" id="LAY_app_tabs">
<div class="layui-icon layadmin-tabs-control layui-icon-prev" layadmin-event="leftPage"></div>
<div class="layui-icon layadmin-tabs-control layui-icon-next" layadmin-event="rightPage"></div>
<div class="layui-icon layadmin-tabs-control layui-icon-down">
<ul class="layui-nav layadmin-tabs-select" lay-filter="layadmin-pagetabs-nav">
<li class="layui-nav-item" lay-unselect>
<a href="javascript:;"></a>
<dl class="layui-nav-child layui-anim-fadein">
<dd layadmin-event="closeThisTabs"><a href="javascript:;">关闭当前标签页</a></dd>
<dd layadmin-event="closeOtherTabs"><a href="javascript:;">关闭其它标签页</a></dd>
<dd layadmin-event="closeAllTabs"><a href="javascript:;">关闭全部标签页</a></dd>
</dl>
</li>
</ul>
</div>
<div class="layui-tab" lay-unauto lay-allowClose="true" lay-filter="layadmin-layout-tabs">
<ul class="layui-tab-title" id="LAY_app_tabsheader">
<li lay-id="/"><i class="layui-icon layui-icon-home"></i></li>
</ul>
</div>
</div>
<!-- 主体内容 - iframe模式 -->
<div class="layui-body" id="LAY_app_body">
<div class="layadmin-tabsbody-item layui-show" id="LAY_iframe_container">
<iframe frameborder="0" src="" class="layadmin-iframe" id="LAY_admin_iframe"></iframe>
</div>
</div>
<!-- 辅助元素 -->
<div class="layadmin-body-shade" layadmin-event="shade"></div>
</div>
<style>
/* iframe样式 */
.layadmin-iframe {
width: 100%;
height: 100%;
border: none;
background: #fff;
}
/* 修复tabs位置 */
.layadmin-pagetabs {
position: fixed;
top: 50px;
right: 0;
left: 220px;
z-index: 999;
background: #fff;
}
/* 修复body位置 - 有tabs时 */
.layui-layout-admin .layui-body {
top: 90px !important;
}
/* 修复body位置 - 无tabs时 */
.layui-layout-admin .layui-body.no-tabs {
top: 50px !important;
}
/* 防止iframe容器被刷新时重新创建 */
.layadmin-tabsbody-item {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
overflow: hidden;
}
</style>
<script>
// 菜单高亮函数
function initMenuHighlight() {
var $ = layui.$;
var admin = layui.admin;
// 关闭所有layer弹窗和遮罩
function closeAllLayers() {
console.log('closeAllLayers 被调用');
try {
if (typeof layer !== 'undefined') {
console.log('调用 layer.closeAll()');
layer.closeAll();
}
} catch(e) {
console.log('layer.closeAll 错误:', e);
}
console.log('移除 layui-layer-shade');
$('.layui-layer-shade').remove();
console.log('移除 layui-layer');
$('.layui-layer').remove();
// 关闭侧边栏遮罩
console.log('隐藏 layadmin-body-shade');
$('.layadmin-body-shade').hide();
}
// 点击侧边栏链接时关闭弹窗
$(document).on('click', '#LAY-system-side-menu a', function() {
console.log('点击了侧边栏链接');
closeAllLayers();
});
// Tab切换时关闭弹窗
layui.element.on('tab(layadmin-layout-tabs)', function() {
console.log('Tab切换');
closeAllLayers();
});
// 监听iframe加载完成,关闭其他iframe上的弹窗
window.addEventListener('message', function(e) {
if (e.data === 'iframe-focused') {
closeAllLayers();
}
});
// 暴露关闭函数到全局
window.closeAllAdminLayers = closeAllLayers;
// 高亮当前菜单
function highlightMenu(href) {
var sideMenu = $('#LAY-system-side-menu');
if (!sideMenu.length) return;
// 移除所有高亮
sideMenu.find('.layui-this').removeClass('layui-this');
if (!href) {
// 获取当前tab
var $curTab = $('#LAY_app_tabsheader .layui-this');
if ($curTab.length) {
href = $curTab.attr('lay-id');
}
}
if (!href || href === '/') return;
// 标准化路径
href = href.replace(/^\//, '');
// 查找匹配的菜单并高亮
sideMenu.find('a[lay-href]').each(function() {
var menuHref = $(this).attr('lay-href').replace(/^\//, '');
if (menuHref === href) {
var $dd = $(this).closest('dd');
var $li = $(this).closest('li.layui-nav-item');
// 高亮当前
$dd.addClass('layui-this');
// 展开父级
$dd.parents('dd').each(function() {
$(this).addClass('layui-nav-itemed');
});
$li.addClass('layui-nav-itemed');
return false;
}
});
}
// 初始化高亮
setTimeout(function() {
highlightMenu();
}, 100);
// Tab切换时高亮
layui.element.on('tab(layadmin-layout-tabs)', function() {
setTimeout(highlightMenu, 50);
});
// Hash变化时高亮
window.addEventListener('hashchange', function() {
setTimeout(highlightMenu, 50);
});
}
// 暴露到全局
window.initMenuHighlight = initMenuHighlight;
</script>
@@ -0,0 +1,346 @@
<title>邮件列表</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>邮件管理</cite></a>
<a><cite>发送邮件</cite></a>
</div>
</div>
<div class="layui-form" lay-filter="layuiadmin-form-admin" style="padding: 20px 30px 0 0;">
<div class="layui-form-item">
<label class="layui-form-label">标题</label>
<div class="layui-input-inline">
<input type="text" name="title" value="" lay-verify="required" placeholder="发送标题" autocomplete="off" class="layui-input">
</div>
<label class="layui-form-label">内容</label>
<div class="layui-input-inline">
<input type="text" name="content" value="" lay-verify="required" placeholder="发送内容" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">金币</label>
<div class="layui-input-inline">
<input type="number" name="gold" value="0" placeholder="金币数量" autocomplete="off" class="layui-input" style="width: 150px;">
</div>
<label class="layui-form-label">钻石</label>
<div class="layui-input-inline">
<input type="number" name="diamond" value="0" placeholder="钻石数量" autocomplete="off" class="layui-input" style="width: 150px;">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">玩家ID</label>
<div class="layui-input-inline">
<textarea name="userIds" placeholder="请输入玩家ID,多个用逗号分隔,留空则发送给所有玩家" class="layui-textarea"></textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">有效天数</label>
<div class="layui-input-inline">
<input type="number" name="expireDays" value="30" placeholder="默认30天" class="layui-input" style="width: 150px;">
</div>
</div>
<!-- 道具配置列表(初始1项,支持动态添加/删除) -->
<div class="layui-form-item" id="rewardItems">
<div class="layui-form-item layui-form-text" data-index="0">
<!-- 道具选择(带搜索功能) -->
<div class="layui-form-item">
<label class="layui-form-label">赠送道具</label>
<div class="layui-input-inline">
<select name="itemNumber[0]" id="itemNumber[0]" lay-search class="layui-select">
<option value=''>无</option>
</select>
<img class="prop-preview" src="" style="width:40px;height:40px;vertical-align:middle;margin-left:8px;display:none;">
</div>
<label class="layui-form-label">道具数量</label>
<div class="layui-input-inline">
<input type="number" name="itemCount[0]" min="1" value="1" class="layui-input" style="width: 200px;">
</div>
</div>
</div>
</div>
<!-- 添加道具按钮(官方普通按钮样式) -->
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button type="button" class="layui-btn layui-btn-normal" onclick="addRewardItem()">
<i class="layui-icon layui-icon-add-circle"></i> 添加新道具
</button>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-inline">
<input type="button" lay-submit lay-filter="sendEmail" value="确认发送" class="layui-btn">
</div>
</div>
</div>
<script>
layui.use(['admin', 'form'], function(){
var $ = layui.$
, admin = layui.admin
, form = layui.form
, setter = layui.setter;
var propList = [];
// 从API获取道具列表
function loadPropList() {
admin.req({
method: "get",
contentType: "application/json;charset=UTF-8",
dataType: "json",
url: setter.baseurl + "adminapi/GameConfig/PropList",
async: false,
done: function (res) {
if (res.code === 0 && res.data) {
propList = res.data;
renderPropSelect(propList);
}
}
});
}
// 渲染道具下拉框
function renderPropSelect(data) {
var html = '<option value="">请选择道具</option>';
// 按类型分组
var groups = {};
data.forEach(function(item) {
var typeName = getTypeName(item.type);
if (!groups[typeName]) {
groups[typeName] = [];
}
groups[typeName].push(item);
});
// 生成optgroup
for (var typeName in groups) {
html += '<optgroup label="' + typeName + '">';
groups[typeName].forEach(function(item) {
html += '<option value="' + item.number + '">' + item.name + '</option>';
});
html += '</optgroup>';
}
$('select[name^="itemNumber"]').each(function() {
var $select = $(this);
var prevVal = $select.val();
$select.html(html);
if (prevVal) $select.val(prevVal);
});
form.render('select');
// 初始化已选道具的图片预览
$('select[name^="itemNumber"]').each(function() {
var val = $(this).val();
if (val) {
var prop = data.find(function(p) { return p.number === val; });
if (prop) {
var $img = $(this).closest('.layui-input-inline').find('.prop-preview');
$img.attr('src', prop.imageUrl || '/uploads/' + prop.number + '.png').show();
}
}
});
}
// 道具选择变更时更新图片预览
$(document).on('change', 'select[name^="itemNumber"]', function() {
var val = $(this).val();
var $img = $(this).closest('.layui-input-inline').find('.prop-preview');
if (val) {
var prop = propList.find(function(p) { return p.number === val; });
if (prop) {
$img.attr('src', prop.imageUrl || '/uploads/' + prop.number + '.png').show();
}
} else {
$img.hide();
}
});
// 获取类型名称
function getTypeName(type) {
var typeNames = {
1: '炮台',
2: '翅膀',
3: '炮塔碎片',
4: '翅膀碎片',
5: '战神',
6: '战神碎片',
7: '刮刮卡',
8: '改名卡',
9: '大喇叭',
10: '技能',
11: '召唤卡',
12: 'VIP经验',
13: '鱼雷'
};
return typeNames[type] || '其他';
}
// 初始加载道具列表
loadPropList();
form.render();
form.on('submit(sendEmail)', function (data) {
var field = data.field;
// 处理用户ID列表
var userIds = null;
if (field.userIds && field.userIds.trim()) {
userIds = field.userIds.split(',').map(function(id) {
return id.trim();
}).filter(function(id) {
return id;
});
}
// 收集道具
var props = [];
$('[data-index]').each(function(idx) {
var $item = $(this);
var number = $item.find('select[name^="itemNumber"]').val();
var count = parseInt($item.find('input[name^="itemCount"]').val()) || 1;
if (number) {
var propInfo = propList.find(function(p) { return p.number === number; });
props.push({
number: number,
name: propInfo ? propInfo.name : '',
imageUrl: propInfo ? propInfo.imageUrl : '',
type: propInfo ? propInfo.type : 0,
count: count
});
}
});
var param = {
userIds: userIds,
title: field.title,
content: field.content,
gold: parseInt(field.gold) || 0,
diamond: parseInt(field.diamond) || 0,
props: props,
expireDays: parseInt(field.expireDays) || 30
};
console.log('发送邮件参数:', param);
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
dataType: "json",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/adminuser/BatchSendEmail",
async: false,
data: JSON.stringify(param),
done: function (res) {
if (res.code === 0) {
layer.msg("发送成功");
} else {
layer.msg(res.info || "发送失败");
}
}
});
return false;
});
// 记录当前道具配置项索引(用于动态生成唯一标识)
var rewardItemIndex = 1;
/**
* 动态添加道具配置项
*/
window.addRewardItem = function () {
var targetContainer = $('#rewardItems');
var optionsHtml = '<option value="">请选择道具</option>';
var groups = {};
propList.forEach(function(item) {
var typeName = getTypeName(item.type);
if (!groups[typeName]) {
groups[typeName] = [];
}
groups[typeName].push(item);
});
for (var typeName in groups) {
optionsHtml += '<optgroup label="' + typeName + '">';
groups[typeName].forEach(function(item) {
optionsHtml += '<option value="' + item.number + '">' + item.name + '</option>';
});
optionsHtml += '</optgroup>';
}
var newItemHtml = `
<div class="layui-form-item layui-form-text" data-index="${rewardItemIndex}">
<div class="layui-form-item">
<label class="layui-form-label">赠送道具</label>
<div class="layui-input-inline">
<select name="itemNumber[${rewardItemIndex}]" lay-search class="layui-select">
${optionsHtml}
</select>
<img class="prop-preview" src="" style="width:40px;height:40px;vertical-align:middle;margin-left:8px;display:none;">
</div>
<label class="layui-form-label">道具数量</label>
<div class="layui-input-inline">
<input type="number" name="itemCount[${rewardItemIndex}]" min="1" value="1" class="layui-input" style="width: 200px;">
</div>
<div class="layui-input-inline">
<button type="button" class="layui-btn layui-btn-danger layui-btn-sm" onclick="removeRewardItem(${rewardItemIndex})">
<i class="layui-icon layui-icon-close"></i> 删除
</button>
</div>
</div>
</div>
`;
targetContainer.append(newItemHtml);
form.render('select');
form.render('input');
rewardItemIndex++;
};
/**
* 删除道具配置项
*/
window.removeRewardItem = function (index) {
var allItems = $('.layui-form-item[data-index]');
if (allItems.length <= 1) {
layer.msg('至少保留1个道具配置项', { icon: 2, time: 1500 });
return;
}
layer.confirm('确定要删除该道具配置吗?', {
title: '删除确认',
icon: 3
}, function () {
$(`.layui-form-item[data-index="${index}"]`).remove();
layer.msg('删除成功', { icon: 1, time: 1200 });
});
};
})
</script>
@@ -0,0 +1,107 @@
<title>邮件统计</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>邮件管理</cite></a>
<a><cite>邮件统计</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-row layui-col-space15">
<div class="layui-col-sm3">
<div class="layuiadmin-card-elem">
<div class="layuiadmin-stat-card bg-blue">
<div class="stat-number" id="totalEmails">-</div>
<div class="stat-desc">总邮件数</div>
</div>
</div>
</div>
<div class="layui-col-sm3">
<div class="layuiadmin-card-elem">
<div class="layuiadmin-stat-card bg-green">
<div class="stat-number" id="todayEmails">-</div>
<div class="stat-desc">今日发送</div>
</div>
</div>
</div>
<div class="layui-col-sm3">
<div class="layuiadmin-card-elem">
<div class="layuiadmin-stat-card bg-orange">
<div class="stat-number" id="monthEmails">-</div>
<div class="stat-desc">本月发送</div>
</div>
</div>
</div>
<div class="layui-col-sm3">
<div class="layuiadmin-card-elem">
<div class="layuiadmin-stat-card bg-red">
<div class="stat-number" id="unclaimedEmails">-</div>
<div class="stat-desc">未领取</div>
</div>
</div>
</div>
</div>
<div class="layui-row layui-col-space15" style="margin-top: 20px;">
<div class="layui-col-sm3">
<div class="layuiadmin-card-elem">
<div class="layuiadmin-stat-card bg-cyan">
<div class="stat-number" id="rewardEmails">-</div>
<div class="stat-desc">已领取</div>
</div>
</div>
</div>
</div>
</div>
<style>
.layuiadmin-card-elem {
padding: 15px;
}
.layuiadmin-stat-card {
padding: 20px;
border-radius: 8px;
text-align: center;
color: #fff;
}
.bg-blue { background: #1E90FF; }
.bg-green { background: #32CD32; }
.bg-orange { background: #FF8C00; }
.bg-red { background: #FF4500; }
.bg-cyan { background: #00CED1; }
.stat-number {
font-size: 32px;
font-weight: bold;
margin-bottom: 10px;
}
.stat-desc {
font-size: 14px;
opacity: 0.9;
}
</style>
<script>
layui.use(['admin', 'form'], function(){
var $ = layui.$
, admin = layui.admin
, setter = layui.setter;
admin.req({
method: "get",
url: setter.baseurl + "adminapi/adminuser/GetEmailStatistics",
dataType: "json",
done: function (res) {
if (res.code === 0 && res.data) {
var data = res.data;
$('#totalEmails').text(data.totalEmails || 0);
$('#todayEmails').text(data.todayEmails || 0);
$('#monthEmails').text(data.monthEmails || 0);
$('#unclaimedEmails').text(data.unclaimedEmails || 0);
$('#rewardEmails').text(data.rewardEmails || 0);
}
}
});
});
</script>
@@ -0,0 +1,408 @@
<title>公告管理</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>系统</cite></a>
<a><cite>公告管理</cite></a>
</div>
</div>
<script src="/admin/lib/wangEditor/wangEditor.min.js"></script>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-body">
<table id="LAY-notice-manage" lay-filter="LAY-notice-manage"></table>
<script type="text/html" id="table-notice-toolbar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="add"><i class="layui-icon layui-icon-add-1"></i> 发布公告</button>
<button class="layui-btn layui-btn-sm layui-btn-primary" lay-event="refresh"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
</div>
</script>
<script type="text/html" id="table-notice-bar">
<a class="layui-btn layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i> 编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="delete"><i class="layui-icon layui-icon-delete"></i> 删除</a>
</script>
</div>
</div>
</div>
<script>
layui.use(['admin', 'table', 'form', 'layer'], function () {
var $ = layui.$,
admin = layui.admin,
table = layui.table,
form = layui.form,
layer = layui.layer,
setter = layui.setter;
var token = layui.data(setter.tableName)[setter.request.tokenName];
var baseUrl = setter.baseurl + 'adminapi/Notice';
var curPage = 1;
var curLimit = 20;
function ajaxJson(url, data, done) {
$.ajax({
url: url,
method: 'post',
contentType: 'application/json',
headers: { 'token': token },
data: JSON.stringify(data),
success: done
});
}
function loadList(page, limit) {
curPage = page || 1;
curLimit = limit || 20;
ajaxJson(baseUrl + '/list', { pageIndex: curPage, pageSize: curLimit }, function (res) {
table.reload('LAY-notice-manage', {
data: res.data || [],
count: res.count || 0,
page: { curr: curPage, limit: curLimit }
});
});
}
table.render({
elem: '#LAY-notice-manage',
data: [],
page: true,
limit: 20,
limits: [10, 20, 50, 100],
toolbar: '#table-notice-toolbar',
defaultToolbar: [],
cols: [[
{ type: 'numbers', title: '序号', width: 60 },
{ field: 'title', title: '公告标题', minWidth: 200 },
{ field: 'recommend', title: '简介', minWidth: 200 },
{ field: 'topImage', title: '顶部图片', width: 120, templet: function(d) { return d.topImage ? '<img src="' + d.topImage + '" style="width:60px;height:36px;object-fit:cover;border-radius:3px;">' : '-'; } },
{ field: 'createTime', title: '发布时间', width: 170, templet: function(d) {
if (!d.createTime) return '-';
var date = new Date(d.createTime * 1000);
return date.getFullYear() + '-' +
String(date.getMonth() + 1).padStart(2, '0') + '-' +
String(date.getDate()).padStart(2, '0') + ' ' +
String(date.getHours()).padStart(2, '0') + ':' +
String(date.getMinutes()).padStart(2, '0') + ':' +
String(date.getSeconds()).padStart(2, '0');
}},
{ width: 160, title: '操作', align: 'center', fixed: 'right', toolbar: '#table-notice-bar' }
]]
});
loadList(1, 20);
table.on('page(LAY-notice-manage)', function (obj) {
loadList(obj.curr, obj.limit);
});
table.on('toolbar(LAY-notice-manage)', function (obj) {
if (obj.event === 'add') {
showNoticeDialog('add', null);
} else if (obj.event === 'refresh') {
loadList(curPage, curLimit);
}
});
table.on('tool(LAY-notice-manage)', function (obj) {
if (obj.event === 'edit') {
$.ajax({
url: baseUrl + '/get',
method: 'get',
headers: { 'token': token },
data: { id: obj.data.id },
success: function (res) {
if (res.code === 0) {
showNoticeDialog('edit', res.data);
} else {
layer.msg(res.info || '获取公告详情失败');
}
}
});
} else if (obj.event === 'delete') {
layer.confirm('确定要删除公告「' + obj.data.title + '」吗?', function (idx) {
$.ajax({
url: baseUrl + '/delete',
method: 'get',
headers: { 'token': token },
data: { id: obj.data.id },
success: function (res) {
layer.close(idx);
if (res.code === 0) {
layer.msg(res.info || '删除成功');
loadList(curPage, curLimit);
} else {
layer.msg(res.info || '删除失败');
}
}
});
});
}
});
function showNoticeDialog(mode, data) {
var isEdit = mode === 'edit';
var title = isEdit ? '编辑公告' : '发布公告';
// 公告图片先上传临时目录,公告保存成功后由服务端移动到 notice 目录。
var uploadUrl = setter.baseurl + 'adminapi/ImageUpload/upload/temp';
var editor = null;
var topImage = isEdit ? (data.topImage || data.TopImage || '') : '';
var contentImages = isEdit ? ((data.contentImages || data.ContentImages || []).slice()) : [];
var html = '<div class="layui-form" lay-filter="notice-form" style="padding:20px 30px 0 0;">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">公告标题</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="title" lay-verify="required" placeholder="请输入公告标题" class="layui-input" value="' + (isEdit ? escHtml(data.title) : '') + '">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">简介</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="recommend" placeholder="请输入公告简介" class="layui-input" value="' + (isEdit ? escHtml(data.recommend || '') : '') + '">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">顶部图片</label>' +
'<div class="layui-input-block">' +
'<div class="top-image-upload" style="display:flex;align-items:center;gap:10px;">' +
'<button type="button" class="layui-btn layui-btn-sm layui-btn-normal" id="btn-upload-top">选择图片</button>' +
'<div id="top-image-preview" style="max-width:200px;max-height:80px;' + (topImage ? '' : 'display:none') + '">' +
(topImage ? '<img src="' + topImage + '" style="max-width:200px;max-height:80px;border-radius:4px;"><i class="layui-icon layui-icon-close-fill" style="position:relative;left:-16px;top:-30px;cursor:pointer;color:red;font-size:18px;" id="btn-remove-top"></i>' : '') +
'</div>' +
'<input type="hidden" name="topImage" value="' + escHtml(topImage) + '">' +
'</div>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">内容图片</label>' +
'<div class="layui-input-block">' +
'<button type="button" class="layui-btn layui-btn-sm layui-btn-normal" id="btn-upload-content"><i class="layui-icon layui-icon-picture"></i> 添加图片</button>' +
'<div id="content-images-list" style="margin-top:8px;display:flex;flex-wrap:wrap;gap:8px;">' +
(contentImages.length ? contentImages.map(function(img, idx) {
return '<div class="content-img-item" data-index="' + idx + '" style="position:relative;width:120px;height:80px;border:1px solid #e6e6e6;border-radius:4px;overflow:hidden;cursor:move;">' +
'<img src="' + img + '" style="width:100%;height:100%;object-fit:cover;">' +
'<i class="layui-icon layui-icon-close-fill btn-remove-img" style="position:absolute;top:2px;right:2px;cursor:pointer;color:red;font-size:18px;background:#fff;border-radius:50%;" title="删除"></i>' +
'<div style="position:absolute;bottom:0;left:0;right:0;background:rgba(0,0,0,0.5);color:#fff;font-size:11px;text-align:center;">第' + (idx+1) + '张</div>' +
'</div>';
}).join('') : '<div class="empty-tip" style="color:#999;font-size:13px;padding:8px 0;">尚未添加图片</div>') +
'</div>' +
'<input type="hidden" name="contentImages" id="content-images-input" value="' + escHtml(JSON.stringify(contentImages)) + '">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">内容</label>' +
'<div class="layui-input-block">' +
'<div id="notice-content-editor" style="height:360px;border:1px solid #e6e6e6;border-radius:4px;"></div>' +
'</div>' +
'</div>' +
'</div>';
layer.open({
type: 1,
title: title,
area: ['850px', '780px'],
content: html,
btn: ['保存', '取消'],
yes: function (index) {
var titleVal = $('input[name="title"]').val().trim();
var recommendVal = $('input[name="recommend"]').val().trim();
var contentVal = editor ? editor.txt.html().trim() : '';
var topImageVal = $('input[name="topImage"]').val().trim();
var contentImagesVal = [];
try {
contentImagesVal = JSON.parse($('#content-images-input').val() || '[]');
} catch(e) { contentImagesVal = []; }
if (!titleVal) { layer.msg('请输入公告标题'); return; }
if (!contentVal) { layer.msg('请输入公告内容'); return; }
var reqData = isEdit
? { id: data.id, title: titleVal, content: contentVal, recommend: recommendVal, topImage: topImageVal, contentImages: contentImagesVal }
: { title: titleVal, content: contentVal, recommend: recommendVal, topImage: topImageVal, contentImages: contentImagesVal };
ajaxJson(baseUrl + (isEdit ? '/update' : '/add'), reqData, function (res) {
if (res.code === 0) {
layer.close(index);
layer.msg(res.info || (isEdit ? '修改成功' : '发布成功'));
loadList(curPage, curLimit);
} else {
layer.msg(res.info || '操作失败');
}
});
},
success: function (layero) {
form.render();
// 初始化 wangEditor 富文本编辑器
editor = new window.wangEditor('#notice-content-editor');
editor.config.placeholder = '请输入公告内容...';
editor.config.zIndex = 2000;
editor.config.excludeMenus = ['fontName', 'fontSize', 'code', 'codeBlock'];
editor.config.customUploadImg = function (resultFiles, insertImgFn) {
var fd = new FormData();
fd.append('file', resultFiles[0]);
$.ajax({
url: uploadUrl,
method: 'post',
headers: { 'token': token },
data: fd,
processData: false,
contentType: false,
success: function (res) {
if (res.code === 0 && res.data) {
insertImgFn(res.data.path || res.data);
} else {
layer.msg(res.info || '图片上传失败');
}
},
error: function () {
layer.msg('图片上传失败');
}
});
};
editor.create();
if (isEdit && data.content) {
editor.txt.html(data.content);
}
// 顶部图片上传
var fileInput = $('<input type="file" accept="image/*" style="display:none">');
layero.find('#btn-upload-top').after(fileInput);
layero.find('#btn-upload-top').on('click', function() { fileInput.click(); });
fileInput.on('change', function() {
var file = this.files[0];
if (!file) return;
var fd = new FormData();
fd.append('file', file);
$.ajax({
url: uploadUrl,
method: 'post',
headers: { 'token': token },
data: fd,
processData: false,
contentType: false,
success: function(res) {
if (res.code === 0 && res.data) {
var path = res.data.path || res.data;
$('input[name="topImage"]').val(path);
var preview = layero.find('#top-image-preview');
preview.show().html('<img src="' + path + '" style="max-width:200px;max-height:80px;border-radius:4px;"><i class="layui-icon layui-icon-close-fill" style="position:relative;left:-16px;top:-30px;cursor:pointer;color:red;font-size:18px;" id="btn-remove-top"></i>');
bindRemoveTop(layero);
} else {
layer.msg(res.info || '上传失败');
}
}
});
this.value = '';
});
bindRemoveTop(layero);
// 内容图片上传
var contentFileInput = $('<input type="file" accept="image/*" style="display:none">');
layero.find('#btn-upload-content').after(contentFileInput);
layero.find('#btn-upload-content').on('click', function() { contentFileInput.click(); });
contentFileInput.on('change', function() {
var file = this.files[0];
if (!file) return;
var fd = new FormData();
fd.append('file', file);
$.ajax({
url: uploadUrl,
method: 'post',
headers: { 'token': token },
data: fd,
processData: false,
contentType: false,
success: function(res) {
if (res.code === 0 && res.data) {
var path = res.data.path || res.data;
contentImages.push(path);
renderContentImages(layero, contentImages);
} else {
layer.msg(res.info || '上传失败');
}
}
});
this.value = '';
});
// 内容图片拖拽排序
var sortable = function(el) {
var items = el.find('.content-img-item');
var dragIdx = -1;
items.each(function() {
$(this).on('mousedown', function() {
dragIdx = $(this).data('index');
});
});
el.on('mouseup', '.content-img-item', function() {
// 简单的点击交换:拖拽逻辑用 layui 自带的 sort 或简化版
});
};
// 启用拖拽排序(使用原生 drag API 简化实现)
var contentListEl = layero.find('#content-images-list');
contentListEl.on('dragstart', '.content-img-item', function(e) {
e.originalEvent.dataTransfer.setData('text/plain', $(this).data('index'));
});
contentListEl.on('dragover', '.content-img-item', function(e) {
e.preventDefault();
});
contentListEl.on('drop', '.content-img-item', function(e) {
e.preventDefault();
var fromIdx = parseInt(e.originalEvent.dataTransfer.getData('text/plain'));
var toIdx = $(this).data('index');
if (fromIdx !== toIdx && !isNaN(fromIdx) && !isNaN(toIdx)) {
var item = contentImages.splice(fromIdx, 1)[0];
contentImages.splice(toIdx, 0, item);
renderContentImages(layero, contentImages);
}
});
// 内容图片删除
contentListEl.on('click', '.btn-remove-img', function() {
var idx = $(this).closest('.content-img-item').data('index');
contentImages.splice(idx, 1);
renderContentImages(layero, contentImages);
});
}
});
}
function bindRemoveTop(layero) {
layero.find('#btn-remove-top').off().on('click', function() {
$('input[name="topImage"]').val('');
layero.find('#top-image-preview').hide().empty();
});
}
function renderContentImages(layero, images) {
var list = layero.find('#content-images-list');
if (images.length === 0) {
list.html('<div class="empty-tip" style="color:#999;font-size:13px;padding:8px 0;">尚未添加图片</div>');
} else {
list.html(images.map(function(img, idx) {
return '<div class="content-img-item" data-index="' + idx + '" draggable="true" style="position:relative;width:120px;height:80px;border:1px solid #e6e6e6;border-radius:4px;overflow:hidden;cursor:move;display:inline-block;">' +
'<img src="' + img + '" style="width:100%;height:100%;object-fit:cover;">' +
'<i class="layui-icon layui-icon-close-fill btn-remove-img" style="position:absolute;top:2px;right:2px;cursor:pointer;color:red;font-size:18px;background:#fff;border-radius:50%;" title="删除"></i>' +
'<div style="position:absolute;bottom:0;left:0;right:0;background:rgba(0,0,0,0.5);color:#fff;font-size:11px;text-align:center;">第' + (idx+1) + '张</div>' +
'</div>';
}).join(''));
}
$('#content-images-input').val(JSON.stringify(images));
}
function escHtml(str) {
if (!str) return '';
return str.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
});
</script>
@@ -0,0 +1,948 @@
<title>房间管理</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>游戏管理</cite></a>
<a><cite>房间管理</cite></a>
</div>
</div>
<div class="layui-fluid">
<!-- Tab切换 -->
<div class="layui-tab layui-tab-brief" lay-filter="room-tab-filter">
<ul class="layui-tab-title">
<li class="layui-this" lay-id="fish">捕鱼房间</li>
<li lay-id="chess">象棋房间</li>
</ul>
<div class="layui-tab-content">
<!-- 捕鱼房间 -->
<div class="layui-tab-item layui-show">
<div class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="layadmin-room-formlist">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">房间等级</label>
<div class="layui-input-block">
<select name="roomLevel" id="roomLevelFilter">
<option value="">全部</option>
<option value="1">初级场</option>
<option value="2">中级场</option>
<option value="3">高级场</option>
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">游戏类型</label>
<div class="layui-input-block">
<select name="gameType" id="gameTypeFilter">
<option value="">全部</option>
<option value="1">风雷之渊</option>
<option value="2">永夜尸城</option>
<option value="3">中国象棋</option>
<option value="4">星际迷航</option>
<option value="5">黑神话悟空</option>
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">排序方式</label>
<div class="layui-input-block">
<select name="sortField" id="sortFieldFilter">
<option value="roomId" selected>按房间号</option>
<option value="currentOnline">按当前在线</option>
<option value="robotCount">按机器人数量</option>
<option value="roomLevel">按房间等级</option>
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">排序</label>
<div class="layui-input-block">
<select name="sortOrder" id="sortOrderFilter">
<option value="asc" selected>升序</option>
<option value="desc">降序</option>
</select>
</div>
</div>
<div class="layui-inline">
<button class="layui-btn" lay-submit lay-filter="LAY-room-search">
<i class="layui-icon layui-icon-search"></i> 查询
</button>
<button class="layui-btn layui-btn-primary" id="LAY-room-refresh">
<i class="layui-icon layui-icon-refresh"></i> 刷新
</button>
<button class="layui-btn layui-btn-normal" id="LAY-room-auto-refresh">
<i class="layui-icon layui-icon-loading"></i> 自动刷新:关
</button>
<button class="layui-btn layui-btn-warm" id="LAY-room-init">
<i class="layui-icon layui-icon-util"></i> 初始化房间
</button>
</div>
</div>
</div>
<div class="layui-card">
<div class="layui-card-body">
<div class="layui-row layui-col-space15">
<div class="layui-col-md3">
<div class="layui-card">
<div class="layui-card-header">总房间数</div>
<div class="layui-card-body layui-text">
<h3 id="stat-total-rooms">0</h3>
</div>
</div>
</div>
<div class="layui-col-md3">
<div class="layui-card">
<div class="layui-card-header">在线玩家</div>
<div class="layui-card-body layui-text">
<h3 id="stat-total-players" style="color: #5FB878;">0</h3>
</div>
</div>
</div>
<div class="layui-col-md3">
<div class="layui-card">
<div class="layui-card-header">机器人</div>
<div class="layui-card-body layui-text">
<h3 id="stat-total-robots" style="color: #FFB800;">0</h3>
</div>
</div>
</div>
<div class="layui-col-md3">
<div class="layui-card">
<div class="layui-card-header">总人数</div>
<div class="layui-card-body layui-text">
<h3 id="stat-total-count" style="color: #1E9FFF;">0</h3>
</div>
</div>
</div>
</div>
<br>
<button class="layui-btn layui-btn-normal" id="btn-regenerate-bot-ids">
<i class="layui-icon layui-icon-refresh"></i> 一键刷新机器人编号
</button>
<table id="LAY-room-manage" lay-filter="LAY-room-manage"></table>
<script type="text/html" id="table-room-admin">
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="detail">
<i class="layui-icon layui-icon-about"></i> 详情
</a>
<a class="layui-btn layui-btn-xs" lay-event="addRobot">
<i class="layui-icon layui-icon-add-1"></i> 添加机器人
</a>
<a class="layui-btn layui-btn-warm layui-btn-xs" lay-event="setRobot">
<i class="layui-icon layui-icon-set"></i> 设置机器人
</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="clear">
<i class="layui-icon layui-icon-close"></i> 清空玩家
</a>
</script>
</div>
</div>
</div>
<!-- 象棋房间 -->
<div class="layui-tab-item">
<div class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="layadmin-chess-formlist">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">房间状态</label>
<div class="layui-input-block">
<select name="state" id="chessStateFilter">
<option value="">全部</option>
<option value="0">空房</option>
<option value="1">待开始</option>
<option value="2">进行中</option>
<option value="3">已结束</option>
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">游戏类型</label>
<div class="layui-input-block">
<select name="gameType" id="chessGameTypeFilter">
<option value="">全部</option>
<option value="1">我方黑</option>
<option value="2">我方红</option>
</select>
</div>
</div>
<div class="layui-inline">
<button class="layui-btn" lay-submit lay-filter="LAY-chess-search">
<i class="layui-icon layui-icon-search"></i> 查询
</button>
<button class="layui-btn layui-btn-primary" id="LAY-chess-refresh">
<i class="layui-icon layui-icon-refresh"></i> 刷新
</button>
<button class="layui-btn layui-btn-normal" id="LAY-chess-auto-refresh">
<i class="layui-icon layui-icon-loading"></i> 自动刷新:关
</button>
<button class="layui-btn layui-btn-warm" id="LAY-chess-reinit">
<i class="layui-icon layui-icon-util"></i> 重新初始化象棋房间
</button>
</div>
</div>
</div>
<div class="layui-card">
<div class="layui-card-body">
<div class="layui-row layui-col-space15">
<div class="layui-col-md2">
<div class="layui-card">
<div class="layui-card-header">总房间数</div>
<div class="layui-card-body layui-text">
<h3 id="chess-stat-total">0</h3>
</div>
</div>
</div>
<div class="layui-col-md2">
<div class="layui-card">
<div class="layui-card-header">空房率</div>
<div class="layui-card-body layui-text">
<h3 id="chess-stat-waiting" style="color: #FFB800;">0</h3>
</div>
</div>
</div>
<div class="layui-col-md2">
<div class="layui-card">
<div class="layui-card-header">进行中</div>
<div class="layui-card-body layui-text">
<h3 id="chess-stat-playing" style="color: #5FB878;">0</h3>
</div>
</div>
</div>
<div class="layui-col-md2">
<div class="layui-card">
<div class="layui-card-header">已结束</div>
<div class="layui-card-body layui-text">
<h3 id="chess-stat-finished" style="color: #999;">0</h3>
</div>
</div>
</div>
<div class="layui-col-md2">
<div class="layui-card">
<div class="layui-card-header">黑方玩家</div>
<div class="layui-card-body layui-text">
<h3 id="chess-stat-black" style="color: #393939;">0</h3>
</div>
</div>
</div>
<div class="layui-col-md2">
<div class="layui-card">
<div class="layui-card-header">红方玩家</div>
<div class="layui-card-body layui-text">
<h3 id="chess-stat-red" style="color: #FF5722;">0</h3>
</div>
</div>
</div>
</div>
<br>
<table id="LAY-chess-room-manage" lay-filter="LAY-chess-room-manage"></table>
<script type="text/html" id="table-chess-admin">
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="detail">
<i class="layui-icon layui-icon-about"></i> 详情
</a>
<a class="layui-btn layui-btn-xs" lay-event="addRobot">
<i class="layui-icon layui-icon-add-1"></i> 添加机器人
</a>
<a class="layui-btn layui-btn-warm layui-btn-xs" lay-event="setRobot">
<i class="layui-icon layui-icon-edit"></i> 设置机器人
</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="clear">
<i class="layui-icon layui-icon-close"></i> 清空房间
</a>
<a class="layui-btn layui-btn-warm layui-btn-xs" lay-event="delete">
<i class="layui-icon layui-icon-delete"></i> 删除
</a>
</script>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
layui.use(['admin', 'table', 'form', 'element'], function () {
var $ = layui.$,
admin = layui.admin,
table = layui.table,
form = layui.form,
element = layui.element,
setter = layui.setter,
layer = layui.layer;
var autoRefreshFish = false;
var refreshIntervalFish = null;
var autoRefreshChess = false;
var refreshIntervalChess = null;
form.render();
// ============ 捕鱼房间 ============
table.render({
elem: "#LAY-room-manage",
url: setter.baseurl + "adminapi/Admin/Room/GetAllRooms",
method: "get",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
page: { curr: 1, limit: 20 },
where: {
roomLevel: "",
gameType: "",
sortField: "roomId",
sortOrder: "asc"
},
request: {
pageName: 'pageIndex',
limitName: 'pageSize'
},
limit: 20,
cols: [[
{ field: "roomId", title: "房间ID", width: 180, fixed: 'left' },
{ field: "roomName", title: "房间名称", width: 120 },
{ field: "roomLevel", title: "等级", width: 80 },
{ field: "roomPartition", title: "分区", width: 70 },
{ field: "roomNumber", title: "编号", width: 70 },
{ field: "gameType", title: "游戏类型", width: 100 },
{ field: "maxPlayerCount", title: "最大人数", width: 90 },
{ field: "currentOnlinePlayerCount", title: "当前在线", width: 90, templet: function(d) {
var count = d.currentOnlinePlayerCount || 0;
return '<span class="layui-badge layui-bg-green">' + count + '</span>';
}},
{ field: "robotCount", title: "机器人", width: 90, templet: function(d) {
var count = d.robotCount || 0;
return '<span class="layui-badge layui-bg-orange">' + count + '</span>';
}},
{
field: "players", title: "玩家列表", minWidth: 200, templet: function(d) {
if (!d.players || d.players.length === 0) return '-';
var names = d.players.map(p => p.userName || p.userId).slice(0, 3);
var result = names.join(', ');
if (d.players.length > 3) result += ' 等' + d.players.length + '人';
return result;
}
},
{ width: 260, title: "操作", align: "center", fixed: "right", toolbar: "#table-room-admin" }
]],
text: "对不起,加载出现异常!",
response: {
statusName: 'code',
statusCode: 0,
msgName: 'info',
countName: 'count',
dataName: 'data'
},
done: function(res) {
loadStatistics();
}
});
function loadStatistics() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/Admin/Room/GetRoomStatistics",
done: function(res) {
if (res.code === 0 && res.data) {
$('#stat-total-rooms').text(res.data.totalRooms || 0);
$('#stat-total-players').text(res.data.totalPlayers || 0);
$('#stat-total-robots').text(res.data.totalRobots || 0);
$('#stat-total-count').text((res.data.totalPlayers || 0) + (res.data.totalRobots || 0));
}
}
});
}
form.on('submit(LAY-room-search)', function (data) {
table.reload('LAY-room-manage', {
where: {
roomLevel: data.field.roomLevel || '',
gameType: data.field.gameType || '',
sortField: data.field.sortField || 'roomId',
sortOrder: data.field.sortOrder || 'desc'
},
page: { curr: 1 }
});
return false;
});
$('#LAY-room-refresh').on('click', function () {
table.reload('LAY-room-manage');
});
$('#btn-regenerate-bot-ids').on('click', function () {
layer.confirm('确定要刷新所有机器人的编号吗?机器人将获得新的ID和编号。', function (index) {
layer.close(index);
var loadIndex = layer.load(2);
admin.req({
method: "get",
url: setter.baseurl + "adminapi/Admin/Room/RegenerateAllBotIds",
done: function (res) {
layer.close(loadIndex);
layer.msg(res.info || '刷新成功');
table.reload('LAY-room-manage');
},
error: function () {
layer.close(loadIndex);
layer.msg('操作失败');
}
});
});
});
$('#LAY-room-auto-refresh').on('click', function () {
autoRefreshFish = !autoRefreshFish;
var btn = $(this);
if (autoRefreshFish) {
btn.html('<i class="layui-icon layui-icon-loading layui-anim layui-anim-rotate layui-anim-loop"></i> 自动刷新:开');
btn.addClass('layui-btn-normal');
refreshIntervalFish = setInterval(function () {
table.reload('LAY-room-manage', { page: { curr: 1 } });
}, 5000);
} else {
btn.html('<i class="layui-icon layui-icon-loading"></i> 自动刷新:关');
btn.removeClass('layui-btn-normal');
if (refreshIntervalFish) {
clearInterval(refreshIntervalFish);
refreshIntervalFish = null;
}
}
});
$('#LAY-room-init').on('click', function () {
layer.confirm('确定要重新初始化所有房间吗?此操作将清空现有房间数据并重新生成。', function (index) {
var btn = $(this);
btn.html('<i class="layui-icon layui-icon-loading layui-anim layui-anim-rotate layui-anim-loop"></i> 初始化中...');
btn.attr('disabled', true);
admin.req({
method: "get",
url: setter.baseurl + "adminapi/Admin/Room/InitRooms",
done: function (res) {
layer.close(index);
if (res.code === 0) {
layer.msg(res.info || '房间初始化成功', { icon: 1 });
table.reload('LAY-room-manage');
loadStatistics();
} else {
layer.msg(res.info || '初始化失败');
}
$('#LAY-room-init').html('<i class="layui-icon layui-icon-util"></i> 初始化房间');
$('#LAY-room-init').attr('disabled', false);
}
});
});
});
table.on("tool(LAY-room-manage)", function (obj) {
var data = obj.data;
if ("detail" === obj.event) {
showFishRoomDetail(data);
}
if ("addRobot" === obj.event) {
layer.prompt({
title: '添加机器人 - ' + data.roomName,
formType: 0
}, function (value, index, elem) {
var count = parseInt(value) || 0;
if (count <= 0) {
layer.msg('请输入有效的数量');
return;
}
admin.req({
method: "get",
url: setter.baseurl + "adminapi/Admin/Room/AddRobot",
data: {
roomId: data.roomId,
count: count
},
done: function (res) {
layer.close(index);
layer.msg(res.info || '添加成功');
table.reload('LAY-room-manage');
}
});
});
}
if ("setRobot" === obj.event) {
layer.prompt({
title: '设置机器人数量 - ' + data.roomName,
formType: 0,
value: data.robotCount || 0
}, function (value, index, elem) {
var count = parseInt(value) || 0;
if (count < 0) {
layer.msg('数量不能为负数');
return;
}
admin.req({
method: "get",
url: setter.baseurl + "adminapi/Admin/Room/SetRobotCount",
data: {
roomId: data.roomId,
count: count
},
done: function (res) {
layer.close(index);
layer.msg(res.info || '设置成功');
table.reload('LAY-room-manage');
}
});
});
}
if ("clear" === obj.event) {
layer.confirm('确定清空房间 ' + data.roomName + ' 的所有玩家吗?', function (index) {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/Admin/Room/ClearRoom",
data: {
roomId: data.roomId
},
done: function (res) {
layer.close(index);
if (res.code === 0) {
layer.msg(res.info || '清空成功');
} else {
layer.msg(res.info || '清空失败');
}
table.reload('LAY-room-manage');
}
});
});
}
});
function showFishRoomDetail(room) {
var playersHtml = '';
if (room.players && room.players.length > 0) {
playersHtml = '<table class="layui-table"><thead><tr><th>玩家ID</th><th>昵称</th><th>金币</th><th>炮倍</th><th>位置</th></tr></thead><tbody>';
room.players.forEach(function (p) {
playersHtml += '<tr><td>' + (p.userId || '-') + '</td><td>' + (p.userName || '-') + '</td><td>' + (p.gold || 0) + '</td><td>' + (p.cannonMultiple || 0) + '</td><td>' + (p.position || 0) + '</td></tr>';
});
playersHtml += '</tbody></table>';
} else {
playersHtml = '<p style="text-align:center;color:#999;">暂无玩家</p>';
}
var content =
'<div style="padding: 20px;">' +
'<fieldset class="layui-elem-field">' +
'<legend>房间信息</legend>' +
'<div class="layui-field-box">' +
'<p>房间ID:' + (room.roomId || '-') + '</p>' +
'<p>房间名称:' + (room.roomName || '-') + '</p>' +
'<p>游戏类型:' + (room.gameType || '-') + '</p>' +
'<p>房间等级:' + (room.roomLevel || '-') + '</p>' +
'<p>房间分区:' + (room.roomPartition || '-') + '</p>' +
'<p>房间编号:' + (room.roomNumber || '-') + '</p>' +
'<p>最大人数:' + (room.maxPlayerCount || 0) + '</p>' +
'<p>当前在线:' + (room.currentOnlinePlayerCount || 0) + '</p>' +
'<p>机器人数:' + (room.robotCount || 0) + '</p>' +
'</div>' +
'</fieldset>' +
'<fieldset class="layui-elem-field">' +
'<legend>玩家列表 (' + (room.players ? room.players.length : 0) + '人)</legend>' +
'<div class="layui-field-box">' +
playersHtml +
'</div>' +
'</fieldset>' +
'</div>';
layer.open({
type: 1,
title: '房间详情',
area: ['700px', '600px'],
content: content
});
}
// ============ 象棋房间 ============
function renderChessTable(state, gameType) {
table.render({
elem: "#LAY-chess-room-manage",
url: setter.baseurl + "adminapi/Admin/Room/GetAllChessRooms",
method: "get",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
page: true,
where: {
state: state || "",
gameType: gameType || ""
},
request: {
pageName: 'pageIndex',
limitName: 'pageSize'
},
limit: 20,
parseData: function(res) {
console.log('象棋房间数据:', res);
return {
"code": res.code,
"msg": res.info,
"count": res.count,
"data": res.data || []
};
},
cols: [[
{ field: "roomId", title: "房间ID", width: 200, fixed: 'left', templet: function(d) {
return d.roomId || '-';
}},
{ field: "state", title: "状态", width: 100, templet: function(d) {
var stateMap = {0: '<span class="layui-badge layui-bg-blue">空房</span>', 1: '<span class="layui-badge layui-bg-orange">待开始</span>', 2: '<span class="layui-badge layui-bg-green">进行中</span>', 3: '<span class="layui-badge layui-bg-gray">已结束</span>'};
return stateMap[d.state] || '-';
}},
{ field: "gameType", title: "游戏类型", width: 100, templet: function(d) {
return d.gameType === 1 ? '我方黑' : '我方红';
}},
{ field: "gold", title: "房间金币", width: 120 },
{ field: "password", title: "密码", width: 100, templet: function(d) {
return d.password ? '<span class="layui-badge">有密码</span>' : '<span class="layui-badge layui-bg-green">无</span>';
}},
{ field: "createTime", title: "创建时间", width: 170, templet: function(d) {
var ct = d.createTime || d.CreateTime;
if (!ct) return '-';
var date = new Date(ct);
return date.getFullYear() + '-' +
String(date.getMonth() + 1).padStart(2, '0') + '-' +
String(date.getDate()).padStart(2, '0') + ' ' +
String(date.getHours()).padStart(2, '0') + ':' +
String(date.getMinutes()).padStart(2, '0') + ':' +
String(date.getSeconds()).padStart(2, '0');
}},
{ field: "userCount", title: "玩家数", width: 80, templet: function(d) {
var count = (d.users && d.users.length) || 0;
return '<span class="layui-badge">' + count + '/2</span>';
}},
{ field: "robotCount", title: "机器人", width: 80, templet: function(d) {
var count = d.robotCount || 0;
return '<span class="layui-badge layui-bg-orange">' + count + '</span>';
}},
{ field: "users", title: "玩家信息", minWidth: 250, templet: function(d) {
if (!d.users || d.users.length === 0) return '<span style="color:#999;">暂无玩家</span>';
var html = '<div style="display:flex;gap:10px;">';
d.users.forEach(function(u) {
var side = u.type === 1 ? '黑方' : '红方';
var ready = u.isReady ? '<span class="layui-badge layui-bg-green">已准备</span>' : '<span class="layui-badge layui-bg-gray">未准备</span>';
html += '<div style="background:#f5f5f5;padding:5px 10px;border-radius:3px;">' +
'<div style="font-weight:bold;">' + (u.userName || u.userId) + '</div>' +
'<div style="font-size:12px;color:#666;">' + side + '</div>' +
'<div style="font-size:12px;">' + ready + '</div>' +
'</div>';
});
html += '</div>';
return html;
}},
{ width: 220, title: "操作", align: "center", fixed: "right", toolbar: "#table-chess-admin" }
]],
text: "对不起,加载出现异常!",
response: {
statusName: 'code',
statusCode: 0,
msgName: 'info',
countName: 'count',
dataName: 'data'
},
done: function(res) {
loadChessStatistics();
}
});
}
function loadChessStatistics() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/Admin/Room/GetChessRoomStatistics",
done: function(res) {
if (res.code === 0 && res.data) {
$('#chess-stat-total').text(res.data.totalRooms || 0);
$('#chess-stat-waiting').text((res.data.emptyRooms || 0) + ' (' + (res.data.emptyRoomRate || 0) + '%)');
$('#chess-stat-playing').text(res.data.playingRooms || 0);
$('#chess-stat-finished').text(res.data.finishedRooms || 0);
$('#chess-stat-black').text(res.data.blackSidePlayers || 0);
$('#chess-stat-red').text(res.data.redSidePlayers || 0);
}
}
});
}
form.on('submit(LAY-chess-search)', function (data) {
renderChessTable(data.field.state, data.field.gameType);
return false;
});
$('#LAY-chess-refresh').on('click', function () {
table.reload('LAY-chess-room-manage');
});
$('#LAY-chess-reinit').on('click', function () {
layer.confirm('确定要清空所有象棋房间并重新初始化吗?此操作不可恢复!', function (index) {
var btn = $(this);
btn.html('<i class="layui-icon layui-icon-loading layui-anim layui-anim-rotate layui-anim-loop"></i> 初始化中...');
btn.attr('disabled', true);
admin.req({
method: "get",
url: setter.baseurl + "adminapi/Admin/Room/ReInitChessRooms",
done: function (res) {
layer.close(index);
if (res.code === 0 || res.code === 200) {
layer.msg(res.info || '初始化成功', { icon: 1 });
table.reload('LAY-chess-room-manage');
loadChessStatistics();
} else {
layer.msg(res.info || '初始化失败');
}
$('#LAY-chess-reinit').html('<i class="layui-icon layui-icon-util"></i> 重新初始化象棋房间');
$('#LAY-chess-reinit').attr('disabled', false);
}
});
});
});
$('#LAY-chess-auto-refresh').on('click', function () {
autoRefreshChess = !autoRefreshChess;
var btn = $(this);
if (autoRefreshChess) {
btn.html('<i class="layui-icon layui-icon-loading layui-anim layui-anim-rotate layui-anim-loop"></i> 自动刷新:开');
btn.addClass('layui-btn-normal');
refreshIntervalChess = setInterval(function () {
table.reload('LAY-chess-room-manage');
}, 5000);
} else {
btn.html('<i class="layui-icon layui-icon-loading"></i> 自动刷新:关');
btn.removeClass('layui-btn-normal');
if (refreshIntervalChess) {
clearInterval(refreshIntervalChess);
refreshIntervalChess = null;
}
}
});
table.on("tool(LAY-chess-room-manage)", function (obj) {
var data = obj.data;
if ("detail" === obj.event) {
showChessRoomDetail(data);
}
if ("clear" === obj.event) {
layer.confirm('确定清空房间 ' + data.roomId + ' 的所有玩家吗?', function (index) {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/Admin/Room/ClearChessRoom",
data: {
roomId: data.roomId
},
done: function (res) {
layer.close(index);
if (res.code === 0) {
layer.msg(res.info || '清空成功');
} else {
layer.msg(res.info || '清空失败');
}
table.reload('LAY-chess-room-manage');
}
});
});
}
if ("addRobot" === obj.event) {
layer.prompt({
title: '添加机器人 - ' + data.roomId,
formType: 0
}, function (value, index, elem) {
var count = parseInt(value) || 0;
if (count <= 0) {
layer.msg('请输入有效的数量');
return;
}
admin.req({
method: "get",
url: setter.baseurl + "adminapi/Admin/Room/AddChessRobot",
data: {
roomId: data.roomId,
count: count
},
done: function (res) {
layer.close(index);
layer.msg(res.info || '添加成功');
table.reload('LAY-chess-room-manage');
}
});
});
}
if ("setRobot" === obj.event) {
var currentCount = data.robotCount || 0;
layer.prompt({
title: '设置机器人数量 - ' + data.roomId + ' (当前: ' + currentCount + ')',
formType: 0,
value: currentCount
}, function (value, index, elem) {
var count = parseInt(value);
if (isNaN(count) || count < 0 || count > 2) {
layer.msg('请输入0~2的整数');
return;
}
admin.req({
method: "get",
url: setter.baseurl + "adminapi/Admin/Room/SetChessRobot",
data: {
roomId: data.roomId,
count: count
},
done: function (res) {
layer.close(index);
layer.msg(res.info || '设置成功');
table.reload('LAY-chess-room-manage');
}
});
});
}
if ("delete" === obj.event) {
layer.confirm('确定删除房间 ' + data.roomId + ' 吗?此操作不可恢复!', function (index) {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/Admin/Room/DeleteChessRoom",
data: {
roomId: data.roomId
},
done: function (res) {
layer.close(index);
layer.msg(res.info || '删除成功');
table.reload('LAY-chess-room-manage');
}
});
});
}
});
function showChessRoomDetail(room) {
var stateMap = {0: '空房', 1: '待开始', 2: '进行中', 3: '已结束'};
var typeMap = {1: '我方黑', 2: '我方红'};
var usersHtml = '';
if (room.users && room.users.length > 0) {
usersHtml = '<table class="layui-table"><thead><tr><th>玩家ID</th><th>昵称</th><th>阵营</th><th>状态</th><th>金币</th><th>操作</th></tr></thead><tbody>';
room.users.forEach(function (u) {
usersHtml += '<tr>' +
'<td>' + (u.userId || '-') + '</td>' +
'<td>' + (u.userName || '-') + '</td>' +
'<td>' + (typeMap[u.type] || '-') + '</td>' +
'<td>' + (u.isReady ? '已准备' : '未准备') + '</td>' +
'<td>' + (u.gold || 0) + '</td>' +
'<td><button type="button" class="layui-btn layui-btn-danger layui-btn-xs chess-kick-player" ' +
'data-room-id="' + (room.roomId || room.RoomId || '') + '" data-user-id="' + (u.userId || '') + '" ' +
(room.state === 2 ? 'disabled title="游戏进行中不能踢人"' : '') + '>踢出</button></td>' +
'</tr>';
});
usersHtml += '</tbody></table>';
} else {
usersHtml = '<p style="text-align:center;color:#999;">暂无玩家</p>';
}
var createTimeStr = '-';
var ct = room.createTime || room.CreateTime;
if (ct) {
var date = new Date(ct);
createTimeStr = date.getFullYear() + '-' +
String(date.getMonth() + 1).padStart(2, '0') + '-' +
String(date.getDate()).padStart(2, '0') + ' ' +
String(date.getHours()).padStart(2, '0') + ':' +
String(date.getMinutes()).padStart(2, '0') + ':' +
String(date.getSeconds()).padStart(2, '0');
}
var content =
'<div style="padding: 20px;">' +
'<fieldset class="layui-elem-field">' +
'<legend>房间信息</legend>' +
'<div class="layui-field-box">' +
'<p>房间ID:' + (room.roomId || room.RoomId || '-') + '</p>' +
'<p>房间状态:' + (stateMap[room.state] || '-') + '</p>' +
'<p>游戏类型:' + (typeMap[room.gameType] || '-') + '</p>' +
'<p>房间金币:' + (room.gold || 0) + '</p>' +
'<p>密码:' + (room.password ? room.password : '无') + '</p>' +
'<p>创建时间:' + createTimeStr + '</p>' +
'<p>机器人数:' + (room.robotCount || 0) + '</p>' +
'<p>创建者:' + (room.createUserId || '-') + '</p>' +
'</div>' +
'</fieldset>' +
'<fieldset class="layui-elem-field">' +
'<legend>玩家列表 (' + ((room.users && room.users.length) || 0) + '人)</legend>' +
'<div class="layui-field-box">' +
usersHtml +
'</div>' +
'</fieldset>' +
'</div>';
layer.open({
type: 1,
title: '象棋房间详情',
area: ['700px', '600px'],
content: content,
success: function(layero, index) {
layero.find('.chess-kick-player').on('click', function() {
var button = $(this);
var roomId = button.attr('data-room-id');
var userId = button.attr('data-user-id');
if (button.prop('disabled')) return;
layer.confirm('确定将玩家 ' + userId + ' 踢出房间 ' + roomId + ' 吗?', function(confirmIndex) {
button.prop('disabled', true);
admin.req({
method: 'get',
url: setter.baseurl + 'adminapi/Admin/Room/KickChessPlayer',
data: { roomId: roomId, userId: userId },
done: function(res) {
layer.close(confirmIndex);
if (res.code === 0) {
layer.msg(res.info || '踢人指令已提交', { icon: 1 });
layer.close(index);
setTimeout(function() {
table.reload('LAY-chess-room-manage');
loadChessStatistics();
}, 800);
} else {
button.prop('disabled', false);
layer.msg(res.info || '踢人失败', { icon: 2 });
}
}
});
});
});
}
});
}
// 页面切换时渲染象棋表格
element.on('tab(room-tab-filter)', function(data){
if (data.index === 1) {
renderChessTable('', '');
}
});
// 页面卸载时清除定时器
$(window).on('unload', function () {
if (refreshIntervalFish) clearInterval(refreshIntervalFish);
if (refreshIntervalChess) clearInterval(refreshIntervalChess);
});
});
</script>
@@ -0,0 +1,68 @@
<title>邮件服务</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>设置</cite></a>
<a><cite>邮件服务</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-row layui-col-space15">
<div class="layui-col-md12">
<div class="layui-card">
<div class="layui-card-header">邮件服务</div>
<div class="layui-card-body">
<div class="layui-form" wid100 lay-filter="">
<div class="layui-form-item">
<label class="layui-form-label">SMTP服务器</label>
<div class="layui-input-inline">
<input type="text" name="smtp_server" value="smtp.aliyun.com" class="layui-input">
</div>
<div class="layui-form-mid layui-word-aux">如:smtp.163.com</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">SMTP端口号</label>
<div class="layui-input-inline" style="width: 80px;">
<input type="text" name="cache" lay-verify="number" value="25" class="layui-input">
</div>
<div class="layui-form-mid layui-word-aux">一般为 25 或 465</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">发件人邮箱</label>
<div class="layui-input-inline">
<input type="text" name="send_email" value="xianxin@layui-inc.com" lay-verify="email" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">发件人昵称</label>
<div class="layui-input-inline">
<input type="text" name="send_nickname" value="贤心" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">邮箱登入密码</label>
<div class="layui-input-inline">
<input type="password" name="send_nickname" value="123456" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="set_system_email">确认保存</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
layui.use('set', layui.factory('set'));
</script>
@@ -0,0 +1,93 @@
<title>网站设置</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>设置</cite></a>
<a><cite>网站设置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-row layui-col-space15">
<div class="layui-col-md12">
<div class="layui-card">
<div class="layui-card-header">网站设置</div>
<div class="layui-card-body" pad15>
<div class="layui-form" wid100 lay-filter="">
<div class="layui-form-item">
<label class="layui-form-label">网站名称</label>
<div class="layui-input-block">
<input type="text" name="sitename" value="layuiAdmin" 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="domain" lay-verify="url" value="http://www.layui.com" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">缓存时间</label>
<div class="layui-input-inline" style="width: 80px;">
<input type="text" name="cache" lay-verify="number" value="0" class="layui-input">
</div>
<div class="layui-input-inline layui-input-company">分钟</div>
<div class="layui-form-mid layui-word-aux">本地开发一般推荐设置为 0,线上环境建议设置为 10。</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">最大文件上传</label>
<div class="layui-input-inline" style="width: 80px;">
<input type="text" name="cache" lay-verify="number" value="2048" class="layui-input">
</div>
<div class="layui-input-inline layui-input-company">KB</div>
<div class="layui-form-mid layui-word-aux">提示:1 M = 1024 KB</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">上传文件类型</label>
<div class="layui-input-block">
<input type="text" name="cache" value="png|gif|jpg|jpeg|zip|rar" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">首页标题</label>
<div class="layui-input-block">
<textarea name="title" class="layui-textarea">layuiAdmin 通用后台管理模板系统</textarea>
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">META关键词</label>
<div class="layui-input-block">
<textarea name="keywords" class="layui-textarea" placeholder="多个关键词用英文状态 , 号分割"></textarea>
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">META描述</label>
<div class="layui-input-block">
<textarea name="descript" class="layui-textarea">layuiAdmin 是 layui 官方出品的通用型后台模板解决方案,提供了单页版和 iframe 版两种开发模式。layuiAdmin 是目前非常流行的后台模板框架,广泛用于各类管理平台。</textarea>
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">版权信息</label>
<div class="layui-input-block">
<textarea name="copyright" class="layui-textarea">© 2018 layui.com MIT license</textarea>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="set_website">确认保存</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
layui.use('set', layui.factory('set'));
</script>
@@ -0,0 +1,21 @@
<div class="layui-card-header">版本信息</div>
<div class="layui-card-body layui-text layadmin-about">
<script type="text/html" template>
<p>当前版本:layuiAdmin-v{{ layui.admin.v }}</p>
<p>基于框架:layui-v{{ layui.v }}</p>
</script>
<div class="layui-btn-container">
<a href="http://www.layui.com/admin/" target="_blank" class="layui-btn layui-btn-danger">获取授权</a>
<a href="http://fly.layui.com/download/layuiAdmin/" target="_blank" class="layui-btn">下载新版</a>
</div>
</div>
<div class="layui-card-header">关于版权</div>
<div class="layui-card-body layui-text layadmin-about">
<blockquote class="layui-elem-quote" style="border: none;">
layuiAdmin 受国家计算机软件著作权保护,未经官网正规渠道授权擅自公开产品源文件、以及直接对产品二次出售的,我们将保留追究法律责任的权利。
</blockquote>
<p>© 2018 <a href="http://www.layui.com/">layui.com</a> 版权所有</p>
</div>
@@ -0,0 +1,98 @@
<title>设置我的资料</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>设置</cite></a>
<a><cite>我的资料</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-row layui-col-space15">
<div class="layui-col-md12">
<div class="layui-card">
<div class="layui-card-header">设置我的资料</div>
<div class="layui-card-body" pad15>
<div class="layui-form" lay-filter="">
<div class="layui-form-item">
<label class="layui-form-label">我的角色</label>
<div class="layui-input-inline">
<select name="role" lay-verify="">
<option value="1" selected>超级管理员</option>
<option value="2" disabled>普通管理员</option>
<option value="3" disabled>审核员</option>
<option value="4" disabled>编辑人员</option>
</select>
</div>
<div class="layui-form-mid layui-word-aux">当前角色不可更改为其它角色</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">用户名</label>
<div class="layui-input-inline">
<input type="text" name="username" value="xianxin" readonly class="layui-input">
</div>
<div class="layui-form-mid layui-word-aux">不可修改。一般用于后台登入名</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">昵称</label>
<div class="layui-input-inline">
<input type="text" name="nickname" value="贤心" lay-verify="nickname" autocomplete="off" placeholder="请输入昵称" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">性别</label>
<div class="layui-input-block">
<input type="radio" name="sex" value="男" title="男">
<input type="radio" name="sex" value="女" title="女" checked>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">头像</label>
<div class="layui-input-inline">
<input name="avatar" lay-verify="required" id="LAY_avatarSrc" placeholder="图片地址" value="http://cdn.layui.com/avatar/168.jpg" class="layui-input">
</div>
<div class="layui-input-inline layui-btn-container" style="width: auto;">
<button type="button" class="layui-btn layui-btn-primary" id="LAY_avatarUpload">
<i class="layui-icon">&#xe67c;</i>上传图片
</button>
<button class="layui-btn layui-btn-primary" layadmin-event="avartatPreview">查看图片</button >
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">手机</label>
<div class="layui-input-inline">
<input type="text" name="cellphone" value="" lay-verify="phone" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">邮箱</label>
<div class="layui-input-inline">
<input type="text" name="email" value="" lay-verify="email" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">备注</label>
<div class="layui-input-block">
<textarea name="remarks" placeholder="请输入内容" class="layui-textarea"></textarea>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="setmyinfo">确认修改</button>
<button type="reset" class="layui-btn layui-btn-primary">重新填写</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
layui.use('set', layui.factory('set'));
</script>
@@ -0,0 +1,53 @@
<title>设置我的密码</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>设置</cite></a>
<a><cite>我的密码</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-row layui-col-space15">
<div class="layui-col-md12">
<div class="layui-card">
<div class="layui-card-header">修改密码</div>
<div class="layui-card-body" pad15>
<div class="layui-form" lay-filter="">
<div class="layui-form-item">
<label class="layui-form-label">当前密码</label>
<div class="layui-input-inline">
<input type="password" name="oldPassword" lay-verify="required" lay-verType="tips" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">新密码</label>
<div class="layui-input-inline">
<input type="password" name="password" lay-verify="pass" lay-verType="tips" autocomplete="off" id="LAY_password" class="layui-input">
</div>
<div class="layui-form-mid layui-word-aux">6到16个字符</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">确认新密码</label>
<div class="layui-input-inline">
<input type="password" name="repassword" lay-verify="repass" lay-verType="tips" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="setmypass">确认修改</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
layui.use('set', layui.factory('set'));
</script>
@@ -0,0 +1,683 @@
<title>常见问题管理</title>
<link rel="stylesheet" href="/admin/component/pear/css/pear Module.css" />
<style>
.faq-container { display: flex; gap: 15px; height: calc(100vh - 180px); }
.faq-tree-panel { width: 280px; background: #fff; border-radius: 4px; padding: 15px; overflow: auto; }
.faq-question-panel { flex: 1; background: #fff; border-radius: 4px; padding: 15px; }
.tree-toolbar { margin-bottom: 15px; display: flex; gap: 8px; }
.tree-node-item {
padding: 8px 12px;
margin-bottom: 4px;
cursor: pointer;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: space-between;
border: 1px solid transparent;
}
.tree-node-item:hover { background: #f5f5f5; }
.tree-node-item.active { background: #e6f7f5; border-color: #009688; }
.tree-node-item .node-info { display: flex; align-items: center; gap: 8px; flex: 1; }
.tree-node-item .node-actions { display: flex; gap: 5px; opacity: 0; }
.tree-node-item:hover .node-actions { opacity: 1; }
.tree-node-item .node-actions a { color: #666; font-size: 12px; }
.tree-node-item .node-actions a:hover { color: #009688; }
.tree-node-item .node-actions a.delete:hover { color: #f56c6c; }
.tree-children { margin-left: 20px; }
.icon-preview { width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; font-size: 14px; }
.edit-dialog .layui-form-item { margin-bottom: 15px; }
.edit-dialog .layui-form-label { width: 90px; }
.edit-dialog .layui-input-block { margin-left: 110px; }
.rich-editor { min-height: 200px; border: 1px solid #e6e6e6; border-radius: 4px; padding: 10px; }
.rich-editor-toolbar { border-bottom: 1px solid #e6e6e6; padding: 5px; background: #fafafa; }
.rich-editor-toolbar button { padding: 3px 8px; margin-right: 3px; border: 1px solid #ddd; background: #fff; border-radius: 3px; cursor: pointer; }
.rich-editor-toolbar button:hover { background: #eee; }
.rich-editor-content { min-height: 180px; padding: 10px; outline: none; }
/* 图标选择器样式 */
.icon-selector { position: relative; }
.icon-selector .icon-display {
display: flex;
align-items: center;
gap: 10px;
padding: 6px 10px;
border: 1px solid #e6e6e6;
border-radius: 2px;
cursor: pointer;
background: #fff;
min-height: 38px;
}
.icon-selector .icon-display:hover { border-color: #009688; }
.icon-selector .icon-display .icon-preview { font-size: 18px; color: #666; }
.icon-selector .icon-display .icon-placeholder { color: #999; font-size: 12px; }
.icon-selector .icon-dropdown {
display: none;
position: absolute;
top: 100%;
left: 0;
right: 0;
background: #fff;
border: 1px solid #e6e6e6;
border-radius: 4px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
z-index: 9999;
padding: 10px;
max-height: 250px;
overflow-y: auto;
}
.icon-selector .icon-dropdown.show { display: block; }
.icon-selector .icon-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 5px; }
.icon-selector .icon-item {
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #e6e6e6;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
color: #666;
}
.icon-selector .icon-item:hover { background: #f5f5f5; border-color: #009688; }
.icon-selector .icon-item.selected { background: #009688; color: #fff; border-color: #009688; }
</style>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>系统</cite></a>
<a><cite>常见问题管理</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="faq-container">
<!-- 左侧:分类树 -->
<div class="faq-tree-panel">
<div class="tree-toolbar">
<button class="layui-btn layui-btn-sm" id="btn-add-root"><i class="layui-icon layui-icon-add-1"></i>添加分类</button>
</div>
<div id="categoryTree"></div>
</div>
<!-- 右侧:问题列表 -->
<div class="faq-question-panel">
<div class="layui-card-header">
<span id="currentCategoryName">请选择分类</span>
<button class="layui-btn layui-btn-sm" id="btn-add-question" style="float:right;"><i class="layui-icon layui-icon-add-1"></i>添加问题</button>
</div>
<table id="table-question" lay-filter="table-question"></table>
</div>
</div>
</div>
<script type="text/html" id="toolbar-question">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="batchdel"><i class="layui-icon layui-icon-delete"></i>批量删除</button>
</div>
</script>
<script type="text/html" id="table-operate">
<a class="layui-btn layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
<script type="text/html" id="statusTpl">
<input type="checkbox" name="isenable" value="{{d.id}}" lay-skin="switch" lay-text="启用|禁用" {{ d.isenable ? 'checked' : '' }} lay-filter="enable-switch">
</script>
<script>
layui.use(['admin', 'table', 'form', 'layer'], function () {
var $ = layui.$,
admin = layui.admin,
table = layui.table,
form = layui.form,
layer = layui.layer,
setter = layui.setter;
var currentCategoryId = null;
var categoryList = [];
// 加载分类树
function loadCategoryTree() {
admin.req({
url: setter.baseurl + 'adminapi/Faq/CategoryTree',
done: function (res) {
if (res.code === 0) {
categoryList = res.data || [];
renderTree(categoryList, $('#categoryTree'));
}
}
});
}
// 渲染树
function renderTree(nodes, $container) {
var html = '';
nodes.forEach(function(node) {
html += '<div class="tree-node-item' + (node.id === currentCategoryId ? ' active' : '') + '" data-id="' + node.id + '">';
html += '<div class="node-info">';
if (node.icon) {
html += '<i class="layui-icon ' + node.icon + ' icon-preview"></i>';
}
html += '<span>' + node.name + '</span>';
html += '<span style="color:#999;font-size:12px;">(' + (node.children ? node.children.length : 0) + ')</span>';
html += '</div>';
html += '<div class="node-actions">';
html += '<a href="javascript:;" class="add-child" title="添加子分类"><i class="layui-icon layui-icon-add-1"></i></a>';
html += '<a href="javascript:;" class="edit" title="编辑"><i class="layui-icon layui-icon-edit"></i></a>';
html += '<a href="javascript:;" class="delete" title="删除"><i class="layui-icon layui-icon-delete"></i></a>';
html += '</div>';
html += '</div>';
if (node.children && node.children.length > 0) {
html += '<div class="tree-children">';
html += renderTreeHtml(node.children);
html += '</div>';
}
});
$container.html(html);
}
function renderTreeHtml(nodes) {
var html = '';
nodes.forEach(function(node) {
html += '<div class="tree-node-item' + (node.id === currentCategoryId ? ' active' : '') + '" data-id="' + node.id + '">';
html += '<div class="node-info">';
if (node.icon) {
html += '<i class="layui-icon ' + node.icon + ' icon-preview"></i>';
}
html += '<span>' + node.name + '</span>';
html += '<span style="color:#999;font-size:12px;">(' + (node.children ? node.children.length : 0) + ')</span>';
html += '</div>';
html += '<div class="node-actions">';
html += '<a href="javascript:;" class="add-child" title="添加子分类"><i class="layui-icon layui-icon-add-1"></i></a>';
html += '<a href="javascript:;" class="edit" title="编辑"><i class="layui-icon layui-icon-edit"></i></a>';
html += '<a href="javascript:;" class="delete" title="删除"><i class="layui-icon layui-icon-delete"></i></a>';
html += '</div>';
html += '</div>';
if (node.children && node.children.length > 0) {
html += '<div class="tree-children">';
html += renderTreeHtml(node.children);
html += '</div>';
}
});
return html;
}
// 分类树点击
$(document).on('click', '.tree-node-item', function(e) {
if ($(e.target).closest('.node-actions').length > 0) return;
$('.tree-node-item').removeClass('active');
$(this).addClass('active');
currentCategoryId = $(this).data('id');
var nodeName = $(this).find('.node-info span').eq(0).text();
$('#currentCategoryName').text(nodeName);
loadQuestions();
});
// 添加根分类
$('#btn-add-root').on('click', function() {
showCategoryDialog(null, null);
});
// 添加子分类
$(document).on('click', '.add-child', function(e) {
e.stopPropagation();
var parentId = $(this).closest('.tree-node-item').data('id');
showCategoryDialog(null, parentId);
});
// 编辑分类
$(document).on('click', '.edit', function(e) {
e.stopPropagation();
var id = $(this).closest('.tree-node-item').data('id');
admin.req({
url: setter.baseurl + 'adminapi/Faq/CategoryDetail',
data: { id: id },
done: function(res) {
if (res.code === 0) {
showCategoryDialog(res.data, res.data.parentid);
}
}
});
});
// 删除分类
$(document).on('click', '.delete', function(e) {
e.stopPropagation();
var id = $(this).closest('.tree-node-item').data('id');
layer.confirm('确定删除该分类吗?', function(idx) {
admin.req({
url: setter.baseurl + 'adminapi/Faq/DeleteCategory',
data: { id: id },
done: function(res) {
layer.close(idx);
if (res.code === 0) {
layer.msg('删除成功');
loadCategoryTree();
} else {
layer.msg(res.info || '删除失败');
}
}
});
});
});
// 分类弹窗
function showCategoryDialog(data, parentId) {
data = data || {};
var iconOptions = [
{ value: 'layui-icon-user', text: '用户' },
{ value: 'layui-icon-set', text: '设置' },
{ value: 'layui-icon-help', text: '帮助' },
{ value: 'layui-icon-notice', text: '通知' },
{ value: 'layui-icon-util', text: '工具' },
{ value: 'layui-icon-release', text: '发布' },
{ value: 'layui-icon-survey', text: '问卷' },
{ value: 'layui-icon-senior', text: '高级' },
{ value: 'layui-icon-app', text: '应用' },
{ value: 'layui-icon-website', text: '网站' },
{ value: 'layui-icon-console', text: '控制台' },
{ value: 'layui-icon-home', text: '首页' },
{ value: 'layui-icon-reduce-circle', text: '减少' },
{ value: 'layui-icon-chart', text: '图表' },
{ value: 'layui-icon-dialogue', text: '对话' },
{ value: 'layui-icon-edit', text: '编辑' },
{ value: 'layui-icon-link', text: '链接' },
{ value: 'layui-icon-picture', text: '图片' },
{ value: 'layui-icon-file', text: '文件' },
{ value: 'layui-icon-find-fill', text: '发现' },
{ value: 'layui-icon-add-circle', text: '添加' },
{ value: 'layui-icon-cart-simple', text: '购物' },
{ value: 'layui-icon-cart', text: '购物车' },
{ value: 'layui-icon-group', text: '群组' },
{ value: 'layui-icon-fire', text: '热门' },
{ value: 'layui-icon-gift', text: '礼物' },
{ value: 'layui-icon-templeate', text: '模板' },
{ value: 'layui-icon-voice', text: '语音' },
{ value: 'layui-icon-mike', text: '麦克风' },
{ value: 'layui-icon-news', text: '新闻' }
];
var iconGridHtml = '';
iconOptions.forEach(function(icon) {
var selected = data.icon === icon.value ? 'selected' : '';
iconGridHtml += '<div class="icon-item ' + selected + '" data-value="' + icon.value + '" title="' + icon.text + '"><i class="layui-icon ' + icon.value + '"></i></div>';
});
var parentOptions = '<option value="">顶级分类</option>';
categoryList.forEach(function(cat) {
if (!data.id || cat.id !== data.id) {
var selected = data.parentid === cat.id ? 'selected' : '';
parentOptions += '<option value="' + cat.id + '" ' + selected + '>' + cat.name + '</option>';
}
});
var selectedIconHtml = data.icon
? '<i class="layui-icon ' + data.icon + ' icon-preview"></i><span>' + (iconOptions.find(function(o){return o.value===data.icon;})?.text || data.icon) + '</span>'
: '<span class="icon-placeholder">点击选择图标</span>';
var html = '<div class="edit-dialog" style="padding:20px;">' +
'<form class="layui-form">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">上级分类</label>' +
'<div class="layui-input-block">' +
'<select name="parentid">' + parentOptions + '</select>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">分类名称</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="name" value="' + (data.name || '') + '" required lay-verify="required" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">分类图标</label>' +
'<div class="layui-input-block">' +
'<div class="icon-selector" id="iconSelector">' +
'<div class="icon-display">' + selectedIconHtml + '</div>' +
'<input type="hidden" name="icon" value="' + (data.icon || '') + '"/>' +
'<div class="icon-dropdown" id="iconDropdown">' +
'<div class="icon-grid">' + iconGridHtml + '</div>' +
'</div>' +
'</div>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">排序</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="sort" value="' + (data.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="isenable" lay-skin="switch" lay-text="启用|禁用" ' + (data.isenable !== false ? 'checked' : '') + '>' +
'</div>' +
'</div>' +
'</form>' +
'</div>';
layer.open({
type: 1,
title: data.id ? '编辑分类' : '添加分类',
area: ['520px', '480px'],
content: html,
btn: ['保存', '取消'],
success: function(layero) {
form.render('select');
// 图标选择器交互
var $selector = layero.find('#iconSelector');
var $display = $selector.find('.icon-display');
var $dropdown = $selector.find('.icon-dropdown');
var $input = $selector.find('input[name="icon"]');
$display.on('click', function(e) {
e.stopPropagation();
$('.icon-dropdown').not($dropdown).removeClass('show');
$dropdown.toggleClass('show');
});
$dropdown.on('click', '.icon-item', function(e) {
e.stopPropagation();
var val = $(this).data('value');
var iconClass = $(this).find('i').attr('class');
var iconText = $(this).attr('title');
$input.val(val);
$dropdown.find('.icon-item').removeClass('selected');
$(this).addClass('selected');
$display.html('<i class="' + iconClass + ' icon-preview"></i><span>' + iconText + '</span>');
$dropdown.removeClass('show');
});
$(document).on('click', function() {
$dropdown.removeClass('show');
});
},
yes: function(index, layero) {
var name = layero.find('input[name="name"]').val();
if (!name) {
layer.msg('请输入分类名称');
return;
}
var param = {
name: name,
parentid: layero.find('select[name="parentid"]').val() || null,
icon: layero.find('select[name="icon"]').val() || null,
sort: parseInt(layero.find('input[name="sort"]').val()) || 0,
isenable: layero.find('input[name="isenable"]').prop('checked')
};
if (data.id) param.id = data.id;
var url = data.id ? 'adminapi/Faq/UpdateCategory' : 'adminapi/Faq/AddCategory';
admin.req({
url: setter.baseurl + url,
contentType: 'application/json',
method: 'post',
data: JSON.stringify(param),
done: function(res) {
layer.close(index);
if (res.code === 0) {
layer.msg('保存成功');
loadCategoryTree();
} else {
layer.msg(res.info || '保存失败');
}
}
});
}
});
}
// 添加问题
$('#btn-add-question').on('click', function() {
if (!currentCategoryId) {
return layer.msg('请先选择分类');
}
showQuestionDialog(null);
});
// 加载问题列表
function loadQuestions() {
if (!currentCategoryId) {
table.render({
elem: '#table-question',
data: [],
cols: [[{ title: '请选择左侧分类查看问题' }]]
});
return;
}
table.render({
elem: '#table-question',
url: setter.baseurl + 'adminapi/Faq/QuestionList',
method: 'post',
contentType: 'application/json',
headers: { 'token': layui.data(setter.tableName)[setter.request.tokenName] },
page: true,
where: {
categoryId: currentCategoryId
},
request: {
pageName: 'pageIndex',
limitName: 'pageSize'
},
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ type: 'numbers', title: '序号', width: 60 },
{ field: "title", title: "问题标题", minWidth: 200 },
{ field: "sort", title: "排序", width: 80, edit: 'text' },
{ field: "isenable", title: "启用", width: 100, templet: '#statusTpl' },
{ title: "操作", width: 120, fixed: 'right', toolbar: '#table-operate' }
]],
toolbar: '#toolbar-question',
defaultToolbar: ['filter', 'exports', 'print'],
response: {
statusName: 'code',
statusCode: 0,
msgName: 'info',
countName: 'count',
dataName: 'data'
}
});
}
// 表格工具栏
table.on('toolbar(table-question)', function(obj) {
if (obj.event === 'batchdel') {
var checkStatus = table.checkStatus('table-question');
var data = checkStatus.data;
if (data.length === 0) return layer.msg('请选择要删除的数据');
layer.confirm('确定删除选中的 ' + data.length + ' 条数据吗?', function(idx) {
var ids = data.map(function(item) { return item.id; });
var failCount = 0;
var promises = ids.map(function(id) {
return new Promise(function(resolve) {
admin.req({
url: setter.baseurl + 'adminapi/Faq/DeleteQuestion',
data: { id: id },
done: function(res) {
if (res.code !== 0) failCount++;
resolve();
}
});
});
});
Promise.all(promises).then(function() {
layer.close(idx);
if (failCount > 0) {
layer.msg('部分删除失败(' + failCount + '条)');
} else {
layer.msg('删除成功');
}
loadQuestions();
});
});
}
});
// 表格行工具栏
table.on('tool(table-question)', function(obj) {
var data = obj.data;
if (obj.event === 'edit') {
showQuestionDialog(data);
} else if (obj.event === 'del') {
layer.confirm('确定删除该问题吗?', function(idx) {
admin.req({
url: setter.baseurl + 'adminapi/Faq/DeleteQuestion',
data: { id: data.id },
done: function(res) {
layer.close(idx);
if (res.code === 0) {
layer.msg('删除成功');
loadQuestions();
} else {
layer.msg(res.info || '删除失败');
}
}
});
});
}
});
// 问题弹窗
function showQuestionDialog(data) {
data = data || {};
var html = '<div class="edit-dialog" style="padding:20px;">' +
'<form class="layui-form" id="questionForm">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">问题标题</label>' +
'<div class="layui-input-block">' +
'<input type="text" name="title" value="' + (data.title || '') + '" required lay-verify="required" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">排序</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="sort" value="' + (data.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="isenable" lay-skin="switch" lay-text="启用|禁用" ' + (data.isenable !== false ? 'checked' : '') + '>' +
'</div>' +
'</div>' +
'<div class="layui-form-item layui-form-text">' +
'<label class="layui-form-label">问题内容</label>' +
'<div class="layui-input-block">' +
'<div class="rich-editor" id="richEditor">' +
'<div class="rich-editor-toolbar">' +
'<button type="button" onclick="execCmd(\'bold\')"><b>B</b></button>' +
'<button type="button" onclick="execCmd(\'italic\')"><i>I</i></button>' +
'<button type="button" onclick="execCmd(\'underline\')"><u>U</u></button>' +
'<button type="button" onclick="execCmd(\'insertUnorderedList\')">• 列表</button>' +
'<button type="button" onclick="execCmd(\'formatBlock\',\'<h3>\')">标题</button>' +
'<button type="button" onclick="execCmd(\'createLink\')">链接</button>' +
'<button type="button" onclick="execCmd(\'formatBlock\',\'<p>\')">段落</button>' +
'</div>' +
'<div class="rich-editor-content" contenteditable="true" id="editorContent">' + (data.content || '') + '</div>' +
'</div>' +
'</div>' +
'</div>' +
'</form>' +
'</div>';
layer.open({
type: 1,
title: data.id ? '编辑问题' : '添加问题',
area: ['700px', '550px'],
content: html,
btn: ['保存', '取消'],
success: function(layero) {
form.render();
},
yes: function(index, layero) {
var title = layero.find('input[name="title"]').val();
if (!title) {
layer.msg('请输入问题标题');
return;
}
var content = layero.find('#editorContent').html();
var param = {
categoryId: currentCategoryId,
title: title,
content: content,
sort: parseInt(layero.find('input[name="sort"]').val()) || 0,
isenable: layero.find('input[name="isenable"]').prop('checked')
};
if (data.id) param.id = data.id;
var url = data.id ? 'adminapi/Faq/UpdateQuestion' : 'adminapi/Faq/AddQuestion';
admin.req({
url: setter.baseurl + url,
contentType: 'application/json',
method: 'post',
data: JSON.stringify(param),
done: function(res) {
layer.close(index);
if (res.code === 0) {
layer.msg('保存成功');
loadQuestions();
} else {
layer.msg(res.info || '保存失败');
}
}
});
}
});
}
// 富文本命令
window.execCmd = function(cmd, value) {
document.execCommand(cmd, false, value || null);
$('#editorContent').focus();
};
// 启用/禁用
form.on('switch(enable-switch)', function(obj) {
var id = this.value;
var isenable = obj.elem.checked;
admin.req({
url: setter.baseurl + 'adminapi/Faq/UpdateQuestion',
contentType: 'application/json',
method: 'post',
data: JSON.stringify({ id: id, isenable: isenable }),
done: function(res) {
if (res.code === 0) {
layer.msg(isenable ? '已启用' : '已禁用');
} else {
layer.msg(res.info || '操作失败');
obj.elem.checked = !isenable; // 回滚开关状态
}
}
});
});
// 排序编辑
table.on('edit(table-question)', function(obj) {
var id = obj.data.id;
var sort = obj.value;
admin.req({
url: setter.baseurl + 'adminapi/Faq/UpdateQuestion',
contentType: 'application/json',
method: 'post',
data: JSON.stringify({ id: id, sort: parseInt(sort) || 0 }),
done: function(res) {
if (res.code === 0) {
layer.msg('排序已更新');
} else {
layer.msg(res.info || '操作失败');
}
}
});
});
// 初始化
loadCategoryTree();
});
</script>
@@ -0,0 +1,377 @@
<title>反馈管理</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>系统</cite></a>
<a><cite>反馈管理</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="layadmin-feedback-formlist">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">用户编号</label>
<div class="layui-input-inline">
<input type="text" name="userNumber" placeholder="请输入" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">用户昵称</label>
<div class="layui-input-inline">
<input type="text" name="userName" placeholder="请输入" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">反馈类型</label>
<div class="layui-input-inline">
<select name="type">
<option value="">全部</option>
<option value="1">功能建议</option>
<option value="2">bug反馈</option>
<option value="3">其他问题</option>
<option value="4">充值问题</option>
<option value="5">游戏问题</option>
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">处理状态</label>
<div class="layui-input-inline">
<select name="status">
<option value="">全部</option>
<option value="0">待处理</option>
<option value="1">处理中</option>
<option value="2">已回复</option>
<option value="3">已解决</option>
<option value="4">已关闭</option>
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">时间范围</label>
<div class="layui-input-inline">
<input type="text" name="timeRange" id="timeRange" placeholder="选择时间范围" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<button class="layui-btn layuiadmin-btn-list" lay-submit lay-filter="LAY-feedback-front-search">
<i class="layui-icon layui-icon-search layuiadmin-button-btn"></i> 搜索
</button>
<button class="layui-btn layui-btn-primary" lay-filter="LAY-feedback-front-reset">
<i class="layui-icon layui-icon-refresh layuiadmin-button-btn"></i> 重置
</button>
</div>
</div>
</div>
<div class="layui-card">
<div class="layui-card-body">
<table id="LAY-feedback-manage" lay-filter="LAY-feedback-manage"></table>
<script type="text/html" id="table-feedback-toolbar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-primary" lay-event="refresh">
<i class="layui-icon layui-icon-refresh"></i> 刷新
</button>
</div>
</script>
<script type="text/html" id="table-feedback-bar">
<a class="layui-btn layui-btn-xs" lay-event="detail"><i class="layui-icon layui-icon-about"></i>详情</a>
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="reply"><i class="layui-icon layui-icon-reply-fill"></i>回复</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
<script type="text/html" id="typeTpl">
{{#
var typeNames = {1:'功能建议',2:'bug反馈',3:'其他问题',4:'充值问题',5:'游戏问题'};
return typeNames[d.type] || '未知';
}}
</script>
<script type="text/html" id="statusTpl">
{{#
var statusMap = {
0:'<span class="layui-badge layui-bg-gray">待处理</span>',
1:'<span class="layui-badge layui-bg-blue">处理中</span>',
2:'<span class="layui-badge layui-bg-green">已回复</span>',
3:'<span class="layui-badge layui-bg-cyan">已解决</span>',
4:'<span class="layui-badge layui-bg-black">已关闭</span>'
};
return statusMap[d.status] || '未知';
}}
</script>
<script type="text/html" id="hasReplyTpl">
{{# if(d.reply && d.reply.length > 0){ }}
<span class="layui-badge layui-bg-green">已回复</span>
{{# } else { }}
<span class="layui-badge layui-bg-gray">未回复</span>
{{# } }}
</script>
</div>
</div>
</div>
<script>
layui.use(['admin', 'table', 'form', 'laydate'], function () {
var $ = layui.$,
admin = layui.admin,
table = layui.table,
form = layui.form,
laydate = layui.laydate,
setter = layui.setter;
// 时间范围选择器
laydate.render({
elem: '#timeRange',
type: 'datetime',
range: '~'
});
form.render(null, 'layadmin-feedback-formlist');
// 监听搜索
form.on('submit(LAY-feedback-front-search)', function (data) {
var field = data.field;
if (field.timeRange) {
var times = field.timeRange.split('~');
field.startTime = new Date(times[0].trim()).getTime();
field.endTime = new Date(times[1].trim()).getTime();
}
delete field.timeRange;
// 转换类型
if (field.type) field.type = parseInt(field.type);
if (field.status) field.status = parseInt(field.status);
table.reload('LAY-feedback-manage', {
where: field,
page: { curr: 1 }
});
});
// 监听重置
form.on('submit(LAY-feedback-front-reset)', function (data) {
$('input[name="userNumber"]').val('');
$('input[name="userName"]').val('');
$('select[name="type"]').val('');
$('select[name="status"]').val('');
$('input[name="timeRange"]').val('');
form.render('select');
table.reload('LAY-feedback-manage', {
where: {},
page: { curr: 1 }
});
});
// 刷新按钮
$('.layui-btn-container').on('click', '[lay-event="refresh"]', function () {
table.reload('LAY-feedback-manage');
});
table.render({
elem: "#LAY-feedback-manage",
url: setter.baseurl + "adminapi/FeedbackManage/List",
method: "post",
contentType: "application/json",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
page: true,
toolbar: '#table-feedback-toolbar',
defaultToolbar: ['filter', 'exports', 'print'],
where: {},
request: {
pageName: 'pageIndex',
limitName: 'pageSize'
},
limit: 10,
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ type: 'numbers', title: '序号', width: 60 },
{ field: "userNumber", title: "用户编号", width: 100 },
{ field: "userName", title: "用户昵称", width: 120 },
{ field: "type", title: "类型", width: 100, templet: '#typeTpl' },
{ field: "title", title: "标题", minWidth: 150 },
{ field: "status", title: "状态", width: 100, templet: '#statusTpl' },
{ field: "reply", title: "回复", width: 100, templet: '#hasReplyTpl' },
{ field: "submitTime", title: "提交时间", width: 160, templet: function(d) {
return formatDateTime(d.submitTime);
}},
{ width: 180, title: "操作", align: "center", fixed: "right", toolbar: "#table-feedback-bar" }
]],
text: "对不起,加载出现异常!",
response: {
statusName: 'code',
statusCode: 0,
msgName: 'info',
countName: 'count',
dataName: 'data'
}
});
// 监听工具条
table.on("tool(LAY-feedback-manage)", function (e) {
var l = e.data;
if ("detail" === e.event) {
showFeedbackDetail(l);
} else if ("reply" === e.event) {
showReplyDialog(l);
} else if ("del" === e.event) {
layer.confirm('确定删除该反馈吗?', function (idx) {
admin.req({
url: setter.baseurl + 'adminapi/FeedbackManage/Delete',
data: { id: l.id },
done: function (res) {
layer.close(idx);
if (res.code === 0) {
layer.msg('删除成功');
table.reload('LAY-feedback-manage');
} else {
layer.msg(res.info || '删除失败');
}
}
});
});
}
});
// 显示反馈详情
function showFeedbackDetail(feedback) {
var typeNames = {1:'功能建议',2:'bug反馈',3:'其他问题',4:'充值问题',5:'游戏问题'};
var statusNames = {0:'待处理',1:'处理中',2:'已回复',3:'已解决',4:'已关闭'};
var content =
'<div style="padding: 20px;">' +
'<fieldset class="layui-elem-field">' +
'<legend>用户信息</legend>' +
'<div class="layui-field-box">' +
'<p><strong>用户编号:</strong>' + (feedback.userNumber || '-') + '</p>' +
'<p><strong>用户昵称:</strong>' + (feedback.userName || '-') + '</p>' +
'<p><strong>联系方式:</strong>' + (feedback.contact || '-') + '</p>' +
'<p><strong>QQ:</strong>' + (feedback.qq || '-') + '</p>' +
'</div>' +
'</fieldset>' +
'<fieldset class="layui-elem-field">' +
'<legend>反馈信息</legend>' +
'<div class="layui-field-box">' +
'<p><strong>类型:</strong>' + (typeNames[feedback.type] || '未知') + '</p>' +
'<p><strong>标题:</strong>' + (feedback.title || '-') + '</p>' +
'<p><strong>内容:</strong></p>' +
'<div style="background:#f5f5f5;padding:10px;border-radius:4px;margin-top:5px;">' + (feedback.content || '-') + '</div>' +
'<p style="margin-top:10px;"><strong>提交时间:</strong>' + formatDateTime(feedback.submitTime) + '</p>' +
'<p><strong>当前状态:</strong>' + (statusNames[feedback.status] || '未知') + '</p>' +
'</div>' +
'</fieldset>';
if (feedback.reply) {
content +=
'<fieldset class="layui-elem-field layui-field-green">' +
'<legend>回复信息</legend>' +
'<div class="layui-field-box">' +
'<p><strong>回复内容:</strong></p>' +
'<div style="background:#f5f5f5;padding:10px;border-radius:4px;margin-top:5px;">' + feedback.reply + '</div>' +
'<p style="margin-top:10px;"><strong>回复时间:</strong>' + formatDateTime(feedback.replyTime) + '</p>' +
'<p><strong>回复人:</strong>' + (feedback.replyAdminName || '-') + '</p>' +
'</div>' +
'</fieldset>';
}
content += '</div>';
layer.open({
type: 1,
title: '反馈详情',
area: ['700px', '600px'],
content: content
});
}
// 显示回复弹窗
function showReplyDialog(feedback) {
var statusNames = {0:'待处理',1:'处理中',2:'已回复',3:'已解决',4:'已关闭'};
layer.open({
type: 1,
title: '回复反馈',
area: ['600px', '450px'],
content: '<div style="padding:20px;">' +
'<form class="layui-form" lay-filter="reply-form">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">当前状态</label>' +
'<div class="layui-input-block">' +
'<select name="status" lay-filter="status-select">' +
'<option value="0" ' + (feedback.status === 0 ? 'selected' : '') + '>待处理</option>' +
'<option value="1" ' + (feedback.status === 1 ? 'selected' : '') + '>处理中</option>' +
'<option value="2" ' + (feedback.status === 2 ? 'selected' : '') + '>已回复</option>' +
'<option value="3" ' + (feedback.status === 3 ? 'selected' : '') + '>已解决</option>' +
'<option value="4" ' + (feedback.status === 4 ? 'selected' : '') + '>已关闭</option>' +
'</select>' +
'</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" style="min-height:150px;">' + (feedback.reply || '') + '</textarea>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<div class="layui-input-block">' +
'<button class="layui-btn" lay-submit lay-filter="reply-submit">提交回复</button>' +
'<button type="button" class="layui-btn layui-btn-primary" onclick="layer.closeAll();">取消</button>' +
'</div>' +
'</div>' +
'</form>' +
'</div>',
success: function(layero, index) {
form.render('select', 'reply-form');
form.on('submit(reply-submit)', function(data) {
admin.req({
url: setter.baseurl + 'adminapi/FeedbackManage/Reply',
contentType: 'application/json',
method: 'post',
data: JSON.stringify({
id: feedback.id,
reply: data.field.reply,
status: parseInt(data.field.status)
}),
done: function(res) {
if (res.code === 0) {
layer.close(index);
layer.msg('回复成功');
table.reload('LAY-feedback-manage');
} else {
layer.msg(res.info || '回复失败');
}
}
});
return false;
});
}
});
}
function formatDateTime(date) {
if (!date) return '-';
var d = new Date(date);
var year = d.getFullYear();
var month = ('0' + (d.getMonth() + 1)).slice(-2);
var day = ('0' + d.getDate()).slice(-2);
var hour = ('0' + d.getHours()).slice(-2);
var minute = ('0' + d.getMinutes()).slice(-2);
var second = ('0' + d.getSeconds()).slice(-2);
return year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second;
}
});
</script>
@@ -0,0 +1,335 @@
<title>操作日志</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>系统</cite></a>
<a><cite>操作日志</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="layadmin-log-formlist">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">管理员</label>
<div class="layui-input-block">
<select name="adminId" id="adminSelect" lay-search>
<option value="">全部</option>
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">操作模块</label>
<div class="layui-input-block">
<select name="module" id="moduleSelect">
<option value="">全部</option>
<option value="1">用户管理</option>
<option value="2">系统用户</option>
<option value="3">角色管理</option>
<option value="4">菜单管理</option>
<option value="5">字典管理</option>
<option value="6">游戏配置</option>
<option value="7">房间管理</option>
<option value="8">客服管理</option>
<option value="9">靓号管理</option>
<option value="10">公告管理</option>
<option value="11">任务管理</option>
<option value="99">其他</option>
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">操作时间</label>
<div class="layui-input-block">
<input type="text" name="timeRange" id="timeRange" placeholder="选择时间范围" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<button class="layui-btn layuiadmin-btn-list" lay-submit lay-filter="LAY-log-front-search">
<i class="layui-icon layui-icon-search layuiadmin-button-btn"></i> 搜索
</button>
<button class="layui-btn layui-btn-primary" lay-filter="LAY-log-front-reset">
<i class="layui-icon layui-icon-refresh layuiadmin-button-btn"></i> 重置
</button>
</div>
</div>
</div>
<div class="layui-card">
<div class="layui-card-body">
<table id="LAY-log-manage" lay-filter="LAY-log-manage"></table>
<script type="text/html" id="table-log-toolbar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-primary" lay-event="refresh">
<i class="layui-icon layui-icon-refresh"></i> 刷新
</button>
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-event="batchdel">
<i class="layui-icon layui-icon-delete"></i> 批量删除
</button>
</div>
</script>
<script type="text/html" id="table-log-bar">
<a class="layui-btn layui-btn-xs" lay-event="detail"><i class="layui-icon layui-icon-about"></i>详情</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
<script type="text/html" id="moduleTpl">
{{# var moduleNames = {1:'用户管理',2:'系统用户',3:'角色管理',4:'菜单管理',5:'字典管理',6:'游戏配置',7:'房间管理',8:'客服管理',9:'靓号管理',10:'公告管理',11:'任务管理',99:'其他'}; }}
{{# return moduleNames[d.module] || '未知'; }}
</script>
<script type="text/html" id="statusTpl">
{{# if(d.status === 1){ }}
<span class="layui-badge layui-bg-green">成功</span>
{{# } else { }}
<span class="layui-badge layui-bg-red">失败</span>
{{# } }}
</script>
</div>
</div>
</div>
<script>
layui.use(['admin', 'table', 'form', 'laydate'], function () {
var $ = layui.$,
admin = layui.admin,
view = layui.view,
table = layui.table,
setter = layui.setter,
form = layui.form,
laydate = layui.laydate;
// 时间范围选择器
laydate.render({
elem: '#timeRange',
type: 'datetime',
range: '~'
});
// 加载管理员下拉
function loadAdmins() {
admin.req({
url: setter.baseurl + 'adminapi/OperationLog/admins',
done: function (res) {
var html = '<option value="">全部</option>';
layui.each(res.data, function (index, item) {
html += '<option value="' + item.value + '">' + item.label + '</option>';
});
$('#adminSelect').html(html);
form.render('select');
}
});
}
loadAdmins();
form.render(null, 'layadmin-log-formlist');
// 监听搜索
form.on('submit(LAY-log-front-search)', function (data) {
var field = data.field;
if (field.timeRange) {
var times = field.timeRange.split('~');
field.startTime = new Date(times[0].trim()).getTime();
field.endTime = new Date(times[1].trim()).getTime();
}
delete field.timeRange;
table.reload('LAY-log-manage', {
where: field,
page: { curr: 1 }
});
});
// 监听重置
form.on('submit(LAY-log-front-reset)', function (data) {
$('#adminSelect').val('');
$('#moduleSelect').val('');
$('#timeRange').val('');
form.render('select');
table.reload('LAY-log-manage', {
where: {},
page: { curr: 1 }
});
});
// 刷新按钮
$('.layui-btn-container').on('click', '[lay-event="refresh"]', function () {
table.reload('LAY-log-manage');
});
table.render({
elem: "#LAY-log-manage",
url: setter.baseurl + "adminapi/OperationLog/list",
method: "post",
contentType: "application/json",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
page: true,
toolbar: '#table-log-toolbar',
defaultToolbar: ['filter', 'exports', 'print'],
where: {},
request: {
pageName: 'pageIndex',
limitName: 'pageSize'
},
limit: 10,
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ type: 'numbers', title: '序号', width: 60 },
{ field: "adminName", title: "管理员", width: 120 },
{ field: "module", title: "模块", width: 100, templet: '#moduleTpl' },
{ field: "actionType", title: "操作类型", width: 120 },
{ field: "description", title: "操作描述", minWidth: 200 },
{ field: "httpMethod", title: "请求方式", width: 100, templet: function(d) {
var color = d.httpMethod === 'GET' ? 'layui-bg-blue' : (d.httpMethod === 'POST' ? 'layui-bg-green' : 'layui-bg-orange');
return '<span class="layui-badge ' + color + '">' + d.httpMethod + '</span>';
}},
{ field: "ip", title: "IP地址", width: 130 },
{ field: "status", title: "状态", width: 80, templet: '#statusTpl' },
{ field: "operateTime", title: "操作时间", width: 160, templet: function(d) {
return formatDateTime(d.operateTime);
}},
{ width: 120, title: "操作", align: "center", fixed: "right", toolbar: "#table-log-bar" }
]],
text: "对不起,加载出现异常!",
response: {
statusName: 'code',
statusCode: 0,
msgName: 'info',
countName: 'count',
dataName: 'data'
}
});
// 监听工具条
table.on("tool(LAY-log-manage)", function (e) {
var l = e.data;
if ("detail" === e.event) {
showLogDetail(l);
} else if ("del" === e.event) {
layer.confirm('确定删除该日志吗?', function (idx) {
admin.req({
url: setter.baseurl + 'adminapi/OperationLog/Delete',
data: { id: l.id },
done: function (res) {
layer.close(idx);
if (res.code === 0) {
layer.msg('删除成功');
table.reload('LAY-log-manage');
} else {
layer.msg(res.info || '删除失败');
}
}
});
});
}
});
// 批量删除
table.on('toolbar(LAY-log-manage)', function (obj) {
if (obj.event === 'batchdel') {
var checkStatus = table.checkStatus('LAY-log-manage');
var data = checkStatus.data;
if (data.length === 0) return layer.msg('请选择要删除的数据');
layer.confirm('确定删除选中的 ' + data.length + ' 条日志吗?', function (idx) {
var ids = data.map(function (item) { return item.id; });
admin.req({
url: setter.baseurl + 'adminapi/OperationLog/BatchDelete',
contentType: 'application/json',
method: 'post',
data: JSON.stringify(ids),
done: function (res) {
layer.close(idx);
if (res.code === 0) {
layer.msg('删除成功');
table.reload('LAY-log-manage');
} else {
layer.msg(res.info || '删除失败');
}
}
});
});
}
});
// 显示日志详情
function showLogDetail(log) {
var content =
'<div style="padding: 20px;">' +
'<fieldset class="layui-elem-field">' +
'<legend>基本信息</legend>' +
'<div class="layui-field-box">' +
'<p><strong>管理员:</strong>' + (log.adminName || '-') + '</p>' +
'<p><strong>模块:</strong>' + getModuleName(log.module) + '</p>' +
'<p><strong>操作类型:</strong>' + (log.actionType || '-') + '</p>' +
'<p><strong>操作描述:</strong>' + (log.description || '-') + '</p>' +
'<p><strong>操作时间:</strong>' + formatDateTime(log.operateTime) + '</p>' +
'<p><strong>IP地址:</strong>' + (log.ip || '-') + '</p>' +
'<p><strong>状态:</strong>' + (log.status === 1 ? '成功' : '失败') + '</p>' +
'</div>' +
'</fieldset>' +
'<fieldset class="layui-elem-field">' +
'<legend>请求信息</legend>' +
'<div class="layui-field-box">' +
'<p><strong>请求方式:</strong>' + (log.httpMethod || '-') + '</p>' +
'<p><strong>请求参数:</strong></p>' +
'<pre style="background:#f5f5f5;padding:10px;border-radius:4px;max-height:200px;overflow:auto;">' + formatJson(log.requestParams) + '</pre>' +
'</div>' +
'</fieldset>' +
'<fieldset class="layui-elem-field">' +
'<legend>响应信息</legend>' +
'<div class="layui-field-box">' +
'<pre style="background:#f5f5f5;padding:10px;border-radius:4px;max-height:200px;overflow:auto;">' + formatJson(log.response) + '</pre>' +
'</div>' +
'</fieldset>' +
'</div>';
layer.open({
type: 1,
title: '操作日志详情',
area: ['700px', '600px'],
content: content
});
}
function getModuleName(module) {
var moduleNames = {
1: '用户管理', 2: '系统用户', 3: '角色管理', 4: '菜单管理',
5: '字典管理', 6: '游戏配置', 7: '房间管理', 8: '客服管理',
9: '靓号管理', 10: '公告管理', 11: '任务管理', 99: '其他'
};
return moduleNames[module] || '未知';
}
function formatDateTime(date) {
if (!date) return '-';
var d = new Date(date);
var year = d.getFullYear();
var month = ('0' + (d.getMonth() + 1)).slice(-2);
var day = ('0' + d.getDate()).slice(-2);
var hour = ('0' + d.getHours()).slice(-2);
var minute = ('0' + d.getMinutes()).slice(-2);
var second = ('0' + d.getSeconds()).slice(-2);
return year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second;
}
function formatJson(str) {
if (!str) return '无';
try {
var obj = typeof str === 'string' ? JSON.parse(str) : str;
return JSON.stringify(obj, null, 2);
} catch (e) {
return str;
}
}
});
</script>
@@ -0,0 +1,340 @@
<title>登录日志</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>系统</cite></a>
<a><cite>登录日志</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="layadmin-loginformlist">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">用户编号</label>
<div class="layui-input-inline">
<input type="text" name="userNumber" placeholder="请输入" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">用户昵称</label>
<div class="layui-input-inline">
<input type="text" name="userName" placeholder="请输入" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">登录方式</label>
<div class="layui-input-inline">
<select name="loginType">
<option value="">全部</option>
<option value="1">账号密码</option>
<option value="2">微信</option>
<option value="3">手机号</option>
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">登录平台</label>
<div class="layui-input-inline">
<select name="platform">
<option value="">全部</option>
<option value="1">Android</option>
<option value="2">iOS</option>
<option value="3">H5</option>
<option value="4">PC</option>
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">登录状态</label>
<div class="layui-input-inline">
<select name="status">
<option value="">全部</option>
<option value="1">成功</option>
<option value="0">失败</option>
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">时间范围</label>
<div class="layui-input-inline">
<input type="text" name="timeRange" id="timeRange" placeholder="选择时间范围" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<button class="layui-btn layuiadmin-btn-list" lay-submit lay-filter="LAY-loginform-front-search">
<i class="layui-icon layui-icon-search layuiadmin-button-btn"></i> 搜索
</button>
<button class="layui-btn layui-btn-primary" lay-filter="LAY-loginform-front-reset">
<i class="layui-icon layui-icon-refresh layuiadmin-button-btn"></i> 重置
</button>
</div>
</div>
</div>
<div class="layui-card">
<div class="layui-card-body">
<table id="LAY-loginlog-manage" lay-filter="LAY-loginlog-manage"></table>
<script type="text/html" id="table-loginlog-toolbar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-primary" lay-event="refresh">
<i class="layui-icon layui-icon-refresh"></i> 刷新
</button>
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-event="batchdel">
<i class="layui-icon layui-icon-delete"></i> 批量删除
</button>
</div>
</script>
<script type="text/html" id="table-loginlog-bar">
<a class="layui-btn layui-btn-xs" lay-event="detail"><i class="layui-icon layui-icon-about"></i>详情</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
<script type="text/html" id="loginTypeTpl">
{{#
var typeNames = {1:'账号密码',2:'微信',3:'手机号'};
return typeNames[d.loginType] || '未知';
}}
</script>
<script type="text/html" id="platformTpl">
{{#
var platformNames = {1:'Android',2:'iOS',3:'H5',4:'PC'};
return platformNames[d.platform] || '未知';
}}
</script>
<script type="text/html" id="statusTpl">
{{# if(d.status === 1){ }}
<span class="layui-badge layui-bg-green">成功</span>
{{# } else { }}
<span class="layui-badge layui-bg-red">失败</span>
{{# } }}
</script>
</div>
</div>
</div>
<script>
layui.use(['admin', 'table', 'form', 'laydate'], function () {
var $ = layui.$,
admin = layui.admin,
table = layui.table,
form = layui.form,
laydate = layui.laydate,
setter = layui.setter;
// 时间范围选择器
laydate.render({
elem: '#timeRange',
type: 'datetime',
range: '~'
});
form.render(null, 'layadmin-loginform-formlist');
// 监听搜索
form.on('submit(LAY-loginform-front-search)', function (data) {
var field = data.field;
if (field.timeRange) {
var times = field.timeRange.split('~');
field.startTime = new Date(times[0].trim()).getTime();
field.endTime = new Date(times[1].trim()).getTime();
}
delete field.timeRange;
// 转换类型
if (field.loginType) field.loginType = parseInt(field.loginType);
if (field.status) field.status = parseInt(field.status);
if (field.platform) field.platform = parseInt(field.platform);
table.reload('LAY-loginlog-manage', {
where: field,
page: { curr: 1 }
});
});
// 监听重置
form.on('submit(LAY-loginform-front-reset)', function (data) {
$('input[name="userNumber"]').val('');
$('input[name="userName"]').val('');
$('select[name="loginType"]').val('');
$('select[name="status"]').val('');
$('select[name="platform"]').val('');
$('input[name="timeRange"]').val('');
form.render('select');
table.reload('LAY-loginlog-manage', {
where: {},
page: { curr: 1 }
});
});
// 刷新按钮
$('.layui-btn-container').on('click', '[lay-event="refresh"]', function () {
table.reload('LAY-loginlog-manage');
});
table.render({
elem: "#LAY-loginlog-manage",
url: setter.baseurl + "adminapi/UserLoginLog/List",
method: "post",
contentType: "application/json",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
page: true,
toolbar: '#table-loginlog-toolbar',
defaultToolbar: ['filter', 'exports', 'print'],
where: {},
request: {
pageName: 'pageIndex',
limitName: 'pageSize'
},
limit: 10,
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ type: 'numbers', title: '序号', width: 60 },
{ field: "userNumber", title: "用户编号", width: 100 },
{ field: "userName", title: "用户昵称", width: 120 },
{ field: "loginType", title: "登录方式", width: 100, templet: '#loginTypeTpl' },
{ field: "platform", title: "登录平台", width: 90, templet: '#platformTpl' },
{ field: "status", title: "状态", width: 80, templet: '#statusTpl' },
{ field: "ip", title: "IP地址", width: 130 },
{ field: "ipLocation", title: "IP归属地", width: 120 },
{ field: "clientVersion", title: "版本", width: 100 },
{ field: "loginTime", title: "登录时间", width: 160, templet: function(d) {
return formatDateTime(d.loginTime);
}},
{ width: 120, title: "操作", align: "center", fixed: "right", toolbar: "#table-loginlog-bar" }
]],
text: "对不起,加载出现异常!",
response: {
statusName: 'code',
statusCode: 0,
msgName: 'info',
countName: 'count',
dataName: 'data'
}
});
// 监听工具条
table.on("tool(LAY-loginlog-manage)", function (e) {
var l = e.data;
if ("detail" === e.event) {
showLogDetail(l);
} else if ("del" === e.event) {
layer.confirm('确定删除该日志吗?', function (idx) {
admin.req({
url: setter.baseurl + 'adminapi/UserLoginLog/Delete',
data: { id: l.id },
done: function (res) {
layer.close(idx);
if (res.code === 0) {
layer.msg('删除成功');
table.reload('LAY-loginlog-manage');
} else {
layer.msg(res.info || '删除失败');
}
}
});
});
}
});
// 批量删除
table.on('toolbar(LAY-loginlog-manage)', function (obj) {
if (obj.event === 'batchdel') {
var checkStatus = table.checkStatus('LAY-loginlog-manage');
var data = checkStatus.data;
if (data.length === 0) return layer.msg('请选择要删除的数据');
layer.confirm('确定删除选中的 ' + data.length + ' 条日志吗?', function (idx) {
var ids = data.map(function (item) { return item.id; });
admin.req({
url: setter.baseurl + 'adminapi/UserLoginLog/BatchDelete',
contentType: 'application/json',
method: 'post',
data: JSON.stringify(ids),
done: function (res) {
layer.close(idx);
if (res.code === 0) {
layer.msg('删除成功');
table.reload('LAY-loginlog-manage');
} else {
layer.msg(res.info || '删除失败');
}
}
});
});
}
});
// 显示日志详情
function showLogDetail(log) {
var typeNames = {1:'账号密码',2:'微信',3:'手机号'};
var platformNames = {1:'Android',2:'iOS',3:'H5',4:'PC'};
var content =
'<div style="padding: 20px;">' +
'<fieldset class="layui-elem-field">' +
'<legend>用户信息</legend>' +
'<div class="layui-field-box">' +
'<p><strong>用户编号:</strong>' + (log.userNumber || '-') + '</p>' +
'<p><strong>用户昵称:</strong>' + (log.userName || '-') + '</p>' +
'<p><strong>用户ID:</strong>' + (log.userId || '-') + '</p>' +
'</div>' +
'</fieldset>' +
'<fieldset class="layui-elem-field">' +
'<legend>登录信息</legend>' +
'<div class="layui-field-box">' +
'<p><strong>登录方式:</strong>' + (typeNames[log.loginType] || '未知') + '</p>' +
'<p><strong>登录平台:</strong>' + (platformNames[log.platform] || '未知') + '</p>' +
'<p><strong>登录状态:</strong>' + (log.status === 1 ? '<span class="layui-badge layui-bg-green">成功</span>' : '<span class="layui-badge layui-bg-red">失败</span>') + '</p>' +
'<p><strong>登录时间:</strong>' + formatDateTime(log.loginTime) + '</p>' +
'<p><strong>失败原因:</strong>' + (log.failReason || '-') + '</p>' +
'</div>' +
'</fieldset>' +
'<fieldset class="layui-elem-field">' +
'<legend>设备信息</legend>' +
'<div class="layui-field-box">' +
'<p><strong>IP地址:</strong>' + (log.ip || '-') + '</p>' +
'<p><strong>IP归属地:</strong>' + (log.ipLocation || '-') + '</p>' +
'<p><strong>设备类型:</strong>' + (log.deviceType || '-') + '</p>' +
'<p><strong>设备ID:</strong>' + (log.deviceId || '-') + '</p>' +
'<p><strong>客户端版本:</strong>' + (log.clientVersion || '-') + '</p>' +
'</div>' +
'</fieldset>' +
'</div>';
layer.open({
type: 1,
title: '登录日志详情',
area: ['600px', '550px'],
content: content
});
}
function formatDateTime(date) {
if (!date) return '-';
var d = new Date(date);
var year = d.getFullYear();
var month = ('0' + (d.getMonth() + 1)).slice(-2);
var day = ('0' + d.getDate()).slice(-2);
var hour = ('0' + d.getHours()).slice(-2);
var minute = ('0' + d.getMinutes()).slice(-2);
var second = ('0' + d.getSeconds()).slice(-2);
return year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second;
}
});
</script>
@@ -0,0 +1,143 @@
<div class="layui-form" lay-filter="layuiadmin-form-admin" style="padding: 20px 30px 0 0;">
<div class="layui-form-item">
<label class="layui-form-label">登录名</label>
<div class="layui-input-inline">
<script type="text/html" template>
<input type="text" name="id" value="{{ d.params.id}}" class="layui-input" style="display:none">
</script>
<script type="text/html" template>
<input type="text" name="userName" value="{{ d.params.userName || '' }}" lay-verify="required" placeholder="请输入用户名" autocomplete="off" class="layui-input">
</script>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">名称</label>
<div class="layui-input-inline">
<script type="text/html" template>
<input type="text" name="displayName" value="{{ d.params.displayName || '' }}" lay-verify="required" placeholder="请输入号码" autocomplete="off" class="layui-input">
</script>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">头像</label>
<div class="layui-input-inline">
<script type="text/html" template>
<button type="button" class="layui-btn" id="layuiadmin-upload-admin">
<i class="layui-icon layui-icon-upload"></i> 图片上传
</button>
<div style="width: 132px;">
<div class="layui-upload-list">
<img class="layui-upload-img" id="ID-upload-demo-img" src="{{ d.params.icon || '' }}" style="width: 100%; height: 92px;">
<div id="ID-upload-demo-text"></div>
<input type="text" name="icon" id="ID-upload-demo-img-v" value="{{ d.params.icon || '' }}" style="display:none"/>
</div>
<div class="layui-progress layui-progress-big" lay-showPercent="yes" lay-filter="filter-demo">
<div class="layui-progress-bar" lay-percent=""></div>
</div>
</div>
</script>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">密码</label>
<div class="layui-input-inline">
<script type="text/html" template>
<input type="text" name="password" value="" lay-verify="" placeholder="请输入密码" autocomplete="off" class="layui-input">
</script>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">角色</label>
<div class="layui-input-inline">
<select name="systemRole" id="systemRole">
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-inline">
<input type="button" lay-submit lay-filter="LAY-user-back-submit" value="确认" class="layui-btn">
</div>
</div>
</div>
<script>
layui.use(['admin', 'form', 'upload'], function () {
var $ = layui.$
, form = layui.form
, admin = layui.admin
, setter = layui.setter
, upload = layui.upload
,element = layui.element;
bindSelect();
function bindSelect() {
admin.req({
method: "get",
contentType: "application/json;charset=UTF-8",
dataType: "json",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/System/role/list",//实际使用请改成服务端真实接口
async: false,
done: function (res) {
res.data.forEach(function (option) {
$('#systemRole').append("<option value='" + option.id + "'>" + option.roleName + "</option>");
});
form.render('select');
}
})
}
var uploadInst = upload.render({
elem: '#layuiadmin-upload-admin',
url: setter.baseurl + "adminapi/ImageUpload/upload/temp",
before: function (obj) {
obj.preview(function (index, file, result) {
$('#ID-upload-demo-img').attr('src', result); // 图片链接(base64)
});
element.progress('filter-demo', '0%'); // 进度条复位
layer.msg('上传中', { icon: 16, time: 0 });
},
done: function (res) {
// 若上传失败
if (res.code > 0) {
return layer.msg('上传失败');
}
console.log($('#ID-upload-demo-img-v').val())
// 上传成功的一些操作
$('#ID-upload-demo-img-v').val(setter.baseurl +res.path);
$('#ID-upload-demo-text').html(res.path); // 置空上传失败的状态
},
error: function () {
// 演示失败状态,并实现重传
var demoText = $('#ID-upload-demo-text');
demoText.html('<span style="color: #FF5722;">上传失败</span> <a class="layui-btn layui-btn-xs demo-reload">重试</a>');
demoText.find('.demo-reload').on('click', function () {
uploadInst.upload();
});
},
// 进度条
progress: function (n, elem, e) {
element.progress('filter-demo', n + '%'); // 可配合 layui 进度条元素使用
if (n == 100) {
layer.msg('上传完毕', { icon: 1 });
}
}
});
})
</script>
@@ -0,0 +1,207 @@
<div class="layui-form" lay-filter="layuiadmin-form-admin" style="padding: 20px 30px 0 0;">
<script type="text/html" lay-done="sendParams(d.params)" template>
</script>
<div class="layui-form-item">
<label class="layui-form-label">标题</label>
<div class="layui-input-inline">
<script type="text/html" template>
<input type="text" name="id" value="{{ d.params.id}}" class="layui-input" style="display:none">
</script>
<script type="text/html" template>
<input type="text" name="title" value="{{ d.params.title || '' }}" lay-verify="required" placeholder="请输入标题" autocomplete="off" class="layui-input">
</script>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">图片</label>
<div class="layui-input-inline">
<script type="text/html" template>
<button type="button" class="layui-btn" id="layuiadmin-upload-admin">
<i class="layui-icon layui-icon-upload"></i> 图片上传
</button>
<div style="width: 132px;">
<div class="layui-upload-list">
<img class="layui-upload-img" id="ID-upload-demo-img" src="{{ d.params.picUrl || '' }}" style="width: 100%; height: 92px;">
<div id="ID-upload-demo-text"></div>
<input type="text" name="picUrl" id="ID-upload-demo-img-v" value="{{ d.params.picUrl || '' }}" style="display:none" />
</div>
<div class="layui-progress layui-progress-big" lay-showPercent="yes" lay-filter="filter-demo">
<div class="layui-progress-bar" lay-percent=""></div>
</div>
</div>
</script>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">跳转地址</label>
<div class="layui-input-inline">
<script type="text/html" template>
<input type="text" name="url" value="{{ d.params.url || '' }}" lay-verify="required" placeholder="请输入号码" autocomplete="off" class="layui-input">
</script>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">时长(秒)</label>
<div class="layui-input-inline">
<script type="text/html" template>
<input type="number" name="times" value="{{d.params.times||0}}" lay-verify="required" placeholder="时长(秒)" autocomplete="off" step="1" class="layui-input">
</script>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">位置</label>
<div class="layui-input-inline">
<div id="positionSelect"></div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-inline">
<input type="button" lay-submit lay-filter="LAY-user-back-submit" value="确认" class="layui-btn">
</div>
</div>
</div>
<script src="/layui/xm-select.js"></script>
<script>
var position = '';
var isLayUI = false;
function sendParams(params) {
position = params.position;
if (isLayUI) {
layuiLoad();
}
}
layuiLoad();
function layuiLoad() {
layui.use(['admin', 'form', 'upload'], function () {
var $ = layui.$
, form = layui.form
, admin = layui.admin
, setter = layui.setter
, upload = layui.upload
, element = layui.element;
var selctdata = [];
admin.req({
method: "get",
contentType: "application/json;charset=UTF-8",
dataType: "json",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/System/dic/list/code?code=bannerposition",//实际使用请改成服务端真实接口
async: false,
done: function (res) {
layui.each(res.data, function (index, val) {
selctdata.push({ name: val.name, value: val.id });
})
}
})
var positionSelect = xmSelect.render({
el: '#positionSelect',
toolbar: { show: true },
data: selctdata
})
positionSelect.setValue(position.map(item => ({
value: item.value,
name: item.title // 将name属性映射为title
})));
//监听提交
form.on('submit(LAY-user-back-submit)', function (data) {
var selectArr = positionSelect.getValue();
const newArr = selectArr.map(item => ({
value: item.value,
title: item.name // 将name属性映射为title
}));
var param = {
id: data.field.id,
title: data.field.title,
url: data.field.url,
times: data.field.times,
picUrl: data.field.picUrl,
position: newArr
};
//提交数据
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
dataType: "json",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/banner/" +(param.id==""?"add":"update"),//实际使用请改成服务端真实接口
async: false,
data: JSON.stringify(param),
done: function (res) {
//$.ajax({});
layui.table.reload('LAY-user-back-manage'); //重载表格
layer.closeAll(); //执行关闭
}
})
//提交 Ajax 成功后,关闭当前弹层并重载表格
});
var uploadInst = upload.render({
elem: '#layuiadmin-upload-admin',
url: setter.baseurl + "adminapi/ImageUpload/upload/temp",
before: function (obj) {
obj.preview(function (index, file, result) {
$('#ID-upload-demo-img').attr('src', result); // 图片链接(base64)
});
element.progress('filter-demo', '0%'); // 进度条复位
layer.msg('上传中', { icon: 16, time: 0 });
},
done: function (res) {
// 若上传失败
if (res.code > 0) {
return layer.msg('上传失败');
}
console.log($('#ID-upload-demo-img-v').val())
// 上传成功的一些操作
$('#ID-upload-demo-img-v').val(setter.baseurl + res.path);
$('#ID-upload-demo-text').html(res.path); // 置空上传失败的状态
},
error: function () {
// 演示失败状态,并实现重传
var demoText = $('#ID-upload-demo-text');
demoText.html('<span style="color: #FF5722;">上传失败</span> <a class="layui-btn layui-btn-xs demo-reload">重试</a>');
demoText.find('.demo-reload').on('click', function () {
uploadInst.upload();
});
},
// 进度条
progress: function (n, elem, e) {
element.progress('filter-demo', n + '%'); // 可配合 layui 进度条元素使用
if (n == 100) {
layer.msg('上传完毕', { icon: 1 });
}
}
});
})
}
</script>
@@ -0,0 +1,96 @@
<title>广告位</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>广告位</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="layadmin-banneradmin-formlist">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">标题</label>
<div class="layui-input-block">
<input type="text" name="keyWord" placeholder="请输入" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<button class="layui-btn layuiadmin-btn-banneradmin" lay-submit lay-filter="LAY-user-front-search">
<i class="layui-icon layui-icon-search layuiadmin-button-btn"></i>
</button>
</div>
</div>
</div>
<div class="layui-card-body">
<div style="padding-bottom: 10px;">
<button class="layui-btn layuiadmin-btn-admin" data-type="add">添加</button>
</div>
<table id="LAY-user-back-manage" lay-filter="LAY-user-back-manage"></table>
<script type="text/html" id="table-banneradmin-admin">
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
</div>
</div>
</div>
<script>
layui.use('banneradmin', layui.factory('banneradmin')).use(['admin', 'banneradmin', 'table'], function () {
var $ = layui.$
, admin = layui.admin
, view = layui.view
, table = layui.table
, setter = layui.setter
, form = layui.form;
form.render(null, 'layadmin-userfront-formlist');
//监听搜索
form.on('submit(LAY-user-front-search)', function (data) {
var field = data.field;
//执行重载
table.reload('LAY-user-back-manage', {
where: field
});
});
//事件
var active = {
add: function () {
admin.popup({
title: '添加广告位'
, area: ["500px", "600px"]
, id: 'LAY-popup-user-add'
, success: function (layero, index) {
view(this.id).render('user/administrators/bannerform').done(function () {
form.render(null, 'layuiadmin-form-admin');
});
}
});
}
};
$('.layui-btn.layuiadmin-btn-admin').on('click', function () {
var type = $(this).data('type');
active[type] ? active[type].call(this) : '';
});
});
</script>
@@ -0,0 +1,233 @@
<title>字典管理</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>用户</cite></a>
<a><cite>字典管理</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-body">
<div style="padding-bottom: 10px;">
<button class="layui-btn layuiadmin-btn-dic" data-type="add">添加</button>
</div>
<table id="LAY-user-dic" lay-filter="LAY-user-dic"></table>
<script type="text/html" id="table-dic-admin-tools">
<div class="layui-btn-container">
<a class="layui-btn layui-btn-warm layui-btn-xs" lay-event="addChild">新增</a>
<a class="layui-btn layui-btn-primary layui-btn-xs" lay-event="edit">编辑</a>
<a class="layui-btn layui-btn-primary layui-btn-xs" lay-event="del">删除</a>
</div>
</script>
</div>
</div>
</div>
<script>
layui.use(['admin', "form", 'treeTable'], function () {
var setter = layui.setter;
var treeTable = layui.treeTable;
var layer = layui.layer;
var admin = layui.admin;
var form = layui.form;
var dropdown = layui.dropdown;
var view = layui.view;
var $ = layui.$;
// 渲染
var active = {
add: function () {
admin.popup({
title: '新增字典'
, area: ['550px', '550px']
, id: 'LAY-popup-content-add'
, success: function (e, i) {
view(this.id).render("user/administrators/dicform").done(function () {
form.render(null, "layuiadmin-form-dic"), form.on("submit(LAY-user-dic-submit)", function (e) {
//提交数据
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
dataType: "json",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/System/dic/add",//实际使用请改成服务端真实接口
async: false,
data: JSON.stringify(e.field),
done: function (res) {
inst.reloadData(),
layer.close(i)
}
})
})
})
}
});
}
};
$('.layui-btn.layuiadmin-btn-dic').on('click', function () {
var type = $(this).data('type');
active[type] ? active[type].call(this) : '';
});
var inst = treeTable.render({
elem: '#LAY-user-dic',
url: setter.baseurl + "adminapi/System/dic/list", // 此处为静态模拟数据,实际使用时需换成真实接口
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
tree: {
/*
// 异步加载子节点
async: {
enable: true,
url: '/static/json/2/treeTable/demo-async.json', // 此处为静态模拟数据,实际使用时需换成真实接口
autoParam: ["parentId=id"]
}
*/
},
maxHeight: '501px',
cols: [[
{ field: 'name', title: '名称',fixed: 'left' },
{ field: 'order', title: '序号', fixed: 'left' },
{ field: 'value', title: '值' },
{ field: 'code', title: '编码' },
{ field: 'remark', title: '备注' },
{ field: 'remark1', title: '备注1' },
{ fixed: "right", title: "操作", width: 190, align: "center", toolbar: "#table-dic-admin-tools" }
]],
page: false
});
// 表头工具栏工具事件
treeTable.on("toolbar(table-dic-admin)", function (obj) {
var config = obj.config;
var tableId = config.id;
var status = treeTable.checkStatus(tableId);
// 获取选中行
if (obj.event === "getChecked") {
if (!status.data.length) return layer.msg('无选中数据');
console.log(status);
layer.alert("当前数据选中已经输出到控制台,<br>您可按 F12 从控制台中查看结果。");
}
});
// 单元格工具事件
treeTable.on('tool(' + inst.config.id + ')', function (obj) {
var layEvent = obj.event; // 获得 lay-event 对应的值
var trElem = obj.tr;
var trData = obj.data;
var tableId = obj.config.id;
l = obj.data;
if (layEvent === "addChild") {
admin.popup({
title: '新增字典'
, area: ['550px', '550px']
, id: 'LAY-popup-content-add'
, success: function (e, i) {
view(this.id).render("user/administrators/dicform").done(function () {
form.render(null, "layuiadmin-form-dic"), form.on("submit(LAY-user-dic-submit)", function (e) {
e.field.parentId = l.id;
console.log(e.field);
//提交数据
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
dataType: "json",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/System/dic/add",//实际使用请改成服务端真实接口
async: false,
data: JSON.stringify(e.field),
done: function (res) {
inst.reloadData(),
layer.close(i)
}
})
})
})
}
});
}
if (layEvent === "edit") {
admin.popup({
title: '编辑字典'
, area: ['550px', '550px']
, id: 'LAY-popup-content-add'
, success: function (e, i) {
view(this.id).render("user/administrators/dicform", l).done(function () {
form.render(null, "layuiadmin-form-dic"), form.on("submit(LAY-user-dic-submit)", function (e) {
//提交数据
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
dataType: "json",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/System/dic/update",//实际使用请改成服务端真实接口
async: false,
data: JSON.stringify(e.field),
done: function (res) {
inst.reloadData(),
layer.close(i)
}
})
})
})
}
});
}
if (layEvent === "del") {
layer.confirm("确定删除此字典么?", function (i) {
//提交数据
admin.req({
method: "get",
url: setter.baseurl + "adminapi/System/dic/delete?id=" + l.id,//实际使用请改成服务端真实接口
async: true,
done: function (res) {
obj.del(),
layer.close(i)
}
})
})
}
});
});
</script>
@@ -0,0 +1,82 @@
<div class="layui-form" lay-filter="layuiadmin-form-dic" style="padding: 20px 30px 0 0;">
<div class="layui-form-item">
<label class="layui-form-label">名字</label>
<div class="layui-input-block">
<script type="text/html" template>
<input type="text" name="id" value="{{ d.params.id}}" class="layui-input" style="display:none">
<input type="text" name="parentId" value="{{ d.params.parentId}}" class="layui-input" style="display:none">
</script>
<script type="text/html" template>
<input type="text" name="name" value="{{ d.params.name || '' }}" lay-verify="required" placeholder="名字" autocomplete="off" class="layui-input">
</script>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">序号</label>
<div class="layui-input-block">
<script type="text/html" template>
<input type="number" name="order" value="{{ d.params.order || 0 }}" lay-verify="required" placeholder="序号" step="1" autocomplete="off" class="layui-input">
</script>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">编码</label>
<div class="layui-input-block">
<script type="text/html" template>
<input type="text" name="code" value="{{ d.params.code || '' }}" autocomplete="off" class="layui-input">
</script>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">值</label>
<div class="layui-input-block">
<script type="text/html" template>
<input type="text" name="value" value="{{ d.params.value || '' }}" autocomplete="off" class="layui-input">
</script>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">备注</label>
<div class="layui-input-block">
<script type="text/html" template>
<input type="text" name="remark" value="{{ d.params.remark || '' }}" autocomplete="off" class="layui-input">
</script>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">备注1</label>
<div class="layui-input-block">
<script type="text/html" template>
<input type="text" name="remark1" value="{{ d.params.remark1 || '' }}" autocomplete="off" class="layui-input">
</script>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-inline">
<input type="button" lay-submit lay-filter="LAY-user-dic-submit" value="确认" class="layui-btn">
</div>
</div>
</div>
<script>
layui.use(['admin', 'form','tree'], function(){
var $ = layui.$
, tree = layui.tree
, form = layui.form
, admin = layui.admin
, setter = layui.setter
,treedata = [];
});
</script>
@@ -0,0 +1,136 @@
<title>后台管理员</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>用户</cite></a>
<a><cite>后台管理员</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="layadmin-useradmin-formlist">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">用户名</label>
<div class="layui-input-block">
<input type="text" name="keyWord" placeholder="请输入" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<button class="layui-btn layuiadmin-btn-useradmin" lay-submit lay-filter="LAY-user-front-search">
<i class="layui-icon layui-icon-search layuiadmin-button-btn"></i>
</button>
</div>
</div>
</div>
<div class="layui-card-body">
<div style="padding-bottom: 10px;">
<button class="layui-btn layuiadmin-btn-admin" data-type="add">添加</button>
</div>
<table id="LAY-user-back-manage" lay-filter="LAY-user-back-manage"></table>
<script type="text/html" id="table-useradmin-admin">
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
</div>
</div>
</div>
<script>
layui.use('useradmin', layui.factory('useradmin')).use(['admin', 'useradmin', 'table'], function () {
var $ = layui.$
, admin = layui.admin
, view = layui.view
, table = layui.table
, setter = layui.setter
, form = layui.form;
form.render(null, 'layadmin-userfront-formlist');
//监听搜索
form.on('submit(LAY-user-front-search)', function (data) {
var field = data.field;
//执行重载
table.reload('LAY-user-back-manage', {
where: field
});
});
//事件
var active = {
add: function () {
admin.popup({
title: '添加管理员'
, area: ["500px", "600px"]
, id: 'LAY-popup-user-add'
, success: function (layero, index) {
view(this.id).render('user/administrators/adminform').done(function () {
form.render(null, 'layuiadmin-form-admin');
//监听提交 - 使用 off().on() 防止重复绑定
$(layero).find('[lay-filter="LAY-user-back-submit"]').off('click.adminsubmit').on('click.adminsubmit', function () {
// 手动获取表单数据
var userName = $(layero).find('input[name="userName"]').val();
var displayName = $(layero).find('input[name="displayName"]').val();
var icon = $(layero).find('input[name="icon"]').val();
var password = $(layero).find('input[name="password"]').val();
var systemRole = $(layero).find('select[name="systemRole"]').val();
var systemRoleText = $(layero).find('select[name="systemRole"] option:selected').text();
var param = {
userName: userName,
displayName: displayName,
icon: icon,
password: password,
systemRole: {
id: systemRole,
name: systemRoleText
}
};
//提交数据
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
dataType: "json",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/System/user/add",//实际使用请改成服务端真实接口
async: false,
data: JSON.stringify(param),
done: function (res) {
//$.ajax({});
layui.table.reload('LAY-user-back-manage'); //重载表格
layer.close(index); //执行关闭
}
});
return false; // 防止表单默认提交
});
});
}
});
}
};
$('.layui-btn.layuiadmin-btn-admin').on('click', function () {
var type = $(this).data('type');
active[type] ? active[type].call(this) : '';
});
});
</script>
@@ -0,0 +1,231 @@
<title>菜单管理</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>用户</cite></a>
<a><cite>菜单管理</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-body">
<div style="padding-bottom: 10px;">
<button class="layui-btn layuiadmin-btn-nav" data-type="add">添加</button>
</div>
<table id="LAY-user-nav" lay-filter="LAY-user-nav"></table>
<script type="text/html" id="table-nav-admin-tools">
<div class="layui-btn-container">
<a class="layui-btn layui-btn-warm layui-btn-xs" lay-event="addChild">新增</a>
<a class="layui-btn layui-btn-primary layui-btn-xs" lay-event="edit">编辑</a>
<a class="layui-btn layui-btn-primary layui-btn-xs" lay-event="del">删除</a>
</div>
</script>
</div>
</div>
</div>
<script>
layui.use(['admin', "form", 'treeTable'], function () {
var setter = layui.setter;
var treeTable = layui.treeTable;
var layer = layui.layer;
var admin = layui.admin;
var form = layui.form;
var dropdown = layui.dropdown;
var view = layui.view;
var $ = layui.$;
// 渲染
var active = {
add: function () {
admin.popup({
title: '新增菜单'
, area: ['550px', '550px']
, id: 'LAY-popup-content-add'
, success: function (e, i) {
view(this.id).render("user/administrators/navform").done(function () {
form.render(null, "layuiadmin-form-nav"), form.on("submit(LAY-user-nav-submit)", function (e) {
//提交数据
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
dataType: "json",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/System/nav/add",//实际使用请改成服务端真实接口
async: false,
data: JSON.stringify(e.field),
done: function (res) {
inst.reloadData(),
layer.close(i)
}
})
})
})
}
});
}
};
$('.layui-btn.layuiadmin-btn-nav').on('click', function () {
var type = $(this).data('type');
active[type] ? active[type].call(this) : '';
});
var inst = treeTable.render({
elem: '#LAY-user-nav',
url: setter.baseurl + "adminapi/System/nav/list", // 此处为静态模拟数据,实际使用时需换成真实接口
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
tree: {
/*
// 异步加载子节点
async: {
enable: true,
url: '/static/json/2/treeTable/demo-async.json', // 此处为静态模拟数据,实际使用时需换成真实接口
autoParam: ["parentId=id"]
}
*/
},
maxHeight: '501px',
cols: [[
{ field: 'name', title: '名称',fixed: 'left' },
{ field: 'order', title: '序号', fixed: 'left' },
{
field: 'url', title: '地址'},
{ fixed: "right", title: "操作", width: 190, align: "center", toolbar: "#table-nav-admin-tools" }
]],
page: false
});
// 表头工具栏工具事件
treeTable.on("toolbar(table-nav-admin)", function (obj) {
var config = obj.config;
var tableId = config.id;
var status = treeTable.checkStatus(tableId);
// 获取选中行
if (obj.event === "getChecked") {
if (!status.data.length) return layer.msg('无选中数据');
console.log(status);
layer.alert("当前数据选中已经输出到控制台,<br>您可按 F12 从控制台中查看结果。");
}
});
// 单元格工具事件
treeTable.on('tool(' + inst.config.id + ')', function (obj) {
var layEvent = obj.event; // 获得 lay-event 对应的值
var trElem = obj.tr;
var trData = obj.data;
var tableId = obj.config.id;
l = obj.data;
if (layEvent === "addChild") {
admin.popup({
title: '新增菜单'
, area: ['550px', '550px']
, id: 'LAY-popup-content-add'
, success: function (e, i) {
view(this.id).render("user/administrators/navform").done(function () {
form.render(null, "layuiadmin-form-nav"), form.on("submit(LAY-user-nav-submit)", function (e) {
e.field.parentId = l.id;
console.log(e.field);
//提交数据
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
dataType: "json",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/System/nav/add",//实际使用请改成服务端真实接口
async: false,
data: JSON.stringify(e.field),
done: function (res) {
inst.reloadData(),
layer.close(i)
}
})
})
})
}
});
}
if (layEvent === "edit") {
admin.popup({
title: '编辑菜单'
, area: ['550px', '550px']
, id: 'LAY-popup-content-add'
, success: function (e, i) {
view(this.id).render("user/administrators/navform", l).done(function () {
form.render(null, "layuiadmin-form-nav"), form.on("submit(LAY-user-nav-submit)", function (e) {
//提交数据
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
dataType: "json",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/System/nav/update",//实际使用请改成服务端真实接口
async: false,
data: JSON.stringify(e.field),
done: function (res) {
inst.reloadData(),
layer.close(i)
}
})
})
})
}
});
}
if (layEvent === "del") {
layer.confirm("确定删除此菜单么?", function (i) {
//提交数据
admin.req({
method: "get",
url: setter.baseurl + "adminapi/System/nav/delete?id=" + l.id,//实际使用请改成服务端真实接口
async: true,
done: function (res) {
obj.del(),
layer.close(i)
}
})
})
}
});
});
</script>
@@ -0,0 +1,56 @@
<div class="layui-form" lay-filter="layuiadmin-form-nav" style="padding: 20px 30px 0 0;">
<div class="layui-form-item">
<label class="layui-form-label">名字</label>
<div class="layui-input-block">
<script type="text/html" template>
<input type="text" name="id" value="{{ d.params.id}}" class="layui-input" style="display:none">
<input type="text" name="parentId" value="{{ d.params.parentId}}" class="layui-input" style="display:none">
</script>
<script type="text/html" template>
<input type="text" name="name" value="{{ d.params.name || '' }}" lay-verify="required" placeholder="名字" autocomplete="off" class="layui-input">
</script>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">序号</label>
<div class="layui-input-block">
<script type="text/html" template>
<input type="number" name="order" value="{{ d.params.order || 0 }}" lay-verify="required" placeholder="序号" step="1" autocomplete="off" class="layui-input">
</script>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">跳转地址</label>
<div class="layui-input-block">
<script type="text/html" template>
<input type="text" name="url" value="{{ d.params.url || '' }}" autocomplete="off" class="layui-input">
</script>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-inline">
<input type="button" lay-submit lay-filter="LAY-user-nav-submit" value="确认" class="layui-btn">
</div>
</div>
</div>
<script>
layui.use(['admin', 'form','tree'], function(){
var $ = layui.$
, tree = layui.tree
, form = layui.form
, admin = layui.admin
, setter = layui.setter
,treedata = [];
});
</script>
@@ -0,0 +1,90 @@
<title>角色管理</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>用户</cite></a>
<a><cite>角色管理</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-body">
<div style="padding-bottom: 10px;">
<button class="layui-btn layuiadmin-btn-role" data-type="add">添加</button>
</div>
<table id="LAY-user-back-role" lay-filter="LAY-user-back-role"></table>
<script type="text/html" id="table-useradmin-admin">
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="role"><i class="layui-icon layui-icon-edit"></i>权限</a>
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
</div>
</div>
</div>
<script>
layui.use('useradmin', layui.factory('useradmin')).use(['admin', 'useradmin', 'table'], function(){
var $ = layui.$
,admin = layui.admin
,view = layui.view
, table = layui.table
, setter = layui.setter
,form = layui.form;
//事件
var active = {
add: function(){
admin.popup({
title: '添加新角色'
,area: ['500px', '480px']
,id: 'LAY-popup-user-add'
,success: function(layero, index){
view(this.id).render('user/administrators/roleform').done(function(){
form.render(null, 'layuiadmin-form-role');
//监听提交
form.on('submit(LAY-user-role-submit)', function(data){
var param = data.field
//提交数据
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
dataType: "json",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/System/role/add",//实际使用请改成服务端真实接口
async: false,
data: JSON.stringify(param),
done: function (res) {
layui.table.reload('LAY-user-back-role'); //重载表格
layer.close(index); //执行关闭
}
})
});
});
}
});
}
}
$('.layui-btn.layuiadmin-btn-role').on('click', function(){
var type = $(this).data('type');
console.log(type);
active[type] ? active[type].call(this) : '';
});
});
</script>
@@ -0,0 +1,42 @@
<div class="layui-form" lay-filter="layuiadmin-form-role" style="padding: 20px 30px 0 0;">
<div class="layui-form-item">
<label class="layui-form-label">角色</label>
<div class="layui-input-block">
<script type="text/html" template>
<input type="text" name="id" value="{{ d.params.id}}" class="layui-input" style="display:none">
</script>
<script type="text/html" template>
<input type="text" name="roleName" value="{{ d.params.roleName || '' }}" lay-verify="required" placeholder="请输入角色名称" autocomplete="off" class="layui-input">
</script>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">具体描述</label>
<div class="layui-input-block">
<script type="text/html" template>
<textarea type="text" name="description" lay-verify="required" autocomplete="off" class="layui-textarea">{{ d.params.description || '' }}</textarea>
</script>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-inline">
<input type="button" lay-submit lay-filter="LAY-user-role-submit" value="确认" class="layui-btn">
</div>
</div>
</div>
<script>
layui.use(['admin', 'form','tree'], function(){
var $ = layui.$
, tree = layui.tree
, form = layui.form
, admin = layui.admin
, setter = layui.setter
,treedata = [];
});
</script>
@@ -0,0 +1,155 @@
<div class="layui-form" lay-filter="layuiadmin-form-role-tree" style="padding: 20px 30px 0 0;">
<div class="layui-form-item">
<label class="layui-form-label">权限范围</label>
<div class="layui-input-block">
<ul id="navigationist"></ul>
<script type="text/html" lay-done="sendParams(d.params)" template>
</script>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-inline">
<input type="button" lay-submit lay-filter="LAY-user-front-submit" value="确认" class="layui-btn">
</div>
</div>
</div>
<script>
var id = '';
var isLayUI = false;
function sendParams(params) {
id = params.id;
if (isLayUI) {
layuiLoad();
}
}
layuiLoad();
function layuiLoad() {
layui.use(['admin', 'form', 'tree', 'user', 'laydate'], function () {
var $ = layui.$
, tree = layui.tree
, form = layui.form
, admin = layui.admin
, setter = layui.setter
, router = layui.router()
, user = layui.user
, laydate = layui.laydate
, treedata = [];
treedatas = [];
treeselecedIds = [];
form.on('submit(LAY-user-front-submit)', function (data) {
var checkData = tree.getChecked("navigatioid");
treeselecedIds = [];
gettreeSelectIds(checkData)
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
dataType: "json",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/System/role/updatenav",//实际使用请改成服务端真实接口
async: false,
data: JSON.stringify({ id: id, navIds: treeselecedIds }),
done: function (res) {
//关闭弹窗
layer.closeAll()
}
})
return false; //阻止表单跳转。如果需要表单跳转,去掉这段即可。
});
//提交数据
admin.req({
method: "get",
contentType: "application/json;charset=UTF-8",
dataType: "json",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/System/nav/list",//实际使用请改成服务端真实接口
async: false,
done: function (res) {
treedata = gettreedata(res.data);
}
})
function gettreedata(arr) {
let arr1 = [];
layui.each(arr, function (index, val) {
if (val.children != null) {
val.children = gettreedata(val.children);
}
arr1.push({ title: val.name, id: val.id, children: val.children, spread: tree, checked: false });
})
return arr1;
}
function gettreeSelectIds(arr) {
layui.each(arr, function (index, val) {
if (val.children != null && val.children.length>0) {
val.children = gettreeSelectIds(val.children);
} else {
treeselecedIds.push(val.id);
}
})
}
treeRender();
if (id != null && id != "") {
admin.req({
method: "get",
contentType: "application/json;charset=UTF-8",
dataType: "json",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
url: setter.baseurl + "adminapi/System/role/get?id=" + id,//实际使用请改成服务端真实接口
async: false,
done: function (res) {
tree.setChecked('navigatioid', res.data.navIds); //批量勾选 id 为 2、3 的节点
}
})
}
function treeRender() {
isLayUI = true;
tree.render({
elem: '#navigationist',
id: 'navigatioid',
data: treedata,
showCheckbox: true, // 这里可以设置为true来显示复选框,但LAYUI原生不支持多选逻辑
oncheck: function (node) {
}
});
}
});
}
</script>
@@ -0,0 +1,312 @@
<title>金币变动记录</title>
<script src="/admin/layui/xm-select.js"></script>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>用户管理</cite></a>
<a><cite>金币变动记录</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="layadmin-assetchange-formlist">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">用户ID</label>
<div class="layui-input-block">
<input type="text" name="userId" placeholder="请输入用户ID" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">关键词</label>
<div class="layui-input-block">
<input type="text" name="keyWord" placeholder="用户编号/昵称" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">货币类型</label>
<div class="layui-input-block">
<div id="currencyTypes-select"></div>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">变动类型</label>
<div class="layui-input-block">
<select name="changeTypes" multiple>
<option value="1">增加</option>
<option value="2">减少</option>
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">事件来源</label>
<div class="layui-input-block">
<select name="sources" multiple>
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">时间范围</label>
<div class="layui-input-block">
<input type="text" name="timeRange" id="timeRange" placeholder="选择时间范围" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<button class="layui-btn layuiadmin-btn-list" lay-submit lay-filter="LAY-assetchange-search">
<i class="layui-icon layui-icon-search layuiadmin-button-btn"></i> 搜索
</button>
<button class="layui-btn layui-btn-primary" lay-filter="LAY-assetchange-reset">
<i class="layui-icon layui-icon-refresh layuiadmin-button-btn"></i> 重置
</button>
</div>
</div>
</div>
<div class="layui-card">
<div class="layui-card-body">
<table id="LAY-assetchange-manage" lay-filter="LAY-assetchange-manage"></table>
<script type="text/html" id="table-assetchange-toolbar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-primary" lay-event="refresh">
<i class="layui-icon layui-icon-refresh"></i> 刷新
</button>
</div>
</script>
<script type="text/html" id="changeTypeTpl">
{{# if(d.type === 1){ }}
<span class="layui-badge layui-bg-green">增加</span>
{{# } else { }}
<span class="layui-badge layui-bg-red">减少</span>
{{# } }}
</script>
<script type="text/html" id="currencyTypeTpl">
{{# if(d.currencyType === 1){ }}
<span class="layui-badge layui-bg-orange">金币</span>
{{# } else if(d.currencyType === 2){ }}
<span class="layui-badge layui-bg-blue">保险箱金币</span>
{{# } else { }}
<span class="layui-badge layui-bg-purple">钻石</span>
{{# } }}
</script>
<script type="text/html" id="countTpl">
<div>
<div>变动前: {{d.beforeCount}}</div>
<div style="color: {{d.type === 1 ? '#5FB878' : '#FF5722'}};">
{{d.type === 1 ? '+' : '-'}}{{d.changeCount}}
</div>
<div>变动后: {{d.afterCount}}</div>
</div>
</script>
</div>
</div>
</div>
<script>
layui.use(['admin', 'table', 'form', 'laydate'], function () {
var $ = layui.$,
admin = layui.admin,
table = layui.table,
setter = layui.setter,
form = layui.form,
laydate = layui.laydate;
// 时间范围选择器
laydate.render({
elem: '#timeRange',
type: 'datetime',
range: '~'
});
// 加载事件来源列表
function loadSources() {
admin.req({
url: setter.baseurl + 'adminapi/UserAssetChange/sources',
done: function (res) {
var html = '';
layui.each(res.data, function (index, item) {
html += '<option value="' + item.value + '">' + item.label + '</option>';
});
$('[name="sources"]').append(html);
form.render('select');
}
});
}
loadSources();
// 初始化货币类型 xm-select
console.log('[资金变动] 开始初始化货币类型 xm-select');
console.log('[资金变动] #currencyTypes-select 元素:', $('#currencyTypes-select').length);
var currencyTypesSelect = xmSelect.render({
el: '#currencyTypes-select',
data: [
{name: '金币', value: 1},
{name: '保险箱金币', value: 2},
{name: '钻石', value: 3}
],
tips: '请选择货币类型',
filterable: true,
model: { label: { type: 'block', block: { showIcon: false } } },
on: function(data) {
console.log('[资金变动] 货币类型选择变化:', data.arr);
}
});
console.log('[资金变动] 货币类型 xm-select 初始化完成:', currencyTypesSelect);
form.render(null, 'layadmin-assetchange-formlist');
// 从URL获取userId参数(从用户列表跳转时带入)
var urlParams = new URLSearchParams(window.location.hash.split('?')[1] || '');
var initUserId = urlParams.get('userId') || '';
var initNickName = urlParams.get('nickName') || '';
if (initUserId) {
$('input[name="userId"]').val(initUserId);
if (initNickName) {
$('.layui-breadcrumb a:last').text('金币变动记录 - ' + initNickName);
}
}
// 监听搜索
form.on('submit(LAY-assetchange-search)', function (data) {
var field = data.field;
console.log('[资金变动] 搜索参数:', field);
// 处理时间范围
if (field.timeRange) {
var times = field.timeRange.split('~');
field.timeStart = Math.floor(new Date(times[0].trim()).getTime() / 1000);
field.timeEnd = Math.floor(new Date(times[1].trim()).getTime() / 1000);
}
delete field.timeRange;
// 处理多选:layui 原生 select multiple(问题3修复:确保currencyTypes是数组)
field.currencyTypes = currencyTypesSelect.getValue('value');
console.log('[资金变动] 货币类型选择:', field.currencyTypes);
if (field.currencyTypes.length === 0) {
delete field.currencyTypes;
}
if (field.changeTypes) {
field.changeTypes = Array.isArray(field.changeTypes) ? field.changeTypes : [field.changeTypes];
} else {
delete field.changeTypes;
}
if (field.sources) {
field.sources = Array.isArray(field.sources) ? field.sources : [field.sources];
} else {
delete field.sources;
}
// 排除击杀鱼和开炮事件
field.excludeSources = ['开炮', '击杀鱼'];
console.log('[资金变动] 完整请求参数:', field);
table.reload('LAY-assetchange-manage', {
where: field,
page: { curr: 1 }
});
});
});
// 监听重置
form.on('submit(LAY-assetchange-reset)', function (data) {
$('input[name="userId"]').val('');
$('input[name="keyWord"]').val('');
$('[name="currencyTypes"]').val('');
$('[name="changeTypes"]').val('');
$('[name="sources"]').val('');
$('#timeRange').val('');
form.render('select');
table.reload('LAY-assetchange-manage', {
where: {},
page: { curr: 1 }
});
});
// 刷新按钮
$('.layui-btn-container').on('click', '[lay-event="refresh"]', function () {
table.reload('LAY-assetchange-manage');
});
table.render({
elem: "#LAY-assetchange-manage",
url: setter.baseurl + "adminapi/UserAssetChange/list",
method: "post",
contentType: "application/json",
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
page: true,
toolbar: '#table-assetchange-toolbar',
defaultToolbar: ['filter', 'exports', 'print'],
where: {
userId: initUserId
},
request: {
pageName: 'pageIndex',
limitName: 'pageSize'
},
limit: 10,
cols: [[
{ type: 'numbers', title: '序号', width: 60 },
{ field: "userId", title: "用户ID", width: 120 },
{ field: "userName", title: "用户名", width: 120 },
{ field: "currencyType", title: "货币类型", width: 120, templet: '#currencyTypeTpl' },
{ field: "type", title: "变动类型", width: 100, templet: '#changeTypeTpl' },
{ field: "source", title: "事件来源", width: 150 },
{ field: "beforeCount", title: "变动前", width: 120 },
{ field: "changeCount", title: "变动值", width: 120, templet: function(d) {
return '<span style="color: ' + (d.type === 1 ? '#5FB878' : '#FF5722') + ';">' +
(d.type === 1 ? '+' : '-') + d.changeCount + '</span>';
}},
{ field: "afterCount", title: "变动后", width: 120 },
{ field: "remark", title: "备注", minWidth: 200 },
{ field: "createTime", title: "变动时间", width: 160, templet: function(d) {
return formatDateTime(d.createTime);
}}
]],
text: "对不起,加载出现异常!",
response: {
statusName: 'code',
statusCode: 0,
msgName: 'info',
countName: 'count',
dataName: 'data'
},
done: function(res) {
console.log('[资金变动] 表格数据加载完成:', res);
if (res.data && res.data.length > 0) {
console.log('[资金变动] 第一条数据示例:', res.data[0]);
}
}
});
function formatDateTime(date) {
if (!date) return '-';
var d = new Date(date);
var year = d.getFullYear();
var month = ('0' + (d.getMonth() + 1)).slice(-2);
var day = ('0' + d.getDate()).slice(-2);
var hour = ('0' + d.getHours()).slice(-2);
var minute = ('0' + d.getMinutes()).slice(-2);
var second = ('0' + d.getSeconds()).slice(-2);
return year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second;
}
});
</script>
@@ -0,0 +1,144 @@
<script type="text/html" template>
<link rel="stylesheet" href="{{ layui.setter.base }}style/login.css?v={{ layui.admin.v }}-1" media="all">
</script>
<div class="layadmin-user-login layadmin-user-display-show" id="LAY-user-login" style="display: none;">
<div class="layadmin-user-login-main">
<div class="layadmin-user-login-box layadmin-user-login-header">
<h2>layuiAdmin</h2>
<p>layui 官方出品的单页面后台管理模板系统</p>
</div>
<div class="layadmin-user-login-box layadmin-user-login-body layui-form">
<script type="text/html" template>
{{# if(layui.router().search.type === 'resetpass'){ }}
<div class="layui-form-item">
<label class="layadmin-user-login-icon layui-icon layui-icon-password" for="LAY-user-login-password"></label>
<input type="password" name="password" id="LAY-user-login-password" lay-verify="pass" placeholder="新密码" class="layui-input">
</div>
<div class="layui-form-item">
<label class="layadmin-user-login-icon layui-icon layui-icon-password" for="LAY-user-login-repass"></label>
<input type="password" name="repass" id="LAY-user-login-repass" lay-verify="required" placeholder="确认密码" class="layui-input">
</div>
<div class="layui-form-item">
<button class="layui-btn layui-btn-fluid" lay-submit lay-filter="LAY-user-forget-resetpass">重置新密码</button>
</div>
{{# } else { }}
<div class="layui-form-item">
<label class="layadmin-user-login-icon layui-icon layui-icon-cellphone" for="LAY-user-login-cellphone"></label>
<input type="text" name="cellphone" id="LAY-user-login-cellphone" lay-verify="phone" placeholder="请输入注册时的手机号" class="layui-input">
</div>
<div class="layui-form-item">
<div class="layui-row">
<div class="layui-col-xs7">
<label class="layadmin-user-login-icon layui-icon layui-icon-vercode" for="LAY-user-login-vercode"></label>
<input type="text" name="vercode" id="LAY-user-login-vercode" lay-verify="required" placeholder="图形验证码" class="layui-input">
</div>
<div class="layui-col-xs5">
<div style="margin-left: 10px;">
<img src="https://www.oschina.net/action/user/captcha" class="layadmin-user-login-codeimg" id="LAY-user-get-vercode">
</div>
</div>
</div>
</div>
<div class="layui-form-item">
<div class="layui-row">
<div class="layui-col-xs7">
<label class="layadmin-user-login-icon layui-icon layui-icon-vercode" for="LAY-user-login-smscode"></label>
<input type="text" name="vercode" id="LAY-user-login-smscode" lay-verify="required" placeholder="短信验证码" class="layui-input">
</div>
<div class="layui-col-xs5">
<div style="margin-left: 10px;">
<button type="button" class="layui-btn layui-btn-primary layui-btn-fluid" id="LAY-user-forget-getsmscode">获取验证码</button>
</div>
</div>
</div>
</div>
<div class="layui-form-item">
<button class="layui-btn layui-btn-fluid" lay-submit lay-filter="LAY-user-forget-submit">找回密码</button>
</div>
{{# } }}
</script>
</div>
</div>
<div class="layui-trans layadmin-user-login-footer">
<p>© 2018 <a href="http://www.layui.com/" target="_blank">layui.com</a></p>
<p>
<span><a href="http://www.layui.com/admin/#get" target="_blank">获取授权</a></span>
<span><a href="http://www.layui.com/admin/pro/" target="_blank">在线演示</a></span>
<span><a href="http://www.layui.com/admin/" target="_blank">前往官网</a></span>
</p>
</div>
</div>
<script>
layui.use(['admin', 'form', 'user'], function(){
var $ = layui.$
,setter = layui.setter
,admin = layui.admin
,form = layui.form
,router = layui.router();
form.render();
//发送短信验证码
admin.sendAuthCode({
elem: '#LAY-user-forget-getsmscode'
,elemPhone: '#LAY-user-login-cellphone'
,elemVercode: '#LAY-user-login-vercode'
,ajax: {
url: './json/user/sms.js' //实际使用请改成服务端真实接口
}
});
//找回密码下一步
form.on('submit(LAY-user-forget-submit)', function(obj){
var field = obj.field;
//请求接口
admin.req({
url: './json/user/forget.js' //实际使用请改成服务端真实接口
,data: field
,done: function(res){
location.hash = router.href + '/type=resetpass';
}
});
return false;
});
//重置密码
form.on('submit(LAY-user-forget-resetpass)', function(obj){
var field = obj.field;
//确认密码
if(field.password !== field.repass){
return layer.msg('两次密码输入不一致');
}
//请求接口
admin.req({
url: './json/user/resetpass.js' //实际使用请改成服务端真实接口
,data: field
,done: function(res){
layer.msg('密码已成功重置', {
offset: '15px'
,icon: 1
,time: 1000
}, function(){
location.hash = '/user/login'; //跳转到登入页
});
}
});
return false;
});
});
</script>
@@ -0,0 +1,90 @@
<script type="text/html" template>
<link rel="stylesheet" href="{{ layui.setter.base }}style/login.css?v={{ layui.admin.v }}-1" media="all">
</script>
<div class="layadmin-user-login layadmin-user-display-show" id="LAY-user-login" style="display: none;">
<div class="layadmin-user-login-main">
<div class="layadmin-user-login-box layadmin-user-login-header">
<h2>通用后台管理系统</h2>
</div>
<div class="layadmin-user-login-box layadmin-user-login-body layui-form">
<div class="layui-form-item">
<label class="layadmin-user-login-icon layui-icon layui-icon-username" for="LAY-user-login-username"></label>
<input type="text" name="username" id="LAY-user-login-username" lay-verify="required" placeholder="用户名" class="layui-input">
</div>
<div class="layui-form-item">
<label class="layadmin-user-login-icon layui-icon layui-icon-password" for="LAY-user-login-password"></label>
<input type="password" name="password" id="LAY-user-login-password" lay-verify="required" placeholder="密码" class="layui-input">
</div>
<!--图形验证码-->
<!--<div class="layui-form-item">
<div class="layui-row">
<div class="layui-col-xs7">
<label class="layadmin-user-login-icon layui-icon layui-icon-vercode" for="LAY-user-login-vercode"></label>
<input type="text" name="vercode" id="LAY-user-login-vercode" lay-verify="required" placeholder="图形验证码" class="layui-input">
</div>
<div class="layui-col-xs5">
<div style="margin-left: 10px;">
<img src="https://www.oschina.net/action/user/captcha" class="layadmin-user-login-codeimg" id="LAY-user-get-vercode">
</div>
</div>
</div>
</div>-->
<div class="layui-form-item">
<button class="layui-btn layui-btn-fluid" lay-submit lay-filter="LAY-user-login-submit">登 入</button>
</div>
</div>
</div>
</div>
<script>
layui.use(['admin', 'form', 'user'], function(){
var $ = layui.$
,setter = layui.setter
,admin = layui.admin
,form = layui.form
,router = layui.router()
,search = router.search;
form.render();
//提交
form.on('submit(LAY-user-login-submit)', function(obj){
//请求登入接口
admin.req({
url: setter.baseurl + 'adminapi/System/user/login' //实际使用请改成服务端真实接口
,data: obj.field
,done: function(res){
//请求成功后,写入 access_token
layui.data(setter.tableName, {
key: setter.request.tokenName
,value: res.data.token
});
//登入成功的提示与跳转
layer.msg('登入成功', {
offset: '15px'
,icon: 1
,time: 1000
}, function(){
location.hash = search.redirect ? decodeURIComponent(search.redirect) : '/';
});
}
});
});
});
</script>
@@ -0,0 +1,125 @@
<script type="text/html" template>
<link rel="stylesheet" href="{{ layui.setter.base }}style/login.css?v={{ layui.admin.v }}-1" media="all">
</script>
<div class="layadmin-user-login layadmin-user-display-show" id="LAY-user-login" style="display: none;">
<div class="layadmin-user-login-main">
<div class="layadmin-user-login-box layadmin-user-login-header">
<h2>layuiAdmin</h2>
<p>layui 官方出品的单页面后台管理模板系统</p>
</div>
<div class="layadmin-user-login-box layadmin-user-login-body layui-form">
<div class="layui-form-item">
<label class="layadmin-user-login-icon layui-icon layui-icon-cellphone" for="LAY-user-login-cellphone"></label>
<input type="text" name="cellphone" id="LAY-user-login-cellphone" lay-verify="phone" placeholder="手机" class="layui-input">
</div>
<div class="layui-form-item">
<div class="layui-row">
<div class="layui-col-xs7">
<label class="layadmin-user-login-icon layui-icon layui-icon-vercode" for="LAY-user-login-vercode"></label>
<input type="text" name="vercode" id="LAY-user-login-vercode" lay-verify="required" placeholder="验证码" class="layui-input">
</div>
<div class="layui-col-xs5">
<div style="margin-left: 10px;">
<button type="button" class="layui-btn layui-btn-primary layui-btn-fluid" id="LAY-user-reg-getsmscode">获取验证码</button>
</div>
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layadmin-user-login-icon layui-icon layui-icon-password" for="LAY-user-login-password"></label>
<input type="password" name="password" id="LAY-user-login-password" lay-verify="pass" placeholder="密码" class="layui-input">
</div>
<div class="layui-form-item">
<label class="layadmin-user-login-icon layui-icon layui-icon-password" for="LAY-user-login-repass"></label>
<input type="password" name="repass" id="LAY-user-login-repass" lay-verify="required" placeholder="确认密码" class="layui-input">
</div>
<div class="layui-form-item">
<label class="layadmin-user-login-icon layui-icon layui-icon-username" for="LAY-user-login-nickname"></label>
<input type="text" name="nickname" id="LAY-user-login-nickname" lay-verify="nickname" placeholder="昵称" class="layui-input">
</div>
<div class="layui-form-item">
<input type="checkbox" name="agreement" lay-skin="primary" title="同意用户协议" checked>
</div>
<div class="layui-form-item">
<button class="layui-btn layui-btn-fluid" lay-submit lay-filter="LAY-user-reg-submit">注 册</button>
</div>
<div class="layui-trans layui-form-item layadmin-user-login-other">
<label>社交账号注册</label>
<a href="javascript:;"><i class="layui-icon layui-icon-login-qq"></i></a>
<a href="javascript:;"><i class="layui-icon layui-icon-login-wechat"></i></a>
<a href="javascript:;"><i class="layui-icon layui-icon-login-weibo"></i></a>
<a lay-href="/user/login" class="layadmin-user-jump-change layadmin-link layui-hide-xs">用已有帐号登入</a>
<a lay-href="/user/login" class="layadmin-user-jump-change layadmin-link layui-hide-sm layui-show-xs-inline-block">登入</a>
</div>
</div>
</div>
<div class="layui-trans layadmin-user-login-footer">
<p>© 2018 <a href="http://www.layui.com/" target="_blank">layui.com</a></p>
<p>
<span><a href="http://www.layui.com/admin/#get" target="_blank">获取授权</a></span>
<span><a href="http://www.layui.com/admin/pro/" target="_blank">在线演示</a></span>
<span><a href="http://www.layui.com/admin/" target="_blank">前往官网</a></span>
</p>
</div>
</div>
<script>
layui.use(['admin', 'form', 'user'], function(){
var $ = layui.$
,setter = layui.setter
,admin = layui.admin
,form = layui.form
,router = layui.router();
form.render();
//发送短信验证码
admin.sendAuthCode({
elem: '#LAY-user-reg-getsmscode'
,elemPhone: '#LAY-user-login-cellphone'
,elemVercode: '#LAY-user-login-vercode'
,ajax: {
url: './json/user/sms.js' //实际使用请改成服务端真实接口
}
});
//提交
form.on('submit(LAY-user-reg-submit)', function(obj){
var field = obj.field;
//确认密码
if(field.password !== field.repass){
return layer.msg('两次密码输入不一致');
}
//是否同意用户协议
if(!field.agreement){
return layer.msg('你必须同意用户协议才能注册');
}
//请求接口
admin.req({
url: './json/user/reg.js' //实际使用请改成服务端真实接口
,data: field
,done: function(res){
layer.msg('注册成功', {
offset: '15px'
,icon: 1
,time: 1000
}, function(){
location.hash = '/user/login'; //跳转到登入页
});
}
});
return false;
});
});
</script>
@@ -0,0 +1,416 @@
<title>用户因子配置</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="">主页</a>
<a><cite>用户管理</cite></a>
<a><cite>用户因子配置</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header layuiadmin-card-header-auto">
<form class="layui-form" lay-filter="layadmin-factor-formlist">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">关键词</label>
<div class="layui-input-inline" style="width:150px;">
<input type="text" name="keyWord" placeholder="编号/昵称/手机" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">注册IP</label>
<div class="layui-input-inline" style="width:130px;">
<input type="text" name="registerIp" placeholder="注册IP" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">登录IP</label>
<div class="layui-input-inline" style="width:130px;">
<input type="text" name="loginIp" placeholder="登录IP" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">IP数量</label>
<div class="layui-input-inline" style="width:60px;">
<select name="ipCountOp">
<option value="">不限</option>
<option value="gt">大于</option>
<option value="gte">大于等于</option>
<option value="lt">小于</option>
<option value="lte">小于等于</option>
<option value="eq">等于</option>
</select>
</div>
<div class="layui-input-inline" style="width:60px;">
<input type="number" name="ipCount" placeholder="数量" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">屏蔽状态</label>
<div class="layui-input-inline" style="width:100px;">
<select name="isBlack">
<option value="">全部</option>
<option value="1">已屏蔽</option>
<option value="0">未屏蔽</option>
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">在线</label>
<div class="layui-input-inline" style="width:100px;">
<select name="isOnline">
<option value="">全部</option>
<option value="1">在线</option>
<option value="0">离线</option>
</select>
</div>
</div>
<div class="layui-inline">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-submit lay-filter="btn-search">
<i class="layui-icon layui-icon-search"></i> 搜索
</button>
<button type="button" class="layui-btn layui-btn-sm layui-btn-primary" id="btn-reset">
<i class="layui-icon layui-icon-refresh"></i> 重置
</button>
</div>
</div>
</form>
</div>
<div class="layui-card-body">
<table id="table-factor-list" lay-filter="table-factor-list"></table>
<script type="text/html" id="toolbar-factor">
<a class="layui-btn layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑因子</a>
<a class="layui-btn layui-btn-xs layui-btn-warm" lay-event="realtime" title="实时房间数据"><i class="layui-icon layui-icon-eye"></i>实时</a>
<a class="layui-btn layui-btn-xs layui-btn-normal" lay-event="setBlack"><i class="layui-icon layui-icon-fonts-del"></i>{{ d.isBlack ? '取消屏蔽' : '屏蔽' }}</a>
</script>
</div>
</div>
</div>
<script>
layui.use(['admin', 'form', 'table'], function () {
var $ = layui.$,
admin = layui.admin,
form = layui.form,
table = layui.table,
setter = layui.setter,
layer = layui.layer;
function formatTime(timestamp) {
if (!timestamp) return '-';
var date = new Date(timestamp * 1000);
var y = date.getFullYear();
var m = (date.getMonth() + 1).toString().padStart(2, '0');
var d = date.getDate().toString().padStart(2, '0');
return y + '-' + m + '-' + d;
}
function esc(s) {
if (s == null) return '';
return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
form.render();
table.render({
elem: '#table-factor-list',
url: setter.baseurl + 'adminapi/AdminUser/GetUserFactorList',
method: 'POST',
contentType: 'application/json;charset=UTF-8',
headers: {
'token': layui.data(setter.tableName)[setter.request.tokenName]
},
page: true,
limit: 15,
limits: [10, 15, 20, 30, 50],
request: {
pageName: 'pageIndex',
limitName: 'pageSize'
},
response: {
statusName: 'code',
statusCode: 0,
msgName: 'info',
countName: 'count',
dataName: 'data'
},
where: {},
cols: [[
{ field: 'userName', title: '昵称', width: 100, templet: function(d) { return esc(d.userName || '-'); } },
{ field: 'number', title: '用户编号', width: 120, templet: function(d) { return esc(d.number || '-'); } },
{ field: 'mobile', title: '手机号', width: 120, templet: function(d) { return esc(d.mobile || '-'); } },
{ field: 'registerIp', title: '注册IP', width: 130, templet: function(d) { return esc(d.registerIp || '-'); } },
{ field: 'loginIp', title: '登录IP', width: 130, templet: function(d) { return esc(d.loginIp || '-'); } },
{ field: 'ipCount', title: 'IP数量', width: 80, templet: function(d) {
var count = d.ipCount || 0;
return count > 5 ? '<span style="color:red;font-weight:bold;">' + count + '</span>' : count;
}},
{ field: 'isBlack', title: '屏蔽', width: 70, templet: function(d) {
return d.isBlack ? '<span class="layui-badge layui-bg-red">已屏蔽</span>' : '<span class="layui-badge layui-bg-green">正常</span>';
}},
{ field: 'violateValue', title: '系统因子', width: 90, templet: function(d) { return (d.violateValue || 0).toFixed(2); } },
{ field: 'additionalFactor', title: '附加值', width: 80, templet: function(d) { return (d.additionalFactor || 0).toFixed(2); } },
{ field: 'totalRecharge', title: '充值金额', width: 90, templet: function(d) { return (d.totalRecharge || 0).toFixed(2); } },
{ field: 'totalRechargeGold', title: '金币本金统计(不计VIP)', width: 155, templet: function(d) { var v = d.totalRechargeGold || 0; return v.toLocaleString(); } },
{ field: 'totalConsumeGold', title: '消费金币', width: 90, templet: function(d) { var v = d.totalConsumeGold || 0; return v.toLocaleString(); } },
{ field: 'currentGold', title: '当前金币', width: 100, templet: function(d) { var v = d.currentGold || 0; return v.toLocaleString(); } },
{ field: 'profit', title: '盈亏', width: 100, templet: function(d) {
var v = d.profit || 0;
var cls = v >= 0 ? 'color:green;' : 'color:red;';
return '<span style="' + cls + 'font-weight:bold;">' + (v >= 0 ? '+' : '') + v.toLocaleString() + '</span>';
}},
{ field: 'startingBonus', title: '注册金币', width: 90, templet: function(d) { var v = d.startingBonus || 0; return v.toLocaleString(); } },
{ field: 'compensateQuota', title: '补偿配额', width: 90, templet: function(d) { return d.compensateQuota || 0; } },
{ field: 'penaltyQuota', title: '罚款配额', width: 90, templet: function(d) { return d.penaltyQuota || 0; } },
{ field: 'missRate', title: '空炮率', width: 80, templet: function(d) { return (d.missRate || 0).toFixed(0) + '%'; } },
{ field: 'compRate', title: '补偿系数', width: 140, templet: function(d) {
var cr = d.compensateRate || {};
return 'B:' + (cr.bossRate || 1) + ' G:' + (cr.goldRate || 1) + ' S:' + (cr.specialRate || 1) + ' O:' + (cr.ordinary || 1);
}},
{ field: 'penaltyRate', title: '罚款系数', width: 140, templet: function(d) {
var pr = d.penaltyRate || {};
return 'B:' + (pr.bossRate || 1) + ' G:' + (pr.goldRate || 1) + ' S:' + (pr.specialRate || 1) + ' O:' + (pr.ordinary || 1);
}},
{ field: 'createTime', title: '注册时间', width: 110, templet: function(d) { return formatTime(d.createTime); } },
{ fixed: 'right', title: '操作', width: 250, align: 'center', toolbar: '#toolbar-factor' }
]],
text: { none: '暂无数据' },
parseData: function(res) {
console.log('API响应:', res);
if (res.code !== 0) {
layer.msg(res.info || '加载失败', { icon: 2 });
}
return {
"code": res.code,
"msg": res.info || "",
"count": res.count || 0,
"data": res.data || []
};
}
});
form.on('submit(btn-search)', function(data) {
var field = data.field;
var where = {
keyWord: field.keyWord || '',
registerIp: field.registerIp || '',
loginIp: field.loginIp || '',
isBlack: field.isBlack === '' ? null : (field.isBlack === '1'),
isOnline: field.isOnline === '' ? null : (field.isOnline === '1'),
ipCountOp: field.ipCountOp || '',
ipCount: field.ipCount ? parseInt(field.ipCount) : null
};
table.reload('table-factor-list', { where: where, page: { curr: 1 } });
return false;
});
$('#btn-reset').on('click', function() {
$('input[name="keyWord"]').val('');
$('input[name="registerIp"]').val('');
$('input[name="loginIp"]').val('');
$('input[name="ipCount"]').val('');
$('select[name="ipCountOp"]').val('');
$('select[name="isBlack"]').val('');
$('select[name="isOnline"]').val('');
form.render('select');
table.reload('table-factor-list', { where: {}, page: { curr: 1 } });
});
table.on('tool(table-factor-list)', function(obj) {
var data = obj.data;
var layEvent = obj.event;
if (layEvent === 'edit') {
var cr = data.compensateRate || {};
var pr = data.penaltyRate || {};
layer.open({
type: 1,
title: '编辑用户因子 - ' + (data.userName || '-'),
area: ['1000px', '750px'],
content: '<form class="layui-form" lay-filter="factor-edit-form" style="padding:20px;">' +
'<div class="layui-form-item"><label class="layui-form-label">系统因子</label><div class="layui-input-inline"><input type="number" step="0.01" name="violateValue" class="layui-input" value="' + (data.violateValue || 1) + '" readonly></div><div class="layui-form-mid">系统自动计算,不可修改。仅可修改下方的附加值</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">附加值</label><div class="layui-input-inline"><input type="number" step="0.01" name="additionalFactor" class="layui-input" value="' + (data.additionalFactor || 0) + '"></div><div class="layui-form-mid">最终因子=当前因子+此值。例: 当前因子0.5+附加值1=1.5。鱼权重5000时击杀率75%</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">空炮率</label><div class="layui-input-inline"><input type="number" step="1" min="0" max="100" name="missRate" class="layui-input" value="' + (data.missRate || 0) + '"></div><div class="layui-form-mid">百分比(0-100),用户开炮打空的概率。值越高用户越难命中鱼</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">补偿配额</label><div class="layui-input-inline"><input type="number" name="compensateQuota" class="layui-input" value="' + (data.compensateQuota || 0) + '"></div><div class="layui-form-mid">配额>0时按鱼种乘以补偿系数提升击杀率。0=不启用补偿,值越大用户击杀补偿额度越高</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">罚款配额</label><div class="layui-input-inline"><input type="number" name="penaltyQuota" class="layui-input" value="' + (data.penaltyQuota || 0) + '"></div><div class="layui-form-mid">配额>0时按鱼种乘以罚款系数降低击杀率。0=不启用罚款,值越大用户击杀压制力度越大</div></div>' +
'<fieldset class="layui-elem-field"><legend>补偿系数(补偿配额>0时生效,按鱼种乘以系数提升击杀率)</legend><div class="layui-form-item"><label class="layui-form-label">Boss</label><div class="layui-input-inline"><input type="number" step="0.1" name="cBossRate" class="layui-input" value="' + (cr.bossRate || 1) + '"></div><div class="layui-form-mid">Boss鱼补偿系数,默认1=无加成。设为2=Boss鱼击杀率翻倍</div></div><div class="layui-form-item"><label class="layui-form-label">黄金</label><div class="layui-input-inline"><input type="number" step="0.1" name="cGoldRate" class="layui-input" value="' + (cr.goldRate || 1) + '"></div><div class="layui-form-mid">黄金鱼补偿系数</div></div><div class="layui-form-item"><label class="layui-form-label">特殊</label><div class="layui-input-inline"><input type="number" step="0.1" name="cSpecialRate" class="layui-input" value="' + (cr.specialRate || 1) + '"></div><div class="layui-form-mid">特殊鱼补偿系数</div></div><div class="layui-form-item"><label class="layui-form-label">普通</label><div class="layui-input-inline"><input type="number" step="0.1" name="cOrdinary" class="layui-input" value="' + (cr.ordinary || 1) + '"></div><div class="layui-form-mid">普通鱼补偿系数</div></div></fieldset>' +
'<fieldset class="layui-elem-field"><legend>罚款系数(罚款配额>0时生效,按鱼种乘以系数降低击杀率)</legend><div class="layui-form-item"><label class="layui-form-label">Boss</label><div class="layui-input-inline"><input type="number" step="0.1" name="pBossRate" class="layui-input" value="' + (pr.bossRate || 1) + '"></div><div class="layui-form-mid">Boss鱼罚款系数,默认1=无影响。设为0.5=Boss鱼击杀率减半</div></div><div class="layui-form-item"><label class="layui-form-label">黄金</label><div class="layui-input-inline"><input type="number" step="0.1" name="pGoldRate" class="layui-input" value="' + (pr.goldRate || 1) + '"></div><div class="layui-form-mid">黄金鱼罚款系数</div></div><div class="layui-form-item"><label class="layui-form-label">特殊</label><div class="layui-input-inline"><input type="number" step="0.1" name="pSpecialRate" class="layui-input" value="' + (pr.specialRate || 1) + '"></div><div class="layui-form-mid">特殊鱼罚款系数</div></div><div class="layui-form-item"><label class="layui-form-label">普通</label><div class="layui-input-inline"><input type="number" step="0.1" name="pOrdinary" class="layui-input" value="' + (pr.ordinary || 1) + '"></div><div class="layui-form-mid">普通鱼罚款系数</div></div></fieldset>' +
'<div class="layui-form-item"><div class="layui-input-block"><button class="layui-btn" lay-submit lay-filter="submit-factor">保存</button></div></div>' +
'</form>',
success: function(layero) {
form.render();
form.on('submit(submit-factor)', function(formData) {
var param = {
userId: data.id,
additionalFactor: parseFloat(formData.field.additionalFactor) || 0,
violateValue: parseFloat(formData.field.violateValue) || 0.2,
missRate: parseFloat(formData.field.missRate) || 0,
compensateQuota: parseInt(formData.field.compensateQuota) || 0,
penaltyQuota: parseInt(formData.field.penaltyQuota) || 0,
compensateRate: {
bossRate: parseFloat(formData.field.cBossRate) || 1,
goldRate: parseFloat(formData.field.cGoldRate) || 1,
specialRate: parseFloat(formData.field.cSpecialRate) || 1,
ordinary: parseFloat(formData.field.cOrdinary) || 1
},
penaltyRate: {
bossRate: parseFloat(formData.field.pBossRate) || 1,
goldRate: parseFloat(formData.field.pGoldRate) || 1,
specialRate: parseFloat(formData.field.pSpecialRate) || 1,
ordinary: parseFloat(formData.field.pOrdinary) || 1
}
};
admin.req({
url: setter.baseurl + 'adminapi/AdminUser/UpdateUserFactor',
method: 'POST',
contentType: 'application/json;charset=UTF-8',
data: JSON.stringify(param),
done: function(res) {
if (res.code === 0) {
layer.closeAll();
layer.msg('保存成功');
table.reload('table-factor-list');
} else {
layer.msg(res.info || '操作失败');
}
}
});
return false;
});
}
});
} else if (layEvent === 'setBlack') {
var isBlack = data.isBlack;
var action = isBlack ? '取消屏蔽' : '屏蔽';
layer.confirm('确定要' + action + '此用户吗?', function(idx) {
admin.req({
url: setter.baseurl + 'adminapi/AdminUser/SetBlackUser?userId=' + data.id + '&isBlack=' + !isBlack,
method: 'POST',
done: function(res) {
if (res.code === 0) {
layer.close(idx);
layer.msg(action + '成功');
table.reload('table-factor-list');
} else {
layer.msg(res.info || '操作失败');
}
}
});
});
} else if (layEvent === 'realtime') {
showRealtimeRoomDialog(data);
}
});
function showRealtimeRoomDialog(userData) {
var userId = userData.id;
var timer = null;
var layerIndex = null;
var html = '<div style="padding:15px;" id="realtime-room-content">' +
'<div style="text-align:center;padding:30px;color:#999;">正在加载实时数据...</div>' +
'</div>';
layerIndex = layer.open({
type: 1,
title: '实时房间数据 - ' + esc(userData.userName || userData.number || ''),
area: ['480px', '620px'],
content: html,
success: function(layero) {
loadRealtime();
timer = setInterval(loadRealtime, 1000);
},
end: function() {
if (timer) clearInterval(timer);
}
});
function loadRealtime() {
admin.req({
url: setter.baseurl + 'adminapi/Admin/Room/GetUserRoomRealtime?userId=' + userId + '&_t=' + Date.now(),
method: 'get',
done: function(res) {
if (!res || res.code !== 0) {
renderContent('<div style="text-align:center;padding:30px;color:#999;">无实时数据</div>');
return;
}
var data = res.data || {};
if (!data.found) {
renderContent('<div style="text-align:center;padding:30px;color:#999;">无实时数据</div>');
return;
}
renderContent(buildTable(data));
},
fail: function() {
renderContent('<div style="text-align:center;padding:30px;color:#999;">加载失败</div>');
}
});
}
function renderContent(innerHtml) {
var $el = $('#realtime-room-content');
if (!$el.length) return;
$el.html(innerHtml);
}
function buildTable(d) {
var roomName = [d.roomName, d.roomNumber ? '(' + d.roomNumber + ')' : ''].filter(Boolean).join('') || '-';
var inRoomHtml = d.inRoom
? '<span style="color:green;font-weight:bold;">在房间</span>'
: '<span style="color:#FF5722;font-weight:bold;">未在房间</span>';
var win = d.winGold || 0;
var lose = d.loseGold || 0;
var profitHtml;
if (d.inRoom) {
var parts = [];
if (win > 0) parts.push('<span style="color:green;font-weight:bold;">赢 +' + win.toLocaleString() + '</span>');
if (lose > 0) parts.push('<span style="color:red;font-weight:bold;">输 -' + lose.toLocaleString() + '</span>');
profitHtml = parts.length > 0 ? parts.join('&nbsp;&nbsp;') : '<span style="color:#999;">持平</span>';
} else {
profitHtml = '-';
}
var quotaRemain = d.compensateRemainingQuota != null
? d.compensateRemainingQuota
: (d.compensateTargetGold > 0 && d.gold != null
? Math.max(0, d.compensateTargetGold - d.gold) : (d.compensateQuota || 0));
var rows = [
['是否在房间', inRoomHtml],
['房间', roomName],
['游戏类型', d.gameTypeName || d.gameType || '-'],
['房间等级', d.roomLevel],
['玩家/机器人', (d.currentOnlinePlayerCount || 0) + ' / ' + (d.robotCount || 0)],
['座位', d.position != null ? (d.position + 1) : '-'],
['系统因子', d.systemFactor != null ? d.systemFactor.toFixed(2) : '-'],
['进房金币', d.beginCoin != null ? d.beginCoin.toLocaleString() : '-'],
['房间金币', (d.gold != null ? d.gold.toLocaleString() : '-')],
['最高金币', (d.maxGold != null ? d.maxGold.toLocaleString() : '-')],
['实时输赢', profitHtml],
['当前炮倍', d.cannonMultiple || '-'],
['金币本金统计(不计VIP)', (d.totalRechargeGold != null ? d.totalRechargeGold.toLocaleString() : '-')],
['状态', d.state === 1 ? '<span style="color:#FF5722;">断线中</span>' : (d.isAfk ? '<span style="color:#FFB800;">挂机中</span>' : '正常')],
['黑名单', d.isBlack ? '<span style="color:#FF5722;">是</span>' : '否'],
['是否忽略因子', d.isIgnore ? '是' : '否'],
['玩家因子(平滑)', d.violateValue != null ? d.violateValue.toFixed(2) : '-'],
['附加因子', d.additionalFactor != null ? d.additionalFactor.toFixed(2) : '-'],
['空炮率', (d.missRate || 0) + '%'],
['补偿额度', d.compensateQuota],
['补偿剩余值', quotaRemain.toLocaleString()],
['惩罚额度', d.penaltyQuota],
['起始赠送', d.startingBonus != null ? d.startingBonus.toLocaleString() : '-'],
['补偿系数(普/Boss/金/特)', [d.compensateRateOrdinary, d.compensateRateBoss, d.compensateRateGold, d.compensateRateSpecial].join(' / ')],
['惩罚系数(普/Boss/金/特)', [d.penaltyRateOrdinary, d.penaltyRateBoss, d.penaltyRateGold, d.penaltyRateSpecial].join(' / ')]
];
var trs = rows.map(function(r) {
return '<tr><td style="background:#f2f2f2;width:130px;font-weight:bold;">' + r[0] + '</td><td>' + r[1] + '</td></tr>';
}).join('');
return '<table class="layui-table" style="margin:0;">' + trs + '</table>' +
'<div style="text-align:center;color:#999;font-size:12px;padding:8px;">每秒自动刷新</div>';
}
}
exports('factorlist', {});
});
</script>
@@ -0,0 +1,56 @@
<div class="layui-form" lay-filter="layuiadmin-form-useradmin" style="padding: 20px 0 0 0;">
<div class="layui-form-item">
<label class="layui-form-label">用户名</label>
<div class="layui-input-inline">
<script type="text/html" template>
<input type="hidden" name="userId" value="{{ d.params.id || '' }}" lay-verify="required" autocomplete="off" class="layui-input">
<input type="text" name="userName" readonly value="{{ d.params.userName || '' }}" lay-verify="required" placeholder="请输入用户名" autocomplete="off" class="layui-input">
</script>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">用户编号</label>
<div class="layui-input-inline">
<script type="text/html" template>
<input type="text" name="number" readonly value="{{ d.params.number || '' }}" placeholder="请输入号码" autocomplete="off" class="layui-input">
</script>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">封号原因</label>
<div class="layui-input-inline">
<script type="text/html" template>
<input type="text" name="reason" value="{{ d.params.banReason || ''}}" placeholder="" autocomplete="off" class="layui-input">
</script>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">天数</label>
<div class="layui-input-inline">
<script type="text/html" template>
<input type="text" name="days" value="{{ d.params.banDays || '' }}" placeholder="" autocomplete="off" class="layui-input">
</script>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-inline">
<input type="button" lay-submit lay-filter="LAY-user-front-submit" value="确认" class="layui-btn">
</div>
</div>
</div>
<script>
layui.use(['admin', 'form', 'upload'], function(){
var $ = layui.$
,form = layui.form
,upload = layui.upload;
})
</script>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,357 @@
<title>用户详情</title>
<div class="layui-card layadmin-header">
<div class="layui-breadcrumb" lay-filter="breadcrumb">
<a lay-href="#/user/user/list">用户列表</a>
<a><cite>用户详情</cite></a>
</div>
</div>
<div class="layui-fluid">
<div class="layui-row layui-col-space15">
<div class="layui-col-md3">
<div class="layui-card">
<div class="layui-card-header">基本信息</div>
<div class="layui-card-body">
<div class="layui-form" wid100 id="basic-info-form">
</div>
</div>
</div>
</div>
<div class="layui-col-md9">
<div class="layui-card">
<div class="layui-card-header">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-primary" id="btn-back"><i class="layui-icon layui-icon-return"></i> 返回</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" id="btn-refresh"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" id="btn-init-pwd"><i class="layui-icon layui-icon-auz"></i> 初始化登录密码</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" id="btn-init-safebox-pwd"><i class="layui-icon layui-icon-auz"></i> 初始化保险箱密码</button>
</div>
</div>
<div class="layui-card-body">
<div class="layui-tab layui-tab-brief" lay-filter="user-detail-tab">
<ul class="layui-tab-title">
<li class="layui-this" lay-id="assets">资产信息</li>
<li lay-id="stat">统计信息</li>
<li lay-id="equip">装备背包</li>
</ul>
<div class="layui-tab-content" style="padding:10px 0 0 0;">
<div class="layui-tab-item layui-show" id="tab-assets"></div>
<div class="layui-tab-item" id="tab-stat"></div>
<div class="layui-tab-item" id="tab-equip"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
layui.use(['admin', 'form', 'element'], function () {
var $ = layui.$,
admin = layui.admin,
form = layui.form,
element = layui.element,
setter = layui.setter,
layer = layui.layer,
router = layui.router();
var userId = router.search.userId;
var userData = null;
if (!userId) {
layer.msg('缺少用户ID参数', {icon: 2});
location.href = '#/user/user/list';
return;
}
$('#btn-back').on('click', function() {
location.href = '#/user/user/list';
});
$('#btn-refresh').on('click', function() {
loadUserDetail();
});
$('#btn-init-pwd').on('click', function() {
layer.confirm('确定要初始化该用户的登录密码为123456吗?', function(idx) {
layer.close(idx);
admin.req({
method: 'get',
url: setter.baseurl + 'adminapi/AdminUser/InitUserPassword?userId=' + encodeURIComponent(userId),
done: function(res) {
if (res.code === 0 || res.code === 200) {
layer.msg(res.info || '初始化成功');
} else {
layer.msg(res.info || '初始化失败');
}
}
});
});
});
$('#btn-init-safebox-pwd').on('click', function() {
layer.confirm('确定要初始化该用户的保险箱密码为123456吗?', function(idx) {
layer.close(idx);
admin.req({
method: 'get',
url: setter.baseurl + 'adminapi/AdminUser/InitSafeBoxPassword?userId=' + encodeURIComponent(userId),
done: function(res) {
if (res.code === 0 || res.code === 200) {
layer.msg(res.info || '初始化成功');
} else {
layer.msg(res.info || '初始化失败');
}
}
});
});
});
function renderEquipTab(user) {
var cannonList = user.cannonList || [];
var wingList = user.wingList || [];
var godWarList = user.godWarList || [];
var props = user.props || [];
var cannonUsing = user.cannonUsing ? user.cannonUsing.number : '';
var wingUsing = user.wingUsing ? user.wingUsing.number : '';
var godWarUsing = user.godUsing ? user.godUsing.number : '';
var cannonHtml = cannonList.length > 0 ? '<table class="layui-table" style="margin:0;"><tr><th>名称</th><th>编号</th><th>状态</th></tr>' + cannonList.map(function(c){ return '<tr><td>' + c.name + '</td><td>' + c.number + '</td><td>' + (cannonUsing === c.number ? '<span style="color:#1e9fff">使用中</span>' : '') + '</td></tr>'; }).join('') + '</table>' : '<span style="color:#999">无</span>';
var wingHtml = wingList.length > 0 ? '<table class="layui-table" style="margin:0;"><tr><th>名称</th><th>编号</th><th>状态</th></tr>' + wingList.map(function(w){ return '<tr><td>' + w.name + '</td><td>' + w.number + '</td><td>' + (wingUsing === w.number ? '<span style="color:#1e9fff">使用中</span>' : '') + '</td></tr>'; }).join('') + '</table>' : '<span style="color:#999">无</span>';
var godWarHtml = godWarList.length > 0 ? '<table class="layui-table" style="margin:0;"><tr><th>名称</th><th>编号</th><th>状态</th></tr>' + godWarList.map(function(g){ return '<tr><td>' + g.name + '</td><td>' + g.number + '</td><td>' + (godWarUsing === g.number ? '<span style="color:#1e9fff">使用中</span>' : '') + '</td></tr>'; }).join('') + '</table>' : '<span style="color:#999">无</span>';
var validProps = props.filter(function(p){ return p.count > 0; });
var propsHtml = validProps.length > 0
? '<table class="layui-table" id="props-table"><thead><tr><th>编号</th><th>名称</th><th>类型</th><th>数量</th><th>操作</th></tr></thead><tbody>'
+ validProps.map(function(p, i){
var typeName = '';
if (p.type === 0) typeName = '普通';
else if (p.type === 1) typeName = '炮台';
else if (p.type === 2) typeName = '翅膀';
else if (p.type === 3) typeName = '战神';
else if (p.type === 7) typeName = '刮刮卡';
else if (p.type === 13) typeName = '鱼雷';
else typeName = '道具' + p.type;
return '<tr><td>' + (p.number || '-') + '</td><td>' + (p.name || '-') + '</td><td>' + typeName + '</td><td>' + p.count + '</td>'
+ '<td>'
+ '<button class="layui-btn layui-btn-xs layui-btn-normal" onclick="editPropCount(\'' + p.number + '\',\'' + (p.name || '') + '\',' + p.count + ')">修改</button> '
+ '<button class="layui-btn layui-btn-xs layui-btn-danger" onclick="deleteProp(\'' + p.number + '\',\'' + (p.name || '') + '\')">删除</button>'
+ '</td></tr>';
}).join('')
+ '</tbody></table>'
: '<span style="color:#999">背包为空</span>';
var equipHtml = '<div style="max-width:100%;">'
+ '<div class="layui-btn-container" style="margin-bottom:10px;">'
+ '<button class="layui-btn layui-btn-sm" onclick="showAddPropForm()"><i class="layui-icon layui-icon-add-1"></i> 添加道具</button>'
+ '<button class="layui-btn layui-btn-sm layui-btn-primary" onclick="loadUserDetail()"><i class="layui-icon layui-icon-refresh"></i> 刷新背包</button>'
+ '</div>'
+ '<fieldset class="layui-elem-field" style="margin-bottom:15px;"><legend>装备列表</legend><div class="layui-field-box">'
+ '<div style="margin-bottom:10px;"><b>炮台:</b></div>' + cannonHtml
+ '<div style="margin:10px 0;"><b>翅膀:</b></div>' + wingHtml
+ '<div style="margin:10px 0;"><b>战神:</b></div>' + godWarHtml
+ '</div></fieldset>'
+ '<fieldset class="layui-elem-field"><legend>背包道具 <span style="font-size:12px;color:#999;">(共 ' + validProps.length + ' 种)</span></legend><div class="layui-field-box">' + propsHtml + '</div></fieldset>'
+ '</div>';
$('#tab-equip').html(equipHtml);
}
window.showAddPropForm = function() {
layer.open({
type: 1,
title: '添加道具',
area: ['450px', '280px'],
content: '<div style="padding:20px;">'
+ '<div class="layui-form-item"><label class="layui-form-label" style="width:80px;">道具编号</label><div class="layui-input-block" style="margin-left:110px;"><input id="input-prop-number" class="layui-input" placeholder="输入道具编号,如 40005"></div></div>'
+ '<div class="layui-form-item"><label class="layui-form-label" style="width:80px;">数量</label><div class="layui-input-block" style="margin-left:110px;"><input id="input-prop-count" class="layui-input" type="number" value="1" min="1"></div></div>'
+ '<div style="text-align:center;margin-top:20px;"><button class="layui-btn" id="btn-add-prop-submit">确定添加</button></div>'
+ '</div>'
});
$('#btn-add-prop-submit').off('click').on('click', function() {
var propNumber = $('#input-prop-number').val().trim();
var count = parseInt($('#input-prop-count').val()) || 1;
if (!propNumber) { layer.msg('请输入道具编号'); return; }
if (count <= 0) { layer.msg('数量必须大于0'); return; }
admin.req({
method: 'post',
url: setter.baseurl + 'adminapi/AdminUser/AddUserProp',
data: { userId: userId, propNumber: propNumber, count: count },
done: function(res) {
if (res.code === 0 || res.code === 200) {
layer.msg('添加成功');
layer.closeAll();
loadUserDetail();
} else {
layer.msg(res.info || '添加失败');
}
}
});
});
};
window.editPropCount = function(propNumber, propName, currentCount) {
layer.open({
type: 1,
title: '修改道具数量 - ' + propName,
area: ['400px', '230px'],
content: '<div style="padding:20px;">'
+ '<div class="layui-form-item"><label class="layui-form-label" style="width:80px;">当前数量</label><div class="layui-form-mid" style="margin-left:110px;">' + currentCount + '</div></div>'
+ '<div class="layui-form-item"><label class="layui-form-label" style="width:80px;">修改为</label><div class="layui-input-block" style="margin-left:110px;"><input id="input-edit-count" class="layui-input" type="number" value="' + currentCount + '" min="0"></div></div>'
+ '<div style="text-align:center;margin-top:20px;font-size:12px;color:#999;">设为 0 则删除该道具</div>'
+ '<div style="text-align:center;margin-top:10px;"><button class="layui-btn" id="btn-edit-prop-submit">确定修改</button></div>'
+ '</div>'
});
$('#btn-edit-prop-submit').off('click').on('click', function() {
var newCount = parseInt($('#input-edit-count').val());
if (isNaN(newCount) || newCount < 0) { layer.msg('请输入有效数量'); return; }
admin.req({
method: 'post',
url: setter.baseurl + 'adminapi/AdminUser/UpdateUserPropCount',
data: { userId: userId, propNumber: propNumber, count: newCount },
done: function(res) {
if (res.code === 0 || res.code === 200) {
layer.msg('修改成功');
layer.closeAll();
loadUserDetail();
} else {
layer.msg(res.info || '修改失败');
}
}
});
});
};
window.deleteProp = function(propNumber, propName) {
layer.confirm('确定要删除道具 [' + propName + '] 吗?', function(idx) {
layer.close(idx);
admin.req({
method: 'post',
url: setter.baseurl + 'adminapi/AdminUser/DeleteUserProp',
data: { userId: userId, propNumber: propNumber },
done: function(res) {
if (res.code === 0 || res.code === 200) {
layer.msg('删除成功');
loadUserDetail();
} else {
layer.msg(res.info || '删除失败');
}
}
});
});
};
function formatTimestamp(timestamp) {
if (!timestamp) return '-';
var date = new Date(timestamp * 1000);
var y = date.getFullYear();
var m = (date.getMonth() + 1).toString().padStart(2, '0');
var d = date.getDate().toString().padStart(2, '0');
return y + '-' + m + '-' + d;
}
window.loadUserDetail = function() {
admin.req({
url: setter.baseurl + "adminapi/AdminUser/GetUserDetail",
data: { userId: userId },
done: function (res) {
if (res.code === 0 || res.code === 200) {
userData = res.data;
renderUserDetail(userData);
} else {
layer.msg(res.info || '获取用户详情失败', {icon: 2});
}
}
});
}
function renderUserDetail(user) {
var userTypeMap = {1: '普通用户', 2: '代理商', 3: '内部人员'};
var uName = user.baseInfo ? (user.baseInfo.userName || '-') : '-';
var uNumber = user.baseInfo ? (user.baseInfo.number || '-') : '-';
var uMobile = user.baseInfo ? (user.baseInfo.mobile || '-') : '-';
var uChannel = user.baseInfo ? (user.baseInfo.channel || '-') : '-';
var uCreateTime = formatTimestamp(user.createTime);
var uRegisterIp = user.registerIp || '-';
var uUserType = user.userType || 1;
var uIsBlack = user.playerFactor && user.playerFactor.isBlack;
var uRealName = user.realName ? (user.realName.realName || '-') : '-';
var uIdCard = user.realName ? (user.realName.idCard || '-') : '-';
document.title = '用户详情 - ' + uName + ' [' + uNumber + ']';
var basicHtml = '<div class="layui-form-item"><label class="layui-form-label">昵称</label><div class="layui-form-mid">' + uName + '</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">编号</label><div class="layui-form-mid">' + uNumber + '</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">手机号</label><div class="layui-form-mid">' + uMobile + '</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">注册IP</label><div class="layui-form-mid">' + uRegisterIp + '</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">渠道</label><div class="layui-form-mid">' + uChannel + '</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">注册时间</label><div class="layui-form-mid">' + uCreateTime + '</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">用户身份</label><div class="layui-form-mid">' + userTypeMap[uUserType] + '</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">屏蔽状态</label><div class="layui-form-mid">' + (uIsBlack ? '<span style="color:#f44336;">已屏蔽</span>' : '<span style="color:#4caf50;">正常</span>') + '</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">真实姓名</label><div class="layui-form-mid">' + uRealName + '</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">身份证号</label><div class="layui-form-mid">' + uIdCard + '</div></div>';
$('#basic-info-form').html(basicHtml);
var uGold = user.assets ? (user.assets.gold || 0) : 0;
var uDiamond = user.assets ? (user.assets.diamond || 0) : 0;
var uLevel = user.assets ? (user.assets.level || 1) : 1;
var uVipLevel = user.assets ? (user.assets.vipLevel || 0) : 0;
var uVipExp = user.assets ? (user.assets.vipExp || 0) : 0;
var uVipExpExtra = user.assets ? (user.assets.vipExpExtra || 0) : 0;
var uExp = user.assets ? (user.assets.exp || 0) : 0;
var uVipDayGift = user.assets && user.assets.isVipDayGift ? '已领取' : '未领取';
var uSafeBoxAmount = user.safeBox ? (user.safeBox.amount || 0) : 0;
var uSafeBoxSaveTime = user.safeBox ? formatTimestamp(user.safeBox.saveTime) : '-';
var uIsSign = user.sign && user.sign.isSign ? '是' : '否';
var uSignTime = user.sign ? formatTimestamp(user.sign.signTime) : '-';
var assetsHtml = '<div class="layui-form" style="max-width:600px;">' +
'<div class="layui-form-item"><label class="layui-form-label" style="width:100px;">金币</label><div class="layui-form-mid" style="color:#ff5722;font-weight:bold;">' + uGold.toLocaleString() + '</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label" style="width:100px;">钻石</label><div class="layui-form-mid" style="color:#1e88e5;font-weight:bold;">' + uDiamond.toLocaleString() + '</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label" style="width:100px;">等级</label><div class="layui-form-mid">Lv.' + uLevel + '</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label" style="width:100px;">VIP等级</label><div class="layui-form-mid">VIP' + uVipLevel + '</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label" style="width:100px;">VIP经验</label><div class="layui-form-mid">' + uVipExp.toLocaleString() + '</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label" style="width:100px;">额外VIP经验</label><div class="layui-form-mid">' + uVipExpExtra.toLocaleString() + '</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label" style="width:100px;">VIP计算规则</label><div class="layui-form-mid">现金充值 × 10 + VIP经验包(金币及游戏输赢不参与)</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label" style="width:100px;">经验值</label><div class="layui-form-mid">' + uExp.toLocaleString() + '</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label" style="width:100px;">VIP礼包</label><div class="layui-form-mid">' + uVipDayGift + '</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label" style="width:100px;">保险箱金额</label><div class="layui-form-mid">' + uSafeBoxAmount.toLocaleString() + '</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label" style="width:100px;">保险箱时间</label><div class="layui-form-mid">' + uSafeBoxSaveTime + '</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label" style="width:100px;">今日签到</label><div class="layui-form-mid">' + uIsSign + '</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label" style="width:100px;">签到时间</label><div class="layui-form-mid">' + uSignTime + '</div></div>' +
'</div>';
$('#tab-assets').html(assetsHtml);
var uLastLoginTime = user.static ? formatTimestamp(user.static.lastLoginTime) : '-';
var uLastLoginIp = user.static ? (user.static.lastLoginIp || '-') : '-';
var uOnlineTime = user.static ? (user.static.onlineTime || 0) : 0;
var uGameTime = user.static ? (user.static.gameTime || 0) : 0;
var uRoomNumber = user.static ? (user.static.roomNumber || '-') : '-';
var uTotalRecharge = user.static ? (user.static.totalRecharge || 0) : 0;
var uTotalConsumeGold = user.static ? (user.static.totalConsumeGold || 0) : 0;
var uReliefGoldCount = user.static ? (user.static.reliefGoldCount || 0) : 0;
var statHtml = '<div class="layui-form" style="max-width:600px;">' +
'<div class="layui-form-item"><label class="layui-form-label" style="width:120px;">最后登录IP</label><div class="layui-form-mid">' + uLastLoginIp + '</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label" style="width:120px;">最后登录时间</label><div class="layui-form-mid">' + uLastLoginTime + '</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label" style="width:120px;">在线时长</label><div class="layui-form-mid">' + uOnlineTime + ' 秒</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label" style="width:120px;">游戏时长</label><div class="layui-form-mid">' + uGameTime + ' 秒</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label" style="width:120px;">累计充值</label><div class="layui-form-mid" style="color:#ff5722;">' + uTotalRecharge + ' 元</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label" style="width:120px;">累计消费</label><div class="layui-form-mid">' + uTotalConsumeGold.toLocaleString() + '</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label" style="width:120px;">所在房间</label><div class="layui-form-mid">' + uRoomNumber + '</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label" style="width:120px;">救济金次数</label><div class="layui-form-mid">' + uReliefGoldCount + '</div></div>' +
'</div>';
$('#tab-stat').html(statHtml);
renderEquipTab(user);
}
loadUserDetail();
});
</script>
@@ -0,0 +1,71 @@
<div class="layui-form" lay-filter="layuiadmin-form-useradmin" style="padding: 20px 0 0 0;">
<div class="layui-form-item">
<label class="layui-form-label">用户名</label>
<div class="layui-input-inline">
<script type="text/html" template>
<input type="hidden" name="id" value="{{ d.params.id || '' }}" lay-verify="required" autocomplete="off" class="layui-input">
<input type="text" name="userName" readonly value="{{ d.params.userName || '' }}" lay-verify="required" placeholder="请输入用户名" autocomplete="off" class="layui-input">
</script>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">用户编号</label>
<div class="layui-input-inline">
<script type="text/html" template>
<input type="text" name="number" readonly value="{{ d.params.number || '' }}" placeholder="请输入号码" autocomplete="off" class="layui-input">
</script>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">钻石</label>
<div class="layui-input-inline">
<script type="text/html" template>
<input type="number" name="diamond" value="{{ d.params.assets.diamond || 0 }}" placeholder="" autocomplete="off" class="layui-input">
</script>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">积分</label>
<div class="layui-input-inline">
<script type="text/html" template>
<input type="number" name="scoreCount" value="{{ d.params.assets.scoreCount || 0 }}" placeholder="" autocomplete="off" class="layui-input">
</script>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">等级</label>
<div class="layui-input-inline">
<script type="text/html" template>
<input type="number" name="level" value="{{ d.params.assets.level || 0 }}" placeholder="" autocomplete="off" class="layui-input">
</script>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">经验值</label>
<div class="layui-input-inline">
<script type="text/html" template>
<input type="number" name="exp" value="{{ d.params.assets.exp || 0 }}" placeholder="" autocomplete="off" class="layui-input">
</script>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-inline">
<input type="button" lay-submit lay-filter="LAY-user-front-submit" value="确认" class="layui-btn">
</div>
</div>
</div>
<script>
layui.use(['admin', 'form', 'upload'], function(){
var $ = layui.$
,form = layui.form
,upload = layui.upload;
})
</script>