hookehuyr

feat(JoinFamily): 更新加入家庭页面样式和输入验证逻辑

refactor(CreateFamily): 优化家训输入框的交互和验证逻辑
style: 统一文件头部注释格式
...@@ -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,16 +97,23 @@ import AppHeader from '../../components/AppHeader.vue'; ...@@ -88,16 +97,23 @@ 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;
108 +
109 + // 如果输入了有效字符且不是最后一个输入框,自动聚焦下一个
110 + if (firstChar && index < 3) {
98 // Taro中无法直接操作DOM进行focus,这里仅保留逻辑 111 // Taro中无法直接操作DOM进行focus,这里仅保留逻辑
99 // 在小程序中,可以通过设置 `focus` 属性来控制 112 // 在小程序中,可以通过设置 `focus` 属性来控制
100 } 113 }
114 + } else {
115 + mottoChars.value[index] = '';
116 + }
101 }; 117 };
102 118
103 const handleKeyDown = (index, e) => { 119 const handleKeyDown = (index, e) => {
...@@ -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: '妻子' },
......