203 lines
6.0 KiB
JavaScript
203 lines
6.0 KiB
JavaScript
/** 个人信息页前端校验(与后端手机号规则对齐:^1[3-9]\\d{9}$) */
|
||
|
||
const PHONE_REG = /^1[3-9]\d{9}$/
|
||
const MIN_NAME_LEN = 2
|
||
const MAX_NAME_LEN = 8
|
||
const NAME_REG = new RegExp(
|
||
`^[\\u4e00-\\u9fa5a-zA-Z·\\s]{${MIN_NAME_LEN},${MAX_NAME_LEN}}$`
|
||
)
|
||
const MEASURE_REG = /^\d+(\.\d)?$/
|
||
|
||
const MIN_HEIGHT = 50
|
||
const MAX_HEIGHT = 250
|
||
const MIN_WEIGHT = 20
|
||
const MAX_WEIGHT = 300
|
||
const MIN_BIRTH_YEAR = 1900
|
||
const MIN_AGE = 14
|
||
const MAX_FITNESS_GOALS = 5
|
||
|
||
export function isMaskedPhone(phone) {
|
||
return String(phone || '').includes('****')
|
||
}
|
||
|
||
export function validateName(name) {
|
||
const value = String(name ?? '').trim()
|
||
if (!value) {
|
||
return { ok: false, message: '请输入姓名' }
|
||
}
|
||
if (!NAME_REG.test(value)) {
|
||
return { ok: false, message: `姓名为${MIN_NAME_LEN}-${MAX_NAME_LEN}个汉字或字母` }
|
||
}
|
||
return { ok: true, value }
|
||
}
|
||
|
||
/** 保存时使用:允许保留已脱敏的旧手机号 */
|
||
export function validatePhone(phone, options = {}) {
|
||
const { allowMasked = true } = options
|
||
const raw = String(phone ?? '').trim()
|
||
if (!raw) {
|
||
return { ok: false, message: '请绑定手机号' }
|
||
}
|
||
if (allowMasked && isMaskedPhone(raw)) {
|
||
const digits = raw.replace(/\D/g, '')
|
||
if (digits.length >= 7) {
|
||
return { ok: true, value: raw }
|
||
}
|
||
return { ok: false, message: '手机号格式不正确' }
|
||
}
|
||
|
||
const digits = raw.replace(/\D/g, '')
|
||
if (!PHONE_REG.test(digits)) {
|
||
return { ok: false, message: '请输入11位有效手机号' }
|
||
}
|
||
return { ok: true, value: digits }
|
||
}
|
||
|
||
/** 换绑时必须输入完整新号 */
|
||
export function validatePhoneForRebind(phone) {
|
||
return validatePhone(phone, { allowMasked: false })
|
||
}
|
||
|
||
function parseMeasure(value) {
|
||
const str = String(value ?? '').trim()
|
||
if (!str || !MEASURE_REG.test(str)) {
|
||
return null
|
||
}
|
||
const num = Number(str)
|
||
return Number.isFinite(num) ? num : null
|
||
}
|
||
|
||
function formatMeasure(num) {
|
||
return Number.isInteger(num) ? String(num) : String(Number(num.toFixed(1)))
|
||
}
|
||
|
||
export function validateHeight(height) {
|
||
const num = parseMeasure(height)
|
||
if (num == null) {
|
||
return { ok: false, message: '请输入有效身高(单位 cm)' }
|
||
}
|
||
if (num < MIN_HEIGHT || num > MAX_HEIGHT) {
|
||
return { ok: false, message: `身高请在 ${MIN_HEIGHT}-${MAX_HEIGHT} cm 之间` }
|
||
}
|
||
return { ok: true, value: formatMeasure(num) }
|
||
}
|
||
|
||
export function validateWeight(weight) {
|
||
const num = parseMeasure(weight)
|
||
if (num == null) {
|
||
return { ok: false, message: '请输入有效体重(单位 kg)' }
|
||
}
|
||
if (num < MIN_WEIGHT || num > MAX_WEIGHT) {
|
||
return { ok: false, message: `体重请在 ${MIN_WEIGHT}-${MAX_WEIGHT} kg 之间` }
|
||
}
|
||
return { ok: true, value: formatMeasure(num) }
|
||
}
|
||
|
||
export function parseBirthdayChinese(birthday) {
|
||
const match = String(birthday ?? '').match(/(\d{4})年(\d{2})月(\d{2})日/)
|
||
if (!match) return null
|
||
return {
|
||
year: Number(match[1]),
|
||
month: Number(match[2]),
|
||
day: Number(match[3])
|
||
}
|
||
}
|
||
|
||
export function validateBirthday(birthday) {
|
||
const parts = parseBirthdayChinese(birthday)
|
||
if (!parts) {
|
||
return { ok: false, message: '请选择生日' }
|
||
}
|
||
const { year, month, day } = parts
|
||
if (year < MIN_BIRTH_YEAR) {
|
||
return { ok: false, message: '生日年份不合理' }
|
||
}
|
||
|
||
const date = new Date(year, month - 1, day)
|
||
if (
|
||
date.getFullYear() !== year ||
|
||
date.getMonth() !== month - 1 ||
|
||
date.getDate() !== day
|
||
) {
|
||
return { ok: false, message: '生日日期无效' }
|
||
}
|
||
|
||
const today = new Date()
|
||
today.setHours(0, 0, 0, 0)
|
||
if (date > today) {
|
||
return { ok: false, message: '生日不能晚于今天' }
|
||
}
|
||
|
||
const minBirth = new Date(
|
||
today.getFullYear() - MIN_AGE,
|
||
today.getMonth(),
|
||
today.getDate()
|
||
)
|
||
if (date > minBirth) {
|
||
return { ok: false, message: `需年满 ${MIN_AGE} 周岁` }
|
||
}
|
||
|
||
return { ok: true, value: `${year}年${String(month).padStart(2, '0')}月${String(day).padStart(2, '0')}日` }
|
||
}
|
||
|
||
export function validateGender(gender) {
|
||
if (gender === 'male' || gender === 'female') {
|
||
return { ok: true, value: gender }
|
||
}
|
||
return { ok: false, message: '请选择性别' }
|
||
}
|
||
|
||
export function validateFitnessGoals(goals, options = []) {
|
||
const list = Array.isArray(goals) ? goals : []
|
||
const allowed = new Set(options)
|
||
const invalid = list.filter((g) => !allowed.has(g))
|
||
if (invalid.length) {
|
||
return { ok: false, message: '健身目标选项无效' }
|
||
}
|
||
if (list.length > MAX_FITNESS_GOALS) {
|
||
return { ok: false, message: `最多选择 ${MAX_FITNESS_GOALS} 个健身目标` }
|
||
}
|
||
return { ok: true, value: [...list] }
|
||
}
|
||
|
||
export function validateUserProfile(profile, goalOptions = []) {
|
||
const nameResult = validateName(profile.name)
|
||
if (!nameResult.ok) return nameResult
|
||
|
||
const phoneResult = validatePhone(profile.phone)
|
||
if (!phoneResult.ok) return phoneResult
|
||
|
||
const genderResult = validateGender(profile.gender)
|
||
if (!genderResult.ok) return genderResult
|
||
|
||
const birthdayResult = validateBirthday(profile.birthday)
|
||
if (!birthdayResult.ok) return birthdayResult
|
||
|
||
const heightResult = validateHeight(profile.height)
|
||
if (!heightResult.ok) return heightResult
|
||
|
||
const weightResult = validateWeight(profile.weight)
|
||
if (!weightResult.ok) return weightResult
|
||
|
||
const goalsResult = validateFitnessGoals(profile.fitnessGoals, goalOptions)
|
||
if (!goalsResult.ok) return goalsResult
|
||
|
||
return {
|
||
ok: true,
|
||
value: {
|
||
...profile,
|
||
name: nameResult.value,
|
||
phone: phoneResult.value,
|
||
gender: genderResult.value,
|
||
birthday: birthdayResult.value,
|
||
height: heightResult.value,
|
||
weight: weightResult.value,
|
||
fitnessGoals: goalsResult.value
|
||
}
|
||
}
|
||
}
|
||
|
||
export function showValidationError(message) {
|
||
uni.showToast({ title: message, icon: 'none' })
|
||
}
|