添加项目文件。

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