/** * 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 };