diff --git a/api/common/duplicate_check.php b/api/common/duplicate_check.php index 45b0d6a..bb63050 100644 --- a/api/common/duplicate_check.php +++ b/api/common/duplicate_check.php @@ -10,6 +10,8 @@ * - 企业名称 name_zh 或 display_name 任一相同即重复 */ require_once __DIR__ . '/db.php'; +require_once __DIR__ . '/response.php'; +require_once __DIR__ . '/validate.php'; /** 字段中文名 */ const DUP_LABELS = [ @@ -191,6 +193,12 @@ function checkSingleAccountDuplicate(PDO $pdo, $platform, $accountId, $profileUr $platform = trim((string)$platform); $accountId = trim((string)$accountId); if ($platform !== '' && $accountId !== '') { + // 格式校验:手机/邮箱平台必须符合对应格式 + if ($platform === 'phone') { + checkFieldFormat('phone', $accountId); + } elseif ($platform === 'email') { + checkFieldFormat('email', $accountId); + } if ($platform === 'phone' || $platform === 'email') { $dup = findDuplicate($pdo, $platform, $accountId, $excludeIds); } else { diff --git a/api/common/validate.php b/api/common/validate.php new file mode 100644 index 0000000..2081390 --- /dev/null +++ b/api/common/validate.php @@ -0,0 +1,55 @@ + 's', 'hometown' => 's', 'work_location' => 's', 'id_type' => 's', 'id_number' => 's', 'source_channel' => 's', 'source_detail' => 's', ]); + // 格式校验:身份证 / 手机 / 邮箱 + if (!empty($fields['id_number'])) { + checkFieldFormat('id_number', $fields['id_number']); + } + if (isset($_POST['phone'])) { + checkFieldFormat('phone', $_POST['phone']); + } + if (isset($_POST['email'])) { + checkFieldFormat('email', $_POST['email']); + } // 唯一性校验(排除自身):身份证 / 手机 / 邮箱 $dups = []; if (!empty($fields['id_number'])) { diff --git a/api/person/add.php b/api/person/add.php index da27eb6..5d0bea2 100644 --- a/api/person/add.php +++ b/api/person/add.php @@ -11,6 +11,7 @@ require_once __DIR__ . '/../common/logger.php'; require_once __DIR__ . '/../common/helpers.php'; require_once __DIR__ . '/../common/completeness.php'; require_once __DIR__ . '/../common/duplicate_check.php'; +require_once __DIR__ . '/../common/validate.php'; checkAjax(); checkPermission('person'); @@ -35,6 +36,7 @@ if ((int)$chk->fetchColumn() > 0) { // 唯一性校验:身份证号码 / 联系方式(手机/邮箱/社媒账号/主页链接),收集全部重复 $dups = []; if (!empty($data['id_number'])) { + checkFieldFormat('id_number', $data['id_number']); $dup = findDuplicate($pdo, 'id_number', $data['id_number']); if ($dup) $dups[] = $dup; } diff --git a/api/person/update.php b/api/person/update.php index ee61944..85ebe5d 100644 --- a/api/person/update.php +++ b/api/person/update.php @@ -10,6 +10,7 @@ require_once __DIR__ . '/../common/logger.php'; require_once __DIR__ . '/../common/helpers.php'; require_once __DIR__ . '/../common/completeness.php'; require_once __DIR__ . '/../common/duplicate_check.php'; +require_once __DIR__ . '/../common/validate.php'; checkAjax(); checkPermission('person'); @@ -33,6 +34,7 @@ if (!$old) { // 唯一性校验(排除自身):身份证号码 / 联系方式,收集全部重复 $dups = []; if (!empty($data['id_number'])) { + checkFieldFormat('id_number', $data['id_number']); $dup = findDuplicate($pdo, 'id_number', $data['id_number'], [$id]); if ($dup) $dups[] = $dup; } diff --git a/static/js/common.js b/static/js/common.js index 9010a9a..9e2a437 100644 --- a/static/js/common.js +++ b/static/js/common.js @@ -138,6 +138,11 @@ function cleanEmail(v) { return String(v == null ? '' : v).trim(); } +/** 格式校验:邮箱 / 国内手机号 / 身份证 */ +function validateEmail(v) { return /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/.test(String(v || '').trim()); } +function validatePhoneCN(v) { return /^1[3-9]\d{9}$/.test(String(v || '').trim()); } +function validateIdCard(v) { return /^\d{15}$|^\d{17}[\dXx]$/.test(String(v || '').trim()); } + /* ============ 会话与权限 ============ */ /** diff --git a/static/js/config.js b/static/js/config.js index 84f8895..236d250 100644 --- a/static/js/config.js +++ b/static/js/config.js @@ -5,7 +5,7 @@ var BASE_URL = '/api/'; var PAGE_SIZE = 20; /** 系统版本号(logo旁展示):修改代码后运行 tools/bump_version.php 自动递增 */ -var APP_VERSION = 'v1.0.37'; +var APP_VERSION = 'v1.0.38'; /** 页脚版权/备案信息(在 config.js 中修改) */ var FOOTER_TEXT = '© 2026 SuperLink 管理系统 版权所有 | 备案号:请替换为真实备案号'; diff --git a/static/js/fragment.js b/static/js/fragment.js index 4beb1bc..45df6dc 100644 --- a/static/js/fragment.js +++ b/static/js/fragment.js @@ -253,6 +253,9 @@ $(function () { if (name) fd[name] = $(this).val(); }); fd.source_type = $('#ha-type').val(); + // 格式校验:手机 / 邮箱 + if (fd.contact_phone && !validatePhoneCN(fd.contact_phone)) { Dialog.error('手机号码格式不正确(应为国内 11 位手机号,如 13812345678)'); return; } + if (fd.contact_email && !validateEmail(fd.contact_email)) { Dialog.error('邮箱格式不正确'); return; } httpPost('fragment/hard_add.php', fd).then(function () { Dialog.success('新增成功', function () { Dialog.close(idx); @@ -344,6 +347,9 @@ $(function () { }); fd.id = id; fd.target_type = $('#cv-type').val(); + // 格式校验:手机 / 邮箱 + if (fd.contact_phone && !validatePhoneCN(fd.contact_phone)) { Dialog.error('手机号码格式不正确(应为国内 11 位手机号,如 13812345678)'); return; } + if (fd.contact_email && !validateEmail(fd.contact_email)) { Dialog.error('邮箱格式不正确'); return; } httpPost('fragment/hard_convert.php', fd).then(function () { Dialog.success('转换成功', function () { Dialog.close(idx); @@ -404,6 +410,10 @@ $(function () { var name = $(this).attr('name'); if (name) fd[name] = $(this).val(); }); + // 格式校验:身份证 / 手机 / 邮箱 + if (fd.id_number && !validateIdCard(fd.id_number)) { Dialog.error('身份证号码格式不正确(应为 15 或 18 位)'); return; } + if (fd.phone && !validatePhoneCN(fd.phone)) { Dialog.error('手机号码格式不正确(应为国内 11 位手机号,如 13812345678)'); return; } + if (fd.email && !validateEmail(fd.email)) { Dialog.error('邮箱格式不正确'); return; } httpPost('fragment/soft_update.php', fd).then(function (res) { Dialog.success(res && res.is_complete ? '补全完成' : '已保存(整体完整度 ' + (res && res.overall) + '%)', function () { Dialog.close(idx); diff --git a/static/js/media.js b/static/js/media.js index 5ef8866..c94ab64 100644 --- a/static/js/media.js +++ b/static/js/media.js @@ -203,6 +203,9 @@ $(function () { } }); if (!data.platform || !data.account_id) { Dialog.error('平台和账号为必填项'); return; } + // 格式校验:平台为手机/邮箱时校验账号格式 + if (data.platform === 'phone' && !validatePhoneCN(data.account_id)) { Dialog.error('手机号码格式不正确(应为国内 11 位手机号,如 13812345678)'); return; } + if (data.platform === 'email' && !validateEmail(data.account_id)) { Dialog.error('邮箱格式不正确'); return; } if (!isEdit && (!data.owner_type || !data.owner_id)) { Dialog.error('归属类型和归属ID为必填项'); return; } // 录入清洗:手机号去所有空格(含中间),邮箱去首尾空格 if (data.platform === 'phone') data.account_id = cleanPhone(data.account_id); diff --git a/static/js/person.js b/static/js/person.js index df95530..171957f 100644 --- a/static/js/person.js +++ b/static/js/person.js @@ -419,6 +419,11 @@ $(function () { }); if (!data.full_name) { Dialog.error('姓名为必填项'); return; } + // 格式校验:身份证 / 手机 / 邮箱 + if (data.id_number && !validateIdCard(data.id_number)) { Dialog.error('身份证号码格式不正确(应为 15 或 18 位)'); return; } + if (data.phone && !validatePhoneCN(data.phone)) { Dialog.error('手机号码格式不正确(应为国内 11 位手机号,如 13812345678)'); return; } + if (data.email && !validateEmail(data.email)) { Dialog.error('邮箱格式不正确'); return; } + // 联系方式(手机/邮箱,均为常用) data.phone = cleanPhone(data.phone); data.email = cleanEmail(data.email);