278 lines
7.8 KiB
JavaScript
278 lines
7.8 KiB
JavaScript
/**
|
||
* 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
|
||
};
|