This commit is contained in:
nanguaboss
2026-08-03 00:07:01 +08:00
commit 71c6e8d9c1
112 changed files with 7815 additions and 0 deletions
+173
View File
@@ -0,0 +1,173 @@
/**
* channel.js - 渠道管理逻辑(渠道列表 + 年度统计 + 效率数据)
*/
$(function () {
renderShell('渠道管理', '业务模块 / 渠道管理');
renderPage();
initPage('channel', function (user) {
if (!checkPagePermission('channel')) return;
loadList(1);
loadStats();
});
var currentPage = 1;
var pageRows = [];
/** 渲染页面内容(统计卡+工具栏+表格+分页) */
function renderPage() {
$('#page-content').html(
'<div class="stat-cards" id="stat-summary"></div>' +
'<div class="card">' +
' <div class="toolbar">' +
' <input type="text" data-filter="keyword" placeholder="渠道名称/联系人关键词">' +
' <input type="text" data-filter="channel_type" placeholder="渠道类型">' +
' <select id="year-select" data-filter="year"><option value="">选择年度</option></select>' +
' <button class="btn btn-primary" id="btn-search">搜索</button>' +
' <button class="btn" id="btn-reset">重置</button>' +
' <span class="spacer"></span>' +
' <button class="btn btn-success" id="btn-add">+ 新增</button>' +
' </div>' +
' <div class="table-wrap"><table class="grid">' +
' <thead><tr><th>ID</th><th>渠道名称</th><th>类型</th><th>联系人</th><th>电话</th><th>邮箱</th><th>效率评分</th><th>备注</th><th>创建时间</th><th>操作</th></tr></thead>' +
' <tbody id="channel-tbody"></tbody>' +
' </table></div>' +
' <div class="pagination" id="pagination"></div>' +
'</div>'
);
}
/* ---------- 列表 ---------- */
function loadList(page) {
currentPage = page;
var params = buildFilterParam();
delete params.year; // 年度仅用于统计
params.page = page;
params.limit = PAGE_SIZE;
httpGet('channel/list.php', params).then(function (data) {
var rows = data.list || [];
pageRows = rows;
var html = '';
rows.forEach(function (r) {
html += '<tr>' +
'<td>' + r.id + '</td>' +
'<td>' + escHtml(r.channel_name) + '</td>' +
'<td>' + escHtml(r.channel_type || '-') + '</td>' +
'<td>' + escHtml(r.contact_person || '-') + '</td>' +
'<td>' + escHtml(r.contact_phone || '-') + '</td>' +
'<td>' + escHtml(r.contact_email || '-') + '</td>' +
'<td>' + escHtml(r.efficiency_score) + '</td>' +
'<td title="' + escHtml(r.remark || '') + '">' + escHtml((r.remark || '-').substring(0, 20)) + '</td>' +
'<td>' + fmtDate(r.created_at) + '</td>' +
'<td class="ops"><a onclick="editChannel(' + r.id + ')">编辑</a>' +
'<a class="danger" onclick="delChannel(' + r.id + ')">删除</a></td></tr>';
});
if (!rows.length) {
html = '<tr><td colspan="10" class="empty-tip" style="padding:40px 0;">暂无渠道数据(渠道表为新增加的业务表,可在 sql/system_tables.sql 中创建)</td></tr>';
}
$('#channel-tbody').html(html);
renderPagination($('#pagination'), data, loadList);
});
}
/* ---------- 年度统计 ---------- */
function loadStats() {
httpGet('channel/stats.php').then(function (data) {
var years = data.years || [];
var html = '<option value="">选择年度</option>';
years.forEach(function (y) {
html += '<option value="' + y + '"' + (y == data.year ? ' selected' : '') + '>' + y + '年</option>';
});
$('#year-select').html(html);
$('#stat-summary').html(
'<div class="stat-card c-blue"><div class="num">' + data.summary.channel_count + '</div><div class="label">' + data.year + '年渠道数</div></div>' +
'<div class="stat-card c-green"><div class="num">' + data.summary.avg_score + '</div><div class="label">平均效率评分</div></div>' +
'<div class="stat-card c-orange"><div class="num">' + data.summary.max_score + '</div><div class="label">最高评分</div></div>' +
'<div class="stat-card c-red"><div class="num">' + data.summary.min_score + '</div><div class="label">最低评分</div></div>'
);
window._channelStatData = data;
});
}
$('#year-select').on('change', function () {
var y = $(this).val();
if (!y) return;
httpGet('channel/stats.php', { year: y }).then(function (data) {
$('#stat-summary').html(
'<div class="stat-card c-blue"><div class="num">' + data.summary.channel_count + '</div><div class="label">' + data.year + '年渠道数</div></div>' +
'<div class="stat-card c-green"><div class="num">' + data.summary.avg_score + '</div><div class="label">平均效率评分</div></div>' +
'<div class="stat-card c-orange"><div class="num">' + data.summary.max_score + '</div><div class="label">最高评分</div></div>' +
'<div class="stat-card c-red"><div class="num">' + data.summary.min_score + '</div><div class="label">最低评分</div></div>'
);
window._channelStatData = data;
});
});
/* ---------- 新增/编辑 ---------- */
$('#btn-add').on('click', function () { openForm(null); });
window.editChannel = function (id) {
var row = null;
pageRows.forEach(function (r) { if (r.id == id) row = r; });
openForm(row);
};
function openForm(ch) {
var isEdit = !!ch;
var c = ch || {};
var content =
'<form id="channel-form" style="padding:18px 22px 4px;">' +
'<div class="form-grid">' +
' <div class="form-item"><label>渠道名称<span class="req">*</span></label><input type="text" name="channel_name" value="' + escHtml(c.channel_name) + '"></div>' +
' <div class="form-item"><label>渠道类型</label><input type="text" name="channel_type" value="' + escHtml(c.channel_type) + '" placeholder="线上/线下/展会/转介绍"></div>' +
' <div class="form-item"><label>联系人</label><input type="text" name="contact_person" value="' + escHtml(c.contact_person) + '"></div>' +
' <div class="form-item"><label>联系电话</label><input type="text" name="contact_phone" value="' + escHtml(c.contact_phone) + '"></div>' +
' <div class="form-item"><label>联系邮箱</label><input type="text" name="contact_email" value="' + escHtml(c.contact_email) + '"></div>' +
' <div class="form-item"><label>效率评分(0-100)</label><input type="number" name="efficiency_score" min="0" max="100" step="0.01" value="' + escHtml(c.efficiency_score) + '"></div>' +
' <div class="form-item full"><label>备注</label><textarea name="remark">' + escHtml(c.remark) + '</textarea></div>' +
'</div>' +
'<div class="dialog-footer"><button type="button" class="btn" id="form-cancel">取消</button>' +
'<button type="submit" class="btn btn-primary">保存</button></div>' +
'</form>';
var idx = Dialog.open({
title: isEdit ? '编辑渠道' : '新增渠道',
area: ['680px', 'auto'],
content: content,
btn: false
});
$('#form-cancel').on('click', function () { Dialog.close(idx); });
$('#channel-form').on('submit', function (e) {
e.preventDefault();
var data = {};
$(this).find('input, textarea').each(function () {
var name = $(this).attr('name');
if (!name) return;
data[name] = $(this).val();
});
if (!data.channel_name) { Dialog.error('渠道名称为必填项'); return; }
if (isEdit) data.id = c.id;
httpPost(isEdit ? 'channel/save.php' : 'channel/save.php', data).then(function () {
Dialog.success('保存成功', function () {
Dialog.close(idx);
loadList(currentPage);
loadStats();
});
});
});
}
window.delChannel = function (id) {
Dialog.confirm('确定删除该渠道吗?', function () {
httpPost('channel/delete.php', { id: id }).then(function () {
Dialog.success('删除成功', function () {
loadList(currentPage);
loadStats();
});
});
});
};
});
+415
View File
@@ -0,0 +1,415 @@
/**
* common.js - 通用函数(httpGet/httpPost/弹窗/分页/日期格式化/菜单渲染/侧边栏/用户菜单)
*/
/* ============ HTTP 封装 ============ */
/**
* GET 请求(返回 Promise<data>)
*/
function httpGet(url, params) {
var full = BASE_URL + url;
if (params) {
var qs = $.param(params);
full += (full.indexOf('?') >= 0 ? '&' : '?') + qs;
}
return $.ajax({
url: full,
method: 'GET',
dataType: 'json'
}).then(handleResp, handleError);
}
/**
* POST 请求(写类操作自动携带 X-Requested-With 头,返回 Promise<data>)
*/
function httpPost(url, data, isFormData) {
var opts = {
url: BASE_URL + url,
method: 'POST',
dataType: 'json',
headers: { 'X-Requested-With': 'XMLHttpRequest' }
};
if (isFormData) {
opts.data = data;
opts.processData = false;
opts.contentType = false;
} else {
opts.data = data;
}
return $.ajax(opts).then(handleResp, handleError);
}
/** 统一处理响应:code!=0 弹错并 reject;401 立即回登录页 */
function handleResp(resp) {
if (resp && resp.code === 0) {
return resp.data;
}
if (resp && resp.code === 401) {
location.href = 'index.html';
return $.Deferred().reject(resp).promise();
}
var msg = (resp && resp.msg) ? resp.msg : '请求失败';
layer.msg(msg, { icon: 2 });
return $.Deferred().reject(resp).promise();
}
function handleError(xhr) {
if (xhr.status === 401) {
location.href = 'index.html';
return $.Deferred().reject(xhr).promise();
}
var msg = '网络错误';
try {
var resp = xhr.responseJSON;
if (resp && resp.msg) msg = resp.msg;
} catch (e) {}
layer.msg(msg, { icon: 2 });
return $.Deferred().reject(xhr).promise();
}
/* ============ 弹窗封装(基于 layer) ============ */
var Dialog = {
alert: function (msg, cb) { layer.alert(msg, { icon: 0 }, cb || function () {}); },
success: function (msg, cb) { layer.msg(msg, { icon: 1, time: 1200 }, cb || function () {}); },
error: function (msg) { layer.msg(msg, { icon: 2 }); },
confirm: function (msg, cb) {
layer.confirm(msg, { icon: 3, title: '确认操作' }, function (idx) {
layer.close(idx);
cb && cb();
});
},
open: function (opts) {
return layer.open({
type: 1,
title: opts.title || '操作',
area: opts.area || ['720px', 'auto'],
content: opts.content,
btn: opts.btn !== false ? ['确定', '取消'] : null,
yes: opts.yes || null,
end: opts.end || null
});
},
close: function (idx) { layer.close(idx); }
};
/* ============ 日期格式化 ============ */
function fmtDate(str, withTime) {
if (!str) return '-';
var d = new Date(str.replace(/-/g, '/'));
if (isNaN(d.getTime())) return str;
var p = function (n) { return n < 10 ? '0' + n : '' + n; };
var s = d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate());
if (withTime) s += ' ' + p(d.getHours()) + ':' + p(d.getMinutes()) + ':' + p(d.getSeconds());
return s;
}
function escHtml(str) {
if (str === null || str === undefined) return '';
return String(str)
.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
.replace(/"/g, '&quot;').replace(/'/g, '&#39;');
}
/* ============ 会话与权限 ============ */
/**
* 校验当前页面权限,无权限则提示并跳回首页
* @param {string} key 菜单标识
* @returns {boolean} 是否有权限
*/
function checkPagePermission(key) {
var perms = (window.CURRENT_USER && window.CURRENT_USER.permissions) || [];
if (perms.indexOf(key) < 0) {
Dialog.alert('您没有访问该模块的权限', function () {
location.href = 'dashboard.html';
});
return false;
}
return true;
}
/**
* 页面初始化:校验登录态 + 渲染左侧菜单 + 填充右上角用户信息
* 注意:成功回调内包裹 try/catch——渲染异常只记日志,不触发登录页跳转;
* .fail 仅在会话真实失效(401)时才跳转登录页。
*/
function initPage(activeKey, cb) {
httpGet('auth/session.php').then(function (user) {
window.CURRENT_USER = user;
try {
renderMenu(activeKey, user.permissions || []);
// 右上角用户菜单
var name = user.real_name || user.username;
$('#um-name').text(name);
$('#um-detail-name').text(name);
$('#um-detail-account').text('@' + user.username);
$('#um-avatar').text(name.charAt(0).toUpperCase());
$('#um-role').text(user.role_name || '-');
$('#um-status').text('已登录').removeClass('um-off').addClass('um-ok');
$('#um-logout').show();
$('#um-login').hide();
cb && cb(user);
} catch (e) {
console.error('initPage render error:', e);
}
}).fail(function () {
// 会话失效/未登录:立即回登录页,不允许停留在当前页面继续操作
try {
var a = arguments[0];
var desc;
if (a && a.responseText) desc = 'HTTP/' + a.status + ' respText=' + a.responseText.substring(0, 300);
else if (a instanceof Error) desc = 'Error: ' + a.message;
else if (a && a.readyState !== undefined) desc = 'jqXHR status=' + a.status + ' statusText=' + a.statusText;
else desc = 'arg0=' + String(a);
console.error('initPage session check failed:', desc);
} catch (e) {
console.error('initPage fail (unserializable)', String(e));
}
location.href = 'index.html';
});
}
/* ============ 侧边栏渲染 ============ */
/**
* 渲染左侧导航菜单(分级,扁平图标;二级菜单不带图标)
* - 分组:拥有任意子项权限即显示;子项逐个按权限过滤
* - 高亮:子项按“当前页面文件名”匹配(避免营销服务共用权限时全部选中);一级项按 key/url
*/
function renderMenu(activeKey, permissions) {
var currentPage = location.pathname.split('/').pop();
var html = '';
MENU_GROUPS.forEach(function (g) {
if (g.children) {
// 过滤出当前用户有权限的子项
var visibleChildren = g.children.filter(function (c) {
return permissions.indexOf(c.key) >= 0;
});
if (!visibleChildren.length) return;
var isOpen = visibleChildren.some(function (c) {
return c.url === currentPage;
});
html += '<div class="menu-group' + (isOpen ? ' open' : '') + '">' +
'<div class="menu-group-title" onclick="toggleMenuGroup(this)">' +
'<span class="icon">' + (ICONS[g.icon] || '') + '</span>' +
'<span class="label">' + g.name + '</span>' +
'<span class="arrow">▸</span></div>' +
'<div class="menu-group-body"' + (isOpen ? ' style="display:block;"' : '') + '>';
visibleChildren.forEach(function (c) {
var isActive = (c.url === currentPage);
html += '<a class="menu-item sub' + (isActive ? ' active' : '') + '" href="' + c.url + '">' +
'<span class="label">' + c.name + '</span></a>';
});
html += '</div></div>';
} else {
if (permissions.indexOf(g.key) < 0) return;
var isActive = (g.key === activeKey || g.url === currentPage);
html += '<a class="menu-item' + (isActive ? ' active' : '') + '" href="' + g.url + '">' +
'<span class="icon">' + (ICONS[g.icon] || '') + '</span>' +
'<span class="label">' + g.name + '</span></a>';
}
});
$('#sidebar-menu').html(html);
}
/** 展开/收起分组菜单(折叠态点击分组时先展开侧边栏) */
window.toggleMenuGroup = function (el) {
var $layout = $('.layout');
if ($layout.hasClass('sidebar-collapsed')) {
$layout.removeClass('sidebar-collapsed');
if (!window.matchMedia('(max-width: 768px)').matches) {
localStorage.setItem('sl_sidebar_collapsed', '0');
}
}
var $group = $(el).closest('.menu-group');
var $body = $group.find('.menu-group-body');
var isOpen = $group.hasClass('open');
if (isOpen) {
$group.removeClass('open');
$body.slideUp(150);
} else {
$group.addClass('open');
$body.slideDown(150);
}
};
/** 手动折叠/展开侧边栏 */
window.toggleSidebar = function () {
var $layout = $('.layout');
$layout.toggleClass('sidebar-collapsed');
var collapsed = $layout.hasClass('sidebar-collapsed');
if (!window.matchMedia('(max-width: 768px)').matches) {
localStorage.setItem('sl_sidebar_collapsed', collapsed ? '1' : '0');
}
};
/** 初始化侧边栏状态:桌面读用户偏好;移动端(<768px)自动折叠 */
function initSidebar() {
var mq = window.matchMedia('(max-width: 768px)');
function apply() {
var $layout = $('.layout');
if (mq.matches) {
$layout.addClass('sidebar-collapsed');
} else if (localStorage.getItem('sl_sidebar_collapsed') === '1') {
$layout.addClass('sidebar-collapsed');
} else {
$layout.removeClass('sidebar-collapsed');
}
}
apply();
if (mq.addEventListener) mq.addEventListener('change', apply);
else if (mq.addListener) mq.addListener(apply);
}
/* ============ 页面通用骨架(侧边栏+顶栏+右上角用户菜单+页脚) ============ */
function renderShell(pageTitle, breadcrumb) {
var html =
'<div class="layout">' +
' <aside class="sidebar">' +
' <a class="logo" href="dashboard.html" title="返回综合概览">Super<span>Link</span><span class="version">' + (window.APP_VERSION || 'v1.0.00') + '</span></a>' +
' <nav class="menu" id="sidebar-menu"></nav>' +
' </aside>' +
' <div class="main">' +
' <div class="topbar">' +
' <span class="sidebar-toggle" id="sidebar-toggle" onclick="toggleSidebar()" title="折叠/展开菜单">' + (ICONS.menu || '') + '</span>' +
' <div class="page-title">' + pageTitle + '</div>' +
(breadcrumb ? '<div class="breadcrumb">' + breadcrumb + '</div>' : '') +
' <div class="topbar-spacer"></div>' +
' <div class="user-menu" id="user-menu">' +
' <span class="user-menu-trigger">' +
' <span class="avatar" id="um-avatar">U</span>' +
' <span id="um-name">加载中...</span>' +
' <span class="caret">▾</span>' +
' </span>' +
' <div class="user-menu-dropdown">' +
' <div class="um-head">' +
' <div class="um-name" id="um-detail-name"></div>' +
' <div class="um-sub" id="um-detail-account"></div>' +
' </div>' +
' <div class="um-item"><span class="um-label">角色</span><b id="um-role">-</b></div>' +
' <div class="um-item"><span class="um-label">登录状态</span><b id="um-status">-</b></div>' +
' <div class="um-divider"></div>' +
' <a class="um-action" id="um-login" style="display:none;">登 录</a>' +
' <a class="um-action um-danger" id="um-logout" style="display:none;">退出登录</a>' +
' </div>' +
' </div>' +
' </div>' +
' <div class="content" id="page-content"></div>' +
' <footer class="footer">' + (window.FOOTER_TEXT || '© SuperLink 管理系统') + '</footer>' +
' </div>' +
'</div>';
document.body.innerHTML = html;
initSidebar();
// 右上角用户菜单:悬停显示(延迟关闭,保证能移入下拉点击)+ 点击切换(触屏)
var menuTimer = null;
$('#user-menu').on('mouseenter', function () {
clearTimeout(menuTimer);
$(this).addClass('open');
});
$('#user-menu').on('mouseleave', function () {
var self = this;
clearTimeout(menuTimer);
menuTimer = setTimeout(function () { $(self).removeClass('open'); }, 250);
});
$('#user-menu .user-menu-trigger').on('click', function (e) {
e.stopPropagation();
$('#user-menu').toggleClass('open');
});
$(document).on('click', function () {
$('#user-menu').removeClass('open');
});
$('#um-logout').on('click', function () {
Dialog.confirm('确定要退出登录吗?', function () {
httpPost('auth/logout.php').then(function () {
location.href = 'index.html';
});
});
});
$('#um-login').on('click', function () {
location.href = 'index.html';
});
}
/* ============ 分页组件 ============ */
/**
* 渲染分页条
* @param {object} pager { total, page, limit }
* @param {function} onPage 翻页回调(page)
*/
function renderPagination($el, pager, onPage) {
var total = pager.total || 0;
var page = pager.page || 1;
var limit = pager.limit || PAGE_SIZE;
var pages = Math.max(1, Math.ceil(total / limit));
var html = '<span>共 ' + total + ' 条 / ' + pages + ' 页</span>';
html += '<span class="pg' + (page <= 1 ? ' disabled' : '') + '" data-p="' + (page - 1) + '">上一页</span>';
var start = Math.max(1, page - 2);
var end = Math.min(pages, start + 4);
start = Math.max(1, end - 4);
for (var i = start; i <= end; i++) {
html += '<span class="pg' + (i === page ? ' current' : '') + '" data-p="' + i + '">' + i + '</span>';
}
html += '<span class="pg' + (page >= pages ? ' disabled' : '') + '" data-p="' + (page + 1) + '">下一页</span>';
$el.html(html);
$el.find('span.pg').each(function () {
var $this = $(this);
if ($this.hasClass('disabled')) return;
$this.on('click', function () {
onPage && onPage(parseInt($this.data('p'), 10));
});
});
}
/* ============ 文件下载(导出) ============ */
function download(url, params) {
var full = BASE_URL + url;
if (params) {
full += (full.indexOf('?') >= 0 ? '&' : '?') + $.param(params);
}
location.href = full;
}
/* ============ 通用表格渲染辅助 ============ */
function buildFilterParam() {
var obj = {};
$('.toolbar [data-filter]').each(function () {
var $this = $(this);
var key = $this.data('filter');
var val = $this.val();
if (val !== '' && val !== null && val !== undefined) {
obj[key] = val;
}
});
return obj;
}
/* ============ 文件上传输入 ============ */
function triggerFileInput(accept, cb) {
var input = document.createElement('input');
input.type = 'file';
input.accept = accept || '.csv';
input.style.display = 'none';
input.onchange = function () {
if (input.files.length > 0) {
cb(input.files[0]);
}
document.body.removeChild(input);
};
document.body.appendChild(input);
input.click();
}
+326
View File
@@ -0,0 +1,326 @@
/**
* company.js - 企业数据管理逻辑(列表+筛选+分页 / 新增编辑弹窗 / 导入导出 / 详情)
*/
$(function () {
renderShell('企业数据', '数据管理 / 企业数据');
renderPage();
initPage('company', function (user) {
if (!checkPagePermission('company')) return;
loadList(1);
});
var currentPage = 1;
/** 渲染页面内容(工具栏+表格+分页) */
function renderPage() {
$('#page-content').html(
'<div class="card">' +
' <div class="toolbar">' +
' <input type="text" data-filter="keyword" placeholder="公司名/注册号关键词">' +
' <input type="text" data-filter="industry" placeholder="行业(精确匹配)">' +
' <select data-filter="business_role"><option value="">全部业务角色</option>' +
' <option value="manufacturer">生产型</option><option value="integrator">集成商</option>' +
' <option value="distributor">代理商</option><option value="brand_operator">品牌运营</option>' +
' <option value="others">其他</option></select>' +
' <input type="text" data-filter="country" placeholder="国家/地区">' +
' <button class="btn btn-primary" id="btn-search">搜索</button>' +
' <button class="btn" id="btn-reset">重置</button>' +
' <span class="spacer"></span>' +
' <button class="btn btn-success" id="btn-add">+ 新增</button>' +
' <button class="btn btn-warning" id="btn-import">导入CSV</button>' +
' <button class="btn" id="btn-export">导出</button>' +
' <button class="btn btn-danger" id="btn-del-batch">批量删除</button>' +
' </div>' +
' <div class="table-wrap"><table class="grid">' +
' <thead><tr><th><input type="checkbox" id="check-all"></th><th>ID</th><th>公司名称</th><th>行业</th><th>行业细分</th><th>国家</th><th>业务角色</th><th>注册号</th><th>法人</th><th>上市</th><th>创建时间</th><th>操作</th></tr></thead>' +
' <tbody id="company-tbody"></tbody>' +
' </table></div>' +
' <div class="pagination" id="pagination"></div>' +
'</div>'
);
$('#check-all').on('change', function () { $('.row-check').prop('checked', this.checked); });
}
/* ---------- 列表 ---------- */
function loadList(page) {
currentPage = page;
var params = buildFilterParam();
params.page = page;
params.limit = PAGE_SIZE;
httpGet('company/list.php', params).then(function (data) {
var rows = data.list || [];
var html = '';
rows.forEach(function (r) {
html += '<tr>' +
'<td><input type="checkbox" class="row-check" value="' + r.id + '"></td>' +
'<td>' + r.id + '</td>' +
'<td title="' + escHtml(r.name_zh || r.name_en || '') + '">' + escHtml(r.name_zh || r.name_en || '-') + '</td>' +
'<td>' + escHtml(r.industry || '-') + '</td>' +
'<td>' + escHtml(r.industry_subdivision || '-') + '</td>' +
'<td>' + escHtml(r.country || '-') + '</td>' +
'<td>' + escHtml(r.business_role || '-') + '</td>' +
'<td>' + escHtml(r.registration_number || '-') + '</td>' +
'<td>' + escHtml(r.legal_representative || '-') + '</td>' +
'<td>' + (r.is_listed ? '<span class="tag">上市</span>' : '-') + '</td>' +
'<td>' + fmtDate(r.created_at) + '</td>' +
'<td class="ops">' +
' <a onclick="viewCompany(' + r.id + ')">详情</a>' +
' <a onclick="viewEmployees(' + r.id + ')">员工</a>' +
' <a onclick="editCompany(' + r.id + ')">编辑</a>' +
' <a class="danger" onclick="delCompany(' + r.id + ')">删除</a>' +
'</td></tr>';
});
if (!rows.length) {
html = '<tr><td colspan="12" class="empty-tip" style="padding:40px 0;">暂无数据</td></tr>';
}
$('#company-tbody').html(html);
renderPagination($('#pagination'), data, loadList);
});
}
/* ---------- 筛选 ---------- */
$('#btn-search').on('click', function () { loadList(1); });
$('#btn-reset').on('click', function () {
$('.toolbar [data-filter]').val('');
loadList(1);
});
/* ---------- 新增/编辑 ---------- */
$('#btn-add').on('click', function () { openForm(null); });
window.editCompany = function (id) {
httpGet('company/detail.php', { id: id }).then(function (d) {
openForm(d.company);
});
};
function openForm(company) {
var isEdit = !!company;
var c = company || {};
var content =
'<form id="company-form" style="padding:18px 22px 4px;">' +
'<div class="form-grid">' +
' <div class="form-item"><label>公司名称<span class="req">*</span></label><input type="text" name="name_zh" value="' + escHtml(c.name_zh) + '"></div>' +
' <div class="form-item"><label>英文名称</label><input type="text" name="name_en" value="' + escHtml(c.name_en) + '"></div>' +
' <div class="form-item"><label>业务角色</label><select name="business_role">' + roleOptions(c.business_role) + '</select></div>' +
' <div class="form-item"><label>国家/地区</label><input type="text" name="country" value="' + escHtml(c.country) + '"></div>' +
' <div class="form-item"><label>注册号</label><input type="text" name="registration_number" value="' + escHtml(c.registration_number) + '"></div>' +
' <div class="form-item"><label>注册地址</label><input type="text" name="address" value="' + escHtml(c.address) + '"></div>' +
' <div class="form-item"><label>法定代表人</label><input type="text" name="legal_representative" value="' + escHtml(c.legal_representative) + '"></div>' +
' <div class="form-item"><label>行业</label><input type="text" name="industry" value="' + escHtml(c.industry) + '"></div>' +
' <div class="form-item"><label>行业细分</label><input type="text" name="industry_subdivision" value="' + escHtml(c.industry_subdivision) + '"></div>' +
' <div class="form-item"><label>官网</label><input type="text" name="website" value="' + escHtml(c.website) + '"></div>' +
' <div class="form-item"><label>注册资本(万元)</label><input type="text" name="registered_capital" value="' + escHtml(c.registered_capital) + '"></div>' +
' <div class="form-item"><label>成立日期</label><input type="date" name="established_date" value="' + escHtml(c.established_date) + '"></div>' +
' <div class="form-item"><label>员工数</label><input type="number" name="latest_employee_count" value="' + escHtml(c.latest_employee_count) + '"></div>' +
' <div class="form-item"><label>最新年营收</label><input type="text" name="latest_annual_revenue" value="' + escHtml(c.latest_annual_revenue) + '"></div>' +
' <div class="form-item"><label>是否上市</label><select name="is_listed">' +
' <option value="0"' + (c.is_listed ? '' : ' selected') + '>否</option>' +
' <option value="1"' + (c.is_listed ? ' selected' : '') + '>是</option></select></div>' +
' <div class="form-item"><label>股票代码</label><input type="text" name="stock_code" value="' + escHtml(c.stock_code) + '"></div>' +
' <div class="form-item full"><label>经营范围</label><textarea name="business_scope">' + escHtml(c.business_scope) + '</textarea></div>' +
'</div>' +
'<div class="dialog-footer"><button type="button" class="btn" id="form-cancel">取消</button>' +
'<button type="submit" class="btn btn-primary">保存</button></div>' +
'</form>';
var idx = Dialog.open({
title: isEdit ? '编辑企业' : '新增企业',
area: ['760px', 'auto'],
content: content,
btn: false
});
$('#form-cancel').on('click', function () { Dialog.close(idx); });
$('#company-form').on('submit', function (e) {
e.preventDefault();
var fd = new FormData(this);
var nameZh = fd.get('name_zh');
var nameEn = fd.get('name_en');
if (isEdit) fd.delete('display_name'); // 编辑不允许改显示名称
if (!isEdit && !nameZh && !nameEn) {
Dialog.error('公司名称或英文名称至少填写一项');
return;
}
httpPost(isEdit ? 'company/update.php' : 'company/add.php', fd, true).then(function () {
Dialog.success('保存成功', function () {
Dialog.close(idx);
loadList(currentPage);
});
});
});
}
function roleOptions(sel) {
var opts = ['', 'manufacturer', 'integrator', 'distributor', 'brand_operator', 'others'];
var labels = { 'manufacturer': '生产型', 'integrator': '集成商', 'distributor': '代理商', 'brand_operator': '品牌运营', 'others': '其他' };
var html = '<option value="">-</option>';
opts.forEach(function (v) {
if (v === '') return;
html += '<option value="' + v + '"' + (sel === v ? ' selected' : '') + '>' + (labels[v] || v) + '</option>';
});
return html;
}
/* ---------- 删除(软删除) ---------- */
window.delCompany = function (id) {
Dialog.confirm('确定删除该企业吗?(软删除,可恢复)', function () {
httpPost('company/delete.php', { id: id }).then(function () {
Dialog.success('删除成功', function () { loadList(currentPage); });
});
});
};
/* ---------- 批量删除 ---------- */
$('#btn-del-batch').on('click', function () {
var ids = [];
$('.row-check:checked').each(function () { ids.push($(this).val()); });
if (!ids.length) { Dialog.error('请先勾选记录'); return; }
Dialog.confirm('确定删除选中的 ' + ids.length + ' 条记录吗?', function () {
httpPost('company/delete.php', { ids: ids.join(',') }).then(function () {
Dialog.success('删除成功', function () { loadList(currentPage); });
});
});
});
/* ---------- 导入 ---------- */
$('#btn-import').on('click', function () {
triggerFileInput('.csv', function (file) {
var fd = new FormData();
fd.append('file', file);
httpPost('company/import.php', fd, true).then(function (d) {
Dialog.alert('导入结果:成功 ' + d.inserted + ' 条,失败 ' + d.failed + ' 条', function () {
loadList(1);
});
});
});
});
/* ---------- 导出(仅导出勾选的企业,未勾选弹窗提醒) ---------- */
$('#btn-export').on('click', function () {
var ids = [];
$('.row-check:checked').each(function () { ids.push($(this).val()); });
if (!ids.length) {
layer.alert('请先选择需要导出的企业数据', { icon: 2, title: '提示' });
return;
}
download('company/export.php', { ids: ids.join(',') });
});
/* ---------- 详情 ---------- */
window.viewCompany = function (id) {
httpGet('company/detail.php', { id: id }).then(function (d) {
var c = d.company;
var html =
'<div style="padding:18px 22px;">' +
' <h3 style="margin-bottom:14px;">' + escHtml(c.name_zh || c.name_en || '企业 #' + c.id) + '</h3>' +
' <div class="detail-grid" style="display:grid;grid-template-columns:1fr 1fr;gap:8px 24px;font-size:13px;">' +
detailRow('公司名称', c.name_zh) + detailRow('英文名称', c.name_en) +
detailRow('业务角色', c.business_role) + detailRow('国家/地区', c.country) +
detailRow('注册号', c.registration_number) + detailRow('地址', c.address) +
detailRow('法定代表人', c.legal_representative) + detailRow('行业', c.industry) +
detailRow('行业细分', c.industry_subdivision) + detailRow('官网', c.website) +
detailRow('注册资本', c.registered_capital) + detailRow('成立日期', c.established_date) +
detailRow('员工数', c.latest_employee_count) + detailRow('年营收', c.latest_annual_revenue) +
detailRow('是否上市', c.is_listed ? '是' : '否') + detailRow('股票代码', c.stock_code) +
'</div>' +
' <h4 style="margin:16px 0 8px;">经营范围</h4><div style="font-size:13px;color:#5a6472;">' + escHtml(c.business_scope || '-') + '</div>' +
' <h4 style="margin:16px 0 8px;">产品品类</h4>' + miniTable(['品类', '描述', '核心'], d.products.map(function (p) { return [p.category_name, p.category_description || '-', p.is_core ? '是' : '否']; })) +
' <h4 style="margin:16px 0 8px;">关联需求</h4>' + miniTable(['联系人', '需求大类', '意向品类', '场景'], d.needs.map(function (n) { return [n.contact_person || '-', n.need_category || '-', n.target_product_category || '-', n.application_scenario || '-']; })) +
' <h4 style="margin:16px 0 8px;">关联联系人</h4>' + miniTable(['姓名', '职位', '联系方式'], d.contacts.map(function (p) { return [p.full_name || '-', p.position || '-', p.contacts || '-']; })) +
'</div>';
Dialog.open({ title: '企业详情', area: ['820px', 'auto'], content: html, btn: ['关闭'] });
});
};
function detailRow(label, val) {
return '<div><b>' + label + ':</b>' + escHtml(val || '-') + '</div>';
}
function miniTable(headers, rows) {
if (!rows.length) return '<div style="font-size:13px;color:#b6bfcc;">暂无数据</div>';
var html = '<table class="grid" style="font-size:12px;">';
html += '<tr>' + headers.map(function (h) { return '<th>' + h + '</th>'; }).join('') + '</tr>';
rows.forEach(function (r) {
html += '<tr>' + r.map(function (v) { return '<td>' + escHtml(v) + '</td>'; }).join('') + '</tr>';
});
return html + '</table>';
}
/* ---------- 员工列表弹窗(按公司查看员工,分页 + 姓名/职级筛选) ---------- */
var empCompanyId = 0;
var empPage = 1;
window.viewEmployees = function (companyId) {
empCompanyId = companyId;
empPage = 1;
var content =
'<div style="padding:16px 18px 6px;">' +
' <div class="toolbar">' +
' <input type="text" id="emp-keyword" placeholder="姓名关键词">' +
' <select id="emp-level"><option value="">全部职级</option></select>' +
' <button class="btn btn-primary btn-sm" id="emp-search">搜索</button>' +
' <button class="btn btn-sm" id="emp-reset">重置</button>' +
' </div>' +
' <div class="table-wrap"><table class="grid">' +
' <thead><tr><th>姓名</th><th>联系方式</th><th>邮箱</th><th>职级</th><th>部门</th><th>岗位</th><th>入职日期</th><th>是否在职</th></tr></thead>' +
' <tbody id="emp-tbody"></tbody>' +
' </table></div>' +
' <div class="pagination" id="emp-pagination"></div>' +
'</div>';
var idx = Dialog.open({ title: '员工列表', area: ['920px', 'auto'], content: content, btn: ['关闭'] });
window.__empDialogIdx = idx;
loadEmployees(1);
$('#emp-search').on('click', function () { loadEmployees(1); });
$('#emp-reset').on('click', function () {
$('#emp-keyword').val('');
$('#emp-level').val('');
loadEmployees(1);
});
};
function loadEmployees(page) {
empPage = page;
var params = { company_id: empCompanyId, page: page, limit: 5 };
var kw = $.trim($('#emp-keyword').val());
var lv = $('#emp-level').val();
if (kw) params.keyword = kw;
if (lv) params.job_level = lv;
httpGet('company/employees.php', params).then(function (data) {
var rows = data.list || [];
var html = '';
rows.forEach(function (r) {
html += '<tr>' +
'<td>' + escHtml(r.full_name) + '</td>' +
'<td>' + escHtml(r.phone || '-') + '</td>' +
'<td>' + escHtml(r.email || '-') + '</td>' +
'<td>' + escHtml(r.job_level || '-') + '</td>' +
'<td>' + escHtml(r.department || '-') + '</td>' +
'<td>' + escHtml(r.position || '-') + '</td>' +
'<td>' + escHtml(r.start_date || '-') + '</td>' +
'<td>' + (r.is_current ? '<span class="tag tag-green">在职</span>' : '<span class="tag tag-gray">离职</span>') + '</td>' +
'</tr>';
});
if (!rows.length) {
html = '<tr><td colspan="8" class="empty-tip" style="padding:30px 0;">暂无员工</td></tr>';
}
$('#emp-tbody').html(html);
// 职级下拉(仅首次填充)
var $lv = $('#emp-level');
if ($lv.find('option').length <= 1) {
var optHtml = '<option value="">全部职级</option>';
(data.levels || []).forEach(function (l) {
optHtml += '<option value="' + escHtml(l) + '">' + escHtml(l) + '</option>';
});
$lv.html(optHtml);
}
renderPagination($('#emp-pagination'), data, loadEmployees);
});
}
});
+123
View File
@@ -0,0 +1,123 @@
/**
* config.js - 全局配置
*/
var BASE_URL = '/api/';
var PAGE_SIZE = 20;
/** 系统版本号(logo旁展示):修改代码后运行 tools/bump_version.php 自动递增 */
var APP_VERSION = 'v1.0.10';
/** 页脚版权/备案信息(在 config.js 中修改) */
var FOOTER_TEXT = '© 2026 SuperLink 管理系统 版权所有 | 备案号:请替换为真实备案号';
/**
* 扁平化图标库(单色 SVG,随文字颜色 currentColor)
* key -> SVG 字符串;菜单/占位图等统一从这里取,不用彩色 emoji
*/
var ICONS = {
dashboard: '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>',
preliminary: '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><rect x="8" y="2" width="8" height="4" rx="1"/><line x1="9" y1="12" x2="15" y2="12"/><line x1="9" y1="16" x2="15" y2="16"/></svg>',
data: '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg>',
company: '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="2" width="16" height="20" rx="1"/><path d="M9 22v-4h6v4"/><line x1="8" y1="6" x2="10" y2="6"/><line x1="14" y1="6" x2="16" y2="6"/><line x1="8" y1="10" x2="10" y2="10"/><line x1="14" y1="10" x2="16" y2="10"/><line x1="8" y1="14" x2="10" y2="14"/><line x1="14" y1="14" x2="16" y2="14"/></svg>',
person: '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>',
media: '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polygon points="10 8 16 12 10 16 10 8"/></svg>',
need: '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/></svg>',
marketing: '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>',
channel: '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>',
document: '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>',
system: '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>',
log: '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>',
users: '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>',
role: '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"/></svg>',
menu: '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>'
};
/** 菜单标识 -> 权限显示信息(角色权限标签等用) */
var MENU_MAP = {
'dashboard': { name: '综合概览', icon: 'dashboard', url: 'dashboard.html' },
'preliminary': { name: '碎片处理', icon: 'preliminary', url: 'preliminary.html' },
'company': { name: '企业数据', icon: 'company', url: 'company.html' },
'person': { name: '人员数据', icon: 'person', url: 'person.html' },
'media': { name: '媒体数据', icon: 'media', url: 'media.html' },
'need': { name: '需求转盘', icon: 'need', url: 'need.html' },
'marketing': { name: '营销服务', icon: 'marketing', url: 'edm.html' },
'channel': { name: '渠道管理', icon: 'channel', url: 'channel.html' },
'document': { name: '文件管理', icon: 'document', url: 'document.html' },
'system': { name: '权限管理', icon: 'system', url: 'user.html' },
'log': { name: '操作日志', icon: 'log', url: 'log.html' }
};
/**
* 左侧导航菜单分组结构(一级/二级)
* - 一级项:直接渲染菜单链接;分组项:children 为二级菜单(二级不显示图标)
* - 子项 key 为其权限标识,多个子项可共用一个权限(如营销服务三个子页)
*/
var MENU_GROUPS = [
{ key: 'dashboard', name: '综合概览', icon: 'dashboard', url: 'dashboard.html' },
{ key: 'preliminary', name: '碎片处理', icon: 'preliminary', url: 'preliminary.html' },
{
key: 'data', name: '数据管理', icon: 'data', children: [
{ key: 'company', name: '企业数据', icon: 'company', url: 'company.html' },
{ key: 'person', name: '人员数据', icon: 'person', url: 'person.html' },
{ key: 'media', name: '媒体数据', icon: 'media', url: 'media.html' }
]
},
{ key: 'need', name: '需求转盘', icon: 'need', url: 'need.html' },
{
key: 'marketing', name: '营销服务', icon: 'marketing', children: [
{ key: 'marketing', name: 'EDM', icon: 'marketing', url: 'edm.html' },
{ key: 'marketing', name: '数据定制', icon: 'marketing', url: 'data_custom.html' },
{ key: 'marketing', name: '媒体批发', icon: 'marketing', url: 'media_wholesale.html' }
]
},
{ key: 'channel', name: '渠道管理', icon: 'channel', url: 'channel.html' },
{ key: 'document', name: '文件管理', icon: 'document', url: 'document.html' },
{
key: 'system', name: '权限管理', icon: 'system', children: [
{ key: 'system', name: '用户管理', icon: 'users', url: 'user.html' },
{ key: 'system', name: '角色管理', icon: 'role', url: 'role.html' }
]
},
{ key: 'log', name: '操作日志', icon: 'log', url: 'log.html' }
];
/**
* 国家/地区 中文 -> 英文(世界地图 ECharts world.js 匹配用)
*/
var CN_TO_EN = {
'中国': 'China', '中国大陆': 'China', '中国香港': 'China', '香港': 'China',
'中国澳门': 'China', '澳门': 'China', '中国台湾': 'China', '台湾': 'China',
'美国': 'United States', '日本': 'Japan', '韩国': 'South Korea', '朝鲜': 'North Korea',
'英国': 'United Kingdom', '法国': 'France', '德国': 'Germany', '意大利': 'Italy',
'西班牙': 'Spain', '葡萄牙': 'Portugal', '荷兰': 'Netherlands', '比利时': 'Belgium',
'瑞士': 'Switzerland', '瑞典': 'Sweden', '挪威': 'Norway', '芬兰': 'Finland',
'丹麦': 'Denmark', '波兰': 'Poland', '俄罗斯': 'Russia', '乌克兰': 'Ukraine',
'土耳其': 'Turkey', '希腊': 'Greece', '奥地利': 'Austria', '爱尔兰': 'Ireland',
'捷克': 'Czech Republic', '匈牙利': 'Hungary', '罗马尼亚': 'Romania', '保加利亚': 'Bulgaria',
'加拿大': 'Canada', '墨西哥': 'Mexico', '巴西': 'Brazil', '阿根廷': 'Argentina',
'智利': 'Chile', '秘鲁': 'Peru', '哥伦比亚': 'Colombia', '委内瑞拉': 'Venezuela',
'印度': 'India', '巴基斯坦': 'Pakistan', '印度尼西亚': 'Indonesia', '马来西亚': 'Malaysia',
'新加坡': 'Singapore', '泰国': 'Thailand', '越南': 'Vietnam', '菲律宾': 'Philippines',
'缅甸': 'Myanmar', '孟加拉国': 'Bangladesh', '斯里兰卡': 'Sri Lanka', '尼泊尔': 'Nepal',
'沙特阿拉伯': 'Saudi Arabia', '阿联酋': 'United Arab Emirates', '卡塔尔': 'Qatar',
'以色列': 'Israel', '伊朗': 'Iran', '伊拉克': 'Iraq', '哈萨克斯坦': 'Kazakhstan',
'澳大利亚': 'Australia', '新西兰': 'New Zealand', '南非': 'South Africa',
'埃及': 'Egypt', '尼日利亚': 'Nigeria', '肯尼亚': 'Kenya', '摩洛哥': 'Morocco',
'阿尔及利亚': 'Algeria', '埃塞俄比亚': 'Ethiopia', '蒙古': 'Mongolia',
'古巴': 'Cuba', '巴拿马': 'Panama', '乌拉圭': 'Uruguay', '厄瓜多尔': 'Ecuador'
};
/** 角色权限可勾选的菜单列表(角色管理弹窗用) */
var PERMISSION_OPTIONS = [
{ key: 'dashboard', name: '综合概览' },
{ key: 'preliminary', name: '碎片处理' },
{ key: 'company', name: '企业数据' },
{ key: 'person', name: '人员数据' },
{ key: 'media', name: '媒体数据' },
{ key: 'need', name: '需求转盘' },
{ key: 'marketing', name: '营销服务' },
{ key: 'channel', name: '渠道管理' },
{ key: 'document', name: '文件管理' },
{ key: 'system', name: '权限管理' },
{ key: 'log', name: '操作日志' }
];
+252
View File
@@ -0,0 +1,252 @@
/**
* dashboard.js - 首页逻辑
* 8个指标卡 + 区域分布(国内=中国地图按省份 / 全球=世界地图按国家,各含企业/人员Tab)
* + 行业分布(企业/人员)+ 需求关键词词云(占位)
*/
$(function () {
renderShell('综合概览', '首页 / 数据看板');
initPage('dashboard', function (user) {
loadStats();
});
// 图表状态(Tab 选中态与数据维度)
var pageData = null;
var regionState = { china: { dim: 'company' }, world: { dim: 'company' } };
var industryState = { dim: 'company' };
/** 数值安全转换:NaN / 空值一律转 0 */
function toNum(v) {
var n = parseInt(v, 10);
return isNaN(n) ? 0 : n;
}
function loadStats() {
httpGet('dashboard/stats.php').then(function (data) {
renderMetricCards(data.metrics || {});
pageData = data;
renderRegionChart('china');
renderRegionChart('world');
renderIndustryChart();
renderWordCloud(data.wordcloud || []);
});
}
/* ============ 8 个指标卡 + 图表容器 ============ */
function renderMetricCards(m) {
var cards = [
{ label: '累计企业', num: toNum(m.company_total), cls: 'c-blue' },
{ label: '累计人员', num: toNum(m.person_total), cls: 'c-green' },
{ label: '昨日新增数据(企业/人员)', num: toNum(m.company_new) + '/' + toNum(m.person_new), cls: 'c-orange' },
{ label: '待处理数据碎片', num: toNum(m.preliminary), cls: 'c-red' },
{ label: '覆盖行业', num: toNum(m.industry_count), cls: 'c-purple' },
{ label: '生产型企业', num: toNum(m.manufacturer), cls: 'c-blue' },
{ label: '集成商/代理商', num: toNum(m.integrator), cls: 'c-cyan' },
{ label: '行业媒体资源', num: toNum(m.media_resource), cls: 'c-teal' }
];
var html = '';
cards.forEach(function (c) {
html += '<div class="stat-card ' + c.cls + '">' +
'<div class="num">' + (c.num === undefined ? '-' : c.num) + '</div>' +
'<div class="label">' + c.label + '</div></div>';
});
$('#page-content').html(
'<div class="stat-cards">' + html + '</div>' +
'<div class="chart-grid">' +
' <div class="chart-box"><div class="chart-title">区域分布(国内)' +
' <span class="tabs" data-group="region-china">' +
' <a class="tab' + (regionState.china.dim === 'company' ? ' active' : '') + '" data-val="company" onclick="switchRegionDim(\'china\',\'company\')">企业</a>' +
' <a class="tab' + (regionState.china.dim === 'person' ? ' active' : '') + '" data-val="person" onclick="switchRegionDim(\'china\',\'person\')">人员</a>' +
' </span></div><div class="chart" id="chart-region-china"></div></div>' +
' <div class="chart-box"><div class="chart-title">区域分布(全球)' +
' <span class="tabs" data-group="region-world">' +
' <a class="tab' + (regionState.world.dim === 'company' ? ' active' : '') + '" data-val="company" onclick="switchRegionDim(\'world\',\'company\')">企业</a>' +
' <a class="tab' + (regionState.world.dim === 'person' ? ' active' : '') + '" data-val="person" onclick="switchRegionDim(\'world\',\'person\')">人员</a>' +
' </span></div><div class="chart" id="chart-region-world"></div></div>' +
' <div class="chart-box"><div class="chart-title">行业分布' +
' <span class="tabs" data-group="industry">' +
' <a class="tab' + (industryState.dim === 'company' ? ' active' : '') + '" data-val="company" onclick="switchIndustryDim(\'company\')">企业</a>' +
' <a class="tab' + (industryState.dim === 'person' ? ' active' : '') + '" data-val="person" onclick="switchIndustryDim(\'person\')">人员</a>' +
' </span></div><div class="chart" id="chart-industry"></div></div>' +
' <div class="chart-box"><div class="chart-title">需求关键词(词云,简化提取,后续接入分词服务)</div><div class="chart" id="chart-wordcloud"></div></div>' +
'</div>'
);
}
/* ============ Tab 切换(内联 onclick,数据属性精确匹配选中态) ============ */
window.switchRegionDim = function (box, dim) {
regionState[box].dim = dim;
syncTabs();
renderRegionChart(box);
};
window.switchIndustryDim = function (dim) {
industryState.dim = dim;
syncTabs();
renderIndustryChart();
};
/** 同步所有 Tab 的选中色块 */
function syncTabs() {
$('.chart-title .tabs').each(function () {
var $tabs = $(this);
var group = $tabs.data('group');
$tabs.find('.tab').each(function () {
var $t = $(this);
var val = $t.data('val');
var active = false;
if (group === 'region-china') active = (regionState.china.dim === val);
else if (group === 'region-world') active = (regionState.world.dim === val);
else if (group === 'industry') active = (industryState.dim === val);
$t.toggleClass('active', active);
});
});
}
/* ============ 区域分布(china=中国地图按省份 / world=世界地图按国家) ============ */
function renderRegionChart(box) {
var el = document.getElementById('chart-region-' + box);
if (!el || !window.echarts) return;
try {
var dim = regionState[box].dim;
var rows = ((pageData.region || {})[dim] || {})[box] || [];
rows = rows.map(function (r) {
return { name: r.name, value: toNum(r.value) };
});
var chart = echarts.getInstanceByDom(el) || echarts.init(el);
var mapName = box === 'china' ? 'china' : 'world';
var hasMap = !!(echarts.getMap && echarts.getMap(mapName));
// 世界地图:ECharts5 不支持 series.nameMap,需把中文国家名转英文(地图用英文名匹配);中文名保留给 tooltip
if (box === 'world') {
rows = rows.map(function (r) {
var en = CN_TO_EN[r.name] || r.name;
return { name: en, value: r.value, displayName: r.name };
});
}
if (!hasMap || !rows.length) {
// 地图未加载或无数据时降级为柱状图
if (!rows.length) {
chart.setOption({
title: { text: '暂无' + (dim === 'company' ? '企业' : '人员') + '数据', left: 'center', top: 'middle', textStyle: { color: '#b6bfcc', fontSize: 14 } }
});
} else {
chart.setOption({
tooltip: { trigger: 'axis' },
grid: { left: 60, right: 20, top: 30, bottom: 80 },
xAxis: { type: 'category', data: rows.map(function (r) { return r.name; }), axisLabel: { rotate: 45 } },
yAxis: { type: 'value' },
series: [{ type: 'bar', data: rows.map(function (r) { return r.value; }), itemStyle: { color: '#16a085' }, barMaxWidth: 40 }]
});
}
return;
}
var values = rows.map(function (r) { return r.value; });
var maxVal = Math.max.apply(null, values);
var option = {
tooltip: {
trigger: 'item',
formatter: function (p) {
// 无数据区域 p.value 为 NaN/undefined,统一显示 0
var v = p.value;
if (v === undefined || v === null || isNaN(v)) v = 0;
return ((p.data && p.data.displayName) || p.name) + ':' + v;
}
},
// 颜色深浅按数据量(数值越大颜色越深),不显示左下角图例
visualMap: {
show: false,
min: 0,
max: maxVal,
inRange: { color: ['#e8f1ff', '#1e3c72'] }
},
series: [{
type: 'map',
map: mapName,
roam: true,
label: { show: false },
emphasis: {
label: { show: true, formatter: function (p) { return ((p.data && p.data.displayName) || p.name); } },
itemStyle: { areaColor: '#f39c12' }
},
data: rows
}]
};
chart.setOption(option);
window.addEventListener('resize', function () { chart.resize(); });
} catch (e) {
console && console.error('region chart error', e);
}
}
/* ============ 行业分布(柱状图,企业/人员) ============ */
function renderIndustryChart() {
var el = document.getElementById('chart-industry');
if (!el || !window.echarts) return;
try {
var dim = industryState.dim;
var rows = ((pageData.industry || {})[dim] || []).map(function (r) {
return { name: r.name, value: toNum(r.value) };
});
var chart = echarts.getInstanceByDom(el) || echarts.init(el);
if (!rows.length) {
chart.setOption({
title: { text: '暂无' + (dim === 'company' ? '企业' : '人员') + '行业数据', left: 'center', top: 'middle', textStyle: { color: '#b6bfcc', fontSize: 14 } }
});
return;
}
chart.setOption({
tooltip: { trigger: 'axis' },
grid: { left: 70, right: 20, top: 30, bottom: 50 },
xAxis: { type: 'category', data: rows.map(function (r) { return r.name; }), axisLabel: { rotate: 30 } },
yAxis: { type: 'value' },
series: [{
type: 'bar',
data: rows.map(function (r) { return r.value; }),
itemStyle: { color: '#2a5298', borderRadius: [3, 3, 0, 0] },
barMaxWidth: 40
}]
});
window.addEventListener('resize', function () { chart.resize(); });
} catch (e) {
console && console.error('industry chart error', e);
}
}
/* ============ 需求关键词词云(占位) ============ */
function renderWordCloud(words) {
var el = document.getElementById('chart-wordcloud');
if (!el || !window.echarts) return;
try {
var chart = echarts.getInstanceByDom(el) || echarts.init(el);
if (!words.length) {
chart.setOption({
title: { text: '暂无关键词数据(占位,后续接入分词服务)', left: 'center', top: 'middle', textStyle: { color: '#b6bfcc', fontSize: 14 } }
});
return;
}
var data = words.map(function (w) {
return {
name: w.name,
value: w.value,
symbolSize: Math.min(60, 14 + w.value * 2),
itemStyle: { color: 'hsl(' + (w.name.length * 37 % 360) + ',65%,55%)' }
};
});
chart.setOption({
tooltip: { formatter: function (p) { return p.name + ':' + toNum(p.value) + '次'; } },
series: [{
type: 'scatter',
data: data,
label: { show: true, formatter: function (p) { return p.name; }, fontSize: 12, color: '#333' },
symbol: 'circle'
}]
});
window.addEventListener('resize', function () { chart.resize(); });
} catch (e) {
console && console.error('wordcloud error', e);
}
}
});
+104
View File
@@ -0,0 +1,104 @@
/**
* document.js - 文件管理逻辑(表格展示 / 上传 / 删除)
*/
$(function () {
renderShell('文件管理', '系统工具 / 文件管理');
renderPage();
initPage('document', function (user) {
if (!checkPagePermission('document')) return;
loadList(1);
});
var currentPage = 1;
/** 渲染页面内容(工具栏+表格+分页) */
function renderPage() {
$('#page-content').html(
'<div class="card">' +
' <div class="toolbar">' +
' <input type="text" data-filter="keyword" placeholder="文档名称关键词">' +
' <input type="text" data-filter="file_type" placeholder="文件类型(如 application/pdf)">' +
' <button class="btn btn-primary" id="btn-search">搜索</button>' +
' <button class="btn" id="btn-reset">重置</button>' +
' <span class="spacer"></span>' +
' <button class="btn btn-success" id="btn-upload">上传文件</button>' +
' <button class="btn btn-danger" id="btn-del-batch">批量删除</button>' +
' </div>' +
' <div class="table-wrap"><table class="grid">' +
' <thead><tr><th><input type="checkbox" id="check-all"></th><th>ID</th><th>文档名称</th><th>文件类型</th><th>文件</th><th>版本</th><th>发布日期</th><th>上传时间</th><th>操作</th></tr></thead>' +
' <tbody id="doc-tbody"></tbody>' +
' </table></div>' +
' <div class="pagination" id="pagination"></div>' +
'</div>'
);
$('#check-all').on('change', function () { $('.row-check').prop('checked', this.checked); });
}
function loadList(page) {
currentPage = page;
var params = buildFilterParam();
params.page = page;
params.limit = PAGE_SIZE;
httpGet('document/list.php', params).then(function (data) {
var rows = data.list || [];
var html = '';
rows.forEach(function (r) {
var isLocal = (r.storage_path || '').indexOf('static/uploads/') === 0;
var link = isLocal ? r.storage_path : (r.storage_path || '#');
html += '<tr>' +
'<td><input type="checkbox" class="row-check" value="' + r.id + '"></td>' +
'<td>' + r.id + '</td>' +
'<td title="' + escHtml(r.doc_name) + '">' + escHtml(r.doc_name) + '</td>' +
'<td>' + escHtml(r.file_type || '-') + '</td>' +
'<td><a href="' + escHtml(link) + '" target="_blank" class="btn btn-sm">查看/下载</a></td>' +
'<td>' + (r.is_current ? '<span class="tag tag-green">当前版本</span>' : '<span class="tag tag-gray">历史</span>') + '</td>' +
'<td>' + escHtml(r.publish_date || '-') + '</td>' +
'<td>' + fmtDate(r.created_at) + '</td>' +
'<td class="ops"><a class="danger" onclick="delDoc(' + r.id + ')">删除</a></td></tr>';
});
if (!rows.length) {
html = '<tr><td colspan="9" class="empty-tip" style="padding:40px 0;">暂无文件</td></tr>';
}
$('#doc-tbody').html(html);
renderPagination($('#pagination'), data, loadList);
});
}
$('#btn-search').on('click', function () { loadList(1); });
$('#btn-reset').on('click', function () {
$('.toolbar [data-filter]').val('');
loadList(1);
});
/* ---------- 上传 ---------- */
$('#btn-upload').on('click', function () {
triggerFileInput('.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.csv,.png,.jpg,.jpeg,.gif,.zip,.rar', function (file) {
var fd = new FormData();
fd.append('file', file);
httpPost('document/upload.php', fd, true).then(function () {
Dialog.success('上传成功', function () { loadList(1); });
});
});
});
/* ---------- 删除(同时删除物理文件) ---------- */
window.delDoc = function (id) {
Dialog.confirm('确定删除该文件吗?将同时删除物理文件,不可恢复!', function () {
httpPost('document/delete.php', { id: id }).then(function () {
Dialog.success('删除成功', function () { loadList(currentPage); });
});
});
};
$('#btn-del-batch').on('click', function () {
var ids = [];
$('.row-check:checked').each(function () { ids.push($(this).val()); });
if (!ids.length) { Dialog.error('请先勾选记录'); return; }
Dialog.confirm('确定删除选中的 ' + ids.length + ' 个文件吗?将同时删除物理文件,不可恢复!', function () {
httpPost('document/delete.php', { ids: ids.join(',') }).then(function () {
Dialog.success('删除成功', function () { loadList(currentPage); });
});
});
});
});
+97
View File
@@ -0,0 +1,97 @@
/**
* login.js - 登录页逻辑(滑块验证 + 表单提交 + 找回密码)
*/
$(function () {
var sliderToken = null;
var slider = new SliderVerify('#slider', function (token) {
sliderToken = token;
$('#login-btn').prop('disabled', false);
});
// 回车提交
$('#password').on('keydown', function (e) {
if (e.keyCode === 13) doLogin();
});
$('#login-btn').on('click', doLogin);
function doLogin() {
var username = $.trim($('#username').val());
var password = $('#password').val();
if (!username) { showMsg('请输入账号', 'error'); return; }
if (!password) { showMsg('请输入密码', 'error'); return; }
if (!sliderToken) { showMsg('请先完成滑块验证', 'error'); return; }
$.ajax({
url: BASE_URL + 'auth/login.php',
method: 'POST',
dataType: 'json',
headers: { 'X-Requested-With': 'XMLHttpRequest' },
data: { username: username, password: password, slider_token: sliderToken }
}).done(function (resp) {
if (resp.code === 0) {
showMsg('登录成功,正在跳转...', 'success');
setTimeout(function () {
location.href = 'dashboard.html';
}, 500);
} else {
showMsg(resp.msg || '登录失败', 'error');
slider.reset();
sliderToken = null;
$('#login-btn').prop('disabled', true);
}
}).fail(function () {
showMsg('网络错误,请重试', 'error');
});
}
function showMsg(msg, type) {
$('#login-msg').attr('class', 'login-msg ' + type).text(msg);
}
// 找回密码弹窗
$('#btn-forgot').on('click', function () {
var idx = layer.open({
type: 1,
title: '找回密码',
area: ['380px', 'auto'],
content:
'<div style="padding:20px 24px;">' +
' <div class="form-item" style="margin-bottom:14px;">' +
' <label>登录账号</label>' +
' <input type="text" id="f-username" style="width:100%;height:38px;border:1px solid #d9dee8;border-radius:4px;padding:0 10px;" placeholder="请输入登录账号">' +
' </div>' +
' <div class="form-item" style="margin-bottom:14px;">' +
' <label>联系邮箱</label>' +
' <input type="text" id="f-email" style="width:100%;height:38px;border:1px solid #d9dee8;border-radius:4px;padding:0 10px;" placeholder="请输入注册邮箱">' +
' </div>' +
' <div id="f-msg" style="display:none;padding:8px 10px;border-radius:4px;font-size:13px;margin-bottom:10px;"></div>' +
'</div>',
btn: ['提交', '取消'],
yes: function (idx) {
var username = $.trim($('#f-username').val());
var email = $.trim($('#f-email').val());
if (!username || !email) {
$('#f-msg').attr('style', 'display:block;padding:8px 10px;border-radius:4px;font-size:13px;margin-bottom:10px;background:#fdecea;color:#c0392b;border:1px solid #f5c6cb;').text('请填写账号和邮箱');
return;
}
$.ajax({
url: BASE_URL + 'auth/forgot.php',
method: 'POST',
dataType: 'json',
headers: { 'X-Requested-With': 'XMLHttpRequest' },
data: { username: username, contact_email: email }
}).done(function (resp) {
var color = resp.code === 0 ? '#27ae60;background:#eafaf1;border:1px solid #a9dfbf' : '#c0392b;background:#fdecea;border:1px solid #f5c6cb';
$('#f-msg').attr('style', 'display:block;padding:8px 10px;border-radius:4px;font-size:13px;margin-bottom:10px;color:' + color).text(resp.msg || '操作完成');
if (resp.code === 0) {
setTimeout(function () { layer.close(idx); }, 1500);
}
}).fail(function () {
$('#f-msg').attr('style', 'display:block;padding:8px 10px;border-radius:4px;font-size:13px;margin-bottom:10px;color:#c0392b;background:#fdecea;border:1px solid #f5c6cb;').text('网络错误,请重试');
});
}
});
});
});
+197
View File
@@ -0,0 +1,197 @@
/**
* marketing.js - 营销服务逻辑(EDM行业+区域二级联动筛选+导出邮箱列表;数据定制/媒体批发占位)
* 页面:edm.html / data_custom.html / media_wholesale.html
*/
$(function () {
// 根据当前页面文件名决定初始化方式
var pageFile = location.pathname.split('/').pop();
if (pageFile === 'data_custom.html') {
initDataCustom();
return;
}
if (pageFile === 'media_wholesale.html') {
initMediaWholesale();
return;
}
/* ============ EDM 页面 ============ */
renderShell('营销服务', '营销服务 / EDM筛选');
renderEdmPage();
initPage('marketing', function (user) {
if (!checkPagePermission('marketing')) return;
loadIndustries();
loadRegions();
loadEmails(1);
});
var currentPage = 1;
/** 渲染 EDM 页面内容(工具栏+表格+分页) */
function renderEdmPage() {
$('#page-content').html(
'<div class="card">' +
' <div class="toolbar">' +
' <select data-filter="industry" id="filter-industry"><option value="">全部行业</option></select>' +
' <select data-filter="province" id="filter-province"><option value="">选择省份/州</option></select>' +
' <select data-filter="city" id="filter-city"><option value="">选择城市/区</option></select>' +
' <input type="text" data-filter="keyword" placeholder="姓名/邮箱关键词">' +
' <button class="btn btn-primary" id="btn-search">筛选</button>' +
' <button class="btn" id="btn-reset">重置</button>' +
' <span class="spacer"></span>' +
' <span style="font-size:13px;color:#8a94a6;">命中 <b id="email-total" style="color:#2a5298;">0</b> 个邮箱</span>' +
' <button class="btn btn-success" id="btn-export">导出CSV</button>' +
' </div>' +
' <div class="table-wrap"><table class="grid">' +
' <thead><tr><th><input type="checkbox" id="check-all"></th><th>邮箱</th><th>姓名</th><th>当前公司</th><th>工作所在地</th><th>家乡</th></tr></thead>' +
' <tbody id="edm-tbody"></tbody>' +
' </table></div>' +
' <div class="pagination" id="pagination"></div>' +
'</div>'
);
$('#check-all').on('change', function () { $('.row-check').prop('checked', this.checked); });
}
function loadIndustries() {
httpGet('marketing/edm.php', { action: 'industries' }).then(function (d) {
var html = '<option value="">全部行业</option>';
(d.industries || []).forEach(function (i) {
html += '<option value="' + escHtml(i) + '">' + escHtml(i) + '</option>';
});
$('#filter-industry').html(html);
});
}
function loadRegions() {
httpGet('marketing/edm.php', { action: 'regions' }).then(function (d) {
var regions = d.regions || {};
var html = '<option value="">选择省份/州</option>';
Object.keys(regions).forEach(function (p) {
html += '<option value="' + escHtml(p) + '">' + escHtml(p) + '</option>';
});
$('#filter-province').html(html).data('regions', regions);
$('#filter-city').html('<option value="">选择城市/区</option>');
});
}
// 二级联动:选省后加载市
$('#filter-province').on('change', function () {
var regions = $(this).data('regions') || {};
var province = $(this).val();
var html = '<option value="">选择城市/区</option>';
(regions[province] || []).forEach(function (c) {
html += '<option value="' + escHtml(c) + '">' + escHtml(c) + '</option>';
});
$('#filter-city').html(html);
});
function loadEmails(page) {
currentPage = page;
var params = buildFilterParam();
params.action = 'filter';
params.page = page;
params.limit = PAGE_SIZE;
httpGet('marketing/edm.php', params).then(function (data) {
var rows = data.list || [];
var html = '';
rows.forEach(function (r) {
html += '<tr>' +
'<td><input type="checkbox" class="row-check" value="' + r.email + '"></td>' +
'<td>' + escHtml(r.email) + '</td>' +
'<td>' + escHtml(r.full_name || '-') + '</td>' +
'<td>' + escHtml(r.company_name || '-') + '</td>' +
'<td>' + escHtml(r.work_location || '-') + '</td>' +
'<td>' + escHtml(r.hometown || '-') + '</td></tr>';
});
if (!rows.length) {
html = '<tr><td colspan="6" class="empty-tip" style="padding:40px 0;">暂无匹配邮箱</td></tr>';
}
$('#edm-tbody').html(html);
renderPagination($('#pagination'), data, loadEmails);
$('#email-total').text(data.total || 0);
});
}
$('#btn-search').on('click', function () { loadEmails(1); });
$('#btn-reset').on('click', function () {
$('.toolbar [data-filter]').val('');
$('#filter-city').html('<option value="">选择城市/区</option>');
loadEmails(1);
});
/* ---------- 导出CSV(仅导出勾选的邮箱,未勾选弹窗提醒) ---------- */
$('#btn-export').on('click', function () {
var emails = [];
$('.row-check:checked').each(function () { emails.push($(this).val()); });
if (!emails.length) {
layer.alert('请先选择需要导出的邮箱', { icon: 2, title: '提示' });
return;
}
download('marketing/edm_export.php', { emails: emails.join(',') });
});
/* ============ 数据定制(占位) ============ */
function initDataCustom() {
renderShell('数据定制', '营销服务 / 数据定制');
initPage('marketing', function (user) {
if (!checkPagePermission('marketing')) return;
httpGet('marketing/data_custom.php').then(function (d) {
$('#page-content').html(
'<div class="card"><div class="placeholder-box">' +
'<div class="big">' + (ICONS.data || '') + '</div>' +
'<h3>' + escHtml(d.title || '数据定制') + '</h3>' +
'<p>' + escHtml(d.description || '功能建设中') + '</p>' +
'</div>' +
'<div class="form-grid" style="max-width:720px;margin:0 auto;">' +
' <div class="form-item"><label>联系人</label><input type="text" id="dc-name"></div>' +
' <div class="form-item"><label>联系电话</label><input type="text" id="dc-phone"></div>' +
' <div class="form-item full"><label>定制需求说明</label><textarea id="dc-req" style="min-height:90px;"></textarea></div>' +
' <div class="form-item full" style="text-align:right;"><button class="btn btn-primary" id="dc-submit">提交需求(占位)</button></div>' +
'</div></div>'
);
$('#dc-submit').on('click', function () {
httpPost('marketing/data_custom.php', {
action: 'submit',
contact_name: $('#dc-name').val(),
contact_phone: $('#dc-phone').val(),
requirements: $('#dc-req').val()
}).then(function (d) {
Dialog.alert(d.msg || '已提交');
});
});
});
});
}
/* ============ 媒体批发(占位) ============ */
function initMediaWholesale() {
renderShell('媒体批发', '营销服务 / 媒体批发');
initPage('marketing', function (user) {
if (!checkPagePermission('marketing')) return;
httpGet('marketing/media_wholesale.php').then(function (d) {
$('#page-content').html(
'<div class="card"><div class="placeholder-box">' +
'<div class="big">' + (ICONS.media || '') + '</div>' +
'<h3>' + escHtml(d.title || '媒体批发') + '</h3>' +
'<p>' + escHtml(d.description || '功能建设中') + '</p>' +
'</div>' +
'<div class="form-grid" style="max-width:720px;margin:0 auto;">' +
' <div class="form-item full"><label>媒体账号ID(逗号分隔)</label><input type="text" id="mw-ids" placeholder="如 1,2,3"></div>' +
' <div class="form-item full"><label>需求说明</label><textarea id="mw-req" style="min-height:90px;"></textarea></div>' +
' <div class="form-item full" style="text-align:right;"><button class="btn btn-primary" id="mw-submit">提交询价(占位)</button></div>' +
'</div></div>'
);
$('#mw-submit').on('click', function () {
httpPost('marketing/media_wholesale.php', {
action: 'inquiry',
media_ids: $('#mw-ids').val(),
remark: $('#mw-req').val()
}).then(function (d) {
Dialog.alert(d.msg || '已提交');
});
});
});
});
}
});
+263
View File
@@ -0,0 +1,263 @@
/**
* media.js - 媒体数据管理逻辑(列表+筛选+分页 / 新增编辑弹窗 / 导入导出 / 详情)
*/
$(function () {
renderShell('媒体数据', '数据管理 / 媒体数据');
renderPage();
initPage('media', function (user) {
if (!checkPagePermission('media')) return;
loadList(1);
});
var currentPage = 1;
/** 渲染页面内容(工具栏+表格+分页) */
function renderPage() {
$('#page-content').html(
'<div class="card">' +
' <div class="toolbar">' +
' <input type="text" data-filter="keyword" placeholder="账号/归属名称关键词">' +
' <select data-filter="platform" id="filter-platform"><option value="">全部平台</option></select>' +
' <select data-filter="certification_type" id="filter-cert"><option value="">全部认证类型</option></select>' +
' <select data-filter="account_level" id="filter-level"><option value="">全部等级</option></select>' +
' <select data-filter="owner_type"><option value="">全部归属</option>' +
' <option value="person">个人</option><option value="company">企业</option></select>' +
' <button class="btn btn-primary" id="btn-search">搜索</button>' +
' <button class="btn" id="btn-reset">重置</button>' +
' <span class="spacer"></span>' +
' <button class="btn btn-success" id="btn-add">+ 新增</button>' +
' <button class="btn btn-warning" id="btn-import">导入CSV</button>' +
' <button class="btn" id="btn-export">导出</button>' +
' <button class="btn btn-danger" id="btn-del-batch">批量删除</button>' +
' </div>' +
' <div class="table-wrap"><table class="grid">' +
' <thead><tr><th><input type="checkbox" id="check-all"></th><th>ID</th><th>归属</th><th>平台</th><th>账号</th><th>认证类型</th><th>等级</th><th>粉丝数</th><th>平均阅读</th><th>内容领域</th><th>操作</th></tr></thead>' +
' <tbody id="media-tbody"></tbody>' +
' </table></div>' +
' <div class="pagination" id="pagination"></div>' +
'</div>'
);
$('#check-all').on('change', function () { $('.row-check').prop('checked', this.checked); });
}
function loadList(page) {
currentPage = page;
var params = buildFilterParam();
params.page = page;
params.limit = PAGE_SIZE;
httpGet('media/list.php', params).then(function (data) {
var rows = data.list || [];
var dict = data.dict || {};
fillDict(dict);
var html = '';
rows.forEach(function (r) {
html += '<tr>' +
'<td><input type="checkbox" class="row-check" value="' + r.id + '"></td>' +
'<td>' + r.id + '</td>' +
'<td>' + escHtml(r.owner_name || r.owner_type + '#' + r.owner_id) + '</td>' +
'<td>' + escHtml(r.platform || '-') + '</td>' +
'<td title="' + escHtml(r.account_id) + '">' + escHtml(r.account_id) + '</td>' +
'<td>' + escHtml(r.certification_type || '-') + '</td>' +
'<td>' + escHtml(r.account_level || '-') + '</td>' +
'<td>' + (r.follower_count || 0) + '</td>' +
'<td>' + (r.avg_read_count || 0) + '</td>' +
'<td>' + escHtml(r.content_categories || '-') + '</td>' +
'<td class="ops">' +
' <a onclick="viewMedia(' + r.id + ')">详情</a>' +
' <a onclick="editMedia(' + r.id + ')">编辑</a>' +
' <a class="danger" onclick="delMedia(' + r.id + ')">删除</a>' +
'</td></tr>';
});
if (!rows.length) {
html = '<tr><td colspan="11" class="empty-tip" style="padding:40px 0;">暂无数据</td></tr>';
}
$('#media-tbody').html(html);
renderPagination($('#pagination'), data, loadList);
});
}
function fillDict(dict) {
if (dict.platforms && dict.platforms.length) {
var html = '<option value="">全部平台</option>';
dict.platforms.forEach(function (p) { html += '<option value="' + escHtml(p) + '">' + escHtml(p) + '</option>'; });
$('#filter-platform').html(html);
}
if (dict.certification_types && dict.certification_types.length) {
var h2 = '<option value="">全部认证类型</option>';
dict.certification_types.forEach(function (p) { h2 += '<option value="' + escHtml(p) + '">' + escHtml(p) + '</option>'; });
$('#filter-cert').html(h2);
}
if (dict.account_levels && dict.account_levels.length) {
var h3 = '<option value="">全部等级</option>';
dict.account_levels.forEach(function (p) { h3 += '<option value="' + escHtml(p) + '">' + escHtml(p) + '</option>'; });
$('#filter-level').html(h3);
}
}
$('#btn-search').on('click', function () { loadList(1); });
$('#btn-reset').on('click', function () {
$('.toolbar [data-filter]').val('');
loadList(1);
});
$('#btn-add').on('click', function () { openForm(null); });
window.editMedia = function (id) {
httpGet('media/detail.php', { id: id }).then(function (d) {
openForm(d.media);
});
};
function openForm(media) {
var isEdit = !!media;
var m = media || {};
var content =
'<form id="media-form" style="padding:18px 22px 4px;">' +
'<div class="form-grid">' +
' <div class="form-item"><label>归属类型<span class="req">*</span></label><select name="owner_type" ' + (isEdit ? 'disabled' : '') + '>' +
' <option value="person"' + (m.owner_type === 'person' ? ' selected' : '') + '>个人</option>' +
' <option value="company"' + (m.owner_type === 'company' ? ' selected' : '') + '>企业</option></select></div>' +
' <div class="form-item"><label>归属ID<span class="req">*</span></label><input type="number" name="owner_id" value="' + escHtml(m.owner_id) + '" ' + (isEdit ? 'disabled' : '') + '></div>' +
' <div class="form-item"><label>平台<span class="req">*</span></label>' +
' <select name="platform">' + platformOpts(m.platform) + '</select></div>' +
' <div class="form-item"><label>账号/号码<span class="req">*</span></label><input type="text" name="account_id" value="' + escHtml(m.account_id) + '"></div>' +
' <div class="form-item"><label>主页链接</label><input type="text" name="profile_url" value="' + escHtml(m.profile_url) + '"></div>' +
' <div class="form-item"><label>默认联系方式</label><select name="is_primary">' +
' <option value="0"' + (!m.is_primary ? ' selected' : '') + '>否</option>' +
' <option value="1"' + (m.is_primary ? ' selected' : '') + '>是</option></select></div>' +
' <div class="form-item"><label>账号等级</label><select name="account_level">' + levelOpts(m.account_level) + '</select></div>' +
' <div class="form-item"><label>认证类型</label><select name="certification_type">' + certOpts(m.certification_type) + '</select></div>' +
' <div class="form-item"><label>粉丝数</label><input type="number" name="follower_count" value="' + escHtml(m.follower_count) + '"></div>' +
' <div class="form-item"><label>平均阅读量</label><input type="number" name="avg_read_count" value="' + escHtml(m.avg_read_count) + '"></div>' +
' <div class="form-item full"><label>内容领域(逗号分隔)</label><input type="text" name="content_categories" value="' + escHtml(m.content_categories) + '"></div>' +
' <div class="form-item full"><label>备注</label><textarea name="remark">' + escHtml(m.remark) + '</textarea></div>' +
'</div>' +
'<div class="dialog-footer"><button type="button" class="btn" id="form-cancel">取消</button>' +
'<button type="submit" class="btn btn-primary">保存</button></div>' +
'</form>';
var idx = Dialog.open({
title: isEdit ? '编辑媒体账号' : '新增媒体账号',
area: ['720px', 'auto'],
content: content,
btn: false
});
$('#form-cancel').on('click', function () { Dialog.close(idx); });
$('#media-form').on('submit', function (e) {
e.preventDefault();
var data = {};
$(this).find('input, select, textarea').each(function () {
var name = $(this).attr('name');
if (!name) return;
if ($(this).attr('type') === 'number') {
data[name] = $(this).val() === '' ? '' : parseInt($(this).val(), 10);
} else {
data[name] = $(this).val();
}
});
if (!data.platform || !data.account_id) { Dialog.error('平台和账号为必填项'); return; }
if (!isEdit && (!data.owner_type || !data.owner_id)) { Dialog.error('归属类型和归属ID为必填项'); return; }
if (isEdit) data.id = m.id;
httpPost(isEdit ? 'media/update.php' : 'media/add.php', data).then(function () {
Dialog.success('保存成功', function () {
Dialog.close(idx);
loadList(currentPage);
});
});
});
}
function platformOpts(sel) {
var list = ['phone', 'email', 'wechat', 'douyin', 'linkedin', 'x', 'facebook', 'instagram', 'tiktok', 'other'];
var html = '';
list.forEach(function (p) {
html += '<option value="' + p + '"' + (sel === p ? ' selected' : '') + '>' + p + '</option>';
});
return html;
}
function levelOpts(sel) {
var list = ['头部', '腰部', '尾部', '达人', '素人', '媒体号'];
var html = '<option value="">-</option>';
list.forEach(function (p) {
html += '<option value="' + p + '"' + (sel === p ? ' selected' : '') + '>' + p + '</option>';
});
return html;
}
function certOpts(sel) {
var list = ['个人认证', '企业认证', '媒体机构认证', '未认证'];
var html = '<option value="">-</option>';
list.forEach(function (p) {
html += '<option value="' + p + '"' + (sel === p ? ' selected' : '') + '>' + p + '</option>';
});
return html;
}
window.delMedia = function (id) {
Dialog.confirm('确定删除该媒体账号吗?(软删除)', function () {
httpPost('media/delete.php', { id: id }).then(function () {
Dialog.success('删除成功', function () { loadList(currentPage); });
});
});
};
$('#btn-del-batch').on('click', function () {
var ids = [];
$('.row-check:checked').each(function () { ids.push($(this).val()); });
if (!ids.length) { Dialog.error('请先勾选记录'); return; }
Dialog.confirm('确定删除选中的 ' + ids.length + ' 条记录吗?', function () {
httpPost('media/delete.php', { ids: ids.join(',') }).then(function () {
Dialog.success('删除成功', function () { loadList(currentPage); });
});
});
});
$('#btn-import').on('click', function () {
triggerFileInput('.csv', function (file) {
var fd = new FormData();
fd.append('file', file);
httpPost('media/import.php', fd, true).then(function (d) {
Dialog.alert('导入结果:成功 ' + d.inserted + ' 条,失败 ' + d.failed + ' 条', function () {
loadList(1);
});
});
});
});
/* ---------- 导出(仅导出勾选的媒体数据,未勾选弹窗提醒) ---------- */
$('#btn-export').on('click', function () {
var ids = [];
$('.row-check:checked').each(function () { ids.push($(this).val()); });
if (!ids.length) {
layer.alert('请先选择需要导出的媒体数据', { icon: 2, title: '提示' });
return;
}
download('media/export.php', { ids: ids.join(',') });
});
window.viewMedia = function (id) {
httpGet('media/detail.php', { id: id }).then(function (d) {
var m = d.media;
var html =
'<div style="padding:18px 22px;">' +
' <h3 style="margin-bottom:14px;">' + escHtml(m.platform) + ':' + escHtml(m.account_id) + '</h3>' +
' <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px 24px;font-size:13px;">' +
' <div><b>归属:</b>' + escHtml(m.owner_type === 'person' ? '个人' : '企业') + ' #' + m.owner_id + '</div>' +
' <div><b>主页:</b>' + (m.profile_url ? '<a href="' + escHtml(m.profile_url) + '" target="_blank">打开</a>' : '-') + '</div>' +
' <div><b>等级:</b>' + escHtml(m.account_level || '-') + '</div>' +
' <div><b>认证:</b>' + escHtml(m.certification_type || '-') + '</div>' +
' <div><b>粉丝数:</b>' + (m.follower_count || 0) + '</div>' +
' <div><b>平均阅读:</b>' + (m.avg_read_count || 0) + '</div>' +
' <div><b>内容领域:</b>' + escHtml(m.content_categories || '-') + '</div>' +
' <div><b>创建时间:</b>' + fmtDate(m.created_at) + '</div>' +
' </div>' +
' <h4 style="margin:16px 0 8px;">备注</h4><div style="font-size:13px;color:#5a6472;">' + escHtml(m.remark || '-') + '</div>' +
' <h4 style="margin:16px 0 8px;">特殊要求</h4><div style="font-size:13px;color:#5a6472;">' + escHtml(m.special_requirements || '-') + '</div>' +
' <h4 style="margin:16px 0 8px;">内部评估</h4><div style="font-size:13px;color:#5a6472;">' + escHtml(m.media_remark || '-') + '</div>' +
'</div>';
Dialog.open({ title: '媒体账号详情', area: ['680px', 'auto'], content: html, btn: ['关闭'] });
});
}
});
+190
View File
@@ -0,0 +1,190 @@
/**
* need.js - 需求转盘逻辑(列表+筛选+分页 / 新增编辑 / 推送按钮)
*/
$(function () {
renderShell('需求转盘', '业务模块 / 需求转盘');
renderPage();
initPage('need', function (user) {
if (!checkPagePermission('need')) return;
loadList(1);
});
var currentPage = 1;
/** 渲染页面内容(工具栏+表格+分页) */
function renderPage() {
$('#page-content').html(
'<div class="card">' +
' <div class="toolbar">' +
' <input type="text" data-filter="keyword" placeholder="描述/联系人/公司名关键词">' +
' <input type="text" data-filter="company_name" placeholder="公司名称">' +
' <input type="text" data-filter="industry" placeholder="行业(精确)">' +
' <input type="text" data-filter="need_category" placeholder="需求大类">' +
' <input type="date" data-filter="start_date" title="开始日期">' +
' <input type="date" data-filter="end_date" title="结束日期">' +
' <button class="btn btn-primary" id="btn-search">搜索</button>' +
' <button class="btn" id="btn-reset">重置</button>' +
' <span class="spacer"></span>' +
' <button class="btn btn-success" id="btn-add">+ 新增</button>' +
' <button class="btn btn-warning" id="btn-push-batch">批量推送</button>' +
' </div>' +
' <div class="table-wrap"><table class="grid">' +
' <thead><tr><th><input type="checkbox" id="check-all"></th><th>ID</th><th>公司</th><th>行业</th><th>联系人</th><th>需求大类</th><th>意向品类</th><th>描述</th><th>状态</th><th>录入时间</th><th>操作</th></tr></thead>' +
' <tbody id="need-tbody"></tbody>' +
' </table></div>' +
' <div class="pagination" id="pagination"></div>' +
'</div>'
);
$('#check-all').on('change', function () { $('.row-check').prop('checked', this.checked); });
}
function loadList(page) {
currentPage = page;
var params = buildFilterParam();
params.page = page;
params.limit = PAGE_SIZE;
httpGet('need/list.php', params).then(function (data) {
var rows = data.list || [];
var html = '';
rows.forEach(function (r) {
html += '<tr>' +
'<td><input type="checkbox" class="row-check" value="' + r.id + '"></td>' +
'<td>' + r.id + '</td>' +
'<td title="' + escHtml(r.display_name || '') + '">' + escHtml(r.display_name || '-') + '</td>' +
'<td>' + escHtml(r.industry || '-') + '</td>' +
'<td>' + escHtml(r.contact_person || '-') + '</td>' +
'<td>' + escHtml(r.need_category || '-') + '</td>' +
'<td>' + escHtml(r.target_product_category || '-') + '</td>' +
'<td title="' + escHtml(r.description || '') + '">' + escHtml((r.description || '-').substring(0, 30)) + '</td>' +
'<td>' + (r.is_valid ? '<span class="tag tag-green">有效</span>' : '<span class="tag tag-gray">已作废</span>') + '</td>' +
'<td>' + fmtDate(r.created_at) + '</td>' +
'<td class="ops">' +
' <a onclick="viewNeed(' + r.id + ')">详情</a>' +
' <a onclick="editNeed(' + r.id + ')">编辑</a>' +
' <a onclick="pushNeed(' + r.id + ')">推送</a>' +
(r.is_valid ? ' <a class="danger" onclick="delNeed(' + r.id + ')">作废</a>' : '') +
'</td></tr>';
});
if (!rows.length) {
html = '<tr><td colspan="11" class="empty-tip" style="padding:40px 0;">暂无数据</td></tr>';
}
$('#need-tbody').html(html);
renderPagination($('#pagination'), data, loadList);
});
}
$('#btn-search').on('click', function () { loadList(1); });
$('#btn-reset').on('click', function () {
$('.toolbar [data-filter]').val('');
loadList(1);
});
$('#btn-add').on('click', function () { openForm(null); });
window.editNeed = function (id) {
httpGet('need/detail.php', { id: id }).then(function (d) {
openForm(d.need);
});
};
function openForm(need) {
var isEdit = !!need;
var n = need || {};
var content =
'<form id="need-form" style="padding:18px 22px 4px;">' +
'<div class="form-grid">' +
' <div class="form-item full"><label>所属公司ID<span class="req">*</span></label><input type="number" name="company_id" value="' + escHtml(n.company_id) + '" ' + (isEdit ? 'disabled' : '') + ' placeholder="公司ID(如 1)"></div>' +
' <div class="form-item"><label>需求联系人</label><input type="text" name="contact_person" value="' + escHtml(n.contact_person) + '"></div>' +
' <div class="form-item"><label>需求大类</label><input type="text" name="need_category" value="' + escHtml(n.need_category) + '" placeholder="设备采购/原材料/咨询服务等"></div>' +
' <div class="form-item"><label>意向产品品类</label><input type="text" name="target_product_category" value="' + escHtml(n.target_product_category) + '"></div>' +
' <div class="form-item"><label>业务场景/应用工序</label><input type="text" name="application_scenario" value="' + escHtml(n.application_scenario) + '" placeholder="如:码垛、焊接"></div>' +
' <div class="form-item full"><label>详细需求描述</label><textarea name="description" style="min-height:100px;">' + escHtml(n.description) + '</textarea></div>' +
'</div>' +
'<div class="dialog-footer"><button type="button" class="btn" id="form-cancel">取消</button>' +
'<button type="submit" class="btn btn-primary">保存</button></div>' +
'</form>';
var idx = Dialog.open({
title: isEdit ? '编辑需求' : '新增需求',
area: ['680px', 'auto'],
content: content,
btn: false
});
$('#form-cancel').on('click', function () { Dialog.close(idx); });
$('#need-form').on('submit', function (e) {
e.preventDefault();
var data = {};
$(this).find('input, textarea').each(function () {
var name = $(this).attr('name');
if (!name) return;
if ($(this).attr('type') === 'number') {
data[name] = $(this).val() === '' ? '' : parseInt($(this).val(), 10);
} else {
data[name] = $(this).val();
}
});
if (!isEdit && !data.company_id) { Dialog.error('所属公司ID为必填项'); return; }
if (isEdit) data.id = n.id;
httpPost(isEdit ? 'need/update.php' : 'need/add.php', data).then(function () {
Dialog.success('保存成功', function () {
Dialog.close(idx);
loadList(currentPage);
});
});
});
}
window.delNeed = function (id) {
Dialog.confirm('确定作废该需求吗?', function () {
httpPost('need/delete.php', { id: id }).then(function () {
Dialog.success('操作成功', function () { loadList(currentPage); });
});
});
};
window.pushNeed = function (id) {
Dialog.confirm('确定推送该需求吗?(当前为占位实现)', function () {
httpPost('need/push.php', { id: id }).then(function (d) {
Dialog.alert(d.msg || '推送成功');
});
});
};
$('#btn-push-batch').on('click', function () {
var ids = [];
$('.row-check:checked').each(function () { ids.push($(this).val()); });
if (!ids.length) { Dialog.error('请先勾选记录'); return; }
Dialog.confirm('确定推送选中的 ' + ids.length + ' 条需求吗?(占位实现)', function () {
httpPost('need/push.php', { ids: ids.join(',') }).then(function (d) {
Dialog.alert(d.msg || '推送成功');
});
});
});
window.viewNeed = function (id) {
httpGet('need/detail.php', { id: id }).then(function (d) {
var n = d.need;
var html =
'<div style="padding:18px 22px;">' +
' <h3 style="margin-bottom:14px;">需求 #' + n.id + '(' + escHtml(n.display_name || '-') + ')</h3>' +
' <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px 24px;font-size:13px;">' +
' <div><b>行业:</b>' + escHtml(n.industry || '-') + '</div>' +
' <div><b>国家:</b>' + escHtml(n.country || '-') + '</div>' +
' <div><b>联系人:</b>' + escHtml(n.contact_person || '-') + '</div>' +
' <div><b>需求大类:</b>' + escHtml(n.need_category || '-') + '</div>' +
' <div><b>意向品类:</b>' + escHtml(n.target_product_category || '-') + '</div>' +
' <div><b>应用场景:</b>' + escHtml(n.application_scenario || '-') + '</div>' +
' <div><b>状态:</b>' + (n.is_valid ? '有效' : '已作废') + '</div>' +
' <div><b>录入时间:</b>' + fmtDate(n.created_at) + '</div>' +
' </div>' +
' <h4 style="margin:16px 0 8px;">详细描述</h4>' +
' <div style="font-size:13px;color:#5a6472;white-space:pre-wrap;">' + escHtml(n.description || '-') + '</div>' +
' <div style="margin-top:16px;text-align:right;"><a href="' + escHtml(n.website || '#') + '" target="_blank" class="btn btn-sm" style="' + (n.website ? '' : 'display:none;') + '">访问官网</a></div>' +
'</div>';
Dialog.open({ title: '需求详情', area: ['680px', 'auto'], content: html, btn: ['关闭'] });
});
}
});
+317
View File
@@ -0,0 +1,317 @@
/**
* person.js - 人员数据管理逻辑(列表+筛选+分页 / 新增编辑弹窗 / 导入导出 / 详情)
*/
$(function () {
renderShell('人员数据', '数据管理 / 人员数据');
renderPage();
initPage('person', function (user) {
if (!checkPagePermission('person')) return;
loadList(1);
});
var currentPage = 1;
/** 渲染页面内容(工具栏+表格+分页) */
function renderPage() {
$('#page-content').html(
'<div class="card">' +
' <div class="toolbar">' +
' <input type="text" data-filter="keyword" placeholder="姓名/证件号/手机/邮箱关键词">' +
' <select data-filter="gender"><option value="">全部性别</option>' +
' <option value="男">男</option><option value="女">女</option>' +
' <option value="其他">其他</option><option value="保密">保密</option></select>' +
' <input type="text" data-filter="nationality" placeholder="国籍">' +
' <input type="text" data-filter="work_location" placeholder="工作所在地">' +
' <button class="btn btn-primary" id="btn-search">搜索</button>' +
' <button class="btn" id="btn-reset">重置</button>' +
' <span class="spacer"></span>' +
' <button class="btn btn-success" id="btn-add">+ 新增</button>' +
' <button class="btn btn-warning" id="btn-import">导入CSV</button>' +
' <button class="btn" id="btn-export">导出</button>' +
' <button class="btn btn-danger" id="btn-del-batch">批量删除</button>' +
' </div>' +
' <div class="table-wrap"><table class="grid">' +
' <thead><tr><th><input type="checkbox" id="check-all"></th><th>ID</th><th>姓名</th><th>性别</th><th>国籍</th><th>工作所在地</th><th>家乡</th><th>学历</th><th>联系方式</th><th>创建时间</th><th>操作</th></tr></thead>' +
' <tbody id="person-tbody"></tbody>' +
' </table></div>' +
' <div class="pagination" id="pagination"></div>' +
'</div>'
);
$('#check-all').on('change', function () { $('.row-check').prop('checked', this.checked); });
}
function loadList(page) {
currentPage = page;
var params = buildFilterParam();
params.page = page;
params.limit = PAGE_SIZE;
httpGet('person/list.php', params).then(function (data) {
var rows = data.list || [];
var html = '';
rows.forEach(function (r) {
html += '<tr>' +
'<td><input type="checkbox" class="row-check" value="' + r.id + '"></td>' +
'<td>' + r.id + '</td>' +
'<td>' + escHtml(r.full_name) + '</td>' +
'<td>' + escHtml(r.gender || '-') + '</td>' +
'<td>' + escHtml(r.nationality || '-') + '</td>' +
'<td>' + escHtml(r.work_location || '-') + '</td>' +
'<td>' + escHtml(r.hometown || '-') + '</td>' +
'<td>' + escHtml(r.education || '-') + '</td>' +
'<td title="' + escHtml(r.contacts || '') + '">' + escHtml((r.contacts || '-').substring(0, 40)) + '</td>' +
'<td>' + fmtDate(r.created_at) + '</td>' +
'<td class="ops">' +
' <a onclick="viewPerson(' + r.id + ')">详情</a>' +
' <a onclick="viewCompanies(' + r.id + ')">公司</a>' +
' <a onclick="editPerson(' + r.id + ')">编辑</a>' +
' <a class="danger" onclick="delPerson(' + r.id + ')">删除</a>' +
'</td></tr>';
});
if (!rows.length) {
html = '<tr><td colspan="11" class="empty-tip" style="padding:40px 0;">暂无数据</td></tr>';
}
$('#person-tbody').html(html);
renderPagination($('#pagination'), data, loadList);
});
}
$('#btn-search').on('click', function () { loadList(1); });
$('#btn-reset').on('click', function () {
$('.toolbar [data-filter]').val('');
loadList(1);
});
$('#btn-add').on('click', function () { openForm(null); });
window.editPerson = function (id) {
httpGet('person/detail.php', { id: id }).then(function (d) {
openForm(d.person, d.accounts);
});
};
function contactsInputHtml(accounts) {
accounts = accounts || [];
var html = '<div id="contacts-box">';
if (!accounts.length) {
accounts = [{ platform: 'email', account_id: '', is_primary: 1 }];
}
accounts.forEach(function (a, i) {
html += '<div class="contact-row" style="display:flex;gap:6px;margin-bottom:6px;">' +
'<select name="c-platform" style="width:110px;height:32px;border:1px solid #d9dee8;border-radius:4px;padding:0 6px;">' +
platformOpts(a.platform) + '</select>' +
'<input type="text" name="c-account" placeholder="账号/号码" value="' + escHtml(a.account_id) + '" style="flex:1;height:32px;border:1px solid #d9dee8;border-radius:4px;padding:0 8px;">' +
'<label style="line-height:32px;font-size:12px;"><input type="checkbox" name="c-primary" ' + (a.is_primary ? 'checked' : '') + '> 默认</label>' +
'<button type="button" class="btn btn-sm" onclick="$(this).parent().remove()">删除</button></div>';
});
return html + '</div>' +
'<button type="button" class="btn btn-sm" id="add-contact">+ 添加联系方式</button>';
}
function platformOpts(sel) {
var list = ['phone', 'email', 'wechat', 'douyin', 'linkedin', 'x', 'facebook', 'instagram', 'tiktok', 'other'];
var html = '';
list.forEach(function (p) {
html += '<option value="' + p + '"' + (sel === p ? ' selected' : '') + '>' + p + '</option>';
});
return html;
}
function openForm(person, accounts) {
var isEdit = !!person;
var p = person || {};
var content =
'<form id="person-form" style="padding:18px 22px 4px;">' +
'<div class="form-grid">' +
' <div class="form-item"><label>姓名<span class="req">*</span></label><input type="text" name="full_name" value="' + escHtml(p.full_name) + '"></div>' +
' <div class="form-item"><label>性别</label><select name="gender">' +
' <option value="保密"' + (!p.gender || p.gender === '保密' ? ' selected' : '') + '>保密</option>' +
' <option value="男"' + (p.gender === '男' ? ' selected' : '') + '>男</option>' +
' <option value="女"' + (p.gender === '女' ? ' selected' : '') + '>女</option>' +
' <option value="其他"' + (p.gender === '其他' ? ' selected' : '') + '>其他</option></select></div>' +
' <div class="form-item"><label>国籍</label><input type="text" name="nationality" value="' + escHtml(p.nationality) + '"></div>' +
' <div class="form-item"><label>证件类型</label><input type="text" name="id_type" value="' + escHtml(p.id_type) + '"></div>' +
' <div class="form-item"><label>证件号码</label><input type="text" name="id_number" value="' + escHtml(p.id_number) + '"></div>' +
' <div class="form-item"><label>学历</label><input type="text" name="education" value="' + escHtml(p.education) + '"></div>' +
' <div class="form-item"><label>毕业院校</label><input type="text" name="graduated_from" value="' + escHtml(p.graduated_from) + '"></div>' +
' <div class="form-item"><label>家乡</label><input type="text" name="hometown" value="' + escHtml(p.hometown) + '"></div>' +
' <div class="form-item"><label>工作所在地</label><input type="text" name="work_location" value="' + escHtml(p.work_location) + '"></div>' +
' <div class="form-item full"><label>联系方式</label>' + contactsInputHtml(accounts) + '</div>' +
'</div>' +
'<div class="dialog-footer"><button type="button" class="btn" id="form-cancel">取消</button>' +
'<button type="submit" class="btn btn-primary">保存</button></div>' +
'</form>';
var idx = Dialog.open({
title: isEdit ? '编辑人员' : '新增人员',
area: ['720px', 'auto'],
content: content,
btn: false
});
$('#form-cancel').on('click', function () { Dialog.close(idx); });
$('#add-contact').on('click', function () {
$('#contacts-box').append(
'<div class="contact-row" style="display:flex;gap:6px;margin-bottom:6px;">' +
'<select name="c-platform" style="width:110px;height:32px;border:1px solid #d9dee8;border-radius:4px;padding:0 6px;">' + platformOpts('') + '</select>' +
'<input type="text" name="c-account" placeholder="账号/号码" style="flex:1;height:32px;border:1px solid #d9dee8;border-radius:4px;padding:0 8px;">' +
'<label style="line-height:32px;font-size:12px;"><input type="checkbox" name="c-primary"> 默认</label>' +
'<button type="button" class="btn btn-sm" onclick="$(this).parent().remove()">删除</button></div>'
);
});
$('#person-form').on('submit', function (e) {
e.preventDefault();
var data = {};
$(this).find('input, select').each(function () {
var name = $(this).attr('name');
if (!name) return;
if (name.indexOf('c-') === 0) return;
if ($(this).attr('type') === 'checkbox') {
data[name] = $(this).prop('checked') ? 1 : 0;
} else {
data[name] = $(this).val();
}
});
if (!data.full_name) { Dialog.error('姓名为必填项'); return; }
// 联系方式
var contacts = [];
$('#contacts-box .contact-row').each(function () {
var $row = $(this);
var account = $.trim($row.find('input[name="c-account"]').val());
var platform = $row.find('select[name="c-platform"]').val();
var primary = $row.find('input[name="c-primary"]').prop('checked') ? 1 : 0;
if (account) {
contacts.push({ platform: platform, account_id: account, is_primary: primary });
}
});
data.contacts = JSON.stringify(contacts);
if (isEdit) data.id = p.id;
httpPost(isEdit ? 'person/update.php' : 'person/add.php', data).then(function () {
Dialog.success('保存成功', function () {
Dialog.close(idx);
loadList(currentPage);
});
});
});
}
window.delPerson = function (id) {
Dialog.confirm('确定删除该人员吗?(软删除,可恢复)', function () {
httpPost('person/delete.php', { id: id }).then(function () {
Dialog.success('删除成功', function () { loadList(currentPage); });
});
});
};
$('#btn-del-batch').on('click', function () {
var ids = [];
$('.row-check:checked').each(function () { ids.push($(this).val()); });
if (!ids.length) { Dialog.error('请先勾选记录'); return; }
Dialog.confirm('确定删除选中的 ' + ids.length + ' 条记录吗?', function () {
httpPost('person/delete.php', { ids: ids.join(',') }).then(function () {
Dialog.success('删除成功', function () { loadList(currentPage); });
});
});
});
$('#btn-import').on('click', function () {
triggerFileInput('.csv', function (file) {
var fd = new FormData();
fd.append('file', file);
httpPost('person/import.php', fd, true).then(function (d) {
Dialog.alert('导入结果:成功 ' + d.inserted + ' 条,失败 ' + d.failed + ' 条', function () {
loadList(1);
});
});
});
});
/* ---------- 导出(仅导出勾选的人员,未勾选弹窗提醒) ---------- */
$('#btn-export').on('click', function () {
var ids = [];
$('.row-check:checked').each(function () { ids.push($(this).val()); });
if (!ids.length) {
layer.alert('请先选择需要导出的人员数据', { icon: 2, title: '提示' });
return;
}
download('person/export.php', { ids: ids.join(',') });
});
window.viewPerson = function (id) {
httpGet('person/detail.php', { id: id }).then(function (d) {
var p = d.person;
var accountsHtml = '';
(d.accounts || []).forEach(function (a) {
accountsHtml += '<div style="font-size:13px;padding:3px 0;">' + escHtml(a.platform) + ':' + escHtml(a.account_id) +
(a.is_primary ? ' <span class="tag">默认</span>' : '') + '</div>';
});
var expHtml = '';
(d.experiences || []).forEach(function (e) {
expHtml += '<tr><td>' + escHtml(e.display_name || '-') + '</td><td>' + escHtml(e.position || '-') + '</td>' +
'<td>' + escHtml(e.department || '-') + '</td><td>' + escHtml(e.job_level || '-') + '</td>' +
'<td>' + (e.is_current ? '<span class="tag tag-green">在职</span>' : fmtDate(e.start_date) + ' ~ ' + (e.end_date || '至今')) + '</td></tr>';
});
var html =
'<div style="padding:18px 22px;">' +
' <h3 style="margin-bottom:14px;">' + escHtml(p.full_name) + '</h3>' +
' <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px 24px;font-size:13px;">' +
' <div><b>性别:</b>' + escHtml(p.gender || '-') + '</div>' +
' <div><b>国籍:</b>' + escHtml(p.nationality || '-') + '</div>' +
' <div><b>证件:</b>' + escHtml(p.id_type || '-') + ' / ' + escHtml(p.id_number || '-') + '</div>' +
' <div><b>学历:</b>' + escHtml(p.education || '-') + '</div>' +
' <div><b>毕业院校:</b>' + escHtml(p.graduated_from || '-') + '</div>' +
' <div><b>家乡:</b>' + escHtml(p.hometown || '-') + '</div>' +
' <div><b>工作所在地:</b>' + escHtml(p.work_location || '-') + '</div>' +
' <div><b>union_id:</b>' + escHtml(p.union_id) + '</div>' +
' </div>' +
' <h4 style="margin:16px 0 8px;">联系方式</h4>' + (accountsHtml || '<div style="font-size:13px;color:#b6bfcc;">暂无</div>') +
' <h4 style="margin:16px 0 8px;">工作经历</h4>' +
' <table class="grid" style="font-size:12px;"><tr><th>公司</th><th>职位</th><th>部门</th><th>级别</th><th>时间</th></tr>' +
(expHtml || '<tr><td colspan="5" style="color:#b6bfcc;text-align:center;">暂无</td></tr>') + '</table>' +
'</div>';
Dialog.open({ title: '人员详情', area: ['760px', 'auto'], content: html, btn: ['关闭'] });
});
}
/* ---------- 任职公司弹窗(按人员查看任职公司,分页) ---------- */
var compPersonId = 0;
window.viewCompanies = function (personId) {
compPersonId = personId;
var content =
'<div style="padding:16px 18px 6px;">' +
' <div class="table-wrap"><table class="grid">' +
' <thead><tr><th>公司名称</th><th>部门</th><th>岗位</th><th>职级</th><th>入职日期</th><th>是否在岗</th></tr></thead>' +
' <tbody id="comp-tbody"></tbody>' +
' </table></div>' +
' <div class="pagination" id="comp-pagination"></div>' +
'</div>';
Dialog.open({ title: '任职公司', area: ['720px', 'auto'], content: content, btn: ['关闭'] });
loadCompanies(1);
};
function loadCompanies(page) {
var params = { person_id: compPersonId, page: page, limit: 5 };
httpGet('person/companies.php', params).then(function (data) {
var rows = data.list || [];
var html = '';
rows.forEach(function (r) {
html += '<tr>' +
'<td>' + escHtml(r.company_name) + '</td>' +
'<td>' + escHtml(r.department || '-') + '</td>' +
'<td>' + escHtml(r.position || '-') + '</td>' +
'<td>' + escHtml(r.job_level || '-') + '</td>' +
'<td>' + escHtml(r.start_date || '-') + '</td>' +
'<td>' + (r.is_current ? '<span class="tag tag-green">在岗</span>' : '<span class="tag tag-gray">离岗</span>') + '</td>' +
'</tr>';
});
if (!rows.length) {
html = '<tr><td colspan="6" class="empty-tip" style="padding:30px 0;">暂无任职记录</td></tr>';
}
$('#comp-tbody').html(html);
renderPagination($('#comp-pagination'), data, loadCompanies);
});
}
});
+96
View File
@@ -0,0 +1,96 @@
/**
* preliminary.js - 碎片处理逻辑(待定逻辑,暂做基础列表 + 状态统计)
*/
$(function () {
renderShell('碎片处理', '数据管理 / 碎片处理(占位)');
renderPage();
initPage('preliminary', function (user) {
if (!checkPagePermission('preliminary')) return;
loadStats();
loadList(1);
});
var currentPage = 1;
/** 渲染页面内容(统计卡+工具栏+表格+分页) */
function renderPage() {
$('#page-content').html(
'<div class="stat-cards" id="stat-cards"></div>' +
'<div class="card">' +
' <div class="toolbar">' +
' <input type="text" data-filter="keyword" placeholder="公司/姓名/电话/邮箱关键词">' +
' <select data-filter="status"><option value="">全部状态</option>' +
' <option value="待处理">待处理</option><option value="待分配">待分配</option>' +
' <option value="处理中">处理中</option><option value="已转换">已转换</option>' +
' <option value="已废弃">已废弃</option><option value="已拒收">已拒收</option></select>' +
' <select data-filter="source_type"><option value="">全部类型</option>' +
' <option value="company">公司</option><option value="person">人员</option>' +
' <option value="product">产品</option><option value="need">需求</option><option value="mixed">混合</option></select>' +
' <input type="date" data-filter="start_date" title="开始日期">' +
' <input type="date" data-filter="end_date" title="结束日期">' +
' <button class="btn btn-primary" id="btn-search">搜索</button>' +
' <button class="btn" id="btn-reset">重置</button>' +
' </div>' +
' <div class="table-wrap"><table class="grid">' +
' <thead><tr><th>ID</th><th>类型</th><th>公司</th><th>姓名</th><th>电话</th><th>邮箱</th><th>意向品类</th><th>描述</th><th>状态</th><th>跟进人</th><th>录入时间</th></tr></thead>' +
' <tbody id="pre-tbody"></tbody>' +
' </table></div>' +
' <div class="pagination" id="pagination"></div>' +
'</div>'
);
}
function loadStats() {
httpGet('preliminary/stats.php').then(function (d) {
var statusMap = d.status || {};
var html = '';
(d.statuses || []).forEach(function (s) {
var cnt = statusMap[s] || 0;
html += '<div class="stat-card c-' + ['blue', 'orange', 'teal', 'green', 'red', 'gray'][(d.statuses || []).indexOf(s) % 6] + '">' +
'<div class="num">' + cnt + '</div><div class="label">' + escHtml(s) + '</div></div>';
});
$('#stat-cards').html(
'<div class="stat-card c-blue"><div class="num">' + d.total + '</div><div class="label">碎片总数</div></div>' +
'<div class="stat-card c-green"><div class="num">' + d.today + '</div><div class="label">今日新增</div></div>' + html
);
});
}
function loadList(page) {
currentPage = page;
var params = buildFilterParam();
params.page = page;
params.limit = PAGE_SIZE;
httpGet('preliminary/list.php', params).then(function (data) {
var rows = data.list || [];
var html = '';
rows.forEach(function (r) {
var statusCls = { '待处理': 'tag-orange', '待分配': 'tag', '处理中': 'tag', '已转换': 'tag-green', '已废弃': 'tag-gray', '已拒收': 'tag-red' }[r.status] || 'tag';
html += '<tr>' +
'<td>' + r.id + '</td>' +
'<td>' + escHtml(r.source_type || '-') + '</td>' +
'<td>' + escHtml(r.company_name || '-') + '</td>' +
'<td>' + escHtml(r.person_name || '-') + '</td>' +
'<td>' + escHtml(r.contact_phone || '-') + '</td>' +
'<td>' + escHtml(r.contact_email || '-') + '</td>' +
'<td>' + escHtml(r.target_product_category || '-') + '</td>' +
'<td title="' + escHtml(r.description || '') + '">' + escHtml((r.description || '-').substring(0, 26)) + '</td>' +
'<td><span class="tag ' + statusCls + '">' + escHtml(r.status) + '</span></td>' +
'<td>' + escHtml(r.follow_person || '-') + '</td>' +
'<td>' + fmtDate(r.created_at) + '</td></tr>';
});
if (!rows.length) {
html = '<tr><td colspan="11" class="empty-tip" style="padding:40px 0;">暂无数据碎片</td></tr>';
}
$('#pre-tbody').html(html);
renderPagination($('#pagination'), data, loadList);
});
}
$('#btn-search').on('click', function () { loadList(1); });
$('#btn-reset').on('click', function () {
$('.toolbar [data-filter]').val('');
loadList(1);
});
});
+416
View File
@@ -0,0 +1,416 @@
/**
* system.js - 系统管理逻辑(用户管理 / 角色管理 / 操作日志)
* 页面:user.html / role.html / log.html
*/
$(function () {
var pageFile = location.pathname.split('/').pop();
if (pageFile === 'user.html') initUser();
else if (pageFile === 'role.html') initRole();
else if (pageFile === 'log.html') initLog();
else renderShell('权限管理', '系统管理');
});
/* ================= 用户管理 ================= */
function initUser() {
renderShell('用户管理', '权限管理 / 用户');
renderUserPage();
initPage('system', function (user) {
if (!checkPagePermission('system')) return;
loadUsers(1);
});
var currentPage = 1;
function renderUserPage() {
$('#page-content').html(
'<div class="card">' +
' <div class="toolbar">' +
' <input type="text" data-filter="keyword" placeholder="账号/姓名关键词">' +
' <select data-filter="role_id" id="filter-role"><option value="">全部角色</option></select>' +
' <button class="btn btn-primary" id="btn-search">搜索</button>' +
' <button class="btn" id="btn-reset">重置</button>' +
' <span class="spacer"></span>' +
' <button class="btn btn-success" id="btn-add">+ 新增用户</button>' +
' </div>' +
' <div class="table-wrap"><table class="grid">' +
' <thead><tr><th>ID</th><th>账号</th><th>姓名</th><th>角色</th><th>状态</th><th>最后登录</th><th>登录IP</th><th>创建时间</th><th>操作</th></tr></thead>' +
' <tbody id="user-tbody"></tbody>' +
' </table></div>' +
' <div class="pagination" id="pagination"></div>' +
'</div>'
);
}
function loadUsers(page) {
currentPage = page;
var params = buildFilterParam();
params.page = page;
params.limit = PAGE_SIZE;
httpGet('system/user_list.php', params).then(function (data) {
var rows = data.list || [];
var html = '';
rows.forEach(function (r) {
html += '<tr>' +
'<td>' + r.id + '</td>' +
'<td>' + escHtml(r.username) + '</td>' +
'<td>' + escHtml(r.real_name || '-') + '</td>' +
'<td>' + escHtml(r.role_name || '-') + '</td>' +
'<td>' + (r.is_active ? '<span class="tag tag-green">启用</span>' : '<span class="tag tag-red">禁用</span>') + '</td>' +
'<td>' + fmtDate(r.last_login_time) + '</td>' +
'<td>' + escHtml(r.last_login_ip || '-') + '</td>' +
'<td>' + fmtDate(r.created_at) + '</td>' +
'<td class="ops">' +
' <a onclick="editUser(' + r.id + ')">编辑</a>' +
(r.id != 1 ? ' <a class="danger" onclick="delUser(' + r.id + ')">删除</a>' : '') +
'</td></tr>';
});
if (!rows.length) {
html = '<tr><td colspan="9" class="empty-tip" style="padding:40px 0;">暂无用户</td></tr>';
}
$('#user-tbody').html(html);
// 角色下拉
var roles = data.roles || [];
var optHtml = '<option value="">全部角色</option>';
roles.forEach(function (r) {
optHtml += '<option value="' + r.id + '">' + escHtml(r.role_name) + '</option>';
});
$('#filter-role').html(optHtml);
renderPagination($('#pagination'), data, loadUsers);
});
}
$('#btn-search').on('click', function () { loadUsers(1); });
$('#btn-reset').on('click', function () {
$('.toolbar [data-filter]').val('');
loadUsers(1);
});
$('#btn-add').on('click', function () { openUserForm(null); });
window.editUser = function (id) {
var row = null;
$('#user-tbody tr').each(function () {
var $tds = $(this).find('td');
if ($tds.length && $tds.eq(0).text() == id) {
row = {
id: id,
username: $tds.eq(1).text(),
real_name: $tds.eq(2).text() === '-' ? '' : $tds.eq(2).text(),
role_name: $tds.eq(3).text(),
is_active: $tds.eq(4).find('.tag').hasClass('tag-green') ? 1 : 0
};
}
});
openUserForm(row);
};
function openUserForm(user) {
var isEdit = !!user;
var u = user || {};
var roleOptionsHtml = '';
$('#filter-role option').each(function () {
var $o = $(this);
if (!$o.val()) return;
roleOptionsHtml += '<option value="' + $o.val() + '"' + (u.role_name === $o.text() ? ' selected' : '') + '>' + escHtml($o.text()) + '</option>';
});
var content =
'<form id="user-form" style="padding:18px 22px 4px;">' +
'<div class="form-grid">' +
' <div class="form-item"><label>登录账号' + (isEdit ? '' : '<span class="req">*</span>') + '</label>' +
(isEdit ? '<input type="text" value="' + escHtml(u.username) + '" disabled>' : '<input type="text" name="username" placeholder="3-50位字母/数字/下划线">') + '</div>' +
' <div class="form-item"><label>真实姓名</label><input type="text" name="real_name" value="' + escHtml(u.real_name) + '"></div>' +
' <div class="form-item"><label>登录密码' + (isEdit ? '(留空不修改)' : '<span class="req">*</span>') + '</label><input type="password" name="password" placeholder="至少6位"></div>' +
' <div class="form-item"><label>角色<span class="req">*</span></label><select name="role_id">' + roleOptionsHtml + '</select></div>' +
' <div class="form-item"><label>状态</label><select name="is_active">' +
' <option value="1"' + (u.is_active !== 0 ? ' selected' : '') + '>启用</option>' +
' <option value="0"' + (u.is_active === 0 ? ' selected' : '') + '>禁用</option></select></div>' +
'</div>' +
'<div class="dialog-footer"><button type="button" class="btn" id="form-cancel">取消</button>' +
'<button type="submit" class="btn btn-primary">保存</button></div>' +
'</form>';
var idx = Dialog.open({
title: isEdit ? '编辑用户' : '新增用户',
area: ['560px', 'auto'],
content: content,
btn: false
});
$('#form-cancel').on('click', function () { Dialog.close(idx); });
$('#user-form').on('submit', function (e) {
e.preventDefault();
var data = {};
$(this).find('input, select').each(function () {
var name = $(this).attr('name');
if (!name) return;
data[name] = $(this).val();
});
if (isEdit) data.id = u.id;
httpPost(isEdit ? 'system/user_update.php' : 'system/user_add.php', data).then(function () {
Dialog.success('保存成功', function () {
Dialog.close(idx);
loadUsers(currentPage);
});
});
});
}
window.delUser = function (id) {
Dialog.confirm('确定删除该用户吗?', function () {
httpPost('system/user_delete.php', { id: id }).then(function () {
Dialog.success('删除成功', function () { loadUsers(currentPage); });
});
});
};
}
/* ================= 角色管理 ================= */
function initRole() {
renderShell('角色管理', '权限管理 / 角色');
renderRolePage();
initPage('system', function (user) {
if (!checkPagePermission('system')) return;
loadRoles(1);
});
var currentPage = 1;
function renderRolePage() {
$('#page-content').html(
'<div class="card">' +
' <div class="toolbar">' +
' <input type="text" data-filter="keyword" placeholder="角色名称关键词">' +
' <button class="btn btn-primary" id="btn-search">搜索</button>' +
' <button class="btn" id="btn-reset">重置</button>' +
' <span class="spacer"></span>' +
' <button class="btn btn-success" id="btn-add">+ 新增角色</button>' +
' </div>' +
' <div class="table-wrap"><table class="grid">' +
' <thead><tr><th>ID</th><th>角色名称</th><th>权限</th><th>用户数</th><th>状态</th><th>创建时间</th><th>操作</th></tr></thead>' +
' <tbody id="role-tbody"></tbody>' +
' </table></div>' +
' <div class="pagination" id="pagination"></div>' +
'</div>'
);
}
function loadRoles(page) {
currentPage = page;
var params = buildFilterParam();
params.page = page;
params.limit = PAGE_SIZE;
httpGet('system/role_list.php', params).then(function (data) {
var rows = data.list || [];
var html = '';
rows.forEach(function (r) {
var permTags = (r.permissions || []).map(function (p) {
var m = MENU_MAP[p];
return '<span class="tag" style="margin:2px 4px 2px 0;">' + (m ? escHtml(m.name) : escHtml(p)) + '</span>';
}).join('');
html += '<tr>' +
'<td>' + r.id + '</td>' +
'<td>' + escHtml(r.role_name) + '</td>' +
'<td>' + (permTags || '<span class="tag tag-gray">无权限</span>') + '</td>' +
'<td>' + r.user_count + '</td>' +
'<td>' + (r.is_active ? '<span class="tag tag-green">启用</span>' : '<span class="tag tag-red">禁用</span>') + '</td>' +
'<td>' + fmtDate(r.created_at) + '</td>' +
'<td class="ops">' +
(r.id != 1 ? ' <a onclick="editRole(' + r.id + ')">编辑</a>' : '') +
(r.id != 1 ? ' <a class="danger" onclick="delRole(' + r.id + ')">删除</a>' : '<span style="color:#b6bfcc;">内置</span>') +
'</td></tr>';
});
if (!rows.length) {
html = '<tr><td colspan="7" class="empty-tip" style="padding:40px 0;">暂无角色</td></tr>';
}
$('#role-tbody').html(html);
renderPagination($('#pagination'), data, loadRoles);
});
}
$('#btn-search').on('click', function () { loadRoles(1); });
$('#btn-reset').on('click', function () {
$('.toolbar [data-filter]').val('');
loadRoles(1);
});
$('#btn-add').on('click', function () { openRoleForm(null); });
window.editRole = function (id) {
var row = null;
$('#role-tbody tr').each(function () {
var $tds = $(this).find('td');
if ($tds.length && $tds.eq(0).text() == id) {
var perms = [];
$tds.eq(2).find('.tag').each(function () { perms.push($(this).text()); });
row = {
id: id,
role_name: $tds.eq(1).text(),
permissions: perms,
is_active: $tds.eq(4).find('.tag').hasClass('tag-green') ? 1 : 0
};
}
});
openRoleForm(row);
};
function openRoleForm(role) {
var isEdit = !!role;
var r = role || {};
var permHtml = '';
PERMISSION_OPTIONS.forEach(function (p) {
var checked = (r.permissions || []).indexOf(p.key) >= 0 ? 'checked' : '';
permHtml += '<label style="display:inline-block;min-width:110px;margin:4px 8px 4px 0;font-size:13px;">' +
'<input type="checkbox" name="perm" value="' + p.key + '" ' + checked + '> ' + p.name + '</label>';
});
var content =
'<form id="role-form" style="padding:18px 22px 4px;">' +
'<div class="form-grid">' +
' <div class="form-item"><label>角色名称<span class="req">*</span></label><input type="text" name="role_name" value="' + escHtml(r.role_name) + '"></div>' +
' <div class="form-item"><label>状态</label><select name="is_active">' +
' <option value="1"' + (r.is_active !== 0 ? ' selected' : '') + '>启用</option>' +
' <option value="0"' + (r.is_active === 0 ? ' selected' : '') + '>禁用</option></select></div>' +
' <div class="form-item full"><label>权限分配</label><div style="border:1px solid #eef0f4;border-radius:4px;padding:10px;background:#fafbfd;">' + permHtml + '</div></div>' +
'</div>' +
'<div class="dialog-footer"><button type="button" class="btn" id="form-cancel">取消</button>' +
'<button type="submit" class="btn btn-primary">保存</button></div>' +
'</form>';
var idx = Dialog.open({
title: isEdit ? '编辑角色' : '新增角色',
area: ['620px', 'auto'],
content: content,
btn: false
});
$('#form-cancel').on('click', function () { Dialog.close(idx); });
$('#role-form').on('submit', function (e) {
e.preventDefault();
var perms = [];
$('input[name="perm"]:checked').each(function () { perms.push($(this).val()); });
var data = {
role_name: $('input[name="role_name"]').val(),
is_active: $('select[name="is_active"]').val(),
permissions: JSON.stringify(perms)
};
if (isEdit) data.id = r.id;
httpPost(isEdit ? 'system/role_update.php' : 'system/role_add.php', data).then(function () {
Dialog.success('保存成功', function () {
Dialog.close(idx);
loadRoles(currentPage);
});
});
});
}
window.delRole = function (id) {
Dialog.confirm('确定删除该角色吗?', function () {
httpPost('system/role_delete.php', { id: id }).then(function () {
Dialog.success('删除成功', function () { loadRoles(currentPage); });
});
});
};
}
/* ================= 操作日志 ================= */
function initLog() {
renderShell('操作日志', '权限管理 / 操作日志(仅超级管理员)');
renderLogPage();
initPage('log', function (user) {
var perms = user.permissions || [];
if (perms.indexOf('log') < 0) {
Dialog.alert('仅超级管理员可查看操作日志', function () {
location.href = 'dashboard.html';
});
return;
}
loadLogs(1);
});
var currentPage = 1;
function renderLogPage() {
$('#page-content').html(
'<div class="card">' +
' <div class="toolbar">' +
' <input type="text" data-filter="keyword" placeholder="账号/内容/模块关键词">' +
' <select data-filter="action" id="filter-action"><option value="">全部操作</option></select>' +
' <select data-filter="module" id="filter-module"><option value="">全部模块</option></select>' +
' <input type="text" data-filter="username" placeholder="操作人账号">' +
' <input type="date" data-filter="start_date" title="开始日期">' +
' <input type="date" data-filter="end_date" title="结束日期">' +
' <button class="btn btn-primary" id="btn-search">搜索</button>' +
' <button class="btn" id="btn-reset">重置</button>' +
' </div>' +
' <div class="table-wrap"><table class="grid">' +
' <thead><tr><th>ID</th><th>账号</th><th>操作</th><th>模块</th><th>对象表</th><th>对象ID</th><th>内容</th><th>IP</th><th>时间</th></tr></thead>' +
' <tbody id="log-tbody"></tbody>' +
' </table></div>' +
' <div class="pagination" id="pagination"></div>' +
'</div>'
);
}
function loadLogs(page) {
currentPage = page;
var params = buildFilterParam();
params.page = page;
params.limit = PAGE_SIZE;
httpGet('system/log_list.php', params).then(function (data) {
var rows = data.list || [];
var html = '';
rows.forEach(function (r) {
var content = '';
try {
var obj = JSON.parse(r.content || 'null');
content = obj ? JSON.stringify(obj) : '';
} catch (e) {}
html += '<tr>' +
'<td>' + r.id + '</td>' +
'<td>' + escHtml(r.username) + '</td>' +
'<td><span class="tag">' + escHtml(r.action) + '</span></td>' +
'<td>' + escHtml(r.module) + '</td>' +
'<td>' + escHtml(r.target_table || '-') + '</td>' +
'<td>' + (r.target_id || '-') + '</td>' +
'<td title="' + escHtml(content) + '">' + escHtml((content || '-').substring(0, 30)) + '</td>' +
'<td>' + escHtml(r.ip || '-') + '</td>' +
'<td>' + fmtDate(r.created_at) + '</td></tr>';
});
if (!rows.length) {
html = '<tr><td colspan="9" class="empty-tip" style="padding:40px 0;">近1个月暂无操作日志</td></tr>';
}
$('#log-tbody').html(html);
renderPagination($('#pagination'), data, loadLogs);
// 筛选下拉
var dict = data.dict || {};
if (dict.actions && dict.actions.length) {
var h = '<option value="">全部操作</option>';
dict.actions.forEach(function (a) { h += '<option value="' + escHtml(a) + '">' + escHtml(a) + '</option>'; });
$('#filter-action').html(h);
}
if (dict.modules && dict.modules.length) {
var h2 = '<option value="">全部模块</option>';
dict.modules.forEach(function (m) { h2 += '<option value="' + escHtml(m) + '">' + escHtml(m) + '</option>'; });
$('#filter-module').html(h2);
}
});
}
$('#btn-search').on('click', function () { loadLogs(1); });
$('#btn-reset').on('click', function () {
$('.toolbar [data-filter]').val('');
loadLogs(1);
});
}