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
+53
View File
@@ -0,0 +1,53 @@
<?php
/**
* 人员新增接口 POST /api/person/add.php
* 必填:full_name;union_id 为空时自动生成。
* 可选:contacts(JSON 数组,如 [{"platform":"email","account_id":"a@b.com","is_primary":1}])
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/helpers.php';
checkAjax();
checkPermission('person');
$data = extractFields(PERSON_FIELDS);
if (empty($data['full_name'])) {
Response::error('姓名(full_name)为必填项', 400);
}
if (empty($data['union_id'])) {
$data['union_id'] = 'P' . date('YmdHis') . substr(uniqid(), -6);
}
$pdo = DB::getInstance()->getPdo();
// union_id 唯一性
$chk = $pdo->prepare("SELECT COUNT(*) FROM persons WHERE union_id = ?");
$chk->execute([$data['union_id']]);
if ((int)$chk->fetchColumn() > 0) {
Response::error('union_id 已存在,请更换');
}
[$sql, $params] = buildInsert($data);
$pdo->prepare("INSERT INTO persons $sql")->execute($params);
$newId = (int)$pdo->lastInsertId();
// 写入联系方式
$contacts = json_decode($_POST['contacts'] ?? '[]', true);
if (is_array($contacts)) {
$ins = $pdo->prepare(
"INSERT INTO social_accounts (owner_type, owner_id, platform, account_id, profile_url, remark, is_primary)
VALUES ('person', ?, ?, ?, ?, ?, ?)"
);
foreach ($contacts as $c) {
$platform = trim($c['platform'] ?? '');
$accountId = trim($c['account_id'] ?? '');
if ($platform === '' || $accountId === '') continue;
$ins->execute([$newId, $platform, $accountId, $c['profile_url'] ?? null, $c['remark'] ?? null, !empty($c['is_primary']) ? 1 : 0]);
}
}
logCurrent('add', 'person', 'persons', $newId, ['data' => $data, 'contacts' => $contacts]);
Response::success(['id' => $newId], '新增成功');
+52
View File
@@ -0,0 +1,52 @@
<?php
/**
* 人员任职公司列表接口 GET /api/person/companies.php
* 入参:person_id(必填)/ page / limit
* 返回:该人员任职的公司列表(公司名称/部门/岗位/职级/入职日期/是否在岗)
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
checkPermission('person');
$personId = (int)($_REQUEST['person_id'] ?? 0);
if ($personId <= 0) {
Response::error('参数错误', 400);
}
[$page, $limit] = pageParams(5);
$pdo = DB::getInstance()->getPdo();
$chk = $pdo->prepare("SELECT COUNT(*) FROM persons WHERE id = ?");
$chk->execute([$personId]);
if ((int)$chk->fetchColumn() === 0) {
Response::error('人员不存在');
}
$stmt = $pdo->prepare(
"SELECT COUNT(*) FROM person_work_experiences pwe
INNER JOIN companies c ON c.id = pwe.company_id
WHERE pwe.person_id = ? AND pwe.is_active = 1"
);
$stmt->execute([$personId]);
$total = (int)$stmt->fetchColumn();
$offset = ($page - 1) * $limit;
$stmt = $pdo->prepare(
"SELECT pwe.id AS work_id, pwe.company_id,
CASE WHEN c.name_zh IS NOT NULL AND c.name_zh <> '' THEN c.name_zh
ELSE COALESCE(c.name_en, c.display_name) END AS company_name,
c.industry, pwe.department, pwe.position, pwe.job_level,
pwe.start_date, pwe.end_date, pwe.is_current
FROM person_work_experiences pwe
INNER JOIN companies c ON c.id = pwe.company_id
WHERE pwe.person_id = ? AND pwe.is_active = 1
ORDER BY pwe.is_current DESC, pwe.start_date DESC
LIMIT $limit OFFSET $offset"
);
$stmt->execute([$personId]);
$list = $stmt->fetchAll();
Response::success(['list' => $list, 'total' => $total, 'page' => $page, 'limit' => $limit]);
+36
View File
@@ -0,0 +1,36 @@
<?php
/**
* 人员删除接口(软删除) POST /api/person/delete.php
* 入参:id(或 ids 逗号分隔批量)
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
checkAjax();
checkPermission('person');
$id = (int)($_POST['id'] ?? 0);
$ids = trim($_POST['ids'] ?? '');
$idList = [];
if ($id > 0) $idList[] = $id;
if ($ids !== '') {
foreach (explode(',', $ids) as $v) {
$v = (int)trim($v);
if ($v > 0) $idList[] = $v;
}
}
$idList = array_unique($idList);
if (!$idList) {
Response::error('参数错误', 400);
}
$pdo = DB::getInstance()->getPdo();
$in = implode(',', array_fill(0, count($idList), '?'));
$stmt = $pdo->prepare("UPDATE persons SET is_active = 0 WHERE id IN ($in)");
$stmt->execute($idList);
$affected = $stmt->rowCount();
logCurrent('delete', 'person', 'persons', null, ['ids' => $idList]);
Response::success(['affected' => $affected], "已删除 $affected 条记录");
+38
View File
@@ -0,0 +1,38 @@
<?php
/**
* 人员详情接口 GET /api/person/detail.php?id=1
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
checkPermission('person');
$id = (int)($_REQUEST['id'] ?? 0);
if ($id <= 0) {
Response::error('参数错误', 400);
}
$pdo = DB::getInstance()->getPdo();
$stmt = $pdo->prepare("SELECT * FROM persons WHERE id = ?");
$stmt->execute([$id]);
$person = $stmt->fetch();
if (!$person) {
Response::error('人员不存在');
}
$accounts = $pdo->prepare("SELECT id, platform, account_id, profile_url, remark, is_primary, is_active FROM social_accounts WHERE owner_type = 'person' AND owner_id = ? ORDER BY is_primary DESC, id DESC");
$accounts->execute([$id]);
$experiences = $pdo->prepare(
"SELECT pwe.id, pwe.company_id, c.display_name, c.industry, pwe.position, pwe.department,
pwe.job_level, pwe.start_date, pwe.end_date, pwe.is_current
FROM person_work_experiences pwe
LEFT JOIN companies c ON c.id = pwe.company_id
WHERE pwe.person_id = ? AND pwe.is_active = 1
ORDER BY pwe.is_current DESC, pwe.start_date DESC"
);
$experiences->execute([$id]);
Response::success(['person' => $person, 'accounts' => $accounts->fetchAll(), 'experiences' => $experiences->fetchAll()]);
+59
View File
@@ -0,0 +1,59 @@
<?php
/**
* 人员导出接口(CSV) GET/POST /api/person/export.php
* 参数同 list.php
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
checkPermission('person');
// 仅导出勾选的人员
$idsRaw = trim($_REQUEST['ids'] ?? '');
$idList = [];
if ($idsRaw !== '') {
foreach (explode(',', $idsRaw) as $v) {
$v = (int)trim($v);
if ($v > 0) {
$idList[] = $v;
}
}
}
$idList = array_unique($idList);
if (!$idList) {
Response::error('请先选择需要导出的人员数据', 1);
}
$where = ['p.id IN (' . implode(',', array_fill(0, count($idList), '?')) . ')', 'p.is_active = 1'];
$params = $idList;
$whereSql = implode(' AND ', $where);
$pdo = DB::getInstance()->getPdo();
$stmt = $pdo->prepare(
"SELECT p.id, p.union_id, p.full_name, p.gender, p.nationality, p.id_type, p.id_number,
p.education, p.graduated_from, p.hometown, p.work_location, p.created_at,
(SELECT sa.account_id FROM social_accounts sa WHERE sa.owner_type='person' AND sa.owner_id=p.id AND sa.platform='phone' AND sa.is_active=1 LIMIT 1) AS phone,
(SELECT sa.account_id FROM social_accounts sa WHERE sa.owner_type='person' AND sa.owner_id=p.id AND sa.platform='email' AND sa.is_active=1 LIMIT 1) AS email
FROM persons p WHERE $whereSql ORDER BY p.id DESC"
);
$stmt->execute($params);
$list = $stmt->fetchAll();
logCurrent('export', 'person', 'persons', null, ['count' => count($list)]);
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename="persons_' . date('Ymd_His') . '.csv"');
echo "\xEF\xBB\xBF";
$out = fopen('php://output', 'w');
fputcsv($out, ['ID', 'union_id', '姓名', '性别', '国籍', '证件类型', '证件号', '学历', '毕业院校', '家乡', '工作所在地', '手机', '邮箱', '创建时间']);
foreach ($list as $r) {
fputcsv($out, [
$r['id'], $r['union_id'], $r['full_name'], $r['gender'], $r['nationality'],
$r['id_type'], $r['id_number'], $r['education'], $r['graduated_from'],
$r['hometown'], $r['work_location'], $r['phone'], $r['email'], $r['created_at'],
]);
}
fclose($out);
exit;
+82
View File
@@ -0,0 +1,82 @@
<?php
/**
* 人员 CSV 导入接口 POST /api/person/import.php (multipart/form-data, 字段名 file)
* CSV 表头:union_id,full_name,gender,nationality,id_type,id_number,education,
* graduated_from,hometown,work_location,phone,email
* 仅 full_name 必填;union_id 留空自动生成;phone/email 写入 social_accounts。
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
checkAjax();
checkPermission('person');
if (empty($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) {
Response::error('请选择要上传的CSV文件', 400);
}
$handle = fopen($_FILES['file']['tmp_name'], 'r');
if (!$handle) {
Response::error('无法读取文件', 400);
}
$first = preg_replace('/^\xEF\xBB\xBF/', '', fgets($handle));
$header = str_getcsv(trim($first));
$pdo = DB::getInstance()->getPdo();
$insPerson = $pdo->prepare(
"INSERT INTO persons (union_id, full_name, gender, nationality, id_type, id_number, education, graduated_from, hometown, work_location)
VALUES (?,?,?,?,?,?,?,?,?,?)"
);
$insAccount = $pdo->prepare(
"INSERT INTO social_accounts (owner_type, owner_id, platform, account_id, is_primary)
VALUES ('person', ?, ?, ?, ?)"
);
$inserted = 0;
$failed = 0;
while (($row = fgetcsv($handle)) !== false) {
$row = array_map('trim', $row);
$rec = [];
foreach ($header as $idx => $col) {
$col = trim($col);
if (isset($row[$idx])) {
$rec[$col] = $row[$idx];
}
}
if (empty($rec['full_name'])) {
$failed++;
continue;
}
try {
$unionId = !empty($rec['union_id']) ? $rec['union_id'] : ('P' . date('YmdHis') . substr(uniqid(), -6));
// 跳过重复 union_id
$chk = $pdo->prepare("SELECT COUNT(*) FROM persons WHERE union_id = ?");
$chk->execute([$unionId]);
if ((int)$chk->fetchColumn() > 0) {
$failed++;
continue;
}
$insPerson->execute([
$unionId, $rec['full_name'], $rec['gender'] ?? '保密', $rec['nationality'] ?? null,
$rec['id_type'] ?? null, $rec['id_number'] ?? null, $rec['education'] ?? null,
$rec['graduated_from'] ?? null, $rec['hometown'] ?? null, $rec['work_location'] ?? null,
]);
$newId = (int)$pdo->lastInsertId();
if (!empty($rec['phone'])) {
$insAccount->execute([$newId, 'phone', $rec['phone'], 1]);
}
if (!empty($rec['email'])) {
$insAccount->execute([$newId, 'email', $rec['email'], 0]);
}
$inserted++;
} catch (Exception $e) {
$failed++;
}
}
fclose($handle);
logCurrent('import', 'person', 'persons', null, ['inserted' => $inserted, 'failed' => $failed]);
Response::success(['inserted' => $inserted, 'failed' => $failed], "导入完成:成功 $inserted 条,失败 $failed 条");
+61
View File
@@ -0,0 +1,61 @@
<?php
/**
* 人员列表接口 GET/POST /api/person/list.php
* 参数:page / limit / keyword(姓名/证件号/手机/邮箱)/ gender / nationality / work_location / is_active
* 返回:{ list, total, page, limit }
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
checkPermission('person');
[$page, $limit] = pageParams();
$keyword = trim($_REQUEST['keyword'] ?? '');
$gender = trim($_REQUEST['gender'] ?? '');
$nationality = trim($_REQUEST['nationality'] ?? '');
$location = trim($_REQUEST['work_location'] ?? '');
$active = isset($_REQUEST['is_active']) && $_REQUEST['is_active'] !== '' ? (int)$_REQUEST['is_active'] : null;
$where = ['p.is_active = 1'];
$params = [];
if ($active !== null) {
$where = ['p.is_active = ?'];
$params = [$active];
}
if ($keyword !== '') {
// 姓名/证件号直查;手机/邮箱通过 social_accounts 关联
$where[] = "(p.full_name LIKE ? OR p.id_number LIKE ? OR p.union_id LIKE ? OR EXISTS (
SELECT 1 FROM social_accounts sa
WHERE sa.owner_type = 'person' AND sa.owner_id = p.id AND sa.is_active = 1
AND sa.account_id LIKE ?
))";
$like = "%$keyword%";
array_push($params, $like, $like, $like, $like);
}
if ($gender !== '') { $where[] = 'p.gender = ?'; $params[] = $gender; }
if ($nationality !== '') { $where[] = 'p.nationality = ?'; $params[] = $nationality; }
if ($location !== '') { $where[] = 'p.work_location LIKE ?'; $params[] = "%$location%"; }
$whereSql = implode(' AND ', $where);
$pdo = DB::getInstance()->getPdo();
$stmt = $pdo->prepare("SELECT COUNT(*) FROM persons p WHERE $whereSql");
$stmt->execute($params);
$total = (int)$stmt->fetchColumn();
$offset = ($page - 1) * $limit;
$stmt = $pdo->prepare(
"SELECT p.id, p.union_id, p.full_name, p.gender, p.nationality, p.id_type, p.id_number,
p.education, p.graduated_from, p.hometown, p.work_location, p.is_active, p.created_at,
(SELECT GROUP_CONCAT(CONCAT(sa.platform, ':', sa.account_id) SEPARATOR ' | ')
FROM social_accounts sa WHERE sa.owner_type = 'person' AND sa.owner_id = p.id AND sa.is_active = 1) AS contacts
FROM persons p
WHERE $whereSql
ORDER BY p.id DESC
LIMIT $limit OFFSET $offset"
);
$stmt->execute($params);
$list = $stmt->fetchAll();
Response::success(['list' => $list, 'total' => $total, 'page' => $page, 'limit' => $limit]);
+58
View File
@@ -0,0 +1,58 @@
<?php
/**
* 人员编辑接口 POST /api/person/update.php
* 入参:id + 白名单字段;可选 contacts(JSON 数组,整体替换联系方式)
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/helpers.php';
checkAjax();
checkPermission('person');
$id = (int)($_POST['id'] ?? 0);
if ($id <= 0) {
Response::error('参数错误', 400);
}
$data = extractFields(PERSON_FIELDS);
unset($data['union_id']); // 不允许修改 union_id
$pdo = DB::getInstance()->getPdo();
$check = $pdo->prepare("SELECT * FROM persons WHERE id = ?");
$check->execute([$id]);
$old = $check->fetch();
if (!$old) {
Response::error('人员不存在');
}
$contactsChanged = false;
if (isset($_POST['contacts'])) {
$contactsChanged = true;
// 整体替换联系方式:先删旧(保留非联系人性质?此处直接删除全部后重建)
$pdo->prepare("DELETE FROM social_accounts WHERE owner_type = 'person' AND owner_id = ?")->execute([$id]);
$contacts = json_decode($_POST['contacts'], true);
if (is_array($contacts)) {
$ins = $pdo->prepare(
"INSERT INTO social_accounts (owner_type, owner_id, platform, account_id, profile_url, remark, is_primary)
VALUES ('person', ?, ?, ?, ?, ?, ?)"
);
foreach ($contacts as $c) {
$platform = trim($c['platform'] ?? '');
$accountId = trim($c['account_id'] ?? '');
if ($platform === '' || $accountId === '') continue;
$ins->execute([$id, $platform, $accountId, $c['profile_url'] ?? null, $c['remark'] ?? null, !empty($c['is_primary']) ? 1 : 0]);
}
}
}
if (!empty($data)) {
[$sets, $params] = buildUpdate($data);
$params[] = $id;
$pdo->prepare("UPDATE persons SET $sets WHERE id = ?")->execute($params);
}
logCurrent('update', 'person', 'persons', $id, ['before' => $old, 'after' => $data, 'contacts_replaced' => $contactsChanged]);
Response::success(null, '更新成功');