初始版本
This commit is contained in:
Executable
+884
@@ -0,0 +1,884 @@
|
||||
define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
|
||||
|
||||
var Controller = {
|
||||
index: function () {
|
||||
Table.api.init({
|
||||
extend: {
|
||||
index_url: 'split.link/index' + location.search,
|
||||
add_url: 'split.link/add',
|
||||
edit_url: 'split.link/edit',
|
||||
del_url: 'split.link/del',
|
||||
multi_url: 'split.link/multi',
|
||||
table: 'split_link',
|
||||
}
|
||||
});
|
||||
|
||||
var table = $("#table");
|
||||
|
||||
table.bootstrapTable({
|
||||
url: $.fn.bootstrapTable.defaults.extend.index_url,
|
||||
pk: 'id',
|
||||
sortName: 'id',
|
||||
sortOrder: 'desc',
|
||||
fixedColumns: true,
|
||||
fixedRightNumber: 1,
|
||||
columns: [
|
||||
[
|
||||
{checkbox: true},
|
||||
{field: 'countries_text', title: __('Countries'), operate: false, formatter: Table.api.formatter.content},
|
||||
{
|
||||
field: 'link_code',
|
||||
title: __('Link_code'),
|
||||
operate: 'LIKE',
|
||||
formatter: Controller.api.formatter.linkCode
|
||||
},
|
||||
{field: 'description', title: __('Description'), operate: 'LIKE', class: 'autocontent', formatter: Table.api.formatter.content},
|
||||
{
|
||||
field: 'auto_reply_text',
|
||||
title: __('Reply statements column'),
|
||||
operate: false,
|
||||
formatter: Controller.api.formatter.autoReplyText
|
||||
},
|
||||
{field: 'ip_protect', title: __('Ip_protect'), searchList: Config.ipProtectList, formatter: Table.api.formatter.status},
|
||||
{field: 'random_shuffle', title: __('Random_shuffle'), searchList: Config.randomShuffleList, formatter: Table.api.formatter.status},
|
||||
{field: 'status', title: __('Status'), searchList: Config.statusList, formatter: Table.api.formatter.status},
|
||||
{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,
|
||||
buttons: [
|
||||
{
|
||||
name: 'autoreply',
|
||||
text: __('Auto reply'),
|
||||
title: __('Auto reply'),
|
||||
icon: 'fa fa-commenting-o',
|
||||
classname: 'btn btn-warning btn-xs btn-split-autoreply',
|
||||
url: 'javascript:;'
|
||||
},
|
||||
{
|
||||
name: 'pixel',
|
||||
text: __('Pixel config'),
|
||||
title: __('Pixel config'),
|
||||
icon: 'fa fa-bullseye',
|
||||
classname: 'btn btn-info btn-xs btn-split-pixel',
|
||||
url: 'javascript:;'
|
||||
}
|
||||
],
|
||||
formatter: Table.api.formatter.operate
|
||||
}
|
||||
]
|
||||
]
|
||||
});
|
||||
|
||||
table.on('click', '.btn-copy-split-link', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
var linkCode = $.trim($(this).data('link-code') || '');
|
||||
if (!linkCode) {
|
||||
return false;
|
||||
}
|
||||
Controller.api.openCopyModal(linkCode);
|
||||
});
|
||||
|
||||
table.on('click', '.btn-copy-link-code-inline', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
var linkCode = $.trim($(this).data('link-code') || '');
|
||||
if (linkCode) {
|
||||
Controller.api.copyText(linkCode);
|
||||
}
|
||||
});
|
||||
|
||||
table.on('click', '.btn-split-autoreply', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
var rowIndex = $(this).data('row-index');
|
||||
var row = Table.api.getrowbyindex(table, rowIndex);
|
||||
if (!row || !row.id) {
|
||||
return false;
|
||||
}
|
||||
Controller.api.openAutoReplyModal(row);
|
||||
});
|
||||
|
||||
table.on('click', '.btn-split-pixel', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
var rowIndex = $(this).data('row-index');
|
||||
var row = Table.api.getrowbyindex(table, rowIndex);
|
||||
if (!row || !row.id) {
|
||||
return false;
|
||||
}
|
||||
Controller.api.openPixelModal(row);
|
||||
});
|
||||
|
||||
Controller.api.bindAutoReplyPreviewTips(table);
|
||||
|
||||
Table.api.bindevent(table);
|
||||
},
|
||||
add: function () {
|
||||
Controller.api.bindevent();
|
||||
Controller.api.bindLinkCodeInput();
|
||||
},
|
||||
edit: function () {
|
||||
Controller.api.bindevent();
|
||||
},
|
||||
api: {
|
||||
/**
|
||||
* 规范化后台跨模块跳转 URL,避免在 split.link 页面内相对解析成 split.link/domain
|
||||
*
|
||||
* @param {string} url API 返回的地址
|
||||
* @param {string} fallback 相对 admin 模块根路径,如 domain / domain/add
|
||||
* @return {string}
|
||||
*/
|
||||
normalizeAdminRouteUrl: function (url, fallback) {
|
||||
fallback = fallback || 'domain';
|
||||
url = $.trim(url || '');
|
||||
if (url === '' || /split\.link\/domain/i.test(url)) {
|
||||
return fallback;
|
||||
}
|
||||
var modulePrefix = (Config.moduleurl || '').replace(/\/+$/, '');
|
||||
if (url.indexOf('://') !== -1) {
|
||||
try {
|
||||
var parsed = new URL(url, window.location.origin);
|
||||
var path = (parsed.pathname || '').replace(/\/+$/, '');
|
||||
if (modulePrefix && path.indexOf(modulePrefix) === 0) {
|
||||
path = path.slice(modulePrefix.length);
|
||||
}
|
||||
path = path.replace(/^\/+/, '');
|
||||
return /^split\.link\/domain/i.test(path) ? fallback : (path || fallback);
|
||||
} catch (e) {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
if (url.charAt(0) === '/') {
|
||||
url = url.replace(/^\/+/, '');
|
||||
var moduleKey = modulePrefix.replace(/^\/+/, '');
|
||||
if (moduleKey && url.indexOf(moduleKey + '/') === 0) {
|
||||
url = url.slice(moduleKey.length + 1);
|
||||
}
|
||||
}
|
||||
return /^split\.link\/domain/i.test(url) ? fallback : url;
|
||||
},
|
||||
/** 弹窗样式(仅注入一次) */
|
||||
modalStyleInjected: false,
|
||||
pixelModalStyleInjected: false,
|
||||
injectModalStyles: function () {
|
||||
if (Controller.api.modalStyleInjected) {
|
||||
return;
|
||||
}
|
||||
Controller.api.modalStyleInjected = true;
|
||||
var css = [
|
||||
'.split-link-copy-modal{padding:16px 20px;box-sizing:border-box;}',
|
||||
'.split-link-copy-modal .form-group{margin-bottom:16px;}',
|
||||
'.split-link-copy-modal .control-label{display:block;font-weight:600;color:#444;margin-bottom:8px;}',
|
||||
'.split-link-copy-modal .split-link-code-row{display:flex;align-items:center;flex-wrap:wrap;gap:8px;}',
|
||||
'.split-link-copy-modal .split-link-code-value{font-size:18px;font-weight:600;color:#337ab7;text-decoration:underline;word-break:break-all;}',
|
||||
'.split-link-copy-modal .split-domain-tabs{margin-bottom:0;border-bottom:1px solid #ddd;}',
|
||||
'.split-link-copy-modal .split-domain-tab-box{border:1px solid #ddd;border-top:none;border-radius:0 0 4px 4px;background:#fafafa;overflow:hidden;}',
|
||||
'.split-link-copy-modal .split-domain-tab-box .tab-pane{max-height:280px;overflow-y:auto;overflow-x:hidden;padding:10px 12px;margin:0;}',
|
||||
'.split-link-copy-modal .split-domain-list{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;align-items:stretch;}',
|
||||
'.split-link-copy-modal .split-domain-item{border:1px solid #e8e8e8;border-radius:4px;background:#fff;transition:border-color .2s,box-shadow .2s;height:100%;min-width:0;}',
|
||||
'.split-link-copy-modal .split-domain-item:hover{border-color:#b8d4f0;}',
|
||||
'.split-link-copy-modal .split-domain-item.is-checked{border-color:#337ab7;box-shadow:0 0 0 1px rgba(51,122,183,.15);}',
|
||||
'.split-link-copy-modal .split-domain-item-label{display:flex;align-items:flex-start;gap:8px;margin:0;padding:8px 10px;cursor:pointer;font-weight:normal;width:100%;height:100%;box-sizing:border-box;}',
|
||||
'.split-link-copy-modal .split-domain-item-label input[type=radio]{margin:3px 0 0;flex-shrink:0;}',
|
||||
'.split-link-copy-modal .split-domain-item-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:5px;}',
|
||||
'.split-link-copy-modal .split-domain-name{font-size:13px;font-weight:600;color:#333;word-break:break-all;line-height:1.4;}',
|
||||
'.split-link-copy-modal .split-domain-status{display:flex;flex-direction:column;align-items:flex-start;gap:4px;}',
|
||||
'.split-link-copy-modal .split-domain-status .label{font-size:10px;font-weight:normal;padding:2px 6px;line-height:1.3;white-space:nowrap;margin:0;display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;}',
|
||||
'.split-link-copy-modal .split-domain-empty{margin:0;padding:20px 12px;color:#999;text-align:center;line-height:1.6;grid-column:1/-1;}',
|
||||
'.split-link-copy-modal .split-tip-box{margin:12px 0;padding:10px 12px;background:#f8f9fa;border-left:3px solid #337ab7;border-radius:0 4px 4px 0;line-height:1.6;color:#666;}',
|
||||
'.split-link-copy-modal .split-generated-url{font-size:12px;}',
|
||||
'.split-link-copy-modal .split-modal-footer{margin-top:16px;padding-top:12px;border-top:1px solid #eee;}'
|
||||
].join('');
|
||||
$('<style id="split-link-copy-modal-style" type="text/css"></style>').text(css).appendTo('head');
|
||||
},
|
||||
injectPixelModalStyles: function () {
|
||||
if (Controller.api.pixelModalStyleInjected) {
|
||||
return;
|
||||
}
|
||||
Controller.api.pixelModalStyleInjected = true;
|
||||
var css = [
|
||||
'.split-pixel-layer .layui-layer-content{padding:0;overflow:hidden;max-height:calc(86vh - 108px);}',
|
||||
'.split-pixel-layer .layui-layer-btn{border-top:1px solid #e8e8e8;background:#fafafa;}',
|
||||
'.split-pixel-modal{padding:18px 22px 14px;box-sizing:border-box;display:flex;flex-direction:column;min-height:480px;height:calc(86vh - 108px);max-height:720px;}',
|
||||
'.split-pixel-modal .split-pixel-tip{margin:0 0 14px;padding:10px 14px;background:#f0f7ff;border-left:3px solid #337ab7;border-radius:0 4px 4px 0;color:#555;font-size:13px;line-height:1.65;}',
|
||||
'.split-pixel-modal .split-pixel-tabs{margin-bottom:0;border-bottom:1px solid #ddd;}',
|
||||
'.split-pixel-modal .split-pixel-tabs>li>a{padding:9px 18px;font-weight:600;color:#666;}',
|
||||
'.split-pixel-modal .split-pixel-tabs>li.active>a{color:#337ab7;border-bottom-color:#fff;}',
|
||||
'.split-pixel-modal .split-pixel-tab-content{flex:1;display:flex;flex-direction:column;min-height:0;padding-top:14px;}',
|
||||
'.split-pixel-modal .split-pixel-tab-content>.tab-pane{display:none;flex:1;flex-direction:column;min-height:0;}',
|
||||
'.split-pixel-modal .split-pixel-tab-content>.tab-pane.active{display:flex;}',
|
||||
'.split-pixel-modal .split-pixel-list{flex:1;display:flex;flex-direction:column;min-height:0;}',
|
||||
'.split-pixel-modal .split-pixel-toolbar{margin-bottom:12px;display:flex;align-items:center;justify-content:space-between;flex-shrink:0;}',
|
||||
'.split-pixel-modal .split-pixel-toolbar .btn-add-pixel-row{font-weight:600;padding:6px 14px;}',
|
||||
'.split-pixel-modal .split-pixel-table-wrap{flex:1;min-height:320px;overflow:auto;border:1px solid #dce3eb;border-radius:6px;background:#fff;box-shadow:inset 0 1px 2px rgba(0,0,0,.03);}',
|
||||
'.split-pixel-modal .split-pixel-table{margin-bottom:0;font-size:13px;table-layout:auto;width:100%;}',
|
||||
'.split-pixel-modal .split-pixel-table thead th{background:linear-gradient(180deg,#f8fafc 0%,#eef2f6 100%);white-space:nowrap;vertical-align:middle;text-align:center;font-weight:600;color:#444;border-bottom:2px solid #dce3eb;padding:10px 8px;position:sticky;top:0;z-index:3;box-shadow:0 1px 0 #dce3eb;}',
|
||||
'.split-pixel-modal .split-pixel-table tbody td{vertical-align:middle;padding:10px 8px;border-color:#edf1f5;}',
|
||||
'.split-pixel-modal .split-pixel-table tbody tr.split-pixel-row:hover{background:#f7fbff;}',
|
||||
'.split-pixel-modal .split-pixel-table tbody tr.split-pixel-row:nth-child(even){background:#fbfcfd;}',
|
||||
'.split-pixel-modal .split-pixel-table tbody tr.split-pixel-row:nth-child(even):hover{background:#f7fbff;}',
|
||||
'.split-pixel-modal .split-pixel-table .form-control{min-width:0;height:32px;line-height:1.42857143;padding:6px 10px;border-radius:4px;}',
|
||||
'.split-pixel-modal .split-pixel-table .pixel-id{min-width:130px;}',
|
||||
'.split-pixel-modal .split-pixel-table .pixel-access-token{min-width:150px;}',
|
||||
'.split-pixel-modal .split-pixel-table .pixel-test-code{min-width:100px;}',
|
||||
'.split-pixel-modal .split-pixel-table th.pixel-event-col,.split-pixel-modal .split-pixel-table td.pixel-event-cell{min-width:148px;width:148px;}',
|
||||
'.split-pixel-modal .split-pixel-table .pixel-event{width:100%;min-width:132px;max-width:none;padding-right:28px;text-overflow:clip;overflow:visible;white-space:nowrap;cursor:pointer;}',
|
||||
'.split-pixel-modal .split-pixel-empty-row td{padding:48px 16px;color:#999;text-align:center;font-size:13px;background:#fafbfc;}',
|
||||
'.split-pixel-modal .split-pixel-sort-group{width:118px;margin:0 auto;}',
|
||||
'.split-pixel-modal .split-pixel-sort-group .form-control{text-align:center;padding-left:4px;padding-right:4px;}',
|
||||
'.split-pixel-modal .pixel-switch-wrap{text-align:center;}',
|
||||
'.split-pixel-modal .pixel-switch-wrap input[type=checkbox]{width:17px;height:17px;margin:0;cursor:pointer;vertical-align:middle;}',
|
||||
'.split-pixel-modal .pixel-row-index{display:inline-block;min-width:26px;height:26px;line-height:26px;border-radius:13px;background:#e8eef5;color:#4a6785;font-weight:600;font-size:12px;}',
|
||||
'.split-pixel-modal .btn-pixel-row-remove{padding:4px 8px;border-radius:4px;}',
|
||||
'.split-pixel-modal .col-token{min-width:160px;}'
|
||||
].join('');
|
||||
$('<style id="split-pixel-modal-style" type="text/css"></style>').text(css).appendTo('head');
|
||||
},
|
||||
formatter: {
|
||||
/**
|
||||
* 回复语:列表最多 50 字 + ...,悬停保留换行显示全文
|
||||
*/
|
||||
autoReplyText: function (value, row, index) {
|
||||
var full = (row.auto_reply != null && row.auto_reply !== '') ? String(row.auto_reply) : '';
|
||||
if (full === '') {
|
||||
return '<span class="text-muted">-</span>';
|
||||
}
|
||||
var preview = value != null && value !== '' ? String(value) : full;
|
||||
var safePreview = Fast.api.escape(preview);
|
||||
var encFull = encodeURIComponent(full);
|
||||
return '<span class="split-auto-reply-preview" data-full="' + encFull + '" style="display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:middle;cursor:default;">'
|
||||
+ safePreview + '</span>';
|
||||
},
|
||||
/**
|
||||
* 分流链接列:链接样式 + COPY 图标,点击链接打开弹窗
|
||||
*/
|
||||
linkCode: function (value) {
|
||||
value = value == null ? '' : value.toString();
|
||||
if (value === '') {
|
||||
return '-';
|
||||
}
|
||||
var safe = Fast.api.escape(value);
|
||||
return '<span class="split-link-code-cell">'
|
||||
+ '<a href="javascript:;" class="btn-copy-split-link split-link-code-link" data-link-code="' + safe + '" style="font-weight:600;color:#337ab7;text-decoration:underline;">' + safe + '</a>'
|
||||
+ ' <a href="javascript:;" class="btn-copy-link-code-inline text-primary" data-link-code="' + safe + '" title="' + __('Copy') + '"><i class="fa fa-copy"></i></a>'
|
||||
+ '</span>';
|
||||
}
|
||||
},
|
||||
openCopyModal: function (linkCode) {
|
||||
Controller.api.loadCopyModalData(function (data) {
|
||||
Controller.api.renderCopyModal(linkCode, data);
|
||||
});
|
||||
},
|
||||
/** 回复语列悬停提示(保留换行) */
|
||||
autoReplyTipIndex: null,
|
||||
bindAutoReplyPreviewTips: function (table) {
|
||||
table.off('mouseenter.splitAutoReply mouseleave.splitAutoReply')
|
||||
.on('mouseenter.splitAutoReply', '.split-auto-reply-preview', function () {
|
||||
var enc = $(this).attr('data-full');
|
||||
if (!enc) {
|
||||
return;
|
||||
}
|
||||
var full = '';
|
||||
try {
|
||||
full = decodeURIComponent(enc);
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
var html = '<div style="white-space:pre-wrap;word-break:break-word;padding:10px 12px;line-height:1.6;max-width:420px;font-size:13px;text-align:left;">'
|
||||
+ Controller.api.escapeHtmlWithNewlines(full) + '</div>';
|
||||
Controller.api.autoReplyTipIndex = Layer.tips(html, this, {
|
||||
tips: [1, ''],
|
||||
time: 0,
|
||||
maxWidth: 450
|
||||
});
|
||||
})
|
||||
.on('mouseleave.splitAutoReply', '.split-auto-reply-preview', function () {
|
||||
if (Controller.api.autoReplyTipIndex != null) {
|
||||
Layer.close(Controller.api.autoReplyTipIndex);
|
||||
Controller.api.autoReplyTipIndex = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
escapeHtmlWithNewlines: function (text) {
|
||||
return $('<div/>').text(text).html().replace(/\n/g, '<br>');
|
||||
},
|
||||
/**
|
||||
* 打开自动回复弹窗
|
||||
*
|
||||
* @param {object} row 列表行数据
|
||||
*/
|
||||
openAutoReplyModal: function (row) {
|
||||
Fast.api.ajax({
|
||||
url: 'split.link/autoreply/ids/' + row.id,
|
||||
type: 'get'
|
||||
}, function (data, ret) {
|
||||
var info = ret.data || data || {};
|
||||
Controller.api.renderAutoReplyModal(row.id, info);
|
||||
return false;
|
||||
});
|
||||
},
|
||||
renderAutoReplyModal: function (linkId, info) {
|
||||
var linkCode = Fast.api.escape(info.link_code || '');
|
||||
var autoReply = info.auto_reply || '';
|
||||
var html = [
|
||||
'<div class="split-auto-reply-modal" style="padding:16px 20px;">',
|
||||
' <div class="form-group">',
|
||||
' <label class="control-label">' + __('Link_code') + '</label>',
|
||||
' <p class="form-control-static" style="font-size:16px;font-weight:600;color:#337ab7;margin:0;">' + linkCode + '</p>',
|
||||
' </div>',
|
||||
' <div class="form-group" style="margin-bottom:0;">',
|
||||
' <label class="control-label" for="split-auto-reply-text">' + __('Reply statements') + '</label>',
|
||||
' <p class="help-block" style="margin-top:0;">' + __('Reply statements tip') + '</p>',
|
||||
' <textarea id="split-auto-reply-text" class="form-control" rows="10" placeholder="">' + Fast.api.escape(autoReply) + '</textarea>',
|
||||
' </div>',
|
||||
'</div>'
|
||||
].join('');
|
||||
|
||||
Layer.open({
|
||||
type: 1,
|
||||
title: __('Auto reply'),
|
||||
area: ['520px', 'auto'],
|
||||
shadeClose: false,
|
||||
content: html,
|
||||
btn: [__('OK'), __('Close')],
|
||||
yes: function (index, layero) {
|
||||
var text = layero.find('#split-auto-reply-text').val();
|
||||
Fast.api.ajax({
|
||||
url: 'split.link/autoreply/ids/' + linkId,
|
||||
type: 'post',
|
||||
data: {auto_reply: text}
|
||||
}, function () {
|
||||
Layer.close(index);
|
||||
Toastr.success(__('Auto reply saved'));
|
||||
});
|
||||
return false;
|
||||
}
|
||||
});
|
||||
},
|
||||
pixelRowUid: function (prefix) {
|
||||
return prefix + '_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 8);
|
||||
},
|
||||
openPixelModal: function (row) {
|
||||
Fast.api.ajax({
|
||||
url: 'split.link/pixel/ids/' + row.id,
|
||||
type: 'get'
|
||||
}, function (data, ret) {
|
||||
var info = ret.data || data || {};
|
||||
Controller.api.renderPixelModal(row.id, info);
|
||||
return false;
|
||||
});
|
||||
},
|
||||
renderPixelModal: function (linkId, info) {
|
||||
Controller.api.injectPixelModalStyles();
|
||||
var eventOptions = $.isArray(info.event_options) ? info.event_options : [
|
||||
'PageView', 'Lead', 'Contact', 'AddToCart', 'Purchase', 'Subscribe'
|
||||
];
|
||||
var fbRows = $.isArray(info.facebook) ? info.facebook : [];
|
||||
var tkRows = $.isArray(info.tiktok) ? info.tiktok : [];
|
||||
|
||||
var html = [
|
||||
'<div class="split-pixel-modal">',
|
||||
' <div class="split-pixel-tip">' + __('Pixel config tip') + '</div>',
|
||||
' <ul class="nav nav-tabs split-pixel-tabs" role="tablist">',
|
||||
' <li role="presentation" class="active"><a href="#split-pixel-fb" role="tab" data-toggle="tab"><i class="fa fa-facebook-square"></i> ' + __('Facebook Pixel') + '</a></li>',
|
||||
' <li role="presentation"><a href="#split-pixel-tk" role="tab" data-toggle="tab"><i class="fa fa-music"></i> ' + __('TikTok Pixel') + '</a></li>',
|
||||
' </ul>',
|
||||
' <div class="tab-content split-pixel-tab-content">',
|
||||
' <div role="tabpanel" class="tab-pane active" id="split-pixel-fb">',
|
||||
Controller.api.buildPixelListShell('facebook', __('Add FB Pixel')),
|
||||
' </div>',
|
||||
' <div role="tabpanel" class="tab-pane" id="split-pixel-tk">',
|
||||
Controller.api.buildPixelListShell('tiktok', __('Add TK Pixel')),
|
||||
' </div>',
|
||||
' </div>',
|
||||
'</div>'
|
||||
].join('');
|
||||
|
||||
Layer.open({
|
||||
type: 1,
|
||||
title: __('Pixel config') + ' - ' + Fast.api.escape(info.link_code || ''),
|
||||
area: ['1140px', '86vh'],
|
||||
maxmin: true,
|
||||
shadeClose: false,
|
||||
content: html,
|
||||
btn: [__('OK'), __('Close')],
|
||||
success: function (layero) {
|
||||
layero.addClass('split-pixel-layer');
|
||||
var $box = layero.find('.split-pixel-modal');
|
||||
var fillRows = function (platform, rows) {
|
||||
var $list = $box.find('.split-pixel-list[data-platform="' + platform + '"]');
|
||||
var $body = $list.find('tbody.split-pixel-list-body');
|
||||
$body.find('tr.split-pixel-row').remove();
|
||||
$.each(rows, function (_, rowData) {
|
||||
$body.append(Controller.api.buildPixelRowHtml(platform, rowData, eventOptions));
|
||||
});
|
||||
Controller.api.syncPixelListEmpty($list);
|
||||
};
|
||||
fillRows('facebook', fbRows);
|
||||
fillRows('tiktok', tkRows);
|
||||
|
||||
$box.on('click', '.btn-add-pixel-row', function () {
|
||||
var platform = $(this).data('platform');
|
||||
var $list = $box.find('.split-pixel-list[data-platform="' + platform + '"]');
|
||||
var $body = $list.find('tbody.split-pixel-list-body');
|
||||
var sortVal = $body.find('tr.split-pixel-row').length;
|
||||
$body.append(Controller.api.buildPixelRowHtml(platform, {
|
||||
id: Controller.api.pixelRowUid(platform === 'facebook' ? 'fb' : 'tk'),
|
||||
enabled: 1,
|
||||
server_postback: 0,
|
||||
pixel_id: '',
|
||||
access_token: '',
|
||||
test_code: '',
|
||||
event: 'PageView',
|
||||
sort: sortVal
|
||||
}, eventOptions));
|
||||
Controller.api.syncPixelListEmpty($list);
|
||||
var $wrap = $list.find('.split-pixel-table-wrap');
|
||||
if ($wrap.length) {
|
||||
$wrap.scrollTop($wrap[0].scrollHeight);
|
||||
}
|
||||
});
|
||||
|
||||
$box.on('click', '.btn-pixel-sort-up', function () {
|
||||
var $row = $(this).closest('tr.split-pixel-row');
|
||||
var $input = $row.find('.pixel-sort-input');
|
||||
$input.val(Math.max(0, (parseInt($input.val(), 10) || 0) + 1));
|
||||
});
|
||||
$box.on('click', '.btn-pixel-sort-down', function () {
|
||||
var $row = $(this).closest('tr.split-pixel-row');
|
||||
var $input = $row.find('.pixel-sort-input');
|
||||
$input.val(Math.max(0, (parseInt($input.val(), 10) || 0) - 1));
|
||||
});
|
||||
$box.on('click', '.btn-pixel-row-remove', function () {
|
||||
var $row = $(this).closest('tr.split-pixel-row');
|
||||
var $list = $row.closest('.split-pixel-list');
|
||||
$row.remove();
|
||||
Controller.api.syncPixelListEmpty($list);
|
||||
});
|
||||
$box.on('change', '.pixel-event', function () {
|
||||
var val = $(this).val() || '';
|
||||
$(this).attr('title', val);
|
||||
});
|
||||
},
|
||||
yes: function (index, layero) {
|
||||
var payload = Controller.api.collectPixelPayload(layero.find('.split-pixel-modal'));
|
||||
var invalid = false;
|
||||
$.each(payload.facebook.concat(payload.tiktok), function (_, item) {
|
||||
if (!$.trim(item.pixel_id)) {
|
||||
invalid = true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (invalid) {
|
||||
Toastr.error(__('Pixel ID required'));
|
||||
return false;
|
||||
}
|
||||
Fast.api.ajax({
|
||||
url: 'split.link/pixel/ids/' + linkId,
|
||||
type: 'post',
|
||||
data: {pixel_config: payload}
|
||||
}, function () {
|
||||
Layer.close(index);
|
||||
Toastr.success(__('Pixel config saved'));
|
||||
});
|
||||
return false;
|
||||
}
|
||||
});
|
||||
},
|
||||
buildPixelListShell: function (platform, addBtnText) {
|
||||
return [
|
||||
'<div class="split-pixel-list" data-platform="' + platform + '">',
|
||||
' <div class="split-pixel-toolbar">',
|
||||
' <button type="button" class="btn btn-success btn-sm btn-add-pixel-row" data-platform="' + platform + '">',
|
||||
' <i class="fa fa-plus"></i> ' + addBtnText,
|
||||
' </button>',
|
||||
' </div>',
|
||||
' <div class="table-responsive split-pixel-table-wrap">',
|
||||
' <table class="table table-bordered table-hover split-pixel-table">',
|
||||
' <thead>',
|
||||
' <tr>',
|
||||
' <th width="52">' + __('Pixel row index') + '</th>',
|
||||
' <th width="58" title="' + __('Pixel enabled') + '">' + __('Pixel enabled') + '</th>',
|
||||
' <th width="78" title="' + __('Server postback') + '">' + __('Server postback') + '</th>',
|
||||
' <th width="140">' + __('Pixel ID') + ' <span class="text-danger">*</span></th>',
|
||||
' <th class="pixel-event-col">' + __('Pixel event') + '</th>',
|
||||
' <th class="col-token">' + __('Access Token') + '</th>',
|
||||
' <th width="108">' + __('Test code') + '</th>',
|
||||
' <th width="124">' + __('Pixel sort') + '</th>',
|
||||
' <th width="58">' + __('Operate') + '</th>',
|
||||
' </tr>',
|
||||
' </thead>',
|
||||
' <tbody class="split-pixel-list-body">',
|
||||
' <tr class="split-pixel-empty-row"><td colspan="9"><i class="fa fa-inbox" style="margin-right:6px;opacity:.5;"></i>' + __('Pixel list empty') + '</td></tr>',
|
||||
' </tbody>',
|
||||
' </table>',
|
||||
' </div>',
|
||||
'</div>'
|
||||
].join('');
|
||||
},
|
||||
syncPixelListEmpty: function ($list) {
|
||||
var $body = $list.find('tbody.split-pixel-list-body');
|
||||
var $rows = $body.find('tr.split-pixel-row');
|
||||
$body.find('tr.split-pixel-empty-row').toggle($rows.length === 0);
|
||||
$rows.each(function (idx) {
|
||||
$(this).find('.pixel-row-index').text(String(idx + 1));
|
||||
});
|
||||
},
|
||||
buildPixelRowHtml: function (platform, row, eventOptions) {
|
||||
row = row || {};
|
||||
var id = row.id || Controller.api.pixelRowUid(platform === 'facebook' ? 'fb' : 'tk');
|
||||
var enabled = parseInt(row.enabled, 10) === 1;
|
||||
var serverPostback = parseInt(row.server_postback, 10) === 1;
|
||||
var eventOpts = '';
|
||||
$.each(eventOptions, function (_, ev) {
|
||||
eventOpts += '<option value="' + ev + '"' + (row.event === ev ? ' selected' : '') + '>' + ev + '</option>';
|
||||
});
|
||||
var tokenPlaceholder = __('Optional');
|
||||
var tokenHint = row.has_access_token ? ' placeholder="' + tokenPlaceholder + ' (' + __('Optional') + ')"' : ' placeholder="' + tokenPlaceholder + '"';
|
||||
var selectedEvent = row.event || 'PageView';
|
||||
|
||||
return [
|
||||
'<tr class="split-pixel-row" data-row-id="' + Fast.api.escape(id) + '">',
|
||||
' <td class="text-center"><span class="pixel-row-index">-</span></td>',
|
||||
' <td class="pixel-switch-wrap"><input type="checkbox" class="pixel-enabled" title="' + __('Pixel enabled') + '" ' + (enabled ? 'checked' : '') + '></td>',
|
||||
' <td class="pixel-switch-wrap"><input type="checkbox" class="pixel-server-postback" title="' + __('Server postback') + '" ' + (serverPostback ? 'checked' : '') + '></td>',
|
||||
' <td><input type="text" class="form-control input-sm pixel-id" value="' + Fast.api.escape(row.pixel_id || '') + '" placeholder="' + __('Pixel ID') + '"></td>',
|
||||
' <td class="pixel-event-cell"><select class="form-control input-sm pixel-event" title="' + Fast.api.escape(selectedEvent) + '">' + eventOpts + '</select></td>',
|
||||
' <td class="col-token"><input type="text" class="form-control input-sm pixel-access-token" value=""' + tokenHint + '></td>',
|
||||
' <td><input type="text" class="form-control input-sm pixel-test-code" value="' + Fast.api.escape(row.test_code || '') + '" placeholder="' + tokenPlaceholder + '"></td>',
|
||||
' <td>',
|
||||
' <div class="input-group input-group-sm split-pixel-sort-group">',
|
||||
' <span class="input-group-btn"><button type="button" class="btn btn-default btn-pixel-sort-down" title="-1"><i class="fa fa-minus"></i></button></span>',
|
||||
' <input type="number" min="0" class="form-control pixel-sort-input" value="' + (parseInt(row.sort, 10) || 0) + '" title="' + __('Pixel sort') + '">',
|
||||
' <span class="input-group-btn"><button type="button" class="btn btn-default btn-pixel-sort-up" title="+1"><i class="fa fa-plus"></i></button></span>',
|
||||
' </div>',
|
||||
' </td>',
|
||||
' <td class="text-center">',
|
||||
' <button type="button" class="btn btn-danger btn-xs btn-pixel-row-remove" title="' + __('Remove row') + '"><i class="fa fa-trash"></i></button>',
|
||||
' </td>',
|
||||
'</tr>'
|
||||
].join('');
|
||||
},
|
||||
collectPixelPayload: function ($box) {
|
||||
var readList = function (platform) {
|
||||
var rows = [];
|
||||
$box.find('.split-pixel-list[data-platform="' + platform + '"] tbody .split-pixel-row').each(function () {
|
||||
var $row = $(this);
|
||||
rows.push({
|
||||
id: $row.attr('data-row-id') || Controller.api.pixelRowUid(platform === 'facebook' ? 'fb' : 'tk'),
|
||||
enabled: $row.find('.pixel-enabled').prop('checked') ? 1 : 0,
|
||||
server_postback: $row.find('.pixel-server-postback').prop('checked') ? 1 : 0,
|
||||
pixel_id: $.trim($row.find('.pixel-id').val()),
|
||||
access_token: $.trim($row.find('.pixel-access-token').val()),
|
||||
test_code: $.trim($row.find('.pixel-test-code').val()),
|
||||
event: $row.find('.pixel-event').val() || 'PageView',
|
||||
sort: Math.max(0, parseInt($row.find('.pixel-sort-input').val(), 10) || 0)
|
||||
});
|
||||
});
|
||||
return rows;
|
||||
};
|
||||
return {
|
||||
facebook: readList('facebook'),
|
||||
tiktok: readList('tiktok')
|
||||
};
|
||||
},
|
||||
loadCopyModalData: function (callback) {
|
||||
Fast.api.ajax({
|
||||
url: 'split.link/copyinfo',
|
||||
type: 'get'
|
||||
}, function (data, ret) {
|
||||
callback(ret.data || data || {});
|
||||
return false;
|
||||
});
|
||||
},
|
||||
renderCopyModal: function (linkCode, data) {
|
||||
Controller.api.injectModalStyles();
|
||||
|
||||
var platformDomains = $.isArray(data.platform_domains) ? data.platform_domains : [];
|
||||
if (!platformDomains.length && data.platform_domain) {
|
||||
var rawSingle = $.trim(data.platform_domain);
|
||||
platformDomains = rawSingle.split(/[\r\n,]+/).map(function (d) {
|
||||
return $.trim(d);
|
||||
}).filter(function (d) {
|
||||
return d.length > 0;
|
||||
});
|
||||
}
|
||||
var myDomains = $.isArray(data.my_domains) ? data.my_domains : [];
|
||||
var domainIndexUrl = Controller.api.normalizeAdminRouteUrl(data.domain_index_url, 'domain');
|
||||
var domainAddUrl = Controller.api.normalizeAdminRouteUrl(data.domain_add_url, 'domain/add');
|
||||
var configIndexUrl = data.config_index_url || 'general/config/index';
|
||||
var defaultType = platformDomains.length ? 'platform' : 'my';
|
||||
var defaultDomain = platformDomains.length ? platformDomains[0] : (myDomains.length ? myDomains[0].domain : '');
|
||||
var activeTab = defaultType === 'platform' ? 'platform' : 'my';
|
||||
|
||||
var platformHtml = '';
|
||||
if (platformDomains.length) {
|
||||
var platformItems = [];
|
||||
$.each(platformDomains, function (i, domain) {
|
||||
platformItems.push(Controller.api.buildDomainRadio({
|
||||
domain: domain,
|
||||
type: 'platform',
|
||||
checked: defaultType === 'platform' && domain === defaultDomain,
|
||||
showStatus: false
|
||||
}));
|
||||
});
|
||||
platformHtml = '<div class="split-domain-list">' + platformItems.join('') + '</div>';
|
||||
} else {
|
||||
platformHtml = '<p class="split-domain-empty">'
|
||||
+ __('Split platform domain empty')
|
||||
+ ' <a href="javascript:;" class="btn-goto-config">' + __('Go system config') + '</a>'
|
||||
+ '</p>';
|
||||
}
|
||||
|
||||
var myDomainsHtml = '';
|
||||
if (myDomains.length) {
|
||||
var items = [];
|
||||
$.each(myDomains, function (i, item) {
|
||||
items.push(Controller.api.buildDomainRadio({
|
||||
domain: item.domain,
|
||||
type: 'my',
|
||||
checked: defaultType === 'my' && item.domain === defaultDomain,
|
||||
nsText: item.ns_status_text || '',
|
||||
dnsText: item.dns_status_text || '',
|
||||
nsStatus: item.ns_status || '',
|
||||
dnsStatus: item.dns_status || '',
|
||||
showStatus: true
|
||||
}));
|
||||
});
|
||||
myDomainsHtml = '<div class="split-domain-list">' + items.join('') + '</div>';
|
||||
} else {
|
||||
myDomainsHtml = '<p class="split-domain-empty">' + __('Split my domain empty') + '</p>';
|
||||
}
|
||||
|
||||
var initialUrl = Controller.api.buildSplitUrl(defaultDomain, linkCode);
|
||||
var safeLinkCode = Fast.api.escape(linkCode);
|
||||
var html = [
|
||||
'<div class="split-link-copy-modal">',
|
||||
' <div class="form-group">',
|
||||
' <label class="control-label">' + __('Link_code') + '</label>',
|
||||
' <div class="split-link-code-row">',
|
||||
' <a href="javascript:;" class="split-link-code-value" data-link-code="' + safeLinkCode + '">' + safeLinkCode + '</a>',
|
||||
' <button type="button" class="btn btn-default btn-sm btn-copy-link-code" data-link-code="' + safeLinkCode + '" title="' + __('Copy') + '"><i class="fa fa-copy"></i></button>',
|
||||
' <button type="button" class="btn btn-default btn-sm btn-manage-domain">' + __('Manage my domains') + '</button>',
|
||||
' </div>',
|
||||
' </div>',
|
||||
' <div class="form-group">',
|
||||
' <label class="control-label">' + __('Select main domain') + '</label>',
|
||||
' <ul class="nav nav-tabs split-domain-tabs" role="tablist">',
|
||||
' <li role="presentation"' + (activeTab === 'platform' ? ' class="active"' : '') + '>',
|
||||
' <a href="#split-tab-platform" role="tab" data-toggle="tab">' + __('Platform assigned domain') + '</a>',
|
||||
' </li>',
|
||||
' <li role="presentation"' + (activeTab === 'my' ? ' class="active"' : '') + '>',
|
||||
' <a href="#split-tab-my" role="tab" data-toggle="tab">' + __('My domains') + '</a>',
|
||||
' </li>',
|
||||
' </ul>',
|
||||
' <div class="tab-content split-domain-tab-box">',
|
||||
' <div role="tabpanel" class="tab-pane' + (activeTab === 'platform' ? ' active' : '') + '" id="split-tab-platform">' + platformHtml + '</div>',
|
||||
' <div role="tabpanel" class="tab-pane' + (activeTab === 'my' ? ' active' : '') + '" id="split-tab-my">' + myDomainsHtml + '</div>',
|
||||
' </div>',
|
||||
' </div>',
|
||||
' <div class="split-tip-box">',
|
||||
__('Split domain prefix tip'),
|
||||
' <button type="button" class="btn btn-xs btn-primary btn-go-add-domain">' + __('Go add domain') + '</button>',
|
||||
' </div>',
|
||||
' <div class="form-group" style="margin-bottom:0;">',
|
||||
' <label class="control-label">' + __('Generated result') + '</label>',
|
||||
' <div class="input-group">',
|
||||
' <input type="text" class="form-control split-generated-url" readonly value="' + Fast.api.escape(initialUrl) + '">',
|
||||
' <span class="input-group-btn">',
|
||||
' <button type="button" class="btn btn-default btn-copy-generated-url"><i class="fa fa-copy"></i> ' + __('Copy') + '</button>',
|
||||
' </span>',
|
||||
' </div>',
|
||||
' </div>',
|
||||
' <div class="text-center split-modal-footer">',
|
||||
' <button type="button" class="btn btn-success btn-open-test"' + (initialUrl ? '' : ' disabled') + '>' + __('Open test') + '</button>',
|
||||
' <button type="button" class="btn btn-default btn-close-copy-modal">' + __('Close') + '</button>',
|
||||
' </div>',
|
||||
'</div>'
|
||||
].join('');
|
||||
|
||||
Layer.open({
|
||||
type: 1,
|
||||
title: __('Copy split link'),
|
||||
area: ['780px', 'auto'],
|
||||
maxmin: false,
|
||||
resize: false,
|
||||
shadeClose: true,
|
||||
content: html,
|
||||
success: function (layero, index) {
|
||||
var $box = layero.find('.split-link-copy-modal');
|
||||
|
||||
var refreshGeneratedUrl = function () {
|
||||
var $checked = $box.find('input[name="split_main_domain"]:checked');
|
||||
var domain = $.trim($checked.val() || '');
|
||||
var url = Controller.api.buildSplitUrl(domain, linkCode);
|
||||
$box.find('.split-generated-url').val(url);
|
||||
$box.find('.btn-open-test').prop('disabled', url === '');
|
||||
$box.find('.split-domain-item').removeClass('is-checked');
|
||||
$checked.closest('.split-domain-item').addClass('is-checked');
|
||||
};
|
||||
|
||||
var ensureTabSelection = function ($pane) {
|
||||
var $radios = $pane.find('input[name="split_main_domain"]');
|
||||
if ($radios.length && !$radios.filter(':checked').length) {
|
||||
$radios.first().prop('checked', true);
|
||||
}
|
||||
};
|
||||
|
||||
$box.on('change', 'input[name="split_main_domain"]', refreshGeneratedUrl);
|
||||
|
||||
$box.on('shown.bs.tab', 'a[data-toggle="tab"]', function (e) {
|
||||
var target = $(e.target).attr('href');
|
||||
if (target) {
|
||||
ensureTabSelection($box.find(target));
|
||||
refreshGeneratedUrl();
|
||||
}
|
||||
});
|
||||
|
||||
$box.on('click', '.btn-copy-link-code', function (e) {
|
||||
e.preventDefault();
|
||||
Controller.api.copyText(linkCode);
|
||||
});
|
||||
|
||||
$box.on('click', '.btn-manage-domain', function (e) {
|
||||
e.preventDefault();
|
||||
Layer.close(index);
|
||||
Backend.api.addtabs(Fast.api.fixurl(domainIndexUrl), __('Domain management'), 'fa fa-globe');
|
||||
});
|
||||
|
||||
$box.on('click', '.btn-go-add-domain', function (e) {
|
||||
e.preventDefault();
|
||||
Layer.close(index);
|
||||
Backend.api.addtabs(Fast.api.fixurl(domainAddUrl), __('Domain management'), 'fa fa-plus');
|
||||
});
|
||||
|
||||
$box.on('click', '.btn-goto-config', function (e) {
|
||||
e.preventDefault();
|
||||
Layer.close(index);
|
||||
Backend.api.addtabs(Fast.api.fixurl(configIndexUrl), __('System config'), 'fa fa-cogs');
|
||||
});
|
||||
|
||||
$box.on('click', '.btn-copy-generated-url', function (e) {
|
||||
e.preventDefault();
|
||||
var url = $.trim($box.find('.split-generated-url').val());
|
||||
if (!url) {
|
||||
Toastr.error(__('Split url empty'));
|
||||
return;
|
||||
}
|
||||
Controller.api.copyText(url);
|
||||
});
|
||||
|
||||
$box.on('click', '.btn-open-test', function (e) {
|
||||
e.preventDefault();
|
||||
var url = $.trim($box.find('.split-generated-url').val());
|
||||
if (url) {
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
});
|
||||
|
||||
$box.on('click', '.btn-close-copy-modal', function (e) {
|
||||
e.preventDefault();
|
||||
Layer.close(index);
|
||||
});
|
||||
|
||||
ensureTabSelection($box.find('.tab-pane.active'));
|
||||
refreshGeneratedUrl();
|
||||
}
|
||||
});
|
||||
},
|
||||
buildDomainRadio: function (options) {
|
||||
var domain = Fast.api.escape(options.domain || '');
|
||||
var type = options.type || 'my';
|
||||
var checked = options.checked ? ' checked' : '';
|
||||
var checkedClass = options.checked ? ' is-checked' : '';
|
||||
var statusHtml = '';
|
||||
if (options.showStatus) {
|
||||
statusHtml = '<span class="split-domain-status">'
|
||||
+ '<span class="label ' + Controller.api.statusLabelClass(options.nsStatus) + '">' + __('NS') + ':' + Fast.api.escape(options.nsText || '-') + '</span>'
|
||||
+ '<span class="label ' + Controller.api.statusLabelClass(options.dnsStatus, true) + '">' + __('DNS') + ':' + Fast.api.escape(options.dnsText || '-') + '</span>'
|
||||
+ '</span>';
|
||||
}
|
||||
return '<div class="split-domain-item' + checkedClass + '">'
|
||||
+ '<label class="split-domain-item-label">'
|
||||
+ '<input type="radio" name="split_main_domain" value="' + domain + '" data-type="' + type + '"' + checked + '>'
|
||||
+ '<span class="split-domain-item-body">'
|
||||
+ '<span class="split-domain-name">' + domain + '</span>'
|
||||
+ statusHtml
|
||||
+ '</span>'
|
||||
+ '</label>'
|
||||
+ '</div>';
|
||||
},
|
||||
statusLabelClass: function (status, isDns) {
|
||||
if (isDns) {
|
||||
if (status === 'created') {
|
||||
return 'label-success';
|
||||
}
|
||||
if (status === 'failed') {
|
||||
return 'label-danger';
|
||||
}
|
||||
return 'label-warning';
|
||||
}
|
||||
if (status === 'verified') {
|
||||
return 'label-success';
|
||||
}
|
||||
if (status === 'failed') {
|
||||
return 'label-danger';
|
||||
}
|
||||
return 'label-warning';
|
||||
},
|
||||
buildSplitUrl: function (domain, linkCode) {
|
||||
domain = $.trim(domain || '').replace(/^https?:\/\//i, '').replace(/\/+$/, '');
|
||||
linkCode = $.trim(linkCode || '');
|
||||
if (!domain || !linkCode) {
|
||||
return '';
|
||||
}
|
||||
return 'https://' + domain + '/s/' + linkCode;
|
||||
},
|
||||
copyText: function (text) {
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
navigator.clipboard.writeText(text).then(function () {
|
||||
Toastr.success(__('Copy success'));
|
||||
}).catch(function () {
|
||||
Controller.api.copyTextFallback(text);
|
||||
});
|
||||
return;
|
||||
}
|
||||
Controller.api.copyTextFallback(text);
|
||||
},
|
||||
copyTextFallback: function (text) {
|
||||
var $temp = $('<textarea>').css({position: 'fixed', left: '-9999px', top: '0'}).val(text);
|
||||
$('body').append($temp);
|
||||
$temp[0].select();
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
Toastr.success(__('Copy success'));
|
||||
} catch (e) {
|
||||
Toastr.error(__('Copy failed'));
|
||||
}
|
||||
$temp.remove();
|
||||
},
|
||||
bindLinkCodeInput: function () {
|
||||
var $input = $('#c-link_code');
|
||||
if (!$input.length || $input.prop('readonly')) {
|
||||
return;
|
||||
}
|
||||
$input.on('input', function () {
|
||||
var val = $(this).val().replace(/[^a-zA-Z]/g, '').toLowerCase().slice(0, 9);
|
||||
if ($(this).val() !== val) {
|
||||
$(this).val(val);
|
||||
}
|
||||
});
|
||||
},
|
||||
bindevent: function () {
|
||||
Form.api.bindevent($("form[role=form]"));
|
||||
}
|
||||
}
|
||||
};
|
||||
return Controller;
|
||||
});
|
||||
Executable
+221
@@ -0,0 +1,221 @@
|
||||
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");
|
||||
|
||||
table.bootstrapTable({
|
||||
url: $.fn.bootstrapTable.defaults.extend.index_url,
|
||||
pk: 'id',
|
||||
sortName: 'id',
|
||||
sortOrder: 'desc',
|
||||
fixedColumns: true,
|
||||
fixedRightNumber: 1,
|
||||
columns: [
|
||||
[
|
||||
{checkbox: true},
|
||||
{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'},
|
||||
{
|
||||
field: 'number_type',
|
||||
title: __('Number_type'),
|
||||
searchList: Config.numberTypeList,
|
||||
formatter: Table.api.formatter.normal
|
||||
},
|
||||
{field: 'visit_count', title: __('Visit_count'), operate: false, sortable: true},
|
||||
{field: 'inbound_count', title: __('Inbound_count'), operate: false, sortable: true},
|
||||
{
|
||||
field: 'platform_status',
|
||||
title: __('Platform_status'),
|
||||
searchList: Config.platformStatusList,
|
||||
formatter: Controller.api.formatter.platformStatus
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
title: __('Status'),
|
||||
searchList: Config.statusList,
|
||||
formatter: Table.api.formatter.toggle,
|
||||
yes: 'normal',
|
||||
no: 'hidden'
|
||||
},
|
||||
{
|
||||
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);
|
||||
});
|
||||
|
||||
Table.api.bindevent(table);
|
||||
},
|
||||
add: function () {
|
||||
Controller.api.bindevent();
|
||||
},
|
||||
edit: function () {
|
||||
Controller.api.bindevent();
|
||||
},
|
||||
api: {
|
||||
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>';
|
||||
}
|
||||
},
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
return Controller;
|
||||
});
|
||||
Executable
+393
@@ -0,0 +1,393 @@
|
||||
define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
|
||||
|
||||
var Controller = {
|
||||
index: function () {
|
||||
Table.api.init({
|
||||
extend: {
|
||||
index_url: 'split.ticket/index' + location.search,
|
||||
add_url: 'split.ticket/add',
|
||||
edit_url: 'split.ticket/edit',
|
||||
del_url: 'split.ticket/del',
|
||||
multi_url: 'split.ticket/multi',
|
||||
table: 'split_ticket',
|
||||
}
|
||||
});
|
||||
|
||||
var table = $("#table");
|
||||
|
||||
table.bootstrapTable({
|
||||
url: $.fn.bootstrapTable.defaults.extend.index_url,
|
||||
pk: 'id',
|
||||
sortName: 'id',
|
||||
sortOrder: 'desc',
|
||||
fixedColumns: true,
|
||||
fixedRightNumber: 1,
|
||||
columns: [
|
||||
[
|
||||
{checkbox: true},
|
||||
{
|
||||
field: 'ticket_type',
|
||||
title: __('Ticket_type'),
|
||||
searchList: Config.ticketTypeList,
|
||||
operate: false,
|
||||
formatter: Controller.api.formatter.ticketTypePlain
|
||||
},
|
||||
{field: 'ticket_name', title: __('Ticket_name'), operate: 'LIKE'},
|
||||
{
|
||||
field: 'link_code_text',
|
||||
title: __('Split_link_id'),
|
||||
operate: false,
|
||||
formatter: Controller.api.formatter.splitLinkCode
|
||||
},
|
||||
{
|
||||
field: 'start_time_text',
|
||||
title: __('Start_time'),
|
||||
operate: 'RANGE',
|
||||
addclass: 'datetimerange',
|
||||
autocomplete: false,
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
field: 'end_time_text',
|
||||
title: __('End_time'),
|
||||
operate: 'RANGE',
|
||||
addclass: 'datetimerange',
|
||||
autocomplete: false,
|
||||
sortable: true
|
||||
},
|
||||
{field: 'order_limit', title: __('Order_limit'), operate: false},
|
||||
{field: 'assign_ratio', title: __('Assign_ratio'), operate: false},
|
||||
{field: 'ticket_total', title: __('Ticket_total'), operate: false, sortable: true},
|
||||
{field: 'complete_count', title: __('Complete_count'), operate: false, sortable: true},
|
||||
{
|
||||
field: 'ticket_progress_text',
|
||||
title: __('Ticket_progress'),
|
||||
operate: false,
|
||||
formatter: Table.api.formatter.content
|
||||
},
|
||||
{
|
||||
field: 'inbound_ratio_text',
|
||||
title: __('Inbound_ratio'),
|
||||
operate: false,
|
||||
formatter: Table.api.formatter.content
|
||||
},
|
||||
{
|
||||
field: 'speed_per_hour',
|
||||
title: __('Speed_per_hour'),
|
||||
operate: false,
|
||||
formatter: Controller.api.formatter.speedPerHour
|
||||
},
|
||||
{
|
||||
field: 'number_count',
|
||||
title: __('Number_count'),
|
||||
operate: false,
|
||||
formatter: Controller.api.formatter.numberCount
|
||||
},
|
||||
{
|
||||
field: 'sync_display_text',
|
||||
title: __('Sync_status'),
|
||||
operate: false,
|
||||
formatter: Controller.api.formatter.syncDisplay
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
title: __('Status'),
|
||||
searchList: Config.statusList,
|
||||
formatter: Table.api.formatter.toggle,
|
||||
yes: 'normal',
|
||||
no: 'hidden'
|
||||
},
|
||||
{
|
||||
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.api.bindevent(table);
|
||||
Controller.api.syncingTicketIds = [];
|
||||
window.__splitTicketPendingPostAddSyncIds = window.__splitTicketPendingPostAddSyncIds || [];
|
||||
|
||||
table.on('load-success.bs.table', function () {
|
||||
var pendingIds = window.__splitTicketPendingPostAddSyncIds;
|
||||
if (!pendingIds || !pendingIds.length) {
|
||||
return;
|
||||
}
|
||||
window.__splitTicketPendingPostAddSyncIds = [];
|
||||
Controller.api.startBackgroundSync(table, pendingIds, false);
|
||||
});
|
||||
|
||||
table.on('check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table', function () {
|
||||
var ids = Table.api.selectedids(table);
|
||||
$('.btn-sync').toggleClass('btn-disabled disabled', ids.length === 0);
|
||||
});
|
||||
|
||||
$('.btn-sync').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
var ids = Table.api.selectedids(table);
|
||||
if (!ids.length) {
|
||||
Toastr.error(__('Please select at least one record'));
|
||||
return false;
|
||||
}
|
||||
var syncConfirmMsg = (typeof Config.syncConfirmMsg !== 'undefined' && Config.syncConfirmMsg)
|
||||
? Config.syncConfirmMsg
|
||||
: '确定同步所选工单吗?任务将在后台执行,完成后自动提示结果。';
|
||||
var syncBackgroundMsg = (typeof Config.syncBackgroundStartedMsg !== 'undefined' && Config.syncBackgroundStartedMsg)
|
||||
? Config.syncBackgroundStartedMsg
|
||||
: '同步任务已在后台执行,请稍候…';
|
||||
Layer.confirm(syncConfirmMsg, {icon: 3, title: __('Sync_status_btn')}, function (index) {
|
||||
Layer.close(index);
|
||||
Toastr.info(syncBackgroundMsg);
|
||||
Controller.api.startBackgroundSync(table, ids, true);
|
||||
});
|
||||
return false;
|
||||
});
|
||||
},
|
||||
add: function () {
|
||||
Form.api.bindevent($('form[role=form]'), function (data, ret) {
|
||||
var ticketId = ret.data && ret.data.id ? parseInt(ret.data.id, 10) : 0;
|
||||
var syncTicketMsg = (typeof Config.syncTicketStartedMsg !== 'undefined' && Config.syncTicketStartedMsg)
|
||||
? Config.syncTicketStartedMsg
|
||||
: '正在同步工单';
|
||||
if (ticketId > 0) {
|
||||
parent.__splitTicketPendingPostAddSyncIds = parent.__splitTicketPendingPostAddSyncIds || [];
|
||||
parent.__splitTicketPendingPostAddSyncIds.push(ticketId);
|
||||
}
|
||||
if (parent && parent.Toastr) {
|
||||
parent.Toastr.info(syncTicketMsg);
|
||||
}
|
||||
parent.$('.btn-refresh').trigger('click');
|
||||
if (window.name) {
|
||||
var layerIndex = parent.Layer.getFrameIndex(window.name);
|
||||
parent.Layer.close(layerIndex);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
Controller.api.fixSelectPlaceholder();
|
||||
Controller.api.bindNumberTypeToggle();
|
||||
Controller.api.bindEndTimeCheck();
|
||||
},
|
||||
edit: function () {
|
||||
Controller.api.bindevent();
|
||||
},
|
||||
api: {
|
||||
/** @type {number[]} 正在手动同步的工单 ID */
|
||||
syncingTicketIds: [],
|
||||
|
||||
/**
|
||||
* 后台同步:标记「同步中」并请求 sync 接口
|
||||
*
|
||||
* @param {object} table bootstrapTable 实例
|
||||
* @param {number[]} ids 工单 ID
|
||||
* @param {boolean} disableSyncBtn 是否禁用工具栏同步按钮
|
||||
*/
|
||||
startBackgroundSync: function (table, ids, disableSyncBtn) {
|
||||
ids = (ids || []).map(function (id) {
|
||||
return parseInt(id, 10);
|
||||
}).filter(function (id) {
|
||||
return !isNaN(id) && id > 0;
|
||||
});
|
||||
if (!ids.length) {
|
||||
return;
|
||||
}
|
||||
Controller.api.markTicketsSyncing(table, ids);
|
||||
if (disableSyncBtn) {
|
||||
$('.btn-sync').addClass('btn-disabled disabled');
|
||||
}
|
||||
Fast.api.ajax({
|
||||
url: 'split.ticket/sync',
|
||||
data: {ids: ids.join(',')},
|
||||
loading: false
|
||||
}, function () {
|
||||
Controller.api.finishTicketsSync(table);
|
||||
}, function () {
|
||||
Controller.api.finishTicketsSync(table);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 将选中工单标记为「同步中」并刷新列表展示(不请求后端)
|
||||
*/
|
||||
markTicketsSyncing: function (table, ids) {
|
||||
Controller.api.syncingTicketIds = (ids || []).map(function (id) {
|
||||
return parseInt(id, 10);
|
||||
}).filter(function (id) {
|
||||
return !isNaN(id) && id > 0;
|
||||
});
|
||||
var data = table.bootstrapTable('getData');
|
||||
table.bootstrapTable('load', data);
|
||||
},
|
||||
|
||||
/**
|
||||
* 同步结束:清除标记并刷新列表数据
|
||||
*/
|
||||
finishTicketsSync: function (table) {
|
||||
Controller.api.syncingTicketIds = [];
|
||||
table.bootstrapTable('refresh');
|
||||
var ids = Table.api.selectedids(table);
|
||||
$('.btn-sync').toggleClass('btn-disabled disabled', ids.length === 0);
|
||||
},
|
||||
|
||||
formatter: {
|
||||
/**
|
||||
* 工单类型:纯文本展示,无链接/标签样式
|
||||
*/
|
||||
ticketTypePlain: function (value, row) {
|
||||
var text = row.ticket_type_text != null && row.ticket_type_text !== ''
|
||||
? String(row.ticket_type_text)
|
||||
: (value != null ? String(value) : '');
|
||||
if (text === '') {
|
||||
return '<span class="text-muted">-</span>';
|
||||
}
|
||||
return '<span class="split-ticket-type-plain">' + Fast.api.escape(text) + '</span>';
|
||||
},
|
||||
/**
|
||||
* 分流链接:纯文本 + 边框背景标记,不可点击
|
||||
*/
|
||||
splitLinkCode: function (value) {
|
||||
value = value == null ? '' : String(value);
|
||||
if ($.trim(value) === '') {
|
||||
return '<span class="text-muted">-</span>';
|
||||
}
|
||||
var safe = Fast.api.escape(value);
|
||||
return '<span class="split-ticket-link-badge" style="display:inline-block;max-width:100%;padding:2px 8px;font-size:12px;line-height:1.5;color:#555;background:#f5f5f5;border:1px solid #ddd;border-radius:3px;word-break:break-all;">'
|
||||
+ safe + '</span>';
|
||||
},
|
||||
speedPerHour: function (value) {
|
||||
var num = parseFloat(value);
|
||||
if (isNaN(num)) {
|
||||
return '0.00';
|
||||
}
|
||||
return num.toFixed(2);
|
||||
},
|
||||
numberCount: function (value, row) {
|
||||
var total = parseInt(value, 10) || 0;
|
||||
var offline = parseInt(row.number_offline_count, 10) || 0;
|
||||
var banned = parseInt(row.number_banned_count, 10) || 0;
|
||||
var tip = __('Number_count_detail').replace('%s', offline).replace('%s', banned);
|
||||
if (offline > 0 || banned > 0) {
|
||||
return '<span data-toggle="tooltip" title="' + Fast.api.escape(tip) + '">' + total + '</span>';
|
||||
}
|
||||
return String(total);
|
||||
},
|
||||
syncDisplay: function (value, row) {
|
||||
var rowId = parseInt(row.id, 10);
|
||||
if (Controller.api.syncingTicketIds.indexOf(rowId) !== -1) {
|
||||
return Controller.api.formatter.syncingDisplayHtml();
|
||||
}
|
||||
var text = value || '';
|
||||
var color = 'danger';
|
||||
if (row.sync_status === 'success') {
|
||||
color = 'success';
|
||||
} else if (row.sync_status === 'pending') {
|
||||
color = 'muted';
|
||||
}
|
||||
return '<span class="text-' + color + '">' + Fast.api.escape(text) + '</span>';
|
||||
},
|
||||
syncingDisplayHtml: function () {
|
||||
var label = (typeof Config.syncInProgressMsg !== 'undefined' && Config.syncInProgressMsg)
|
||||
? Config.syncInProgressMsg
|
||||
: '同步中';
|
||||
return '<span class="split-ticket-sync-pending">'
|
||||
+ '<i class="fa fa-refresh fa-spin"></i>'
|
||||
+ '<span class="split-ticket-sync-label">' + Fast.api.escape(label) + '</span>'
|
||||
+ '</span>';
|
||||
}
|
||||
},
|
||||
bindevent: function () {
|
||||
Form.api.bindevent($('form[role=form]'));
|
||||
Controller.api.fixSelectPlaceholder();
|
||||
Controller.api.bindNumberTypeToggle();
|
||||
Controller.api.bindEndTimeCheck();
|
||||
},
|
||||
/**
|
||||
* selectpicker 空选项文案改为中文「请选择」
|
||||
*/
|
||||
fixSelectPlaceholder: function () {
|
||||
var text = __('Please select');
|
||||
if (!text || text === 'Please select' || text === 'Please Select') {
|
||||
text = '请选择';
|
||||
}
|
||||
$('#c-ticket_type, #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');
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 号码类型为 custom 时显示自定义输入框
|
||||
*/
|
||||
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');
|
||||
$custom.val('');
|
||||
}
|
||||
if ($custom.data('validator')) {
|
||||
$custom.trigger('validate');
|
||||
}
|
||||
};
|
||||
$type.on('changed.bs.select change', toggle);
|
||||
toggle();
|
||||
},
|
||||
/**
|
||||
* 前端预校验:到期时间须晚于开始时间(后端为准)
|
||||
*/
|
||||
bindEndTimeCheck: function () {
|
||||
var $form = $('form[role=form]');
|
||||
var $start = $('#c-start_time');
|
||||
var $end = $('#c-end_time');
|
||||
if (!$start.length || !$end.length) {
|
||||
return;
|
||||
}
|
||||
var parseTs = function (str) {
|
||||
str = $.trim(str || '');
|
||||
if (!str) {
|
||||
return 0;
|
||||
}
|
||||
var d = new Date(str.replace(/-/g, '/'));
|
||||
return isNaN(d.getTime()) ? 0 : Math.floor(d.getTime() / 1000);
|
||||
};
|
||||
$form.on('submit', function (e) {
|
||||
var s = parseTs($start.val());
|
||||
var en = parseTs($end.val());
|
||||
if (s > 0 && en > 0 && en <= s) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
Layer.msg(__('End time must after start'));
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
return Controller;
|
||||
});
|
||||
Reference in New Issue
Block a user