@@ -448,6 +498,7 @@ $(document).ready(function(){
return ''
+ '
';
}
}
@@ -469,6 +520,7 @@ function updateBatchButtons(){
$("#btnBatchHostnameTxt").prop('disabled', !hasSelection);
$("#btnBatchCertTxt").prop('disabled', !hasSelection);
$("#btnBatchRefresh").prop('disabled', !hasSelection);
+ $("#btnBatchCfOptimized").prop('disabled', !hasSelection);
}
function filterHostnameList(searchText){
@@ -1483,7 +1535,844 @@ function confirmBatchRefresh(){
});
}
-// 提交批量 TXT 记录的通用函数
+// ==================== CF 优选解析 ====================
+
+var cfOptimizedTargets = [
+ {value: 'www.visa.cn', label: 'Visa中国'},
+ {value: 'mfa.gov.ua', label: '乌克兰外交部'},
+ {value: 'www.shopify.com', label: 'Shopify官方'},
+ {value: 'store.ubi.com', label: 'Ubisoft'},
+ {value: 'staticdelivery.nexusmods.com', label: 'NexusMods'}
+];
+var cfOptimizedApiTargets = [];
+
+function loadCfOptimizedTargetsFromApi(callback){
+ cfOptimizedApiTargets = [];
+ $.ajax({
+ type: 'POST',
+ url: '/optimizeip/opiplist/data',
+ data: {offset: 0, limit: 100, type: 1, status: null, kw: ''},
+ dataType: 'json',
+ success: function(res){
+ if(res.rows && res.rows.length > 0){
+ for(var i = 0; i < res.rows.length; i++){
+ var row = res.rows[i];
+ if(row.cdn_type == 1 && row.active == 1 && row.rr && row.domain){
+ var targetValue = row.rr + '.' + row.domain;
+ var targetLabel = row.remark || (row.rr + '.' + row.domain);
+ cfOptimizedApiTargets.push({value: targetValue, label: targetLabel});
+ }
+ }
+ }
+ if(callback) callback();
+ },
+ error: function(){
+ if(callback) callback();
+ }
+ });
+}
+
+function renderCfOptimizedTargetOptions(){
+ var allTargets = cfOptimizedApiTargets.concat(cfOptimizedTargets);
+ var html = '';
+ if(cfOptimizedApiTargets.length > 0){
+ html += '
';
+ }
+ for(var i = 0; i < allTargets.length; i++){
+ var opt = allTargets[i];
+ var isApi = i < cfOptimizedApiTargets.length;
+ if(!isApi && i === cfOptimizedApiTargets.length && cfOptimizedApiTargets.length > 0){
+ html += '
';
+ $('#cfOptimizedTargetList').html(html);
+ $('#cfOptimizedTargetList input[name=cfOptimizedTarget]').on('change', function(){
+ if($(this).val() === '_custom'){
+ $('#cfOptimizedCustomInput').show();
+ } else {
+ $('#cfOptimizedCustomInput').hide();
+ }
+ });
+}
+
+var cfOptimizedSingleHostname = null;
+var batchCfOptimizedData = [];
+
+function resetCfOptimizedState(){
+ var confirmBtn = $("#cfOptimizedConfirmBtn");
+ confirmBtn.prop('disabled', false).text('开始处理').data('cfmode', '').removeData('savedDnsDomains cfExistingId preDetectResults cfExistingRec');
+ $('#cfOptimizedExistingRecord').hide();
+ $('#cfOptimizedDnsGroupArea [id^=lineArea_]').hide();
+ $('#cfOptimizedDnsGroupArea [id^=subLineArea_]').hide();
+ $('#singleLineArea').hide();
+ $('#singleSubLineArea').hide();
+ $('#batchCfOptimizedList').html('').hide();
+ cfOptimizedSingleHostname = null;
+ for(var ci = 0; ci < batchCfOptimizedData.length; ci++){ delete batchCfOptimizedData[ci]._dnsSel; }
+}
+
+$(document).ready(function(){
+ $('#modal-cf-optimized').on('hidden.bs.modal', function(){
+ resetCfOptimizedState();
+ });
+});
+
+function getCfOptimizedTargetValue(){
+ var selectedTarget = $('input[name=cfOptimizedTarget]:checked').val();
+ if(!selectedTarget) return null;
+ if(selectedTarget === '_custom'){
+ var customValue = $.trim($('#cfOptimizedCustomValue').val());
+ var customType = $('#cfOptimizedCustomType').val() || 'CNAME';
+ if(!customValue) return null;
+ return {value: customValue, type: customType};
+ }
+ return {value: selectedTarget, type: 'CNAME'};
+}
+
+// 批量入口
+function batchCfOptimized(){
+ resetCfOptimizedState();
+ var selectedRows = $("#listTable").bootstrapTable('getSelections');
+ if(selectedRows.length === 0){
+ layer.msg('请先选择要设置 CF 优选解析的自定义主机名', {icon: 0});
+ return;
+ }
+
+ $('#cfOptimizedModalTitle').text('批量 CF 优选解析');
+ $('#cfOptimizedConfirmBtn').text('开始处理');
+
+ loadCfOptimizedTargetsFromApi(function(){
+ renderCfOptimizedTargetOptions();
+
+ // 先获取所有主机名的 DNS 域名信息,按 DNS 域名分组
+ batchCfOptimizedData = [];
+ var processedCount = 0;
+ var totalCount = selectedRows.length;
+
+ function checkGroupDone(){
+ if(processedCount === totalCount){
+ // 按 DNS 域名分组
+ var dnsGroups = {};
+ for(var i = 0; i < batchCfOptimizedData.length; i++){
+ var item = batchCfOptimizedData[i];
+ if(!item.targets || !item.targets.length){
+ item.dnsDomain = '未知域名';
+ } else {
+ item.dnsDomain = item.targets[0].domain_name || '未知域名';
+ }
+ if(!dnsGroups[item.dnsDomain]){
+ dnsGroups[item.dnsDomain] = [];
+ }
+ dnsGroups[item.dnsDomain].push(item);
+ }
+ // 渲染分组 UI
+ renderCfOptimizedDnsGroups(dnsGroups, false);
+ $("#modal-cf-optimized").modal('show');
+ }
+ }
+
+ selectedRows.forEach(function(row){
+ var hostname = row.hostname;
+ resolveTxtTargets(hostname, '', function(targets){
+ processedCount++;
+ batchCfOptimizedData.push({row: row, hostname: hostname, targets: targets});
+ checkGroupDone();
+ });
+ });
+ });
+}
+
+// 单个入口
+function openCfOptimizedForHostname(hostname){
+ resetCfOptimizedState();
+ cfOptimizedSingleHostname = hostname;
+ $('#cfOptimizedModalTitle').text('CF 优选解析');
+ $('#cfOptimizedConfirmBtn').text('确定');
+
+ loadCfOptimizedTargetsFromApi(function(){
+ renderCfOptimizedTargetOptions();
+
+ resolveTxtTargets(hostname, '', function(targets){
+ var dnsDomain = (targets && targets.length) ? (targets[0].domain_name || '未知域名') : '未知域名';
+ batchCfOptimizedData = [{row: {hostname: hostname}, hostname: hostname, targets: targets, dnsDomain: dnsDomain}];
+ var dnsGroups = {};
+ dnsGroups[dnsDomain] = [{row: {hostname: hostname}, hostname: hostname, targets: targets, dnsDomain: dnsDomain}];
+ renderCfOptimizedDnsGroups(dnsGroups, true);
+ $("#modal-cf-optimized").modal('show');
+ });
+ });
+}
+
+// 渲染按 DNS 域名分组的 UI
+function renderCfOptimizedDnsGroups(dnsGroups, isSingle){
+ var groupIndex = 0;
+ var groupHtml = '';
+
+ for(var dnsDomain in dnsGroups){
+ if(!dnsGroups.hasOwnProperty(dnsDomain)) continue;
+ var items = dnsGroups[dnsDomain];
+ var hostnamesStr = items.map(function(item){ return item.hostname; }).join(', ');
+
+ // 收集该组内所有主机名的 targets,去重合并选项
+ var allTargets = [];
+ var seenTargetIds = {};
+ for(var ti = 0; ti < items.length; ti++){
+ if(items[ti].targets){
+ for(var tj = 0; tj < items[ti].targets.length; tj++){
+ var t = items[ti].targets[tj];
+ if(t.domain_id && !seenTargetIds[t.domain_id]){
+ seenTargetIds[t.domain_id] = true;
+ allTargets.push(t);
+ }
+ }
+ }
+ }
+
+ groupHtml += '
';
+ groupIndex++;
+ }
+
+ if(Object.keys(dnsGroups).length > 1 || !isSingle){
+ // 多域名或批量模式:显示分组区域
+ $('#cfOptimizedDnsGroupArea').html(groupHtml).show();
+ $('#cfOptimizedSingleDnsArea').hide();
+ $('#batchCfOptimizedList').hide();
+ $('#batchCfOptimizedAlert').html('确定要为选中的
个主机名设置 CF 优选 CNAME 解析吗?');
+ } else {
+ // 单域名单个模式:显示简化界面
+ $('#cfOptimizedDnsGroupArea').hide();
+ $('#cfOptimizedSingleDnsArea').show();
+
+ var hostnamesHtml = '';
+ for(var d in dnsGroups){
+ if(!dnsGroups.hasOwnProperty(d)) continue;
+ hostnamesHtml += '
';
+ }
+ $('#batchCfOptimizedList').html(hostnamesHtml).show();
+ $('#batchCfOptimizedAlert').html('
将主机名 CNAME 解析到 Cloudflare 优选 IP 域名,实现加速访问。');
+
+ // 把第一个组的选项复制到单选框
+ if(groupHtml){
+ var tempDiv = $('
').html(groupHtml);
+ var selectOptions = tempDiv.find('select:first option');
+ var optionsHtml = '';
+ selectOptions.each(function(){ optionsHtml += $(this)[0].outerHTML; });
+ $('#cfOptimizedDnsProvider').html(optionsHtml);
+ }
+ }
+
+ // 绑定 DNS 服务商选择事件:选择后加载该域名的线路列表(DOM 已渲染完毕)
+ function bindLineEvents(){
+ // 批量模式:DNS 服务商选择 → 加载线路(事件委托)
+ $('#cfOptimizedDnsGroupArea').off('change.cfline', '.cf-dns-provider').on('change.cfline', '.cf-dns-provider', function(){
+ var domainId = $(this).val();
+ var groupIndex = $(this).data('groupIndex');
+ var lineArea = $('#lineArea_' + groupIndex);
+ var lineSelect = $('#lineSelect_' + groupIndex);
+ var subLineArea = $('#subLineArea_' + groupIndex);
+
+ if(!domainId){ lineArea.hide(); subLineArea.hide(); return; }
+ lineArea.show();
+ subLineArea.hide();
+ lineSelect.html('
');
+
+ $.ajax({
+ type: 'POST', url: '/cloudflare/get_domain_default_line', data: {domain_id: domainId}, dataType: 'json',
+ success: function(res){
+ if(res.code === 0 && res.data && res.data.lines && res.data.lines.length > 0){
+ lineSelect.data('allLines', res.data.lines);
+ var opts = '';
+ for(var li = 0; li < res.data.lines.length; li++){
+ var l = res.data.lines[li];
+ if(!l.parent){
+ opts += '
';
+ }
+ }
+ lineSelect.html(opts || '
');
+ } else {
+ var defLine = (res.code === 0 && res.data && res.data.default_line) ? res.data.default_line : '0';
+ lineSelect.html('
');
+ lineSelect.data('allLines', []);
+ }
+ },
+ error: function(){ lineSelect.html('
'); lineSelect.data('allLines', []); }
+ });
+ });
+
+ // 批量模式:主线路选择 → 显示子线路(事件委托,更可靠)
+ $('#cfOptimizedDnsGroupArea').off('change.cfsub', '.cf-line-main').on('change.cfsub', '.cf-line-main', function(){
+ var groupIndex = $(this).attr('id').replace('lineSelect_', '');
+ var subLineArea = $('#subLineArea_' + groupIndex);
+ var subLineSelect = $('#subLineSelect_' + groupIndex);
+ var allLines = $(this).data('allLines') || [];
+ var selectedVal = $(this).val();
+
+ // 找该父线路的子线路
+ var children = [];
+ for(var ci = 0; ci < allLines.length; ci++){
+ if(allLines[ci].parent === selectedVal){
+ children.push(allLines[ci]);
+ }
+ }
+
+ if(children.length > 0){
+ var opts = '';
+ for(var si = 0; si < children.length; si++){
+ opts += '
';
+ }
+ subLineSelect.html(opts);
+ subLineArea.show();
+ } else {
+ subLineArea.hide();
+ }
+ });
+
+ // 单域名模式:DNS 服务商选择 → 加载线路
+ $('#cfOptimizedSingleDnsArea #cfOptimizedDnsProvider').off('change.cfline').on('change.cfline', function(){
+ var domainId = $(this).val();
+ var lineArea = $('#singleLineArea');
+ var lineSelect = $('#singleLineSelect');
+ var subLineArea = $('#singleSubLineArea');
+
+ if(!domainId){ lineArea.hide(); subLineArea.hide(); return; }
+ lineArea.show();
+ subLineArea.hide();
+ lineSelect.html('
');
+
+ $.ajax({
+ type: 'POST', url: '/cloudflare/get_domain_default_line', data: {domain_id: domainId}, dataType: 'json',
+ success: function(res){
+ if(res.code === 0 && res.data && res.data.lines && res.data.lines.length > 0){
+ lineSelect.data('allLines', res.data.lines);
+ var opts = '';
+ for(var li = 0; li < res.data.lines.length; li++){
+ var l = res.data.lines[li];
+ if(!l.parent){
+ opts += '
';
+ }
+ }
+ lineSelect.html(opts || '
');
+ } else {
+ var defLine = (res.code === 0 && res.data && res.data.default_line) ? res.data.default_line : '0';
+ lineSelect.html('
');
+ lineSelect.data('allLines', []);
+ }
+ },
+ error: function(){ lineSelect.html('
'); lineSelect.data('allLines', []); }
+ });
+ });
+
+ // 单域名模式:主线路选择 → 显示子线路(直接绑定,因为 ID 固定)
+ $('#singleLineSelect').off('change.cfsub').on('change.cfsub', function(){
+ var subLineArea = $('#singleSubLineArea');
+ var subLineSelect = $('#singleSubLineSelect');
+ var allLines = $(this).data('allLines') || [];
+ var selectedVal = $(this).val();
+
+ var children = [];
+ for(var ci = 0; ci < allLines.length; ci++){
+ if(allLines[ci].parent === selectedVal){
+ children.push(allLines[ci]);
+ }
+ }
+
+ if(children.length > 0){
+ var opts = '';
+ for(var si = 0; si < children.length; si++){
+ opts += '
';
+ }
+ subLineSelect.html(opts);
+ subLineArea.show();
+ } else {
+ subLineArea.hide();
+ }
+ });
+ }
+ bindLineEvents();
+}
+
+// 确认处理
+function confirmBatchCfOptimized(){
+ var confirmBtn = $("#cfOptimizedConfirmBtn");
+ var targetInfo = getCfOptimizedTargetValue();
+ if(!targetInfo || !targetInfo.value){ layer.msg('请选择 CNAME 目标', {icon: 0}); return; }
+ var cnameTarget = targetInfo.value;
+ var recordType = targetInfo.type || 'CNAME';
+
+ // 收集所有组的选择(或使用上次已保存的选择)
+ var dnsDomains = {};
+ var allSelected = true;
+ var savedDomains = confirmBtn.data('savedDnsDomains');
+
+ if(savedDomains){
+ // 批量二次确认:使用预检测时保存的选择
+ dnsDomains = savedDomains;
+ } else if($('#cfOptimizedDnsGroupArea').is(':visible')){
+ // 多域名分组模式:首次收集(按域名匹配,不依赖 select 顺序)
+ $('#cfOptimizedDnsGroupArea select[id^=dnsProvider_]').each(function(){
+ var dnsDomain = $(this).data('dnsDomain') || '';
+ var val = $(this).val();
+ var domainId = val;
+ var groupIndex = $(this).data('groupIndex');
+ var lineVal = $('#lineSelect_' + groupIndex).val() || '0';
+ var subLineVal = $('#subLineSelect_' + groupIndex).val();
+ if(subLineVal) lineVal = subLineVal;
+ // 获取线路的显示名称(用于预检测结果显示)
+ var lineLabel = '';
+ if(subLineVal){
+ lineLabel = $('#subLineSelect_' + groupIndex).find('option:selected').text() || '';
+ } else {
+ lineLabel = $('#lineSelect_' + groupIndex).find('option:selected').text() || '';
+ }
+ dnsDomains[dnsDomain] = {domainId: domainId, dnsDomain: dnsDomain, line: lineVal, lineLabel: lineLabel};
+ if(!val) allSelected = false;
+ });
+ } else {
+ // 单域名模式
+ var domainId = $('#cfOptimizedDnsProvider').val();
+ if(!domainId) allSelected = false;
+ var singleLineVal = $('#singleLineSelect').val() || '0';
+ var singleSubLineVal = $('#singleSubLineSelect').val();
+ if(singleSubLineVal) singleLineVal = singleSubLineVal;
+ var singleLineLabel = '';
+ if(singleSubLineVal){
+ singleLineLabel = $('#singleSubLineSelect').find('option:selected').text() || '';
+ } else {
+ singleLineLabel = $('#singleLineSelect').find('option:selected').text() || '';
+ }
+ // 直接从数据取实际域名作为 key(此时 orderedDomains 尚未构建,不能引用它)
+ var singleDnsKey = batchCfOptimizedData.length > 0 ? (batchCfOptimizedData[0].dnsDomain || '0') : '0';
+ dnsDomains[singleDnsKey] = {domainId: domainId, line: singleLineVal, lineLabel: singleLineLabel};
+ }
+
+ if(!allSelected){ layer.msg('请为所有 DNS 域名选择解析服务商', {icon: 0}); return; }
+
+ // 保存选择供批量二次确认时复用(此时 DOM 可能已被隐藏)
+ confirmBtn.data('savedDnsDomains', dnsDomains);
+
+ // 将 DNS 选择直接存到每个主机名数据上(不依赖 DOM 可见状态)
+ var orderedDomains = [];
+ var seenDomains = {};
+ for(var di = 0; di < batchCfOptimizedData.length; di++){
+ var dd = batchCfOptimizedData[di].dnsDomain;
+ if(dd && !seenDomains[dd]){ seenDomains[dd] = true; orderedDomains.push(dd); }
+ }
+ if(orderedDomains.length > 1){
+ // 多域名分组模式(按域名匹配,不依赖顺序)
+ for(var gi = 0; gi < orderedDomains.length; gi++){
+ var sel = dnsDomains[orderedDomains[gi]];
+ if(!sel) continue;
+ for(var ei = 0; ei < batchCfOptimizedData.length; ei++){
+ if(batchCfOptimizedData[ei].dnsDomain === orderedDomains[gi]){
+ batchCfOptimizedData[ei]._dnsSel = sel;
+ }
+ }
+ }
+ } else {
+ // 单分组模式(dnsDomains key 是域名非数字索引,用 orderedDomains[0] 取正确值)
+ var singleSel = orderedDomains.length > 0 ? dnsDomains[orderedDomains[0]] : null;
+ for(var si = 0; si < batchCfOptimizedData.length; si++){
+ batchCfOptimizedData[si]._dnsSel = singleSel;
+ }
+ }
+
+ var btnMode = confirmBtn.data('cfmode');
+
+ // 单个模式的二次确认(检测已存在记录)
+ if(cfOptimizedSingleHostname && btnMode !== 'execute'){
+ var singleDomainKey = orderedDomains.length > 0 ? orderedDomains[0] : 0;
+ var sel = dnsDomains[singleDomainKey];
+ var singleItem = batchCfOptimizedData[0];
+ var singleDnsDomain = singleItem.dnsDomain || '';
+ var singleRecordName = cfOptimizedSingleHostname;
+ if(singleDnsDomain && cfOptimizedSingleHostname.length > singleDnsDomain.length && cfOptimizedSingleHostname.substr(cfOptimizedSingleHostname.length - singleDnsDomain.length - 1) === '.' + singleDnsDomain){
+ singleRecordName = cfOptimizedSingleHostname.substr(0, cfOptimizedSingleHostname.length - singleDnsDomain.length - 1);
+ } else if(cfOptimizedSingleHostname === singleDnsDomain){
+ singleRecordName = '@';
+ }
+ confirmBtn.prop('disabled', true).text('检测中...');
+
+ $.ajax({
+ type: 'POST',
+ url: '/record/list',
+ data: {id: sel.domainId, rr: singleRecordName},
+ dataType: 'json',
+ success: function(res){
+ confirmBtn.prop('disabled', false);
+ // A/CNAME/AAAA 在同一线路互斥,预检测也匹配全部三种
+ var allowedTypes = ['A', 'AAAA', 'CNAME'];
+ var existingRecord = null;
+ var existingInfo = '';
+ // 获取用户选择的线路 value 和 label
+ var selLine = (sel && sel.line) ? sel.line : '0';
+ var selLineLabel = (sel && sel.lineLabel) ? sel.lineLabel : '';
+ if(res.code === 0 && res.data && res.data.length > 0){
+ for(var i = 0; i < res.data.length; i++){
+ var recType = (res.data[i].Type || '').toUpperCase();
+ if(allowedTypes.indexOf(recType) === -1) continue;
+ var recLine = String(res.data[i].Line || res.data[i].line || '');
+ var recLineName = res.data[i].LineName || (res.data[i].line_name || '');
+ // 线路精确匹配:优先比较 value,不匹配则用 label 兜底
+ var isLineMatch = (recLine === selLine);
+ if(!isLineMatch && selLineLabel && recLineName){
+ isLineMatch = (recLineName === selLineLabel);
+ }
+ if(isLineMatch){
+ existingRecord = res.data[i];
+ break;
+ }
+ }
+ }
+ if(existingRecord){
+ var recType = (existingRecord.Type || '').toUpperCase();
+ var recLineName = existingRecord.LineName || (existingRecord.line_name || String(existingRecord.Line || existingRecord.line || '默认'));
+ var displaySelLabel = selLineLabel || selLine || '默认';
+ var targetRecType = recordType || 'CNAME';
+ var typeChangeInfo = (recType === targetRecType) ? (recType + '') : ('目标类型:' + targetRecType + ' → 现有类型:' + recType);
+ existingInfo = typeChangeInfo + ' | 线路:' + recLineName + ' → 目标线路:' + displaySelLabel + ' | 值:' + (existingRecord.Value || existingRecord.value || '-');
+ $('#cfOptimizedExistingInfo').text(existingInfo);
+ $('#cfOptimizedExistingRecord').show();
+ confirmBtn.text('确认修改').data('cfmode', 'execute').data('cfExistingId', existingRecord.RecordId).data('cfExistingRec', existingRecord);
+ } else {
+ confirmBtn.data('cfmode', 'execute');
+ executeCfOptimized(cnameTarget, dnsDomains, recordType);
+ }
+ },
+ error: function(){
+ confirmBtn.prop('disabled', false).text('确定');
+ layer.alert('检测已有记录失败', {icon: 2});
+ }
+ });
+ return;
+ }
+
+ // 批量模式的二次确认 或 单个模式已确认
+ if(btnMode === 'execute' || btnMode === 'batchExecute'){
+ executeCfOptimized(cnameTarget, dnsDomains, recordType);
+ return;
+ }
+
+ // 批量模式第一次点击:预检测
+ confirmBtn.prop('disabled', true).text('检测中...');
+ $('#cfOptimizedExistingRecord').hide();
+
+ preDetectAllHostnames(cnameTarget, dnsDomains, recordType, function(results){
+ confirmBtn.prop('disabled', false);
+
+ var modifyCount = results.modifyList.length;
+ var addCount = results.addList.length;
+ var failCount = results.failList.length;
+
+ // 更新列表显示检测结果
+ var listHtml = '';
+ batchCfOptimizedData.forEach(function(item){
+ var h = item.hostname;
+ if(results.modifySet[h] && results.modifySet[h] !== true){
+ var rec = results.modifySet[h];
+ var recType = (rec.Type || '').toUpperCase();
+ var recLineName = rec.LineName || (rec.line_name || String(rec.Line || rec.line || '默认'));
+ var selLineLabel = (item._dnsSel && item._dnsSel.lineLabel) ? item._dnsSel.lineLabel : (item._dnsSel && item._dnsSel.line) || '默认';
+ var targetRecType = recordType || 'CNAME';
+ var typeInfo = (recType === targetRecType) ? ('类型:' + recType) : ('目标类型:' + targetRecType + ' → 现有类型:' + recType);
+ listHtml += '
';
+ listHtml += '
' + htmlEscape(h) + ' ⚠ 将修改已有记录';
+ listHtml += '
' + typeInfo + ' | 线路:' + htmlEscape(recLineName) + ' → 目标线路:' + htmlEscape(selLineLabel) + ' | 值:' + htmlEscape(rec.Value || rec.value || '-') + '
';
+ listHtml += '
';
+ } else if(results.addSet[h]){
+ var addLineLabel = (item._dnsSel && item._dnsSel.lineLabel) ? item._dnsSel.lineLabel : '默认';
+ var addTargetType = recordType || 'CNAME';
+ listHtml += '
';
+ listHtml += '
' + htmlEscape(h) + ' ✓ 将新增记录';
+ listHtml += '
目标类型:' + addTargetType + ' | 目标线路:' + htmlEscape(addLineLabel) + '
';
+ listHtml += '
';
+ } else {
+ listHtml += '
';
+ listHtml += '' + htmlEscape(h) + ' ✗ 检测失败';
+ listHtml += '
';
+ }
+ });
+
+ $('#batchCfOptimizedList').html(listHtml).show();
+
+ var summary = '';
+ if(modifyCount > 0) summary += '
' + modifyCount + ' 个将修改已有记录';
+ if(addCount > 0){ if(summary) summary += ' | '; summary += '
' + addCount + ' 个将新增记录'; }
+ if(failCount > 0){ if(summary) summary += ' | '; summary += '
' + failCount + ' 个检测失败'; }
+
+ $('#batchCfOptimizedAlert').html(summary);
+
+ // 无论检测结果如何都允许继续(执行阶段会再次检测并决定添加/修改)
+ $('#cfOptimizedDnsGroupArea').hide();
+ $('#cfOptimizedSingleDnsArea').hide();
+ $('#cfOptimizedExistingRecord').show().removeClass('alert-warning alert-info').addClass(failCount > 0 ? 'alert-warning' : 'alert-info');
+ if(failCount > 0 && failCount === totalCount){
+ $('#cfOptimizedExistingInfo').text('检测失败的主机名将尝试直接添加记录');
+ } else if(failCount > 0){
+ $('#cfOptimizedExistingInfo').text('部分主机名检测失败,失败的将尝试直接添加。请确认后点击"确认执行"');
+ } else {
+ $('#cfOptimizedExistingInfo').text('请确认以上检测结果后点击"确认执行"');
+ }
+ confirmBtn.text('确认执行').data('cfmode', 'batchExecute');
+
+ // 保存预检测结果供后续使用
+ confirmBtn.data('preDetectResults', results);
+ });
+}
+
+// 预检测所有主机名是否有已存在记录
+function preDetectAllHostnames(cnameTarget, dnsDomains, recordType, callback){
+ // A/CNAME/AAAA 在同一线路互斥,预检测也匹配全部三种
+ var allowedTypes = ['A', 'AAAA', 'CNAME'];
+ var detectedCount = 0;
+ var totalCount = batchCfOptimizedData.length;
+ var modifyList = [], addList = [], failList = [];
+ var modifySet = {}, addSet = {};
+
+ function checkDone(){
+ if(detectedCount === totalCount){
+ callback({modifyList: modifyList, addList: addList, failList: failList, modifySet: modifySet, addSet: addSet});
+ }
+ }
+
+ // 直接使用已存到每个主机名数据上的 _dnsSel
+ batchCfOptimizedData.forEach(function(item){
+ var hostname = item.hostname;
+ var sel = item._dnsSel;
+
+ // _dnsSel 为空时 fallback 到 targets 数据
+ if(!sel && item.targets && item.targets.length > 0){
+ sel = {domainId: item.targets[0].domain_id};
+ }
+ if(!sel || !sel.domainId){ failList.push(hostname); detectedCount++; if(detectedCount === totalCount) checkDone(); return; }
+
+ // 直接从主机名和 DNS 域名计算 record_name(不依赖 targets 中的值,避免误判)
+ var dnsDomain = item.dnsDomain || '';
+ var recordName = hostname;
+ if(dnsDomain && hostname.length > dnsDomain.length && hostname.substr(hostname.length - dnsDomain.length - 1) === '.' + dnsDomain){
+ recordName = hostname.substr(0, hostname.length - dnsDomain.length - 1);
+ } else if(hostname === dnsDomain){
+ recordName = '@';
+ }
+ if(!recordName) recordName = hostname;
+ detectedCount++;
+
+ (function(hn, rn, s){
+ // 获取用户选择的线路 value 和 label(label 用于兜底匹配)
+ var selLine = s.line || '0';
+ var selLineLabel = s.lineLabel || '';
+ $.ajax({
+ type: 'POST',
+ url: '/record/list',
+ data: {id: s.domainId, rr: rn},
+ dataType: 'json',
+ success: function(res){
+ var existingRecord = null;
+ if(res.code === 0 && res.data && res.data.length > 0){
+ for(var i = 0; i < res.data.length; i++){
+ var recType = (res.data[i].Type || '').toUpperCase();
+ if(allowedTypes.indexOf(recType) === -1) continue;
+ var recLine = String(res.data[i].Line || res.data[i].line || '');
+ var recLineName = res.data[i].LineName || (res.data[i].line_name || '');
+ // 线路精确匹配:优先比较 value,不匹配则用 label 兜底
+ var isLineMatch = (recLine === selLine);
+ if(!isLineMatch && selLineLabel && recLineName){
+ isLineMatch = (recLineName === selLineLabel);
+ }
+ if(isLineMatch){
+ existingRecord = res.data[i];
+ break;
+ }
+ }
+ }
+ if(existingRecord){
+ modifyList.push(hn);
+ modifySet[hn] = existingRecord;
+ } else {
+ addList.push(hn);
+ addSet[hn] = true;
+ }
+ checkDone();
+ },
+ error: function(){
+ failList.push(hn);
+ checkDone();
+ }
+ });
+ })(hostname, recordName, sel);
+ });
+}
+
+// 执行 CF 优选操作
+function executeCfOptimized(cnameTarget, dnsDomains, recordType){
+ var confirmBtn = $("#cfOptimizedConfirmBtn");
+ confirmBtn.prop('disabled', true).text('处理中...');
+
+ var ii = layer.load(2);
+ var batchResults = {success: 0, failed: 0, errors: []};
+ var processedCount = 0;
+ var totalCount = batchCfOptimizedData.length;
+ var execRecordType = recordType || 'CNAME';
+ // 执行阶段:A/CNAME/AAAA 在同一线路互斥,找到任一种都走修改路径(用用户选择的类型覆盖)
+ var allowedTypes = ['A', 'AAAA', 'CNAME'];
+
+ function checkCompletion(){
+ if(processedCount === totalCount){
+ layer.close(ii);
+ resetCfOptimizedState();
+ $("#modal-cf-optimized").modal('hide');
+ var msg = 'CF 优选解析完成:成功 ' + batchResults.success + ' 个,失败 ' + batchResults.failed + ' 个';
+ if(batchResults.errors.length > 0) msg += '\n\n失败详情:\n' + batchResults.errors.join('\n');
+ layer.alert(msg, {icon: batchResults.failed > 0 ? 2 : 1});
+ }
+ }
+
+ // 处理单个模式的预存 RecordId
+ var singlePendingId = null;
+ if(cfOptimizedSingleHostname && confirmBtn.data('cfExistingId')){
+ singlePendingId = confirmBtn.data('cfExistingId');
+ cfOptimizedSingleHostname = null;
+ }
+
+ function processOneItem(item, line){
+ var hostname = item.hostname;
+ var sel = item._dnsSel;
+
+ // _dnsSel 为空时 fallback 到 targets 数据
+ if(!sel && item.targets && item.targets.length > 0){
+ sel = {domainId: item.targets[0].domain_id, line: line};
+ }
+
+ // 直接从主机名和 DNS 域名计算 record_name
+ var dnsDomain = item.dnsDomain || '';
+ var recordName = hostname;
+ if(dnsDomain && hostname.length > dnsDomain.length && hostname.substr(hostname.length - dnsDomain.length - 1) === '.' + dnsDomain){
+ recordName = hostname.substr(0, hostname.length - dnsDomain.length - 1);
+ } else if(hostname === dnsDomain){
+ recordName = '@';
+ }
+ if(!recordName) recordName = hostname;
+
+ if(!sel || !sel.domainId){
+ batchResults.failed++; batchResults.errors.push(hostname + ': 无匹配的DNS服务商'); processedCount++; checkCompletion(); return;
+ }
+
+ // 如果有预存的单个模式 RecordId 且是同一个主机名,直接使用
+ var useExistingId = singlePendingId && batchCfOptimizedData.length === 1;
+ if(useExistingId){
+ submitOneRecord(sel.domainId, recordName, cnameTarget, line, singlePendingId, hostname, batchResults, execRecordType, function(){
+ processedCount++; checkCompletion();
+ });
+ return;
+ }
+
+ // 否则检测后提交(按用户选择的线路匹配)
+ $.ajax({
+ type: 'POST',
+ url: '/record/list',
+ data: {id: sel.domainId, rr: recordName},
+ dataType: 'json',
+ success: function(res){
+ var existingRecordId = null;
+ if(res.code === 0 && res.data && res.data.length > 0){
+ for(var i = 0; i < res.data.length; i++){
+ var recType = (res.data[i].Type || '').toUpperCase();
+ var recLine = String(res.data[i].Line || res.data[i].line || '');
+ if(allowedTypes.indexOf(recType) !== -1 && recLine === line){
+ existingRecordId = res.data[i].RecordId; break;
+ }
+ }
+ }
+ submitOneRecord(sel.domainId, recordName, cnameTarget, line, existingRecordId, hostname, batchResults, execRecordType, function(){
+ processedCount++; checkCompletion();
+ });
+ },
+ error: function(){
+ batchResults.failed++; batchResults.errors.push(hostname + ': 检测已有记录失败'); processedCount++; checkCompletion();
+ }
+ });
+ }
+
+ function submitOneRecord(domainId, recordName, cnameTarget, line, existingRecordId, hostname, br, recType, cb){
+ var postData = {name: recordName, type: (recType || 'CNAME'), value: cnameTarget, line: line, ttl: 600, mx: 1, weight: 0, remark: 'Cloudflare 优选解析'};
+ if(existingRecordId) postData.recordid = existingRecordId;
+ var url = existingRecordId ? ('/record/update/' + domainId) : ('/record/add/' + domainId);
+ var actionMsg = existingRecordId ? '修改' : '添加';
+
+ $.ajax({
+ type: 'POST', url: url, data: postData, dataType: 'json',
+ success: function(res){
+ if(res.code === 0){
+ br.success++;
+ } else {
+ if(existingRecordId && res.code !== 0){
+ var retryData = {name: recordName, type: (recType || 'CNAME'), value: cnameTarget, line: line, ttl: 600, mx: 1, weight: 0, remark: 'Cloudflare 优选解析'};
+ $.ajax({
+ type: 'POST', url: '/record/add/' + domainId, data: retryData, dataType: 'json',
+ success: function(retryRes){
+ if(retryRes.code === 0){ br.success++; } else { br.failed++; br.errors.push(hostname + ': 添加失败 - ' + (retryRes.msg || '')); }
+ cb();
+ },
+ error: function(){ br.failed++; br.errors.push(hostname + ': 添加网络错误'); cb(); }
+ });
+ return;
+ }
+ br.failed++;
+ br.errors.push(hostname + ': ' + actionMsg + '失败 - ' + (res.msg || ''));
+ }
+ cb();
+ },
+ error: function(){
+ br.failed++; br.errors.push(hostname + ': 网络错误'); cb();
+ }
+ });
+ }
+
+ // 每个主机名使用自己组选择的线路,直接处理
+ batchCfOptimizedData.forEach(function(item){
+ var itemLine = (item._dnsSel && item._dnsSel.line) ? item._dnsSel.line : '0';
+ processOneItem(item, itemLine);
+ });
+}
+
function submitBatchTxtRecord(txtName, txtValue, target, remark, callback){
// 先获取目标域名的默认线路
$.ajax({
diff --git a/app/view/domain/smartparse.html b/app/view/domain/smartparse.html
index a35f466..16ff64c 100644
--- a/app/view/domain/smartparse.html
+++ b/app/view/domain/smartparse.html
@@ -78,14 +78,10 @@
}
.record-table .action-cell {
white-space: nowrap;
- display: flex;
- justify-content: center;
- align-items: center;
- gap: 4px;
- flex-wrap: nowrap;
+ text-align: center;
}
.record-table .action-cell .btn {
- margin: 0;
+ margin: 2px 1px;
}
.no-records {
text-align: center;
@@ -649,6 +645,10 @@ var currentPage = 1;
var pageSize = 20;
var totalRecords = 0;
+// 搜索状态
+var isSearching = false;
+var searchKeyword = '';
+
// 域名列表(用于批量添加时匹配)
var domainList = [];
{foreach $domainList as $domain}
@@ -744,6 +744,11 @@ $(document).ready(function(){
$('#domainSelect').on('change', function(){
var domainId = $(this).val();
if(domainId){
+ // 清空搜索状态
+ isSearching = false;
+ searchKeyword = '';
+ $('#searchKeyword').val('');
+
loadDomainInfo(domainId);
currentPage = 1;
loadRecordList();
@@ -1477,6 +1482,12 @@ function loadRecordList(){
var domainId = $('#domainSelect').val();
if(!domainId) return;
+ // 如果处于搜索状态,执行搜索
+ if(isSearching && searchKeyword){
+ searchRecords();
+ return;
+ }
+
var ii = layer.load(2);
$.ajax({
type : 'POST',
@@ -1519,7 +1530,7 @@ function renderRecordList(records){
html += '
';
html += ' | ';
- html += '' + (row.Name == '@' ? '@ (主域名)' : row.Name) + ' | ';
+ html += '' + (row.Name == '@' ? '@ (主域名)' : row.Name) + ' | ';
html += '' + row.Type + ' | ';
html += '';
html += '';
@@ -1621,11 +1632,17 @@ function searchRecords(){
if(!keyword){
// 无搜索词时,恢复正常加载
+ isSearching = false;
+ searchKeyword = '';
currentPage = 1;
loadRecordList();
return;
}
+ // 设置搜索状态
+ isSearching = true;
+ searchKeyword = keyword;
+
// 获取所有记录进行搜索(分页获取所有数据)
var ii = layer.load(2);
var allData = [];
|