feat(JoinFamily): 更新加入家庭页面样式和输入验证逻辑
refactor(CreateFamily): 优化家训输入框的交互和验证逻辑 style: 统一文件头部注释格式
Showing
3 changed files
with
87 additions
and
27 deletions
| ... | @@ -77,7 +77,9 @@ | ... | @@ -77,7 +77,9 @@ |
| 77 | type="text" | 77 | type="text" |
| 78 | v-model="familyMotto[index]" | 78 | v-model="familyMotto[index]" |
| 79 | :placeholder="familyMottoPlaceholder[index]" | 79 | :placeholder="familyMottoPlaceholder[index]" |
| 80 | - maxlength="1" | 80 | + @input="(e) => handleInputChange(index, e.target.value)" |
| 81 | + @focus="focusedIndex = index" | ||
| 82 | + @blur="handleBlur(index)" | ||
| 81 | class="w-full h-full bg-transparent text-center" | 83 | class="w-full h-full bg-transparent text-center" |
| 82 | style="font-size: 38rpx;" | 84 | style="font-size: 38rpx;" |
| 83 | /> | 85 | /> |
| ... | @@ -170,8 +172,7 @@ const familyMotto = ref(['', '', '', '']); | ... | @@ -170,8 +172,7 @@ const familyMotto = ref(['', '', '', '']); |
| 170 | const familyMottoPlaceholder = ref(['孝', '敬', '和', '睦']); | 172 | const familyMottoPlaceholder = ref(['孝', '敬', '和', '睦']); |
| 171 | const familySizes = ['2人', '3-5人', '6人+']; | 173 | const familySizes = ['2人', '3-5人', '6人+']; |
| 172 | const familyAvatar = ref(''); | 174 | const familyAvatar = ref(''); |
| 173 | - | 175 | +const focusedIndex = ref(-1); |
| 174 | - | ||
| 175 | 176 | ||
| 176 | // 图片预览相关 | 177 | // 图片预览相关 |
| 177 | const previewVisible = ref(false); | 178 | const previewVisible = ref(false); |
| ... | @@ -185,6 +186,26 @@ const previewIndex = ref(0); | ... | @@ -185,6 +186,26 @@ const previewIndex = ref(0); |
| 185 | // }; | 186 | // }; |
| 186 | 187 | ||
| 187 | /** | 188 | /** |
| 189 | + * 处理输入变化 | ||
| 190 | + */ | ||
| 191 | +const handleInputChange = (index, value) => { | ||
| 192 | + // 只保留第一个有效字符(汉字、数字、大小写字母) | ||
| 193 | + const validChar = value.match(/[\u4e00-\u9fa5a-zA-Z0-9]/)?.[0] || ''; | ||
| 194 | + familyMotto.value[index] = validChar; | ||
| 195 | +}; | ||
| 196 | + | ||
| 197 | +/** | ||
| 198 | + * 处理失焦事件 | ||
| 199 | + */ | ||
| 200 | +const handleBlur = (index) => { | ||
| 201 | + focusedIndex.value = -1; | ||
| 202 | + // 确保只保留有效字符(汉字、数字、大小写字母) | ||
| 203 | + const value = familyMotto.value[index]; | ||
| 204 | + const validChar = value.match(/[\u4e00-\u9fa5a-zA-Z0-9]/)?.[0] || ''; | ||
| 205 | + familyMotto.value[index] = validChar; | ||
| 206 | +}; | ||
| 207 | + | ||
| 208 | +/** | ||
| 188 | * 显示提示信息 | 209 | * 显示提示信息 |
| 189 | */ | 210 | */ |
| 190 | const showToast = (message, type = 'success') => { | 211 | const showToast = (message, type = 'success') => { | ... | ... |
| 1 | +/* | ||
| 2 | + * @Date: 2025-08-27 18:25:24 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2025-08-27 23:18:26 | ||
| 5 | + * @FilePath: /lls_program/src/pages/JoinFamily/index.config.js | ||
| 6 | + * @Description: 文件描述 | ||
| 7 | + */ | ||
| 1 | export default { | 8 | export default { |
| 2 | - navigationBarTitleText: '首页' | 9 | + navigationBarTitleText: '加入家庭' |
| 3 | } | 10 | } | ... | ... |
| 1 | <template> | 1 | <template> |
| 2 | <view class="min-h-screen flex flex-col bg-white"> | 2 | <view class="min-h-screen flex flex-col bg-white"> |
| 3 | - <AppHeader title="加入家庭" /> | 3 | + <!-- <AppHeader title="加入家庭" /> --> |
| 4 | - <view class="flex-1 px-4 py-6 flex flex-col"> | 4 | + <view class="flex-1 px-4 pt-3 pb-6 flex flex-col"> |
| 5 | <!-- Title --> | 5 | <!-- Title --> |
| 6 | - <h2 class="text-xl font-bold text-center mb-2 mt-4"> | 6 | + <h2 class="text-xl font-bold text-center mb-2"> |
| 7 | 输入家训口令 | 7 | 输入家训口令 |
| 8 | </h2> | 8 | </h2> |
| 9 | <!-- Description --> | 9 | <!-- Description --> |
| 10 | - <p class="text-gray-600 text-center text-sm mb-6"> | 10 | + <view class="text-gray-600 text-center text-sm mb-6"> |
| 11 | 请输入家人提供的家训口令,加入家庭一起参与健康挑战 | 11 | 请输入家人提供的家训口令,加入家庭一起参与健康挑战 |
| 12 | - </p> | 12 | + </view> |
| 13 | <!-- Input boxes --> | 13 | <!-- Input boxes --> |
| 14 | <view class="flex justify-center gap-3 w-full mb-6"> | 14 | <view class="flex justify-center gap-3 w-full mb-6"> |
| 15 | - <view v-for="(char, index) in mottoChars" :key="index" class="w-16 h-16"> | 15 | + <view |
| 16 | + v-for="(char, index) in mottoChars" | ||
| 17 | + :key="index" | ||
| 18 | + class="w-16 h-16 border border-gray-300 rounded-md flex items-center justify-center" | ||
| 19 | + :style="{ | ||
| 20 | + borderColor: focusedIndex === index ? '#3b82f6' : '#d1d5db' | ||
| 21 | + }" | ||
| 22 | + > | ||
| 16 | <input | 23 | <input |
| 17 | :ref="(el) => (inputRefs[index] = el)" | 24 | :ref="(el) => (inputRefs[index] = el)" |
| 18 | type="text" | 25 | type="text" |
| 19 | v-model="mottoChars[index]" | 26 | v-model="mottoChars[index]" |
| 20 | @input="(e) => handleInputChange(index, e.target.value)" | 27 | @input="(e) => handleInputChange(index, e.target.value)" |
| 21 | @keydown="(e) => handleKeyDown(index, e)" | 28 | @keydown="(e) => handleKeyDown(index, e)" |
| 22 | - maxlength="1" | 29 | + @focus="focusedIndex = index" |
| 23 | - class="w-full h-full text-center text-xl border border-gray-300 rounded-md focus:border-blue-500 focus:outline-none" | 30 | + @blur="handleBlur(index)" |
| 31 | + class="w-full h-full text-center text-xl bg-transparent outline-none" | ||
| 32 | + style="border: none;" | ||
| 24 | /> | 33 | /> |
| 25 | </view> | 34 | </view> |
| 26 | </view> | 35 | </view> |
| 27 | <!-- Help text --> | 36 | <!-- Help text --> |
| 28 | - <p class="text-gray-500 text-center text-sm mb-8"> | 37 | + <view class="text-gray-500 text-center text-sm mb-8"> |
| 29 | 没有口令?请联系您的大家长获取 | 38 | 没有口令?请联系您的大家长获取 |
| 30 | - </p> | 39 | + </view> |
| 31 | <!-- Role selection --> | 40 | <!-- Role selection --> |
| 32 | <view class="mb-6"> | 41 | <view class="mb-6"> |
| 33 | - <h3 class="text-base font-medium mb-4 border-t pt-4"> | 42 | + <h3 class="text-base font-medium mb-4 border-t border-gray-300 pt-4"> |
| 34 | 选择您的身份 | 43 | 选择您的身份 |
| 35 | </h3> | 44 | </h3> |
| 36 | <view class="grid grid-cols-2 gap-3"> | 45 | <view class="grid grid-cols-2 gap-3"> |
| 37 | - <button | 46 | + <view |
| 38 | v-for="role in familyRoles" | 47 | v-for="role in familyRoles" |
| 39 | :key="role.id" | 48 | :key="role.id" |
| 40 | :class="[ | 49 | :class="[ |
| ... | @@ -61,20 +70,20 @@ | ... | @@ -61,20 +70,20 @@ |
| 61 | {{ role.label }} | 70 | {{ role.label }} |
| 62 | </span> | 71 | </span> |
| 63 | </view> | 72 | </view> |
| 64 | - </button> | 73 | + </view> |
| 65 | </view> | 74 | </view> |
| 66 | </view> | 75 | </view> |
| 67 | <!-- Submit Button --> | 76 | <!-- Submit Button --> |
| 68 | - <button | 77 | + <view |
| 69 | @click="handleJoinFamily" | 78 | @click="handleJoinFamily" |
| 70 | :disabled="!isComplete" | 79 | :disabled="!isComplete" |
| 71 | :class="[ | 80 | :class="[ |
| 72 | - 'w-full py-4 text-white text-lg font-medium rounded-lg mt-auto', | 81 | + 'w-full py-4 text-white text-lg font-medium rounded-lg mt-auto text-center', |
| 73 | isComplete ? 'bg-blue-500' : 'bg-gray-300' | 82 | isComplete ? 'bg-blue-500' : 'bg-gray-300' |
| 74 | ]" | 83 | ]" |
| 75 | > | 84 | > |
| 76 | 加入家庭 | 85 | 加入家庭 |
| 77 | - </button> | 86 | + </view> |
| 78 | </view> | 87 | </view> |
| 79 | </view> | 88 | </view> |
| 80 | </template> | 89 | </template> |
| ... | @@ -88,15 +97,22 @@ import AppHeader from '../../components/AppHeader.vue'; | ... | @@ -88,15 +97,22 @@ import AppHeader from '../../components/AppHeader.vue'; |
| 88 | const mottoChars = ref(['', '', '', '']); | 97 | const mottoChars = ref(['', '', '', '']); |
| 89 | const selectedRole = ref(''); | 98 | const selectedRole = ref(''); |
| 90 | const inputRefs = ref([]); | 99 | const inputRefs = ref([]); |
| 100 | +const focusedIndex = ref(-1); | ||
| 91 | 101 | ||
| 92 | const handleInputChange = (index, value) => { | 102 | const handleInputChange = (index, value) => { |
| 93 | - if (value && !/^[\u4e00-\u9fa5]$/.test(value)) { | 103 | + // 允许输入多个字符,但只保留第一个有效字符(汉字、数字、大小写字母),兼容输入法 |
| 94 | - return; | 104 | + if (value) { |
| 95 | - } | 105 | + // 提取第一个有效字符(汉字、数字、大小写字母) |
| 96 | - mottoChars.value[index] = value; | 106 | + const firstChar = value.match(/[\u4e00-\u9fa5a-zA-Z0-9]/)?.[0] || ''; |
| 97 | - if (value && index < 3) { | 107 | + mottoChars.value[index] = firstChar; |
| 98 | - // Taro中无法直接操作DOM进行focus,这里仅保留逻辑 | 108 | + |
| 99 | - // 在小程序中,可以通过设置 `focus` 属性来控制 | 109 | + // 如果输入了有效字符且不是最后一个输入框,自动聚焦下一个 |
| 110 | + if (firstChar && index < 3) { | ||
| 111 | + // Taro中无法直接操作DOM进行focus,这里仅保留逻辑 | ||
| 112 | + // 在小程序中,可以通过设置 `focus` 属性来控制 | ||
| 113 | + } | ||
| 114 | + } else { | ||
| 115 | + mottoChars.value[index] = ''; | ||
| 100 | } | 116 | } |
| 101 | }; | 117 | }; |
| 102 | 118 | ||
| ... | @@ -106,6 +122,22 @@ const handleKeyDown = (index, e) => { | ... | @@ -106,6 +122,22 @@ const handleKeyDown = (index, e) => { |
| 106 | } | 122 | } |
| 107 | }; | 123 | }; |
| 108 | 124 | ||
| 125 | +/** | ||
| 126 | + * 处理输入框失焦事件 | ||
| 127 | + * @param {number} index - 输入框索引 | ||
| 128 | + */ | ||
| 129 | +const handleBlur = (index) => { | ||
| 130 | + // 重置焦点状态 | ||
| 131 | + focusedIndex.value = -1; | ||
| 132 | + | ||
| 133 | + // 失焦时再次验证输入值,确保只保留有效字符(汉字、数字、大小写字母) | ||
| 134 | + const currentValue = mottoChars.value[index]; | ||
| 135 | + if (currentValue) { | ||
| 136 | + const firstChar = currentValue.match(/[\u4e00-\u9fa5a-zA-Z0-9]/)?.[0] || ''; | ||
| 137 | + mottoChars.value[index] = firstChar; | ||
| 138 | + } | ||
| 139 | +}; | ||
| 140 | + | ||
| 109 | const familyRoles = [ | 141 | const familyRoles = [ |
| 110 | { id: 'husband', label: '丈夫' }, | 142 | { id: 'husband', label: '丈夫' }, |
| 111 | { id: 'wife', label: '妻子' }, | 143 | { id: 'wife', label: '妻子' }, | ... | ... |
-
Please register or login to post a comment