hookehuyr

feat: 添加编辑家庭页面功能

- 新增 EditFamily 页面组件,包含家庭信息编辑表单
- 在 app.config.js 中注册新页面路由
- 修改 Dashboard 页面跳转逻辑至新页面
- 调整 CreateFamily 页面按钮样式
...@@ -25,6 +25,7 @@ export default { ...@@ -25,6 +25,7 @@ export default {
25 'pages/UserAgreement/index', 25 'pages/UserAgreement/index',
26 'pages/CouponDetail/index', 26 'pages/CouponDetail/index',
27 'pages/EditProfile/index', 27 'pages/EditProfile/index',
28 + 'pages/EditFamily/index',
28 ], 29 ],
29 window: { 30 window: {
30 backgroundTextStyle: 'light', 31 backgroundTextStyle: 'light',
......
1 <!-- 1 <!--
2 * @Date: 2025-08-27 17:44:53 2 * @Date: 2025-08-27 17:44:53
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-08-27 20:51:19 4 + * @LastEditTime: 2025-08-28 12:55:42
5 * @FilePath: /lls_program/src/pages/CreateFamily/index.vue 5 * @FilePath: /lls_program/src/pages/CreateFamily/index.vue
6 * @Description: 文件描述 6 * @Description: 文件描述
7 --> 7 -->
...@@ -140,7 +140,7 @@ ...@@ -140,7 +140,7 @@
140 <!-- Submit Button --> 140 <!-- Submit Button -->
141 <view 141 <view
142 @click="handleCreateFamily" 142 @click="handleCreateFamily"
143 - class="w-full py-4 bg-blue-500 text-white text-lg font-medium rounded-lg flex items-center justify-center" 143 + class="w-full py-3 bg-blue-500 text-white text-lg font-medium rounded-lg flex items-center justify-center"
144 > 144 >
145 创建家庭 145 创建家庭
146 </view> 146 </view>
......
...@@ -143,7 +143,7 @@ const handleSyncSteps = () => { ...@@ -143,7 +143,7 @@ const handleSyncSteps = () => {
143 }; 143 };
144 144
145 const goToProfile = () => { 145 const goToProfile = () => {
146 - Taro.navigateTo({ url: '/pages/Profile/index' }); 146 + Taro.navigateTo({ url: '/pages/EditFamily/index' });
147 }; 147 };
148 148
149 const goToActivities = () => { 149 const goToActivities = () => {
......
1 +<!--
2 + * @Date: 2025-08-27 17:44:53
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-08-28 12:55:02
5 + * @FilePath: /lls_program/src/pages/EditFamily/index.vue
6 + * @Description: 文件描述
7 +-->
8 +<template>
9 + <view class="min-h-screen flex flex-col bg-white">
10 + <!-- <AppHeader title="编辑家庭" /> -->
11 + <view class="flex-1 px-4 py-6 overflow-auto">
12 + <view class="mb-6">
13 + <view class="text-gray-600 mb-6">
14 + 请填写家庭信息,创建您的专属家庭空间
15 + </view>
16 + <!-- Family Name -->
17 + <view class="mb-6">
18 + <view class="bg-white rounded-lg border border-gray-200 p-4">
19 + <view class="block text-lg font-medium mb-2">家庭名称</view>
20 + <input
21 + type="text"
22 + v-model="familyName"
23 + class="w-full text-gray-600 focus:outline-none"
24 + placeholder="请输入家庭名称"
25 + />
26 + </view>
27 + </view>
28 + <!-- Family Introduction -->
29 + <view class="mb-6">
30 + <view class="bg-white rounded-lg border border-gray-200 p-4">
31 + <view class="block text-lg font-medium mb-2">家庭介绍</view>
32 + <textarea
33 + v-model="familyIntro"
34 + class="w-full text-gray-600 focus:outline-none resize-none"
35 + placeholder="请输入您家庭的特色、成员特点等家庭标签"
36 + :rows="2"
37 + />
38 + </view>
39 + </view>
40 + <!-- Family Size -->
41 + <view class="mb-6">
42 + <view class="bg-white rounded-lg border border-gray-200 p-4">
43 + <view class="block text-lg font-medium mb-4">家庭规模</view>
44 + <view class="flex gap-2">
45 + <view
46 + v-for="size in familySizes"
47 + :key="size"
48 + @click="familySize = size"
49 + :class="[
50 + 'flex-1 py-3 rounded-lg border text-center',
51 + familySize === size
52 + ? 'border-blue-500 bg-blue-50 text-blue-500'
53 + : 'border-gray-200 text-gray-700'
54 + ]"
55 + >
56 + {{ size }}
57 + </view>
58 + </view>
59 + </view>
60 + </view>
61 + <!-- Family Motto -->
62 + <view class="mb-6">
63 + <view class="bg-white rounded-lg border border-gray-200 p-4">
64 + <view class="flex justify-between items-center mb-4">
65 + <view class="block text-lg font-medium">家训口令</view>
66 + <!-- <view
67 + @click="generateRandomMotto"
68 + class="px-3 py-1 bg-blue-100 text-blue-600 rounded-full text-sm"
69 + >
70 + 随机生成
71 + </view> -->
72 + </view>
73 + <view class="flex gap-2 mb-4">
74 + <view v-for="(char, index) in familyMotto" :key="index" class="flex-1">
75 + <view class="w-full aspect-square flex items-center justify-center bg-gray-100 rounded-lg">
76 + <input
77 + type="text"
78 + v-model="familyMotto[index]"
79 + :placeholder="familyMottoPlaceholder[index]"
80 + @input="(e) => handleInputChange(index, e.target.value)"
81 + @focus="focusedIndex = index"
82 + @blur="handleBlur(index)"
83 + class="w-full h-full bg-transparent text-center"
84 + style="font-size: 38rpx;"
85 + />
86 + </view>
87 + </view>
88 + <!-- <view class="flex-1 flex items-center justify-center">
89 + <view class="w-full aspect-square flex items-center justify-center bg-gray-100 rounded-lg text-blue-500">
90 + <Edit size="20" />
91 + </view>
92 + </view> -->
93 + </view>
94 + <view class="flex items-center text-sm text-gray-600">
95 + <Tips size="16" class="text-yellow-500 mr-2" />
96 + <view>设置有意义的家训口令,便于家人记忆和加入</view>
97 + </view>
98 + </view>
99 + </view>
100 + <!-- Family Avatar -->
101 + <view class="mb-10">
102 + <view class="bg-white rounded-lg border border-gray-200 p-4">
103 + <view class="block text-lg font-medium mb-2">
104 + 家庭头像(选填)
105 + </view>
106 + <!-- 已上传头像显示 -->
107 + <view v-if="familyAvatar" class="mb-4">
108 + <view class="relative inline-block">
109 + <image
110 + :src="familyAvatar"
111 + class="w-24 h-24 rounded-lg object-cover"
112 + mode="aspectFill"
113 + @tap="previewAvatar"
114 + />
115 + <view
116 + @click="deleteAvatar"
117 + class="absolute -top-2 -right-2 w-5 h-5 bg-red-500 rounded-full flex items-center justify-center"
118 + >
119 + <view class="text-white text-xs">×</view>
120 + </view>
121 + </view>
122 + </view>
123 + <!-- 上传区域 -->
124 + <view
125 + v-if="!familyAvatar"
126 + class="border border-dashed border-gray-300 rounded-lg p-6 flex flex-col items-center justify-center"
127 + @click="chooseImage"
128 + >
129 + <view class="text-gray-400 mb-2">
130 + <Photograph size="24" />
131 + </view>
132 + <view class="text-center text-gray-400">点击上传图片</view>
133 + <view class="text-center text-gray-400 text-xs mt-1">
134 + 支持jpg、png格式,大小不超过10MB
135 + </view>
136 + </view>
137 + </view>
138 + </view>
139 + </view>
140 + <!-- Submit Button -->
141 + <view
142 + @click="handleSaveFamily"
143 + class="w-full py-3 bg-blue-500 text-white text-lg font-medium rounded-lg flex items-center justify-center"
144 + >
145 + 保存
146 + </view>
147 + </view>
148 +
149 +
150 +
151 + <!-- 图片预览 -->
152 + <nut-image-preview
153 + v-model:show="previewVisible"
154 + :images="previewImages"
155 + :init-no="previewIndex"
156 + @close="closePreview"
157 + />
158 + </view>
159 +</template>
160 +
161 +<script setup>
162 +import { ref, onMounted } from 'vue';
163 +import Taro from '@tarojs/taro';
164 +import { Edit, Tips, Photograph } from '@nutui/icons-vue-taro';
165 +// import AppHeader from '../../components/AppHeader.vue';
166 +import BASE_URL from '@/utils/config';
167 +
168 +const familyName = ref('');
169 +const familyIntro = ref('');
170 +const familySize = ref('3-5人');
171 +const familyMotto = ref(['', '', '', '']);
172 +const familyMottoPlaceholder = ref(['孝', '敬', '和', '睦']);
173 +const familySizes = ['2人', '3-5人', '6人+'];
174 +const familyAvatar = ref('');
175 +const focusedIndex = ref(-1);
176 +
177 +// 图片预览相关
178 +const previewVisible = ref(false);
179 +const previewImages = ref([]);
180 +const previewIndex = ref(0);
181 +
182 +onMounted(() => {
183 + Taro.setNavigationBarTitle({ title: '编辑家庭' });
184 + // Mock data for current family information
185 + familyName.value = '幸福一家';
186 + familyIntro.value = '我们是相亲相爱的一家人';
187 + familySize.value = '3-5人';
188 + familyMotto.value = ['家', '和', '万', '事'];
189 + familyAvatar.value = 'https://img.yzcdn.cn/vant/cat.jpeg';
190 +});
191 +
192 +/**
193 + * 处理输入变化
194 + */
195 +const handleInputChange = (index, value) => {
196 + // 只保留第一个有效字符(汉字、数字、大小写字母)
197 + const validChar = value.match(/[\u4e00-\u9fa5a-zA-Z0-9]/)?.[0] || '';
198 + familyMotto.value[index] = validChar;
199 +};
200 +
201 +/**
202 + * 处理失焦事件
203 + */
204 +const handleBlur = (index) => {
205 + focusedIndex.value = -1;
206 + // 确保只保留有效字符(汉字、数字、大小写字母)
207 + const value = familyMotto.value[index];
208 + const validChar = value.match(/[\u4e00-\u9fa5a-zA-Z0-9]/)?.[0] || '';
209 + familyMotto.value[index] = validChar;
210 +};
211 +
212 +/**
213 + * 显示提示信息
214 + */
215 +const showToast = (message, type = 'success') => {
216 + const icon = type === 'error' ? 'error' : 'success';
217 + Taro.showToast({
218 + title: message,
219 + icon: icon,
220 + duration: 2000
221 + });
222 +};
223 +
224 +/**
225 + * 选择图片
226 + */
227 +const chooseImage = () => {
228 + Taro.chooseImage({
229 + count: 1,
230 + sizeType: ['compressed'],
231 + sourceType: ['album', 'camera'],
232 + success: function (res) {
233 + const tempFilePath = res.tempFilePaths[0];
234 +
235 + // 检查文件大小(10MB = 10 * 1024 * 1024 bytes)
236 + Taro.getFileInfo({
237 + filePath: tempFilePath,
238 + success: function (fileInfo) {
239 + if (fileInfo.size > 10 * 1024 * 1024) {
240 + showToast('图片大小不能超过10MB', 'error');
241 + return;
242 + }
243 + uploadImage(tempFilePath);
244 + },
245 + fail: function () {
246 + // 如果获取文件信息失败,直接上传
247 + uploadImage(tempFilePath);
248 + }
249 + });
250 + },
251 + fail: function () {
252 + showToast('选择图片失败', 'error');
253 + }
254 + });
255 +};
256 +
257 +/**
258 + * 上传图片到服务器
259 + */
260 +const uploadImage = (filePath) => {
261 + // 显示上传中提示
262 + Taro.showLoading({
263 + title: '上传中',
264 + mask: true
265 + });
266 +
267 + wx.uploadFile({
268 + url: BASE_URL + '/admin/?m=srv&a=upload',
269 + filePath,
270 + name: 'file',
271 + header: {
272 + 'content-type': 'multipart/form-data',
273 + },
274 + success: function (res) {
275 + let upload_data = JSON.parse(res.data);
276 + Taro.hideLoading({
277 + success: () => {
278 + if (res.statusCode === 200) {
279 + familyAvatar.value = upload_data.data.src;
280 + showToast('上传成功', 'success');
281 + } else {
282 + showToast('服务器错误,稍后重试!', 'error');
283 + }
284 + },
285 + });
286 + },
287 + fail: function (res) {
288 + Taro.hideLoading({
289 + success: () => {
290 + showToast('上传失败,稍后重试!', 'error');
291 + }
292 + });
293 + }
294 + });
295 +};
296 +
297 +/**
298 + * 预览头像
299 + */
300 +const previewAvatar = () => {
301 + if (!familyAvatar.value) {
302 + showToast('暂无图片可预览', 'error');
303 + return;
304 + }
305 + previewImages.value = [{ src: familyAvatar.value }];
306 + previewIndex.value = 0;
307 + previewVisible.value = true;
308 +};
309 +
310 +/**
311 + * 删除头像
312 + */
313 +const deleteAvatar = () => {
314 + familyAvatar.value = '';
315 + showToast('头像已删除', 'success');
316 +};
317 +
318 +/**
319 + * 关闭预览
320 + */
321 +const closePreview = () => {
322 + previewVisible.value = false;
323 +};
324 +
325 +/**
326 + * 表单验证
327 + */
328 +const validateForm = () => {
329 + if (!familyName.value.trim()) {
330 + showToast('请输入家庭名称', 'error');
331 + return false;
332 + }
333 +
334 + if (!familyIntro.value.trim()) {
335 + showToast('请输入家庭介绍', 'error');
336 + return false;
337 + }
338 +
339 + if (!familySize.value) {
340 + showToast('请选择家庭规模', 'error');
341 + return false;
342 + }
343 +
344 + // 检查家训口令是否完整填写
345 + const mottoComplete = familyMotto.value.every(char => char.trim() !== '');
346 + if (!mottoComplete) {
347 + showToast('请完整填写家训口令', 'error');
348 + return false;
349 + }
350 +
351 + return true;
352 +};
353 +
354 +/**
355 + * 保存家庭信息
356 + */
357 +const handleSaveFamily = () => {
358 + if (!validateForm()) {
359 + return;
360 + }
361 +
362 + // 在实际应用中,这里会调用API保存家庭信息
363 + showToast('家庭信息保存成功', 'success');
364 +
365 + setTimeout(() => {
366 + Taro.navigateBack();
367 + }, 1500);
368 +};
369 +</script>