hookehuyr

feat(EditProfile): 新增编辑个人信息页面

添加编辑个人信息页面,包含头像上传、昵称修改、出生年月选择和轮椅出行需求选择功能
......@@ -12,7 +12,11 @@ declare module 'vue' {
GlassCard: typeof import('./src/components/GlassCard.vue')['default']
NavBar: typeof import('./src/components/navBar.vue')['default']
NutButton: typeof import('@nutui/nutui-taro')['Button']
NutDatePicker: typeof import('@nutui/nutui-taro')['DatePicker']
NutImagePreview: typeof import('@nutui/nutui-taro')['ImagePreview']
NutInput: typeof import('@nutui/nutui-taro')['Input']
NutPicker: typeof import('@nutui/nutui-taro')['Picker']
NutPopup: typeof import('@nutui/nutui-taro')['Popup']
Picker: typeof import('./src/components/time-picker-data/picker.vue')['default']
PointsCollector: typeof import('./src/components/PointsCollector.vue')['default']
PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default']
......
/*
* @Date: 2025-06-28 10:33:00
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-08-28 10:21:44
* @LastEditTime: 2025-08-28 11:22:42
* @FilePath: /lls_program/src/app.config.js
* @Description: 文件描述
*/
......@@ -24,6 +24,7 @@ export default {
'pages/PrivacyPolicy/index',
'pages/UserAgreement/index',
'pages/CouponDetail/index',
'pages/EditProfile/index',
],
window: {
backgroundTextStyle: 'light',
......
......@@ -146,11 +146,7 @@ const goToProfile = () => {
Taro.navigateTo({ url: '/pages/Profile/index' });
};
const goToRewards = () => {
Taro.navigateTo({ url: '/pages/RewardCategories/index' });
};
const goToActivities = () => {
Taro.switchTab({ url: '/pages/Activities/index' });
};
</script>
......
/*
* @Date: 2025-08-28 11:22:13
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-08-28 11:22:19
* @FilePath: /lls_program/src/pages/EditProfile/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="#4A90E2" block @click="handleSave">保存</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 } from 'vue';
import Taro from '@tarojs/taro';
import { My, Date as DateIcon, Right } from '@nutui/icons-vue-taro';
/**
* @description 表单数据
*/
const formData = reactive({
avatar_url: '',
nickname: '',
birthday: '',
wheelchair: null, // 0 for No, 1 for Yes
wheelchair_text: '',
});
// --- 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 tempFilePath = res.tempFilePaths[0];
// In a real app, you would upload this file to your server
// and get back a URL. For this demo, we'll just use the local path.
Taro.showLoading({ title: '上传中...' });
// Mock upload process
setTimeout(() => {
formData.avatar_url = tempFilePath;
Taro.hideLoading();
Taro.showToast({ title: '上传成功', icon: 'success' });
}, 1000);
},
fail: () => {
Taro.showToast({ title: '选择图片失败', icon: 'none' });
}
});
};
// --- Save ---
/**
* @description 保存用户信息
*/
const handleSave = () => {
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:47:46
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-08-28 09:57:23
* @LastEditTime: 2025-08-28 11:16:24
* @FilePath: /lls_program/src/pages/Profile/index.vue
* @Description: 文件描述
-->
......@@ -19,7 +19,7 @@
<h1 class="text-xl font-bold text-white">张爷爷</h1>
</view>
</view>
<view class="text-white">
<view @tap="goToEditProfile" class="text-white">
<span>编辑</span>
</view>
</view>
......@@ -86,4 +86,8 @@ const menuItems = shallowRef([
onClick: () => Taro.navigateTo({ url: '/pages/PrivacyPolicy/index' })
}
]);
const goToEditProfile = () => {
Taro.navigateTo({ url: '/pages/EditProfile/index' });
};
</script>
......