Files
superlink/static/js/person.js
T
nanguaboss 2cb688ed46 0806
2026-08-06 00:50:23 +08:00

445 lines
23 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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>' +
' </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="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>' + escHtml(r.phone || '-') + '</td>' +
'<td title="' + escHtml(r.email || '') + '">' + escHtml((r.email || '-').substring(0, 30)) + '</td>' +
'<td>' + fmtDate(r.created_at) + '</td>' +
'<td class="ops">' +
' <a onclick="viewPerson(' + r.id + ')">详情</a>' +
' <a onclick="viewResume(' + r.id + ')">履历</a>' +
' <a onclick="editPerson(' + r.id + ')">编辑</a>' +
'</td></tr>';
});
if (!rows.length) {
html = '<tr><td colspan="12" 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, null, null, null, null); });
window.editPerson = function (id) {
httpGet('person/detail.php', { id: id }).then(function (d) {
openForm(d.person, d.accounts, d.phone, d.email, d.experiences);
});
};
/** 手机号输入后自动识别归属地(录入手机号码时自动识别:国内-国家省-市;港澳台-中国香港/澳门/台湾;海外-国籍) */
function bindPhoneGeo($phoneInput, $geoHint) {
var timer = null;
$phoneInput.on('blur', function () {
var val = $.trim($(this).val());
if (!val) { $geoHint.text('').hide(); return; }
clearTimeout(timer);
timer = setTimeout(function () {
httpGet('common/phone_geo.php', { phone: val }).then(function (d) {
if (d && d.label && d.label !== '未知') {
$geoHint.text('归属地:' + d.label).show();
} else {
$geoHint.text('归属地:未能识别').show();
}
}).catch(function () { $geoHint.text('').hide(); });
}, 300);
});
}
/** 省市区三级联动下拉(数据来自 date_dict_area 字典)
* value 格式:"省-市-区县"(直辖市省略市,如 "北京市-东城区") */
function areaSelectsHtml(target, value) {
return '<div class="area-cascade" data-target="' + target + '" style="display:flex;gap:6px;">' +
'<select class="area-prov" style="flex:1;min-width:0;"><option value="">省</option></select>' +
'<select class="area-city" style="flex:1;min-width:0;"><option value="">市</option></select>' +
'<select class="area-dist" style="flex:1;min-width:0;"><option value="">区/县</option></select>' +
'</div>' +
'<input type="hidden" name="' + target + '" value="' + escHtml(value || '') + '">';
}
/** 构建省/市/区三级联动:填充选项并按已有值回选 */
function initAreaCascade(areas) {
var provMap = {}; // code -> name
var childMap = {}; // parent_code -> [{code,name}]
(areas || []).forEach(function (a) {
if (!a.parent_code) {
provMap[a.code] = a.name;
} else {
(childMap[a.parent_code] = childMap[a.parent_code] || []).push({ code: a.code, name: a.name });
}
});
var provCodes = Object.keys(provMap);
function fillProv($box) {
var html = '<option value="">省</option>';
provCodes.forEach(function (code) {
html += '<option value="' + code + '">' + escHtml(provMap[code]) + '</option>';
});
$box.find('.area-prov').html(html);
}
function fillCity($box, provCode) {
var html = '<option value="">市</option>';
(childMap[provCode] || []).forEach(function (c) {
html += '<option value="' + c.code + '">' + escHtml(c.name) + '</option>';
});
$box.find('.area-city').html(html);
$box.find('.area-dist').html('<option value="">区/县</option>');
}
function fillDist($box, cityCode) {
var html = '<option value="">区/县</option>';
(childMap[cityCode] || []).forEach(function (c) {
html += '<option value="' + c.code + '">' + escHtml(c.name) + '</option>';
});
$box.find('.area-dist').html(html);
}
function combine($box) {
var target = $box.data('target');
var provName = $box.find('.area-prov option:selected').text();
var cityName = $box.find('.area-city option:selected').text();
var distName = $box.find('.area-dist option:selected').text();
var parts = [];
if ($box.find('.area-prov').val()) parts.push(provName);
if ($box.find('.area-city').val() && cityName !== provName) parts.push(cityName);
if ($box.find('.area-dist').val()) parts.push(distName);
$box.closest('.form-item').find('input[name="' + target + '"]').val(parts.join('-'));
}
$('.area-cascade').each(function () {
var $box = $(this);
var target = $box.data('target');
var current = $box.closest('.form-item').find('input[name="' + target + '"]').val() || '';
var parts = current.split('-');
var provName = parts[0] || '';
var cityName = parts[1] || '';
var distName = parts[2] || '';
fillProv($box);
// 回选省
var provCode = '';
provCodes.forEach(function (code) {
if (provMap[code] === provName) {
provCode = code;
$box.find('.area-prov').val(code);
}
});
if (!provCode) return;
fillCity($box, provCode);
var cityList = childMap[provCode] || [];
var cityCode = '';
// 直接按市名匹配;直辖市(省/市同名,如"北京市-东城区")时 parts[1] 实为区县
var direct = cityList.filter(function (c) { return c.name === cityName; });
if (direct.length) {
cityCode = direct[0].code;
$box.find('.area-city').val(cityCode);
} else if (cityList.length === 1 && cityList[0].name === provName) {
cityCode = cityList[0].code;
$box.find('.area-city').val(cityCode);
if (parts[1] && !distName) {
distName = parts[1]; // 直辖市:第二段实为区县
}
}
if (!cityCode) return;
fillDist($box, cityCode);
(childMap[cityCode] || []).forEach(function (c) {
if (c.name === distName) $box.find('.area-dist').val(c.code);
});
});
// 联动事件
$('.area-cascade').off('change.area').on('change.area', '.area-prov', function () {
var $box = $(this).closest('.area-cascade');
fillCity($box, $(this).val());
combine($box);
});
$('.area-cascade').off('change.areaCity').on('change.areaCity', '.area-city', function () {
var $box = $(this).closest('.area-cascade');
fillDist($box, $(this).val());
combine($box);
});
$('.area-cascade').off('change.areaDist').on('change.areaDist', '.area-dist', function () {
combine($(this).closest('.area-cascade'));
});
}
function openForm(person, accounts, phone, email, experiences) {
var isEdit = !!person;
var p = person || {};
var pPhone = phone || '';
var pEmail = email || '';
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) + '" list="person-country-list" placeholder="选择或输入">' +
' <datalist id="person-country-list"></datalist></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>' + areaSelectsHtml('hometown', p.hometown) + '</div>' +
' <div class="form-item"><label>工作所在地</label>' + areaSelectsHtml('work_location', p.work_location) + '</div>' +
' <div class="form-item"><label>手机</label>' +
' <div><input type="text" name="phone" id="person-phone" value="' + escHtml(pPhone) + '" placeholder="常用手机号">' +
' <div class="geo-hint" id="person-phone-geo" style="font-size:12px;color:#2f8f46;margin-top:4px;display:none;"></div></div></div>' +
' <div class="form-item"><label>邮箱</label><input type="text" name="email" value="' + escHtml(pEmail) + '" placeholder="常用邮箱"></div>' +
' <div class="form-item"><label>来源渠道</label><select name="source_channel" id="person-source-channel"><option value="">-</option></select></div>' +
' <div class="form-item"><label>来源详情</label><input type="text" name="source_detail" value="' + escHtml(p.source_detail) + '" placeholder="如:华南工博会"></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
});
// 手机号归属地自动识别
bindPhoneGeo($('#person-phone'), $('#person-phone-geo'));
// 加载国籍字典 + 行政区划字典(三级联动) + 来源渠道字典
httpGet('common/dicts.php', { type: 'all' }).then(function (d) {
var $dl = $('#person-country-list');
var html = '';
(d.countries || []).forEach(function (c) { html += '<option value="' + escHtml(c.name) + '"></option>'; });
$dl.html(html);
// 省市区三级联动
initAreaCascade(d.areas || []);
var $sc = $('#person-source-channel');
var scHtml = '<option value="">-</option>';
var hasCur = false;
(d.source_channels || []).forEach(function (s) {
var sel = (s.name === p.source_channel) ? ' selected' : '';
if (sel) hasCur = true;
scHtml += '<option value="' + escHtml(s.name) + '"' + sel + '>' + escHtml(s.name) + '</option>';
});
if (p.source_channel && !hasCur) {
scHtml += '<option value="' + escHtml(p.source_channel) + '" selected>' + escHtml(p.source_channel) + '(自定义)</option>';
}
$sc.html(scHtml);
}).catch(function () {});
$('#form-cancel').on('click', function () { Dialog.close(idx); });
$('#person-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 (!data.full_name) { Dialog.error('姓名为必填项'); return; }
// 联系方式(手机/邮箱,均为常用)
var contacts = [];
if ($.trim(data.phone || '')) {
contacts.push({ platform: 'phone', account_id: $.trim(data.phone), is_primary: 1, is_defult: 1 });
}
if ($.trim(data.email || '')) {
contacts.push({ platform: 'email', account_id: $.trim(data.email), is_primary: 1, is_defult: 1 });
}
data.contacts = JSON.stringify(contacts);
delete data.phone;
delete data.email;
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);
});
});
});
}
/* ---------- 导入 ---------- */
$('#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 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>来源渠道:</b>' + escHtml(p.source_channel || '-') + '</div>' +
' <div><b>来源详情:</b>' + escHtml(p.source_detail || '-') + '</div>' +
' <div><b>union_id:</b>' + escHtml(p.union_id) + '</div>' +
' </div>' +
' <h4 style="margin:16px 0 8px;">联系方式</h4>' +
' <div style="font-size:13px;"><b>手机:</b>' + escHtml(d.phone || '-') + '</div>' +
' <div style="font-size:13px;margin-top:4px;"><b>邮箱:</b>' + escHtml(d.email || '-') + '</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.viewResume = function (personId) {
compPersonId = personId;
var content =
'<div style="padding:14px 18px 6px;overflow:hidden;height:330px;">' +
' <div class="toolbar" style="margin-bottom:10px;">' +
' <span style="font-size:13px;color:#5a6472;">状态:</span>' +
' <label style="font-size:13px;color:#5a6472;"><input type="checkbox" id="comp-cur" checked> 在岗</label>' +
' <label style="font-size:13px;color:#5a6472;"><input type="checkbox" id="comp-off" checked> 离职</label>' +
' <button class="btn btn-primary btn-sm" id="comp-search">筛选</button>' +
' <button class="btn btn-sm" id="comp-reset">重置</button>' +
' </div>' +
' <div class="table-wrap"><table class="grid" style="font-size:13px;">' +
' <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);
$('#comp-search').on('click', function () { loadCompanies(1); });
$('#comp-reset').on('click', function () {
$('#comp-cur').prop('checked', true);
$('#comp-off').prop('checked', true);
loadCompanies(1);
});
};
function loadCompanies(page) {
var params = { person_id: compPersonId, page: page, limit: 5 };
var cur = $('#comp-cur').prop('checked');
var off = $('#comp-off').prop('checked');
// 状态筛选:只勾一项则按该项过滤;都不勾视为全部
if (cur && !off) params.is_current = 1;
else if (!cur && off) params.is_current = 0;
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);
});
}
});