下载账单、城市和职业选择器

This commit is contained in:
2025-09-28 17:30:44 +08:00
parent d6fb08eee4
commit 8a3e41cef6
5 changed files with 486 additions and 229 deletions

View File

@@ -1,50 +1,50 @@
import React, { useState, useEffect } from 'react';
import { View, Text, Image, ScrollView, Button } from '@tarojs/components';
import { PopupPicker } from '@/components/Picker/index'
import Taro from '@tarojs/taro';
import './index.scss';
import { UserInfo } from '@/components/UserInfo';
import { UserService } from '@/services/userService';
import { clear_login_state } from '@/services/loginService';
import { convert_db_gender_to_display } from '@/utils/genderUtils';
import { EditModal } from '@/components';
import React, { useState, useEffect } from "react";
import { View, Text, Image, ScrollView, Button } from "@tarojs/components";
import { PopupPicker } from "@/components/Picker/index";
import Taro from "@tarojs/taro";
import "./index.scss";
import { UserInfo } from "@/components/UserInfo";
import { UserService, PickerOption } from "@/services/userService";
import { clear_login_state } from "@/services/loginService";
import { convert_db_gender_to_display } from "@/utils/genderUtils";
import { EditModal } from "@/components";
import img from "@/config/images";
const EditProfilePage: React.FC = () => {
// 用户信息状态
const [user_info, setUserInfo] = useState<UserInfo>({
id: '1',
nickname: '加载中...',
avatar: require('@/static/userInfo/default_avatar.svg'),
join_date: '加载中...',
id: "1",
nickname: "加载中...",
avatar: require("@/static/userInfo/default_avatar.svg"),
join_date: "加载中...",
stats: {
following: 0,
friends: 0,
hosted: 0,
participated: 0
participated: 0,
},
personal_profile: '加载中...',
occupation: '加载中...',
ntrp_level: 'NTRP 3.0',
phone: '',
gender: '',
country: '',
province: '',
city: '',
personal_profile: "加载中...",
occupation: "加载中...",
ntrp_level: "NTRP 3.0",
phone: "",
gender: "",
country: "",
province: "",
city: "",
});
// 表单状态
const [form_data, setFormData] = useState({
nickname: '',
personal_profile: '',
occupation: '',
ntrp_level: '4.0',
phone: '',
gender: '',
birthday: '2000-01-01',
country: '',
province: '',
city: ''
nickname: "",
personal_profile: "",
occupation: "",
ntrp_level: "4.0",
phone: "",
gender: "",
birthday: "2000-01-01",
country: "",
province: "",
city: "",
});
// 加载状态
@@ -52,18 +52,44 @@ const EditProfilePage: React.FC = () => {
// 编辑弹窗状态
const [edit_modal_visible, setEditModalVisible] = useState(false);
const [editing_field, setEditingField] = useState<string>('');
const [editing_field, setEditingField] = useState<string>("");
const [gender_picker_visible, setGenderPickerVisible] = useState(false);
const [birthday_picker_visible, setBirthdayPickerVisible] = useState(false);
const [location_picker_visible, setLocationPickerVisible] = useState(false);
const [ntrp_picker_visible, setNtrpPickerVisible] = useState(false);
const [occupation_picker_visible, setOccupationPickerVisible] = useState(false);
const [occupation_picker_visible, setOccupationPickerVisible] =
useState(false);
// 职业数据
const [professions, setProfessions] = useState<PickerOption[]>([]);
// 城市数据
const [cities, setCities] = useState<PickerOption[]>([]);
// 页面加载时初始化数据
useEffect(() => {
load_user_info();
getProfessions();
getCities();
}, []);
const getProfessions = async () => {
try {
const res = await UserService.getProfessions();
setProfessions(res);
} catch (e) {
console.log("获取职业失败:", e);
}
};
const getCities = async () => {
try {
const res = await UserService.getCities();
setCities(res);
} catch (e) {
console.log("获取职业失败:", e);
}
};
// 加载用户信息
const load_user_info = async () => {
try {
@@ -71,23 +97,23 @@ const EditProfilePage: React.FC = () => {
const user_data = await UserService.get_user_info();
setUserInfo(user_data);
setFormData({
nickname: user_data.nickname || '',
personal_profile: user_data.personal_profile || '',
occupation: user_data.occupation || '',
ntrp_level: user_data.ntrp_level || 'NTRP 4.0',
phone: user_data.phone || '',
gender: user_data.gender || '',
birthday: user_data.birthday || '',
country: user_data.country || '',
province: user_data.province || '',
city: user_data.city || ''
nickname: user_data.nickname || "",
personal_profile: user_data.personal_profile || "",
occupation: user_data.occupation || "",
ntrp_level: user_data.ntrp_level || "NTRP 4.0",
phone: user_data.phone || "",
gender: user_data.gender || "",
birthday: user_data.birthday || "",
country: user_data.country || "",
province: user_data.province || "",
city: user_data.city || "",
});
} catch (error) {
console.error('加载用户信息失败:', error);
console.error("加载用户信息失败:", error);
Taro.showToast({
title: '加载用户信息失败',
icon: 'error',
duration: 2000
title: "加载用户信息失败",
icon: "error",
duration: 2000,
});
} finally {
setLoading(false);
@@ -98,51 +124,51 @@ const EditProfilePage: React.FC = () => {
const handle_avatar_upload = () => {
Taro.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
sizeType: ["compressed"],
sourceType: ["album", "camera"],
success: async (res) => {
const tempFilePath = res.tempFilePaths[0];
try {
const avatar_url = await UserService.upload_avatar(tempFilePath);
setUserInfo(prev => ({ ...prev, avatar: avatar_url }));
setUserInfo((prev) => ({ ...prev, avatar: avatar_url }));
Taro.showToast({
title: '头像上传成功',
icon: 'success'
title: "头像上传成功",
icon: "success",
});
} catch (error) {
console.error('头像上传失败:', error);
console.error("头像上传失败:", error);
Taro.showToast({
title: '头像上传失败',
icon: 'none'
title: "头像上传失败",
icon: "none",
});
}
}
},
});
};
// 处理编辑弹窗
const handle_open_edit_modal = (field: string) => {
if (field === 'gender') {
if (field === "gender") {
setGenderPickerVisible(true);
return;
}
if (field === 'birthday') {
if (field === "birthday") {
setBirthdayPickerVisible(true);
return;
}
if (field === 'location') {
if (field === "location") {
setLocationPickerVisible(true);
return;
}
if (field === 'ntrp_level') {
if (field === "ntrp_level") {
setNtrpPickerVisible(true);
return;
}
if (field === 'occupation') {
if (field === "occupation") {
setOccupationPickerVisible(true);
return;
}
if (field === 'nickname') {
if (field === "nickname") {
// 手动输入
setEditingField(field);
setEditModalVisible(true);
@@ -159,60 +185,67 @@ const EditProfilePage: React.FC = () => {
await UserService.update_user_info(update_data);
// 更新本地状态
setFormData(prev => ({ ...prev, [editing_field]: value }));
setUserInfo(prev => ({ ...prev, [editing_field]: value }));
setFormData((prev) => ({ ...prev, [editing_field]: value }));
setUserInfo((prev) => ({ ...prev, [editing_field]: value }));
// 关闭弹窗
setEditModalVisible(false);
setEditingField('');
setEditingField("");
// 显示成功提示
Taro.showToast({
title: '保存成功',
icon: 'success'
title: "保存成功",
icon: "success",
});
} catch (error) {
console.error('保存失败:', error);
console.error("保存失败:", error);
Taro.showToast({
title: '保存失败',
icon: 'error'
title: "保存失败",
icon: "error",
});
}
};
const handle_edit_modal_cancel = () => {
setEditModalVisible(false);
setEditingField('');
setEditingField("");
};
// 处理字段编辑
const handle_field_edit = async (field: string | { [key: string]: string }, value?: string) => {
const handle_field_edit = async (
field: string | { [key: string]: string },
value?: string
) => {
try {
if (typeof field === 'object' && field !== null && !Array.isArray(field)) {
if (
typeof field === "object" &&
field !== null &&
!Array.isArray(field)
) {
await UserService.update_user_info({ ...field });
// 更新本地状态
setFormData(prev => ({ ...prev, ...field }));
setUserInfo(prev => ({ ...prev, ...field }));
setFormData((prev) => ({ ...prev, ...field }));
setUserInfo((prev) => ({ ...prev, ...field }));
} else {
// 调用更新用户信息接口,只传递修改的字段
const update_data = { [field as string]: value };
await UserService.update_user_info(update_data);
// 更新本地状态
setFormData(prev => ({ ...prev, [field as string]: value }));
setUserInfo(prev => ({ ...prev, [field as string]: value }));
setFormData((prev) => ({ ...prev, [field as string]: value }));
setUserInfo((prev) => ({ ...prev, [field as string]: value }));
}
// 显示成功提示
Taro.showToast({
title: '保存成功',
icon: 'success'
title: "保存成功",
icon: "success",
});
} catch (error) {
console.error('保存失败:', error);
console.error("保存失败:", error);
Taro.showToast({
title: '保存失败',
icon: 'error'
title: "保存失败",
icon: "error",
});
}
};
@@ -220,13 +253,19 @@ const EditProfilePage: React.FC = () => {
// 处理性别选择
const handle_gender_change = (e: any) => {
const gender_value = e[0];
handle_field_edit('gender', gender_value);
handle_field_edit("gender", gender_value);
};
// 处理生日选择
const handle_birthday_change = (e: any) => {
const [year, month, day] = e;
handle_field_edit('birthday', `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`);
handle_field_edit(
"birthday",
`${year}-${String(month).padStart(2, "0")}-${String(day).padStart(
2,
"0"
)}`
);
};
// 处理地区选择
@@ -238,61 +277,66 @@ const EditProfilePage: React.FC = () => {
// 处理NTRP水平选择
const handle_ntrp_level_change = (e: any) => {
const ntrp_level_value = e[0];
handle_field_edit('ntrp_level', ntrp_level_value);
handle_field_edit("ntrp_level", ntrp_level_value);
};
// 处理职业选择
const handle_occupation_change = (e: any) => {
const [country, province] = e;
handle_field_edit('occupation', `${country} ${province}`);
handle_field_edit("occupation", `${country} ${province}`);
};
// 处理退出登录
const handle_logout = () => {
Taro.showModal({
title: '确认退出',
content: '确定要退出登录吗?',
title: "确认退出",
content: "确定要退出登录吗?",
success: (res) => {
if (res.confirm) {
// 清除用户数据
clear_login_state();
Taro.reLaunch({
url: '/login_pages/index/index'
url: "/login_pages/index/index",
});
}
}
},
});
};
const onGetPhoneNumber = async (e) => {
if (!e.detail || !e.detail.code) {
Taro.showToast({
title: '获取手机号失败,请重试',
icon: 'none',
duration: 2000
title: "获取手机号失败,请重试",
icon: "none",
duration: 2000,
});
return;
}
try {
const phone = await UserService.parse_phone(e.detail.code);
handle_field_edit('phone', phone);
handle_field_edit("phone", phone);
} catch (e) {
console.error('解析手机号失败:', e);
console.error("解析手机号失败:", e);
Taro.showToast({
title: '解析手机号失败,请重试',
icon: 'none',
duration: 2000
title: "解析手机号失败,请重试",
icon: "none",
duration: 2000,
});
}
}
};
return (
<View className="edit_profile_page">
{/* 导航栏 */}
<View className="custom-navbar">
<View className="detail-navigator">
<View className="detail-navigator-back" onClick={() => { Taro.navigateBack() }}>
<View
className="detail-navigator-back"
onClick={() => {
Taro.navigateBack();
}}
>
<Image
className="detail-navigator-back-icon"
src={img.ICON_NAVIGATOR_BACK}
@@ -315,26 +359,35 @@ const EditProfilePage: React.FC = () => {
<View className="avatar_overlay">
<Image
className="upload_icon"
src={require('@/static/userInfo/edit2.svg')}
src={require("@/static/userInfo/edit2.svg")}
/>
</View>
</View>
</View>
{/* 基本信息编辑 */}
<View className="form_section">
{/* 名字 */}
<View className="form_group">
<View className="form_item" onClick={() => handle_open_edit_modal('nickname')}>
<View
className="form_item"
onClick={() => handle_open_edit_modal("nickname")}
>
<View className="item_left">
<Image className="item_icon" src={require('@/static/userInfo/user.svg')} />
<Image
className="item_icon"
src={require("@/static/userInfo/user.svg")}
/>
<Text className="item_label"></Text>
</View>
<View className="item_right">
<Text className="item_value">{form_data.nickname || '188的王晨'}</Text>
<Image className="arrow_icon" src={require('@/static/list/icon-list-right-arrow.svg')} />
<Text className="item_value">
{form_data.nickname || "188的王晨"}
</Text>
<Image
className="arrow_icon"
src={require("@/static/list/icon-list-right-arrow.svg")}
/>
</View>
</View>
<View className="divider"></View>
@@ -342,14 +395,25 @@ const EditProfilePage: React.FC = () => {
{/* 性别 */}
<View className="form_group">
<View className="form_item" onClick={() => handle_open_edit_modal('gender')}>
<View
className="form_item"
onClick={() => handle_open_edit_modal("gender")}
>
<View className="item_left">
<Image className="item_icon" src={require('@/static/userInfo/gender.svg')} />
<Image
className="item_icon"
src={require("@/static/userInfo/gender.svg")}
/>
<Text className="item_label"></Text>
</View>
<View className="item_right">
<Text className="item_value">{convert_db_gender_to_display(form_data.gender)}</Text>
<Image className="arrow_icon" src={require('@/static/list/icon-list-right-arrow.svg')} />
<Text className="item_value">
{convert_db_gender_to_display(form_data.gender)}
</Text>
<Image
className="arrow_icon"
src={require("@/static/list/icon-list-right-arrow.svg")}
/>
</View>
</View>
<View className="divider"></View>
@@ -357,14 +421,23 @@ const EditProfilePage: React.FC = () => {
{/* 生日 */}
<View className="form_group">
<View className="form_item" onClick={() => handle_open_edit_modal('birthday')}>
<View
className="form_item"
onClick={() => handle_open_edit_modal("birthday")}
>
<View className="item_left">
<Image className="item_icon" src={require('@/static/userInfo/birthday.svg')} />
<Image
className="item_icon"
src={require("@/static/userInfo/birthday.svg")}
/>
<Text className="item_label"></Text>
</View>
<View className="item_right">
<Text className="item_value">{form_data.birthday}</Text>
<Image className="arrow_icon" src={require('@/static/list/icon-list-right-arrow.svg')} />
<Image
className="arrow_icon"
src={require("@/static/list/icon-list-right-arrow.svg")}
/>
</View>
</View>
</View>
@@ -373,16 +446,26 @@ const EditProfilePage: React.FC = () => {
{/* 简介编辑 */}
<View className="form_section">
<View className="form_group">
<View className="form_item" onClick={() => handle_open_edit_modal('personal_profile')}>
<View
className="form_item"
onClick={() => handle_open_edit_modal("personal_profile")}
>
<View className="item_left">
<Image className="item_icon" src={require('@/static/userInfo/introduce.svg')} />
<Image
className="item_icon"
src={require("@/static/userInfo/introduce.svg")}
/>
<Text className="item_label"></Text>
</View>
<View className="item_right">
<Text className="item_value">
{form_data.personal_profile.replace(/\n/g, ' ') || '介绍一下自己'}
{form_data.personal_profile.replace(/\n/g, " ") ||
"介绍一下自己"}
</Text>
<Image className="arrow_icon" src={require('@/static/list/icon-list-right-arrow.svg')} />
<Image
className="arrow_icon"
src={require("@/static/list/icon-list-right-arrow.svg")}
/>
</View>
</View>
</View>
@@ -392,40 +475,67 @@ const EditProfilePage: React.FC = () => {
<View className="form_section">
<View className="form_group">
{/* 地区 */}
<View className="form_item" onClick={() => handle_open_edit_modal('location')}>
<View
className="form_item"
onClick={() => handle_open_edit_modal("location")}
>
<View className="item_left">
<Image className="item_icon" src={require('@/static/userInfo/gender.svg')} />
<Image
className="item_icon"
src={require("@/static/userInfo/gender.svg")}
/>
<Text className="item_label"></Text>
</View>
<View className="item_right">
<Text className="item_value">{`${form_data.country} ${form_data.province} ${form_data.city}`}</Text>
<Image className="arrow_icon" src={require('@/static/list/icon-list-right-arrow.svg')} />
<Image
className="arrow_icon"
src={require("@/static/list/icon-list-right-arrow.svg")}
/>
</View>
</View>
<View className="divider"></View>
{/* NTRP水平 */}
<View className="form_item" onClick={() => handle_open_edit_modal('ntrp_level')}>
<View
className="form_item"
onClick={() => handle_open_edit_modal("ntrp_level")}
>
<View className="item_left">
<Image className="item_icon" src={require('@/static/userInfo/ball.svg')} />
<Image
className="item_icon"
src={require("@/static/userInfo/ball.svg")}
/>
<Text className="item_label">NTRP </Text>
</View>
<View className="item_right">
<Text className="item_value">{form_data.ntrp_level}</Text>
<Image className="arrow_icon" src={require('@/static/list/icon-list-right-arrow.svg')} />
<Image
className="arrow_icon"
src={require("@/static/list/icon-list-right-arrow.svg")}
/>
</View>
</View>
<View className="divider"></View>
{/* 职业 */}
<View className="form_item" onClick={() => handle_open_edit_modal('occupation')}>
<View
className="form_item"
onClick={() => handle_open_edit_modal("occupation")}
>
<View className="item_left">
<Image className="item_icon" src={require('@/static/userInfo/business.svg')} />
<Image
className="item_icon"
src={require("@/static/userInfo/business.svg")}
/>
<Text className="item_label"></Text>
</View>
<View className="item_right">
<Text className="item_value">{form_data.occupation}</Text>
<Image className="arrow_icon" src={require('@/static/list/icon-list-right-arrow.svg')} />
<Image
className="arrow_icon"
src={require("@/static/list/icon-list-right-arrow.svg")}
/>
</View>
</View>
</View>
@@ -436,7 +546,10 @@ const EditProfilePage: React.FC = () => {
<View className="form_group">
<View className="form_item">
<View className="item_left">
<Image className="item_icon" src={require('@/static/userInfo/phone.svg')} />
<Image
className="item_icon"
src={require("@/static/userInfo/phone.svg")}
/>
<Text className="item_label"></Text>
</View>
<View className="item_right">
@@ -448,8 +561,17 @@ const EditProfilePage: React.FC = () => {
onInput={handle_phone_input}
onBlur={handle_phone_blur}
/> */}
<Button className={form_data.phone ? '' : 'placeholer'} openType='getPhoneNumber' onGetPhoneNumber={onGetPhoneNumber}>{form_data.phone || '未绑定'}</Button>
<Image className="arrow_icon" src={require('@/static/list/icon-list-right-arrow.svg')} />
<Button
className={form_data.phone ? "" : "placeholer"}
openType="getPhoneNumber"
onGetPhoneNumber={onGetPhoneNumber}
>
{form_data.phone || "未绑定"}
</Button>
<Image
className="arrow_icon"
src={require("@/static/list/icon-list-right-arrow.svg")}
/>
</View>
</View>
<View className="divider"></View>
@@ -477,63 +599,94 @@ const EditProfilePage: React.FC = () => {
<EditModal
visible={edit_modal_visible}
type={editing_field}
title={editing_field === 'nickname' ? '编辑名字' : '编辑简介'}
placeholder={editing_field === 'nickname' ? '请输入您的名字' : '介绍一下你的喜好,或者训练习惯'}
initialValue={form_data[editing_field as keyof typeof form_data] || ''}
maxLength={editing_field === 'nickname' ? 20 : 100}
title={editing_field === "nickname" ? "编辑名字" : "编辑简介"}
placeholder={
editing_field === "nickname"
? "请输入您的名字"
: "介绍一下你的喜好,或者训练习惯"
}
initialValue={form_data[editing_field as keyof typeof form_data] || ""}
maxLength={editing_field === "nickname" ? 20 : 100}
onSave={handle_edit_modal_save}
onCancel={handle_edit_modal_cancel}
validationMessage={editing_field === 'nickname' ? '请填写 1-20 个字符' : '请填写 2-100 个字符'}
validationMessage={
editing_field === "nickname"
? "请填写 1-20 个字符"
: "请填写 2-100 个字符"
}
/>
{/* 性别选择弹窗 */}
{gender_picker_visible && <PopupPicker
options={[
[{ text: '男', value: '0' },
{ text: '女', value: '1' },
{ text: '保密', value: '2' }
]]}
visible={gender_picker_visible}
setvisible={setGenderPickerVisible}
value={[form_data.gender]}
onChange={handle_gender_change} />}
{gender_picker_visible && (
<PopupPicker
options={[
[
{ text: "男", value: "0" },
{ text: "女", value: "1" },
{ text: "保密", value: "2" },
],
]}
visible={gender_picker_visible}
setvisible={setGenderPickerVisible}
value={[form_data.gender]}
onChange={handle_gender_change}
/>
)}
{/* 生日选择弹窗 */}
{birthday_picker_visible && <PopupPicker
visible={birthday_picker_visible}
setvisible={setBirthdayPickerVisible}
value={[new Date(form_data.birthday).getFullYear(), new Date(form_data.birthday).getMonth() + 1, new Date(form_data.birthday).getDate()]}
type="day"
onChange={handle_birthday_change} />}
{birthday_picker_visible && (
<PopupPicker
visible={birthday_picker_visible}
setvisible={setBirthdayPickerVisible}
value={[
new Date(form_data.birthday).getFullYear(),
new Date(form_data.birthday).getMonth() + 1,
new Date(form_data.birthday).getDate(),
]}
type="day"
onChange={handle_birthday_change}
/>
)}
{/* 地区选择弹窗 */}
{location_picker_visible && <PopupPicker
options={[[{ text: "中国", value: "中国" }], [{ text: "上海", value: "上海" }], [{ text: "浦东新区", value: "浦东新区" }, {text: "静安区", value: "静安区"}]]}
visible={location_picker_visible}
setvisible={setLocationPickerVisible}
value={[form_data.country, form_data.province, form_data.city]}
onChange={handle_location_change} />}
{location_picker_visible && (
<PopupPicker
options={cities}
visible={location_picker_visible}
setvisible={setLocationPickerVisible}
value={[form_data.country, form_data.province, form_data.city]}
onChange={handle_location_change}
/>
)}
{/* NTRP水平选择弹窗 */}
{ntrp_picker_visible && <PopupPicker
options={[
[{ text: '1.5', value: '1.5' },
{ text: '2.0', value: '2.0' },
{ text: '2.5', value: '2.5' },
{ text: '3.0', value: '3.0' },
{ text: '3.5', value: '3.5' },
{ text: '4.0', value: '4.0' },
{ text: '4.5', value: '4.5' },
]]}
type="ntrp"
img={user_info.avatar}
visible={ntrp_picker_visible}
setvisible={setNtrpPickerVisible}
value={[form_data.ntrp_level]}
onChange={handle_ntrp_level_change} />}
{ntrp_picker_visible && (
<PopupPicker
options={[
[
{ text: "1.5", value: "1.5" },
{ text: "2.0", value: "2.0" },
{ text: "2.5", value: "2.5" },
{ text: "3.0", value: "3.0" },
{ text: "3.5", value: "3.5" },
{ text: "4.0", value: "4.0" },
{ text: "4.5", value: "4.5" },
],
]}
type="ntrp"
img={user_info.avatar}
visible={ntrp_picker_visible}
setvisible={setNtrpPickerVisible}
value={[form_data.ntrp_level]}
onChange={handle_ntrp_level_change}
/>
)}
{/* 职业选择弹窗 */}
{occupation_picker_visible && <PopupPicker
options={[[{ text: "时尚", value: "时尚" }], [{ text: "美妆博主", value: "美妆博主" },{ text: "设计师", value: "设计师" }]]}
visible={occupation_picker_visible}
setvisible={setOccupationPickerVisible}
value={[...form_data.occupation.split(' ')]}
onChange={handle_occupation_change} />}
{occupation_picker_visible && (
<PopupPicker
options={professions}
visible={occupation_picker_visible}
setvisible={setOccupationPickerVisible}
value={[...form_data.occupation.split(" ")]}
onChange={handle_occupation_change}
/>
)}
</View>
);
};