hookehuyr

feat(profile): 添加个人信息完善页面及导航检查

添加个人信息完善页面组件,包含头像上传、昵称、出生年月和轮椅出行需求表单
在Welcome页面添加导航前检查个人信息是否完善的逻辑
更新app.config.js添加新页面路由
/*
* @Date: 2025-06-28 10:33:00
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-08-28 14:52:14
* @LastEditTime: 2025-08-28 21:48:50
* @FilePath: /lls_program/src/app.config.js
* @Description: 文件描述
*/
......@@ -18,6 +18,7 @@ export default {
'pages/Rewards/index',
'pages/MyRewards/index',
'pages/Profile/index',
'pages/AddProfile/index',
'pages/Feedback/index',
'pages/PointsDetail/index',
'pages/RewardDetail/index',
......
/*
* @Date: 2025-08-28 11:22:13
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-08-28 22:02:32
* @FilePath: /lls_program/src/pages/AddProfile/index.config.js
* @Description: 文件描述
*/
export default {
navigationBarTitleText: '完善个人信息'
}
<template>
<view class="min-h-screen bg-gray-50 p-5 pb-24">
<!-- Avatar -->
<view class="flex flex-col items-center py-8" @click="changeAvatar">
<view class="w-24 h-24 rounded-full bg-gray-100 flex items-center justify-center mb-2 overflow-hidden">
<image v-if="formData.avatar_url" :src="formData.avatar_url" class="w-full h-full" mode="aspectFill" />
<My size="40" color="#888" v-else />
</view>
<text class="text-gray-500 text-sm">上传头像</text>
</view>
<!-- Form -->
<view class="space-y-6">
<!-- Nickname -->
<view class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2">昵称</label>
<view class="bg-white rounded-xl p-2 border border-gray-200">
<nut-input
v-model="formData.nickname"
placeholder="请输入昵称"
:border="false"
class="!bg-transparent !border-none !p-0 text-base"
/>
</view>
</view>
<!-- Birthday -->
<view class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2">出生年月</label>
<view class="bg-white rounded-xl p-4 border border-gray-200" @click="showDatePicker = true">
<view class="flex justify-between items-center">
<text :class="{'text-gray-400': !formData.birthday, 'text-gray-900': formData.birthday}" class="text-base">
{{ formData.birthday || '-/-/-/' }}
</text>
<DateIcon size="20" color="#888" />
</view>
</view>
</view>
<!-- Wheelchair -->
<view class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2">是否需要轮椅出行</label>
<view class="bg-white rounded-xl p-4 border border-gray-200" @click="showWheelchairPicker = true">
<view class="flex justify-between items-center">
<text :class="{'text-gray-400': !formData.wheelchair_text, 'text-gray-900': formData.wheelchair_text}" class="text-base">
{{ formData.wheelchair_text || '请选择' }}
</text>
<Right size="16" color="#888" />
</view>
</view>
</view>
</view>
<!-- Save Button -->
<view class="fixed bottom-0 left-0 right-0 p-4 bg-white border-t border-gray-100">
<nut-button type="primary" size="large" :color="isFormValid ? '#4A90E2' : '#D8D8D8'" block @click="handleSave" :disabled="!isFormValid">保存</nut-button>
</view>
<!-- Popups -->
<nut-popup v-model:visible="showDatePicker" position="bottom">
<nut-date-picker
v-model="currentDate"
@confirm="onDateConfirm"
@cancel="showDatePicker = false"
:min-date="minDate"
:max-date="maxDate"
title="选择出生年月"
></nut-date-picker>
</nut-popup>
<nut-popup v-model:visible="showWheelchairPicker" position="bottom">
<nut-picker
v-model="wheelchairValue"
:columns="wheelchairColumns"
@confirm="onWheelchairConfirm"
@cancel="showWheelchairPicker = false"
title="是否需要轮椅出行"
></nut-picker>
</nut-popup>
<nut-image-preview v-model:show="previewVisible" :images="previewImages" />
</view>
</template>
<script setup>
import { ref, reactive, onMounted, computed } from 'vue';
import Taro from '@tarojs/taro';
import { My, Date as DateIcon, Right } from '@nutui/icons-vue-taro';
import BASE_URL from '@/utils/config';
/**
* @description 表单数据
*/
const formData = reactive({
avatar_url: '',
nickname: '',
birthday: '',
wheelchair: null, // 0 for No, 1 for Yes
wheelchair_text: '',
});
/**
* @description 检查表单是否有效
*/
const isFormValid = computed(() => {
return formData.nickname && formData.birthday && formData.wheelchair !== null;
});
// --- Date Picker ---
/**
* @description 控制日期选择器显示
*/
const showDatePicker = ref(false);
/**
* @description 最小可选日期
*/
const minDate = new Date(1920, 0, 1);
/**
* @description 最大可选日期
*/
const maxDate = new Date();
/**
* @description 当前选中的日期
*/
const currentDate = ref(new Date());
/**
* @description 确认选择日期
* @param {object} param0 - 包含 selectedValue 的对象
*/
const onDateConfirm = ({ selectedValue }) => {
formData.birthday = selectedValue.join('-');
showDatePicker.value = false;
};
// --- Wheelchair Picker ---
/**
* @description 控制轮椅选择器显示
*/
const showWheelchairPicker = ref(false);
/**
* @description 当前选中的轮椅选项
*/
const wheelchairValue = ref([]);
/**
* @description 轮椅选择器选项
*/
const wheelchairColumns = ref([
{ text: '是', value: 1 },
{ text: '否', value: 0 },
]);
/**
* @description 确认选择轮椅选项
* @param {object} param0 - 包含 selectedValue 和 selectedOptions 的对象
*/
const onWheelchairConfirm = ({ selectedValue, selectedOptions }) => {
formData.wheelchair = selectedValue[0];
formData.wheelchair_text = selectedOptions.map((option) => option.text).join('');
showWheelchairPicker.value = false;
};
// --- Avatar ---
/**
* @description 控制图片预览显示
*/
const previewVisible = ref(false);
/**
* @description 预览的图片列表
*/
const previewImages = ref([]);
/**
* @description 更换头像
*/
const changeAvatar = () => {
Taro.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
const tempFile = res.tempFiles[0];
if (tempFile.size > 10 * 1024 * 1024) {
Taro.showToast({
title: '图片大小不能超过10MB',
icon: 'none',
});
return;
}
Taro.showLoading({ title: '上传中...' });
Taro.uploadFile({
url: BASE_URL + '/admin/?m=srv&a=upload',
filePath: tempFile.path,
name: 'file',
success: (uploadRes) => {
Taro.hideLoading();
const data = JSON.parse(uploadRes.data);
if (data.code === 0) {
formData.avatar_url = data.data.url;
Taro.showToast({ title: '上传成功', icon: 'success' });
} else {
Taro.showToast({ title: data.msg || '上传失败', icon: 'none' });
}
},
fail: () => {
Taro.hideLoading();
Taro.showToast({ title: '上传失败,请稍后重试', icon: 'none' });
},
});
},
fail: () => {
Taro.showToast({ title: '选择图片失败', icon: 'none' });
},
});
};
// --- Save ---
/**
* @description 保存用户信息
*/
const handleSave = () => {
if (!isFormValid.value) {
Taro.showToast({
title: '请填写所有必填项',
icon: 'none',
});
return;
}
console.log('Saving data:', formData);
Taro.showLoading({ title: '保存中...' });
// Mock save process
setTimeout(() => {
Taro.hideLoading();
Taro.showToast({
title: '保存成功',
icon: 'success',
duration: 1500,
complete: () => {
Taro.navigateBack();
},
});
}, 1000);
};
/**
* @description 页面加载时获取初始数据
*/
onMounted(() => {
// Mock fetching user data, in a real app, you would call an API
// const mockUserData = {
// avatar_url: 'https://img.yzcdn.cn/vant/cat.jpeg',
// nickname: '张三',
// birthday: '1990-05-15',
// wheelchair: 0,
// wheelchair_text: '否',
// };
// Object.assign(formData, mockUserData);
// Initialize pickers with current data
// currentDate.value = new Date(mockUserData.birthday);
// wheelchairValue.value = [mockUserData.wheelchair];
});
</script>
<style>
.nut-input.mt-2 {
padding: 12px 0 !important;
border-bottom: 1px solid #e5e7eb;
}
</style>
<!--
* @Date: 2025-08-27 17:43:45
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-08-28 21:45:22
* @LastEditTime: 2025-08-28 22:08:17
* @FilePath: /lls_program/src/pages/Welcome/index.vue
* @Description: 文件描述
-->
......@@ -61,10 +61,10 @@
</view>
<!-- Action Buttons -->
<view class="space-y-4 mt-auto">
<view @click="navigateTo('/pages/CreateFamily/index')" class="w-full py-3.5 bg-blue-500 text-white text-lg font-medium rounded-full text-center">
<view @tap="handleNavigate('/pages/CreateFamily/index')" class="w-full py-3.5 bg-blue-500 text-white text-lg font-medium rounded-full text-center">
创建家庭
</view>
<view @click="navigateTo('/pages/JoinFamily/index')" class="w-full py-3.5 bg-white text-gray-800 text-lg font-medium rounded-full border border-gray-300 text-center" style="margin-bottom: 1rem;">
<view @tap="handleNavigate('/pages/JoinFamily/index')" class="w-full py-3.5 bg-white text-gray-800 text-lg font-medium rounded-full border border-gray-300 text-center" style="margin-bottom: 1rem;">
加入家庭
</view>
</view>
......@@ -82,6 +82,27 @@ import welcomeHomeImg from '../../assets/images/welcome_home.png';
const navigateTo = (url) => {
Taro.navigateTo({ url });
};
const handleNavigate = (url) => {
// TODO: 模拟检查个人信息是否完善
const hasProfile = true; // 假设未完善
if (!hasProfile) {
Taro.showModal({
title: '提示',
content: '参加活动需要完善个人信息',
cancelText: '关闭',
confirmText: '完善信息',
success: (res) => {
if (res.confirm) {
Taro.navigateTo({ url: '/pages/AddProfile/index' });
}
},
});
} else {
navigateTo(url);
}
};
</script>
<style lang="less">
......