hookehuyr

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

- 新增 EditFamily 页面组件,包含家庭信息编辑表单
- 在 app.config.js 中注册新页面路由
- 修改 Dashboard 页面跳转逻辑至新页面
- 调整 CreateFamily 页面按钮样式
......@@ -25,6 +25,7 @@ export default {
'pages/UserAgreement/index',
'pages/CouponDetail/index',
'pages/EditProfile/index',
'pages/EditFamily/index',
],
window: {
backgroundTextStyle: 'light',
......
<!--
* @Date: 2025-08-27 17:44:53
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-08-27 20:51:19
* @LastEditTime: 2025-08-28 12:55:42
* @FilePath: /lls_program/src/pages/CreateFamily/index.vue
* @Description: 文件描述
-->
......@@ -140,7 +140,7 @@
<!-- Submit Button -->
<view
@click="handleCreateFamily"
class="w-full py-4 bg-blue-500 text-white text-lg font-medium rounded-lg flex items-center justify-center"
class="w-full py-3 bg-blue-500 text-white text-lg font-medium rounded-lg flex items-center justify-center"
>
创建家庭
</view>
......
......@@ -143,7 +143,7 @@ const handleSyncSteps = () => {
};
const goToProfile = () => {
Taro.navigateTo({ url: '/pages/Profile/index' });
Taro.navigateTo({ url: '/pages/EditFamily/index' });
};
const goToActivities = () => {
......
<!--
* @Date: 2025-08-27 17:44:53
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-08-28 12:55:02
* @FilePath: /lls_program/src/pages/EditFamily/index.vue
* @Description: 文件描述
-->
<template>
<view class="min-h-screen flex flex-col bg-white">
<!-- <AppHeader title="编辑家庭" /> -->
<view class="flex-1 px-4 py-6 overflow-auto">
<view class="mb-6">
<view class="text-gray-600 mb-6">
请填写家庭信息,创建您的专属家庭空间
</view>
<!-- Family Name -->
<view class="mb-6">
<view class="bg-white rounded-lg border border-gray-200 p-4">
<view class="block text-lg font-medium mb-2">家庭名称</view>
<input
type="text"
v-model="familyName"
class="w-full text-gray-600 focus:outline-none"
placeholder="请输入家庭名称"
/>
</view>
</view>
<!-- Family Introduction -->
<view class="mb-6">
<view class="bg-white rounded-lg border border-gray-200 p-4">
<view class="block text-lg font-medium mb-2">家庭介绍</view>
<textarea
v-model="familyIntro"
class="w-full text-gray-600 focus:outline-none resize-none"
placeholder="请输入您家庭的特色、成员特点等家庭标签"
:rows="2"
/>
</view>
</view>
<!-- Family Size -->
<view class="mb-6">
<view class="bg-white rounded-lg border border-gray-200 p-4">
<view class="block text-lg font-medium mb-4">家庭规模</view>
<view class="flex gap-2">
<view
v-for="size in familySizes"
:key="size"
@click="familySize = size"
:class="[
'flex-1 py-3 rounded-lg border text-center',
familySize === size
? 'border-blue-500 bg-blue-50 text-blue-500'
: 'border-gray-200 text-gray-700'
]"
>
{{ size }}
</view>
</view>
</view>
</view>
<!-- Family Motto -->
<view class="mb-6">
<view class="bg-white rounded-lg border border-gray-200 p-4">
<view class="flex justify-between items-center mb-4">
<view class="block text-lg font-medium">家训口令</view>
<!-- <view
@click="generateRandomMotto"
class="px-3 py-1 bg-blue-100 text-blue-600 rounded-full text-sm"
>
随机生成
</view> -->
</view>
<view class="flex gap-2 mb-4">
<view v-for="(char, index) in familyMotto" :key="index" class="flex-1">
<view class="w-full aspect-square flex items-center justify-center bg-gray-100 rounded-lg">
<input
type="text"
v-model="familyMotto[index]"
:placeholder="familyMottoPlaceholder[index]"
@input="(e) => handleInputChange(index, e.target.value)"
@focus="focusedIndex = index"
@blur="handleBlur(index)"
class="w-full h-full bg-transparent text-center"
style="font-size: 38rpx;"
/>
</view>
</view>
<!-- <view class="flex-1 flex items-center justify-center">
<view class="w-full aspect-square flex items-center justify-center bg-gray-100 rounded-lg text-blue-500">
<Edit size="20" />
</view>
</view> -->
</view>
<view class="flex items-center text-sm text-gray-600">
<Tips size="16" class="text-yellow-500 mr-2" />
<view>设置有意义的家训口令,便于家人记忆和加入</view>
</view>
</view>
</view>
<!-- Family Avatar -->
<view class="mb-10">
<view class="bg-white rounded-lg border border-gray-200 p-4">
<view class="block text-lg font-medium mb-2">
家庭头像(选填)
</view>
<!-- 已上传头像显示 -->
<view v-if="familyAvatar" class="mb-4">
<view class="relative inline-block">
<image
:src="familyAvatar"
class="w-24 h-24 rounded-lg object-cover"
mode="aspectFill"
@tap="previewAvatar"
/>
<view
@click="deleteAvatar"
class="absolute -top-2 -right-2 w-5 h-5 bg-red-500 rounded-full flex items-center justify-center"
>
<view class="text-white text-xs">×</view>
</view>
</view>
</view>
<!-- 上传区域 -->
<view
v-if="!familyAvatar"
class="border border-dashed border-gray-300 rounded-lg p-6 flex flex-col items-center justify-center"
@click="chooseImage"
>
<view class="text-gray-400 mb-2">
<Photograph size="24" />
</view>
<view class="text-center text-gray-400">点击上传图片</view>
<view class="text-center text-gray-400 text-xs mt-1">
支持jpg、png格式,大小不超过10MB
</view>
</view>
</view>
</view>
</view>
<!-- Submit Button -->
<view
@click="handleSaveFamily"
class="w-full py-3 bg-blue-500 text-white text-lg font-medium rounded-lg flex items-center justify-center"
>
保存
</view>
</view>
<!-- 图片预览 -->
<nut-image-preview
v-model:show="previewVisible"
:images="previewImages"
:init-no="previewIndex"
@close="closePreview"
/>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import Taro from '@tarojs/taro';
import { Edit, Tips, Photograph } from '@nutui/icons-vue-taro';
// import AppHeader from '../../components/AppHeader.vue';
import BASE_URL from '@/utils/config';
const familyName = ref('');
const familyIntro = ref('');
const familySize = ref('3-5人');
const familyMotto = ref(['', '', '', '']);
const familyMottoPlaceholder = ref(['孝', '敬', '和', '睦']);
const familySizes = ['2人', '3-5人', '6人+'];
const familyAvatar = ref('');
const focusedIndex = ref(-1);
// 图片预览相关
const previewVisible = ref(false);
const previewImages = ref([]);
const previewIndex = ref(0);
onMounted(() => {
Taro.setNavigationBarTitle({ title: '编辑家庭' });
// Mock data for current family information
familyName.value = '幸福一家';
familyIntro.value = '我们是相亲相爱的一家人';
familySize.value = '3-5人';
familyMotto.value = ['家', '和', '万', '事'];
familyAvatar.value = 'https://img.yzcdn.cn/vant/cat.jpeg';
});
/**
* 处理输入变化
*/
const handleInputChange = (index, value) => {
// 只保留第一个有效字符(汉字、数字、大小写字母)
const validChar = value.match(/[\u4e00-\u9fa5a-zA-Z0-9]/)?.[0] || '';
familyMotto.value[index] = validChar;
};
/**
* 处理失焦事件
*/
const handleBlur = (index) => {
focusedIndex.value = -1;
// 确保只保留有效字符(汉字、数字、大小写字母)
const value = familyMotto.value[index];
const validChar = value.match(/[\u4e00-\u9fa5a-zA-Z0-9]/)?.[0] || '';
familyMotto.value[index] = validChar;
};
/**
* 显示提示信息
*/
const showToast = (message, type = 'success') => {
const icon = type === 'error' ? 'error' : 'success';
Taro.showToast({
title: message,
icon: icon,
duration: 2000
});
};
/**
* 选择图片
*/
const chooseImage = () => {
Taro.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: function (res) {
const tempFilePath = res.tempFilePaths[0];
// 检查文件大小(10MB = 10 * 1024 * 1024 bytes)
Taro.getFileInfo({
filePath: tempFilePath,
success: function (fileInfo) {
if (fileInfo.size > 10 * 1024 * 1024) {
showToast('图片大小不能超过10MB', 'error');
return;
}
uploadImage(tempFilePath);
},
fail: function () {
// 如果获取文件信息失败,直接上传
uploadImage(tempFilePath);
}
});
},
fail: function () {
showToast('选择图片失败', 'error');
}
});
};
/**
* 上传图片到服务器
*/
const uploadImage = (filePath) => {
// 显示上传中提示
Taro.showLoading({
title: '上传中',
mask: true
});
wx.uploadFile({
url: BASE_URL + '/admin/?m=srv&a=upload',
filePath,
name: 'file',
header: {
'content-type': 'multipart/form-data',
},
success: function (res) {
let upload_data = JSON.parse(res.data);
Taro.hideLoading({
success: () => {
if (res.statusCode === 200) {
familyAvatar.value = upload_data.data.src;
showToast('上传成功', 'success');
} else {
showToast('服务器错误,稍后重试!', 'error');
}
},
});
},
fail: function (res) {
Taro.hideLoading({
success: () => {
showToast('上传失败,稍后重试!', 'error');
}
});
}
});
};
/**
* 预览头像
*/
const previewAvatar = () => {
if (!familyAvatar.value) {
showToast('暂无图片可预览', 'error');
return;
}
previewImages.value = [{ src: familyAvatar.value }];
previewIndex.value = 0;
previewVisible.value = true;
};
/**
* 删除头像
*/
const deleteAvatar = () => {
familyAvatar.value = '';
showToast('头像已删除', 'success');
};
/**
* 关闭预览
*/
const closePreview = () => {
previewVisible.value = false;
};
/**
* 表单验证
*/
const validateForm = () => {
if (!familyName.value.trim()) {
showToast('请输入家庭名称', 'error');
return false;
}
if (!familyIntro.value.trim()) {
showToast('请输入家庭介绍', 'error');
return false;
}
if (!familySize.value) {
showToast('请选择家庭规模', 'error');
return false;
}
// 检查家训口令是否完整填写
const mottoComplete = familyMotto.value.every(char => char.trim() !== '');
if (!mottoComplete) {
showToast('请完整填写家训口令', 'error');
return false;
}
return true;
};
/**
* 保存家庭信息
*/
const handleSaveFamily = () => {
if (!validateForm()) {
return;
}
// 在实际应用中,这里会调用API保存家庭信息
showToast('家庭信息保存成功', 'success');
setTimeout(() => {
Taro.navigateBack();
}, 1500);
};
</script>