appointment_system/miniprogram/src/utils/form-validation.js
2025-12-19 00:37:31 +08:00

278 lines
7.8 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.

/**
* Form Validation Utility Module
* Provides reusable validation functions for service appointment forms
*
* Feature: service-appointment-forms
* Requirements: 1.2, 1.5
*/
/**
* Validates that at least one contact method is provided
* Feature: service-appointment-forms, Property 1: Contact Method Validation
* Validates: Requirements 1.2
*
* @param {string} wechat - WeChat ID
* @param {string} phone - Phone number
* @param {string} whatsapp - WhatsApp number
* @returns {boolean} True if at least one contact method is provided
*/
export function validateContactMethods(wechat, phone, whatsapp) {
const hasWechat = wechat && typeof wechat === 'string' && wechat.trim().length > 0;
const hasPhone = phone && typeof phone === 'string' && phone.trim().length > 0;
const hasWhatsapp = whatsapp && typeof whatsapp === 'string' && whatsapp.trim().length > 0;
return !!(hasWechat || hasPhone || hasWhatsapp);
}
/**
* Validates that a date range is valid (end date >= start date)
* Feature: service-appointment-forms, Property 2: Date Range Validation
* Validates: Requirements 2.5
*
* @param {string} startDate - Start date in YYYY-MM-DD format
* @param {string} endDate - End date in YYYY-MM-DD format
* @returns {object} Validation result with valid flag and optional reason
*/
export function validateDateRange(startDate, endDate) {
if (!startDate || !endDate) {
return { valid: false, reason: 'Both dates are required' };
}
const start = new Date(startDate);
const end = new Date(endDate);
if (isNaN(start.getTime()) || isNaN(end.getTime())) {
return { valid: false, reason: 'Invalid date format' };
}
if (end < start) {
return { valid: false, reason: 'End date cannot be earlier than start date' };
}
return { valid: true };
}
/**
* Validates that at least one person is selected
* Feature: service-appointment-forms, Property 3: Passenger Count Validation
* Validates: Requirements 3.3, 5.3
*
* @param {object} counts - Object containing count fields
* @returns {boolean} True if total count > 0
*/
export function validatePersonCount(counts) {
if (!counts || typeof counts !== 'object') {
return false;
}
const countFields = [
'adultCount',
'childCount',
'infantCount',
'boyCount',
'girlCount',
'passengerCount',
'roomCount'
];
let totalCount = 0;
for (const field of countFields) {
const value = counts[field];
if (typeof value === 'number' && value > 0) {
totalCount += value;
}
}
return totalCount > 0;
}
/**
* Validates that a required string field is not empty
*
* @param {string} value - The value to validate
* @returns {boolean} True if value is a non-empty string
*/
export function validateRequiredString(value) {
return value && typeof value === 'string' && value.trim().length > 0;
}
/**
* Validates that a numeric value is a positive integer
* Feature: service-appointment-forms, Property 6: Numeric Input Validation
* Validates: Requirements 10.2, 13.2, 14.2, 15.2
*
* @param {number|string} value - The value to validate
* @returns {boolean} True if value is a valid positive integer
*/
export function validatePositiveInteger(value) {
if (value === undefined || value === null || value === '') {
return false;
}
const num = typeof value === 'string' ? parseInt(value, 10) : value;
return Number.isInteger(num) && num > 0;
}
/**
* Validates that a numeric value is a non-negative integer (0 or greater)
*
* @param {number|string} value - The value to validate
* @returns {boolean} True if value is a valid non-negative integer
*/
export function validateNonNegativeInteger(value) {
if (value === undefined || value === null || value === '') {
return false;
}
const num = typeof value === 'string' ? parseInt(value, 10) : value;
return Number.isInteger(num) && num >= 0;
}
/**
* Scrolls to a specific element on the page
* Feature: service-appointment-forms
* Validates: Requirements 1.5
*
* @param {object} context - Vue component context (this)
* @param {string} selector - CSS selector for the element
*/
export function scrollToElement(context, selector) {
// #ifdef MP-WEIXIN || H5
const systemInfo = uni.getSystemInfoSync();
const screenHeight = systemInfo.windowHeight;
const query = uni.createSelectorQuery().in(context);
query.select(selector).boundingClientRect();
query.selectViewport().scrollOffset();
query.exec((res) => {
if (res[0] && res[1]) {
const rect = res[0];
const scrollInfo = res[1];
// Calculate target scroll position to center the element
const targetScrollTop = scrollInfo.scrollTop + rect.top - (screenHeight / 2) + (rect.height / 2);
uni.pageScrollTo({
scrollTop: Math.max(0, targetScrollTop),
duration: 300
});
}
});
// #endif
}
/**
* Triggers flash animation on a field
* Feature: service-appointment-forms
* Validates: Requirements 1.5
*
* @param {object} context - Vue component context (this)
* @param {string} fieldName - Name of the field to flash
* @param {number} duration - Duration of flash animation in ms (default: 1500)
*/
export function triggerFlashAnimation(context, fieldName, duration = 1500) {
if (context && typeof context === 'object') {
context.flashingField = fieldName;
setTimeout(() => {
context.flashingField = '';
}, duration);
}
}
/**
* Shows validation error and handles UI feedback
* Combines toast message, scroll to element, and flash animation
*
* @param {object} context - Vue component context (this)
* @param {object} validation - Validation object with field, selector, and message
*/
export function showValidationError(context, validation) {
// Show toast message
uni.showToast({
title: validation.message,
icon: 'none'
});
// Scroll to the element
if (validation.selector) {
scrollToElement(context, validation.selector);
}
// Trigger flash animation
if (validation.field) {
triggerFlashAnimation(context, validation.field);
}
}
/**
* Runs a list of validations and returns the first failing one
*
* @param {Array} validations - Array of validation objects with check function
* @returns {object|null} First failing validation or null if all pass
*/
export function runValidations(validations) {
for (const validation of validations) {
if (validation.check()) {
return validation;
}
}
return null;
}
/**
* Creates a standard validation rule object
*
* @param {string} field - Field identifier
* @param {string} selector - DOM selector for scrolling
* @param {function} check - Validation function that returns true if invalid
* @param {string} message - Error message to display
* @returns {object} Validation rule object
*/
export function createValidationRule(field, selector, check, message) {
return {
field,
selector,
check,
message
};
}
/**
* Common validation rules factory for personal info section
*
* @param {object} formData - Form data object with userName, userWechat, userPhone, userWhats
* @returns {Array} Array of validation rules for personal info
*/
export function createPersonalInfoValidations(formData) {
return [
createValidationRule(
'userName',
'#fieldUserName',
() => !validateRequiredString(formData.userName),
'请输入真实姓名'
),
createValidationRule(
'contact',
'#fieldContact',
() => !validateContactMethods(formData.userWechat, formData.userPhone, formData.userWhats),
'请至少填写一种联系方式(微信号/手机号/WhatsApp'
)
];
}
export default {
validateContactMethods,
validateDateRange,
validatePersonCount,
validateRequiredString,
validatePositiveInteger,
validateNonNegativeInteger,
scrollToElement,
triggerFlashAnimation,
showValidationError,
runValidations,
createValidationRule,
createPersonalInfoValidations
};