Files
links/patches/public/assets/js/backend/split/number.js
T

534 lines
26 KiB
JavaScript
Raw Normal View History

2026-06-04 14:15:12 +08:00
define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
var Controller = {
index: function () {
Table.api.init({
extend: {
index_url: 'split.number/index' + location.search,
add_url: 'split.number/add',
edit_url: 'split.number/edit',
del_url: 'split.number/del',
multi_url: 'split.number/multi',
table: 'split_number',
}
});
var table = $("#table");
2026-06-20 04:47:34 +08:00
var searchSelect = Controller.api.searchSelectColumn;
var searchSelectMeta = Controller.api.searchSelectMeta;
2026-06-04 14:15:12 +08:00
table.bootstrapTable({
url: $.fn.bootstrapTable.defaults.extend.index_url,
pk: 'id',
sortName: 'id',
sortOrder: 'desc',
fixedColumns: true,
fixedRightNumber: 1,
2026-06-20 04:47:34 +08:00
searchFormVisible: true,
2026-06-04 14:15:12 +08:00
columns: [
[
{checkbox: true},
2026-06-20 04:47:34 +08:00
searchSelect('split_link_id', __('Link_url'), Config.splitLinkFilterList || {}, true),
2026-06-04 14:15:12 +08:00
{field: 'id', title: __('Id'), sortable: true},
{
field: 'link_url_text',
title: __('Link_url'),
operate: false,
formatter: Table.api.formatter.content
},
{field: 'ticket_name', title: __('Ticket_name'), operate: 'LIKE'},
{
field: 'number',
title: __('Number'),
operate: 'LIKE',
formatter: Controller.api.formatter.numberWithDeferred
},
2026-06-20 04:47:34 +08:00
$.extend(
{field: 'number_type', title: __('Number_type'), searchList: Config.numberTypeList, formatter: Table.api.formatter.normal},
searchSelectMeta(false)
),
2026-06-04 14:15:12 +08:00
{field: 'visit_count', title: __('Visit_count'), operate: false, sortable: true},
{field: 'inbound_count', title: __('Inbound_count'), operate: false, sortable: true},
2026-06-20 04:47:34 +08:00
$.extend(
{field: 'platform_status', title: __('Platform_status'), searchList: Config.platformStatusList, formatter: Controller.api.formatter.platformStatus},
searchSelectMeta(false)
),
$.extend(
{field: 'status', title: __('Status'), searchList: Config.statusList, formatter: Table.api.formatter.toggle, yes: 'normal', no: 'hidden'},
searchSelectMeta(false)
),
$.extend(
{
field: 'ratio_deferred',
title: __('Ratio_deferred'),
visible: false,
searchable: true,
operate: '=',
searchList: Controller.api.buildRatioDeferredSearchList()
},
searchSelectMeta(false)
),
2026-06-04 14:15:12 +08:00
{
field: 'createtime',
title: __('Createtime'),
operate: 'RANGE',
addclass: 'datetimerange',
autocomplete: false,
formatter: Table.api.formatter.datetime,
sortable: true
},
{
field: 'operate',
title: __('Operate'),
table: table,
events: Table.api.events.operate,
formatter: Table.api.formatter.operate
}
]
]
});
table.on('check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table', function () {
var ids = Table.api.selectedids(table);
$('.btn-batch-update-status').toggleClass('btn-disabled disabled', ids.length === 0);
});
$('.btn-batch-update-status').on('click', function (e) {
e.preventDefault();
e.stopPropagation();
if ($(this).hasClass('disabled')) {
return false;
}
var ids = Table.api.selectedids(table);
if (!ids.length) {
Toastr.warning(__('Please select at least one item'));
return false;
}
Controller.api.openBatchUpdateModal(ids, table);
});
2026-06-20 04:47:34 +08:00
$('.btn-batch-operate').on('click', function (e) {
e.preventDefault();
e.stopPropagation();
Controller.api.openBatchOperateModal(table);
});
table.on('post-body.bs.table', function () {
table.find('[data-toggle="tooltip"]').tooltip({container: 'body'});
});
2026-06-20 04:47:34 +08:00
table.on('post-common-search.bs.table', function (e, tbl) {
Controller.api.initCommonSearchSelectpicker(tbl);
});
2026-06-04 14:15:12 +08:00
Table.api.bindevent(table);
Controller.api.bindDeferredFilter(table);
Controller.api.bindDeferredCleanup(table);
2026-06-04 14:15:12 +08:00
},
add: function () {
Controller.api.bindevent();
},
edit: function () {
Controller.api.bindevent();
},
api: {
2026-06-20 04:47:34 +08:00
searchSelectColumn: function (field, title, searchList, liveSearch) {
return {
field: field,
title: title,
visible: false,
searchList: searchList || {},
operate: '=',
addclass: 'selectpicker',
extend: Controller.api.searchSelectExtend(liveSearch !== false)
};
},
searchSelectMeta: function (liveSearch) {
return {
addclass: 'selectpicker',
extend: Controller.api.searchSelectExtend(liveSearch !== false)
};
},
searchSelectExtend: function (liveSearch) {
var text = __('Please select');
if (!text || text === 'Please select' || text === 'Please Select') {
text = '请选择';
}
var ext = 'data-none-selected-text="' + text + '" title="' + text + '"';
if (liveSearch) {
ext += ' data-live-search="true"';
}
return ext;
},
/**
* PHP json_encode 会把 ['0'=>..,'1'=>..] 压成数组,FastAdmin 通用搜索会误用文案作 option value
*/
buildRatioDeferredSearchList: function () {
var list = Config.ratioDeferredList;
if ($.isArray(list)) {
return {0: list[0], 1: list[1]};
}
return list || {};
},
2026-06-20 04:47:34 +08:00
initCommonSearchSelectpicker: function (tbl) {
var $form = $('form.form-commonsearch', tbl.$container);
if ($form.length) {
Form.events.selectpicker($form);
}
},
2026-06-09 03:36:30 +08:00
formatter: {
platformStatus: function (value, row) {
var text = row.platform_status_text != null && row.platform_status_text !== ''
? String(row.platform_status_text)
: (Config.platformStatusList && Config.platformStatusList[value] ? Config.platformStatusList[value] : value);
var cls = value === 'online' ? 'success' : (value === 'offline' ? 'default' : 'warning');
return '<span class="text-' + cls + '">' + Fast.api.escape(text || '') + '</span>';
},
isRatioDeferred: function (row) {
return parseInt(row.ratio_deferred, 10) === 1 && String(row.status || '') === 'normal';
},
ratioDeferredTooltip: function (row) {
var streak = parseInt(row.no_inbound_click_streak, 10) || 0;
var template = __('Ratio deferred tooltip');
if (!template || template === 'Ratio deferred tooltip') {
template = '连续 %s 次访问无进线增长,已触线下号比率,选号已后置,等待同步裁决';
}
return template.indexOf('%s') >= 0 ? template.replace('%s', String(streak)) : template;
},
ratioDeferredBadgeHtml: function (tooltip) {
var label = __('Ratio deferred badge');
if (!label || label === 'Ratio deferred badge') {
label = '降权中';
}
return '<span class="label label-warning split-ratio-deferred-badge" style="margin-left:6px;font-size:11px;font-weight:500;vertical-align:middle;"'
+ ' data-toggle="tooltip" data-placement="top" title="' + Fast.api.escape(tooltip) + '">'
+ '<i class="fa fa-level-down"></i> ' + Fast.api.escape(label)
+ '</span>';
},
numberWithDeferred: function (value, row) {
var html = Fast.api.escape(String(value || ''));
if (Controller.api.formatter.isRatioDeferred(row)) {
html += Controller.api.formatter.ratioDeferredBadgeHtml(
Controller.api.formatter.ratioDeferredTooltip(row)
);
}
return html;
}
},
refreshSearchSelect: function ($field) {
if ($field.length && $field.hasClass('selectpicker') && $field.data('selectpicker')) {
$field.selectpicker('refresh');
}
},
syncStatusTabs: function (table, statusValue) {
var $tabs = table.closest('.panel-intro').find('.panel-heading [data-field="status"]');
if (!$tabs.length) {
return;
}
$tabs.find('li').removeClass('active');
$tabs.find('li a[data-value="' + (statusValue === null || statusValue === undefined ? '' : statusValue) + '"]')
.parent()
.addClass('active');
},
/**
* 工具栏「降权中」快捷筛选:与列表徽章一致(ratio_deferred=1 且 status=normal
*/
bindDeferredFilter: function (table) {
var active = false;
var savedStatus = '';
var $btn = $('.btn-filter-ratio-deferred');
if (!$btn.length) {
return;
}
var applyFilter = function () {
var $form = table.closest('.bootstrap-table').find('form.form-commonsearch');
if (!$form.length) {
return false;
}
var $ratioField = $form.find('[name="ratio_deferred"]');
var $statusField = $form.find('[name="status"]');
if (active) {
savedStatus = $statusField.length ? String($statusField.val() || '') : '';
if ($ratioField.length) {
$ratioField.val('1');
Controller.api.refreshSearchSelect($ratioField);
}
if ($statusField.length) {
$statusField.val('normal');
Controller.api.refreshSearchSelect($statusField);
}
Controller.api.syncStatusTabs(table, 'normal');
} else {
if ($ratioField.length) {
$ratioField.val('');
Controller.api.refreshSearchSelect($ratioField);
}
if ($statusField.length) {
$statusField.val(savedStatus);
Controller.api.refreshSearchSelect($statusField);
}
Controller.api.syncStatusTabs(table, savedStatus);
savedStatus = '';
}
table.trigger('uncheckbox');
$form.trigger('submit');
return true;
};
$btn.on('click', function (e) {
e.preventDefault();
active = !active;
$btn.toggleClass('btn-warning', active).toggleClass('btn-default', !active);
if (!applyFilter()) {
active = !active;
$btn.toggleClass('btn-warning', active).toggleClass('btn-default', !active);
Toastr.warning('筛选表单未就绪,请刷新页面后重试');
}
});
},
/**
* 清理无效降权池:无落地页访问基线却标记 ratio_deferred=1 的号码
*/
bindDeferredCleanup: function (table) {
var $btn = $('.btn-cleanup-ratio-deferred');
if (!$btn.length) {
return;
2026-06-09 03:36:30 +08:00
}
$btn.on('click', function (e) {
e.preventDefault();
Fast.api.ajax({
url: 'split.number/cleanupdeferredpreview',
type: 'get'
}, function (data) {
var count = parseInt(data.count, 10) || 0;
if (count <= 0) {
Toastr.info(__('Ratio deferred cleanup none'));
return false;
}
var template = __('Ratio deferred cleanup confirm');
if (!template || template === 'Ratio deferred cleanup confirm') {
template = '将清除 %d 条无效降权记录。确定继续?';
}
var message = template.indexOf('%d') >= 0
? template.replace('%d', String(count))
: template;
Layer.confirm(message, {icon: 3, title: __('Warning')}, function (index) {
Layer.close(index);
Fast.api.ajax({
url: 'split.number/cleanupdeferred',
type: 'post'
}, function (data, ret) {
table.bootstrapTable('refresh');
Toastr.success(ret.msg || __('Ratio deferred cleanup success'));
});
});
return false;
});
});
2026-06-09 03:36:30 +08:00
},
2026-06-04 14:15:12 +08:00
bindevent: function () {
Form.api.bindevent($('form[role=form]'));
Controller.api.fixSelectPlaceholder();
Controller.api.bindNumberTypeToggle();
},
fixSelectPlaceholder: function () {
var text = __('Please select');
if (!text || text === 'Please select' || text === 'Please Select') {
text = '请选择';
}
$('#c-split_link_id').each(function () {
var $el = $(this);
$el.attr({'data-none-selected-text': text, 'title': text});
$el.find('option[value=""]').first().text(text);
if ($el.data('selectpicker')) {
$el.selectpicker('render');
}
});
},
bindNumberTypeToggle: function () {
var $type = $('#c-number_type');
var $wrap = $('.split-number-type-custom');
var $custom = $('#c-number_type_custom');
if (!$type.length) {
return;
}
var toggle = function () {
var val = $type.val();
if (val === 'custom') {
$wrap.removeClass('hide');
$custom.attr('data-rule', 'required');
} else {
$wrap.addClass('hide');
$custom.removeAttr('data-rule');
if (!$wrap.closest('form').attr('id') || $('#edit-form').length === 0) {
$custom.val('');
}
}
};
$type.on('changed.bs.select change', toggle);
toggle();
},
openBatchUpdateModal: function (ids, table) {
var statusHtml = '';
$.each(Config.statusList || {}, function (key, label) {
var checked = key === 'normal' ? ' checked' : '';
statusHtml += '<label class="radio-inline"><input type="radio" name="batch_status" value="' + key + '"' + checked + '> ' + label + '</label>';
});
var manualHtml = '';
$.each(Config.manualManageList || {}, function (key, label) {
var checked = key === '0' ? ' checked' : '';
manualHtml += '<label class="radio-inline"><input type="radio" name="batch_manual_manage" value="' + key + '"' + checked + '> ' + label + '</label>';
});
var html = [
'<div class="split-batch-update-modal" style="padding:18px 22px;">',
' <div class="form-group">',
' <label class="control-label">' + __('Batch selected count') + '</label>',
' <p class="form-control-static" style="font-size:18px;font-weight:600;margin:0;">' + ids.length + ' ' + __('Unit count') + '</p>',
' </div>',
' <div class="form-group">',
' <label class="control-label">' + __('Status') + '</label>',
' <div>' + statusHtml + '</div>',
' </div>',
' <div class="form-group" style="margin-bottom:0;">',
' <label class="control-label">' + __('Manual_manage') + '</label>',
' <div>' + manualHtml + '</div>',
' </div>',
'</div>'
].join('');
Layer.open({
type: 1,
title: __('Batch update title'),
area: ['420px', 'auto'],
shadeClose: false,
content: html,
btn: [__('OK'), __('Cancel')],
yes: function (index, layero) {
var status = layero.find('input[name="batch_status"]:checked').val();
var manualManage = layero.find('input[name="batch_manual_manage"]:checked').val();
if (typeof status === 'undefined' || typeof manualManage === 'undefined') {
Toastr.error(__('Invalid parameters'));
return false;
}
Fast.api.ajax({
url: 'split.number/batchupdate',
type: 'post',
data: {
ids: ids.join(','),
status: status,
manual_manage: manualManage
}
}, function () {
Layer.close(index);
table.bootstrapTable('refresh');
$('.btn-batch-update-status').addClass('btn-disabled disabled');
Toastr.success(__('Batch update success'));
});
return false;
}
});
2026-06-20 04:47:34 +08:00
},
openBatchOperateModal: function (table) {
var pleaseSelect = __('Please select');
if (!pleaseSelect || pleaseSelect === 'Please select' || pleaseSelect === 'Please Select') {
pleaseSelect = '请选择';
}
var linkOptions = ['<option value="">' + Fast.api.escape(pleaseSelect) + '</option>'];
$.each(Config.splitLinkSelectList || {}, function (id, label) {
linkOptions.push('<option value="' + Fast.api.escape(String(id)) + '">' + Fast.api.escape(String(label)) + '</option>');
});
var html = [
'<div class="split-batch-operate-modal" style="padding:18px 22px;">',
' <div class="form-group">',
' <label class="control-label">' + __('Link_url') + '<span class="text-danger">*</span></label>',
' <select id="batch-operate-split-link" class="form-control selectpicker" data-live-search="true" data-none-selected-text="' + pleaseSelect + '" title="' + pleaseSelect + '">',
linkOptions.join(''),
' </select>',
' </div>',
' <div class="form-group">',
' <label class="control-label">' + __('Number') + '<span class="text-danger">*</span></label>',
' <textarea id="batch-operate-numbers" class="form-control" rows="8" placeholder="' + Fast.api.escape(__('Numbers one per line')) + '"></textarea>',
' </div>',
' <div class="form-group">',
' <label class="control-label">' + __('Batch operate action') + '<span class="text-danger">*</span></label>',
' <select id="batch-operate-action" class="form-control selectpicker" data-none-selected-text="' + pleaseSelect + '" title="' + pleaseSelect + '">',
' <option value="enable">' + Fast.api.escape(__('Batch operate enable')) + '</option>',
' <option value="disable">' + Fast.api.escape(__('Batch operate disable')) + '</option>',
' <option value="delete">' + Fast.api.escape(__('Batch operate delete')) + '</option>',
' </select>',
' </div>',
' <div class="form-group" style="margin-bottom:0;text-align:right;">',
' <button type="button" class="btn btn-primary btn-batch-operate-confirm">' + __('OK') + '</button> ',
' <button type="button" class="btn btn-default btn-batch-operate-cancel">' + __('Cancel') + '</button>',
' </div>',
'</div>'
].join('');
var layerIndex = Layer.open({
type: 1,
title: __('Batch operate title'),
area: ['520px', 'auto'],
shadeClose: false,
content: html,
success: function (layero, index) {
Form.events.selectpicker(layero);
layero.find('.btn-batch-operate-cancel').on('click', function () {
Layer.close(index);
});
layero.find('.btn-batch-operate-confirm').on('click', function () {
var splitLinkId = $.trim(layero.find('#batch-operate-split-link').val());
var numbers = $.trim(layero.find('#batch-operate-numbers').val());
var action = $.trim(layero.find('#batch-operate-action').val());
if (!splitLinkId) {
Toastr.warning(__('Please select split link'));
return false;
}
if (!numbers) {
Toastr.warning(__('Please fill at least one number'));
return false;
}
if (!action) {
Toastr.error(__('Invalid batch operate action'));
return false;
}
var submit = function () {
Fast.api.ajax({
url: 'split.number/batchoperate',
type: 'post',
data: {
split_link_id: splitLinkId,
numbers: numbers,
action: action
}
}, function (data, ret) {
Layer.close(index);
table.bootstrapTable('refresh');
Toastr.success(ret.msg || __('Batch operate success'));
});
};
if (action === 'delete') {
Layer.confirm(__('Batch operate delete confirm'), {icon: 3, title: __('Warning')}, function (confirmIndex) {
Layer.close(confirmIndex);
submit();
});
return false;
}
submit();
return false;
});
}
});
2026-06-04 14:15:12 +08:00
}
}
};
return Controller;
});