feat: 重做个人信息页交互
This commit is contained in:
14
package-lock.json
generated
14
package-lock.json
generated
@@ -24,6 +24,7 @@
|
|||||||
"@tarojs/webpack5-runner": "^4.0.0",
|
"@tarojs/webpack5-runner": "^4.0.0",
|
||||||
"@types/react": "^18.2.66",
|
"@types/react": "^18.2.66",
|
||||||
"babel-preset-taro": "^4.0.0",
|
"babel-preset-taro": "^4.0.0",
|
||||||
|
"miniprogram-api-typings": "^5.2.0",
|
||||||
"typescript": "^5.4.5"
|
"typescript": "^5.4.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -10249,6 +10250,13 @@
|
|||||||
"miniprogram-exparser": "latest"
|
"miniprogram-exparser": "latest"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/j-component/node_modules/miniprogram-api-typings": {
|
||||||
|
"version": "3.12.3",
|
||||||
|
"resolved": "https://registry.npmmirror.com/miniprogram-api-typings/-/miniprogram-api-typings-3.12.3.tgz",
|
||||||
|
"integrity": "sha512-o7bOfrU28MEMCBWo83nXv0ROQSBFxJcfCl4f2wTYqah64ipC5RGqLJfvWJTWhlQt2ECVwspSzM8LgvnfMo7TEQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/jackspeak": {
|
"node_modules/jackspeak": {
|
||||||
"version": "2.3.6",
|
"version": "2.3.6",
|
||||||
"resolved": "https://registry.npmmirror.com/jackspeak/-/jackspeak-2.3.6.tgz",
|
"resolved": "https://registry.npmmirror.com/jackspeak/-/jackspeak-2.3.6.tgz",
|
||||||
@@ -11395,9 +11403,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/miniprogram-api-typings": {
|
"node_modules/miniprogram-api-typings": {
|
||||||
"version": "3.12.3",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmmirror.com/miniprogram-api-typings/-/miniprogram-api-typings-3.12.3.tgz",
|
"resolved": "https://registry.npmmirror.com/miniprogram-api-typings/-/miniprogram-api-typings-5.2.0.tgz",
|
||||||
"integrity": "sha512-o7bOfrU28MEMCBWo83nXv0ROQSBFxJcfCl4f2wTYqah64ipC5RGqLJfvWJTWhlQt2ECVwspSzM8LgvnfMo7TEQ==",
|
"integrity": "sha512-dkel1zG/eAfApabCtZnr9Y69+5z89GtWVPb6aCTvTJ0gu9mk+A0wCwdxlKWReFfXhcvhuonFrfYDwfSnSEkxsA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
"@tarojs/webpack5-runner": "^4.0.0",
|
"@tarojs/webpack5-runner": "^4.0.0",
|
||||||
"@types/react": "^18.2.66",
|
"@types/react": "^18.2.66",
|
||||||
"babel-preset-taro": "^4.0.0",
|
"babel-preset-taro": "^4.0.0",
|
||||||
|
"miniprogram-api-typings": "^5.2.0",
|
||||||
"typescript": "^5.4.5"
|
"typescript": "^5.4.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import "./app.scss";
|
import "./app.scss";
|
||||||
|
import type { ReactNode } from "react";
|
||||||
|
|
||||||
function App(props) {
|
function App(props: { children?: ReactNode }) {
|
||||||
const { children } = props;
|
const { children } = props;
|
||||||
return children ?? null;
|
return children ?? null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
export default definePageConfig({
|
export default definePageConfig({
|
||||||
|
navigationStyle: "custom",
|
||||||
navigationBarTitleText: "个人信息"
|
navigationBarTitleText: "个人信息"
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,214 @@
|
|||||||
.profile-page {
|
.profile-page {
|
||||||
display: block;
|
min-height: 100vh;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: calc(var(--profile-top-safe-height, 0px) + 22rpx) 0 72rpx;
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, rgba(39, 44, 60, 0.98) 0 150rpx, transparent 150rpx),
|
||||||
|
linear-gradient(180deg, #181d2a 0%, #171c29 100%);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-page__nav {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 26rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-page__back {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
width: 112rpx;
|
||||||
|
height: 72rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-page__back-icon {
|
||||||
|
width: 24rpx;
|
||||||
|
height: 24rpx;
|
||||||
|
border-left: 4rpx solid var(--color-text-white);
|
||||||
|
border-bottom: 4rpx solid var(--color-text-white);
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-page__title {
|
||||||
|
color: var(--color-text-white);
|
||||||
|
font-size: 52rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-page__save {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 126rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
background: linear-gradient(135deg, var(--color-brand-start), var(--color-brand-end));
|
||||||
|
box-shadow: 0 12rpx 24rpx var(--color-brand-shadow-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-page__save-text {
|
||||||
|
color: var(--color-text-white);
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-page__content {
|
||||||
|
padding: 70rpx 30rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-page__avatar-block {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-page__avatar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 154rpx;
|
||||||
|
height: 154rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 50% 34%, #f8fafc 0 20rpx, transparent 20rpx),
|
||||||
|
linear-gradient(180deg, #b8c1cf 0%, #7f8ba0 100%);
|
||||||
|
box-shadow: 0 16rpx 40rpx rgba(7, 10, 20, 0.26);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-page__avatar-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(180deg, #dbe2ed 0%, #aeb8c7 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-page__avatar-placeholder {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 50%;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 50% 34%, #f6f8fb 0 24rpx, transparent 25rpx),
|
||||||
|
linear-gradient(180deg, #d9dfea 0 58%, #b7c1d1 58% 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-page__avatar-head {
|
||||||
|
position: absolute;
|
||||||
|
top: 36rpx;
|
||||||
|
left: 50%;
|
||||||
|
width: 42rpx;
|
||||||
|
height: 42rpx;
|
||||||
|
margin-left: -21rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #2d3650;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-page__avatar-body {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 24rpx;
|
||||||
|
width: 90rpx;
|
||||||
|
height: 54rpx;
|
||||||
|
margin-left: -45rpx;
|
||||||
|
border-radius: 50rpx 50rpx 28rpx 28rpx;
|
||||||
|
background: #2d3650;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-page__avatar-tip {
|
||||||
|
margin-top: 42rpx;
|
||||||
|
color: #08e0da;
|
||||||
|
font-size: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-page__nickname-block {
|
||||||
|
margin: 110rpx 30rpx 0;
|
||||||
|
padding: 0 12rpx 18rpx;
|
||||||
|
border-top: 2rpx solid rgba(255, 255, 255, 0.28);
|
||||||
|
border-bottom: 2rpx solid rgba(255, 255, 255, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-page__nickname-input {
|
||||||
|
width: 100%;
|
||||||
|
height: 108rpx;
|
||||||
|
color: rgba(255, 255, 255, 0.64);
|
||||||
|
font-size: 34rpx;
|
||||||
|
line-height: 108rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-page__nickname-placeholder {
|
||||||
|
color: rgba(255, 255, 255, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-page__info-card {
|
||||||
|
margin-top: 92rpx;
|
||||||
|
padding: 18rpx 36rpx;
|
||||||
|
border-radius: 30rpx;
|
||||||
|
background: rgba(41, 46, 61, 0.96);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 2rpx rgba(255, 255, 255, 0.03),
|
||||||
|
0 16rpx 34rpx rgba(5, 9, 18, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-page__info-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
min-height: 154rpx;
|
||||||
|
gap: 20rpx;
|
||||||
|
border-bottom: 2rpx solid rgba(255, 255, 255, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-page__info-row--last {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-page__info-main {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 16rpx;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-page__info-label {
|
||||||
|
color: var(--color-text-white);
|
||||||
|
font-size: 34rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-page__info-value {
|
||||||
|
color: rgba(255, 255, 255, 0.42);
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-page__info-value--muted {
|
||||||
|
color: rgba(255, 255, 255, 0.26);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-page__info-action {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 18rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-page__info-action-text {
|
||||||
|
color: #08e0da;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-page__info-action-text--muted {
|
||||||
|
color: rgba(255, 255, 255, 0.42);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-page__info-action-text--bound {
|
||||||
|
color: #08e0da;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-page__info-arrow {
|
||||||
|
color: rgba(255, 255, 255, 0.72);
|
||||||
|
font-size: 28rpx;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,181 @@
|
|||||||
import SecondaryPage, { type SecondaryPageSection } from "../../components/secondary-page";
|
import { Input, Text, View } from "@tarojs/components";
|
||||||
|
import Taro from "@tarojs/taro";
|
||||||
|
import type { CSSProperties } from "react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
import "./index.scss";
|
import "./index.scss";
|
||||||
|
|
||||||
const sections: SecondaryPageSection[] = [
|
type ProfileState = {
|
||||||
{
|
avatar: string;
|
||||||
title: "资料编辑",
|
nickname: string;
|
||||||
items: [
|
phone: string;
|
||||||
{ label: "头像", value: "支持后续替换" },
|
email: string;
|
||||||
{ label: "昵称", value: "张天爱" },
|
wechatBound: boolean;
|
||||||
{ label: "手机号", value: "135****2598" },
|
};
|
||||||
{ label: "修改密码", value: "待接入" }
|
|
||||||
]
|
type InfoAction = "replacePhone" | "replaceEmail" | "bindWechat";
|
||||||
|
|
||||||
|
const defaultProfile: ProfileState = {
|
||||||
|
avatar: "",
|
||||||
|
nickname: "玛利亚",
|
||||||
|
phone: "139****0753",
|
||||||
|
email: "",
|
||||||
|
wechatBound: false
|
||||||
|
};
|
||||||
|
|
||||||
|
function getActionToast(action: InfoAction) {
|
||||||
|
switch (action) {
|
||||||
|
case "replacePhone":
|
||||||
|
return "手机号更换功能待接入";
|
||||||
|
case "replaceEmail":
|
||||||
|
return "邮箱更换功能待接入";
|
||||||
|
case "bindWechat":
|
||||||
|
return "微信绑定功能待接入";
|
||||||
|
default:
|
||||||
|
return "功能待接入";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
];
|
|
||||||
|
|
||||||
export default function ProfilePage() {
|
export default function ProfilePage() {
|
||||||
|
const [profile, setProfile] = useState(defaultProfile);
|
||||||
|
const [draftNickname, setDraftNickname] = useState(defaultProfile.nickname);
|
||||||
|
const [topSafeHeight, setTopSafeHeight] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const windowInfo = typeof Taro.getWindowInfo === "function" ? Taro.getWindowInfo() : Taro.getSystemInfoSync();
|
||||||
|
const menuButtonRect =
|
||||||
|
typeof Taro.getMenuButtonBoundingClientRect === "function" ? Taro.getMenuButtonBoundingClientRect() : null;
|
||||||
|
const safeTop = menuButtonRect?.bottom || windowInfo.statusBarHeight || 0;
|
||||||
|
|
||||||
|
setTopSafeHeight(safeTop);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const pageStyle = {
|
||||||
|
"--profile-top-safe-height": `${topSafeHeight}px`
|
||||||
|
} as CSSProperties;
|
||||||
|
|
||||||
|
const showToast = (title: string, icon: "none" | "success" = "none") => {
|
||||||
|
Taro.showToast({
|
||||||
|
title,
|
||||||
|
icon
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBack = () => {
|
||||||
|
const pages = Taro.getCurrentPages();
|
||||||
|
|
||||||
|
if (pages.length > 1) {
|
||||||
|
Taro.navigateBack({ delta: 1 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Taro.redirectTo({ url: "/pages/mine/index" });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
const nickname = draftNickname.trim() || defaultProfile.nickname;
|
||||||
|
|
||||||
|
setProfile((current) => ({
|
||||||
|
...current,
|
||||||
|
nickname
|
||||||
|
}));
|
||||||
|
setDraftNickname(nickname);
|
||||||
|
showToast("保存成功", "success");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAvatarClick = () => {
|
||||||
|
showToast("头像更换功能待接入");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInfoAction = (action: InfoAction) => {
|
||||||
|
showToast(getActionToast(action));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SecondaryPage
|
<View className="profile-page" style={pageStyle}>
|
||||||
eyebrow="PROFILE"
|
<View className="profile-page__nav">
|
||||||
title="个人信息"
|
<View className="profile-page__back" onClick={handleBack}>
|
||||||
description="这里先保留资料编辑骨架,后续接入真实账号体系时可以直接补上头像上传、昵称编辑和密码修改。"
|
<View className="profile-page__back-icon" />
|
||||||
sections={sections}
|
</View>
|
||||||
footerTip="当前页面以结构预留为主,暂不提交真实修改。"
|
|
||||||
|
<Text className="profile-page__title">个人信息</Text>
|
||||||
|
|
||||||
|
<View className="profile-page__save" onClick={handleSave}>
|
||||||
|
<Text className="profile-page__save-text">保存</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className="profile-page__content">
|
||||||
|
<View className="profile-page__avatar-block" onClick={handleAvatarClick}>
|
||||||
|
<View className="profile-page__avatar">
|
||||||
|
{profile.avatar ? (
|
||||||
|
<View className="profile-page__avatar-image" />
|
||||||
|
) : (
|
||||||
|
<View className="profile-page__avatar-placeholder">
|
||||||
|
<View className="profile-page__avatar-head" />
|
||||||
|
<View className="profile-page__avatar-body" />
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Text className="profile-page__avatar-tip">点击更换头像</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className="profile-page__nickname-block">
|
||||||
|
<Input
|
||||||
|
className="profile-page__nickname-input"
|
||||||
|
value={draftNickname}
|
||||||
|
maxlength={20}
|
||||||
|
placeholder="请输入昵称"
|
||||||
|
placeholderClass="profile-page__nickname-placeholder"
|
||||||
|
onInput={(event) => setDraftNickname(event.detail.value)}
|
||||||
/>
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className="profile-page__info-card">
|
||||||
|
<View className="profile-page__info-row" onClick={() => handleInfoAction("replacePhone")}>
|
||||||
|
<View className="profile-page__info-main">
|
||||||
|
<Text className="profile-page__info-label">手机号</Text>
|
||||||
|
<Text className="profile-page__info-value">({profile.phone})</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className="profile-page__info-action">
|
||||||
|
<Text className="profile-page__info-action-text">更换</Text>
|
||||||
|
<Text className="profile-page__info-arrow">></Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className="profile-page__info-row" onClick={() => handleInfoAction("replaceEmail")}>
|
||||||
|
<View className="profile-page__info-main">
|
||||||
|
<Text className="profile-page__info-label">邮箱</Text>
|
||||||
|
<Text className="profile-page__info-value profile-page__info-value--muted">
|
||||||
|
{profile.email || "暂未填写"}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className="profile-page__info-action">
|
||||||
|
<Text className="profile-page__info-action-text">更换</Text>
|
||||||
|
<Text className="profile-page__info-arrow">></Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className="profile-page__info-row profile-page__info-row--last" onClick={() => handleInfoAction("bindWechat")}>
|
||||||
|
<View className="profile-page__info-main">
|
||||||
|
<Text className="profile-page__info-label">微信</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className="profile-page__info-action">
|
||||||
|
<Text
|
||||||
|
className={`profile-page__info-action-text ${
|
||||||
|
profile.wechatBound ? "profile-page__info-action-text--bound" : "profile-page__info-action-text--muted"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{profile.wechatBound ? "已绑定" : "去绑定"}
|
||||||
|
</Text>
|
||||||
|
<Text className="profile-page__info-arrow">></Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
},
|
},
|
||||||
"types": [
|
"types": [
|
||||||
"@tarojs/taro",
|
"@tarojs/taro",
|
||||||
"wechat-miniprogram"
|
"miniprogram-api-typings"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
|
|||||||
Reference in New Issue
Block a user