Merge branch 'codex/profile-page-merge'
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",
|
||||
"@types/react": "^18.2.66",
|
||||
"babel-preset-taro": "^4.0.0",
|
||||
"miniprogram-api-typings": "^5.2.0",
|
||||
"typescript": "^5.4.5"
|
||||
}
|
||||
},
|
||||
@@ -10249,6 +10250,13 @@
|
||||
"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": {
|
||||
"version": "2.3.6",
|
||||
"resolved": "https://registry.npmmirror.com/jackspeak/-/jackspeak-2.3.6.tgz",
|
||||
@@ -11395,9 +11403,9 @@
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/miniprogram-api-typings/-/miniprogram-api-typings-5.2.0.tgz",
|
||||
"integrity": "sha512-dkel1zG/eAfApabCtZnr9Y69+5z89GtWVPb6aCTvTJ0gu9mk+A0wCwdxlKWReFfXhcvhuonFrfYDwfSnSEkxsA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"@tarojs/webpack5-runner": "^4.0.0",
|
||||
"@types/react": "^18.2.66",
|
||||
"babel-preset-taro": "^4.0.0",
|
||||
"miniprogram-api-typings": "^5.2.0",
|
||||
"typescript": "^5.4.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import "./app.scss";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
function App(props) {
|
||||
function App(props: { children?: ReactNode }) {
|
||||
const { children } = props;
|
||||
return children ?? null;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: "custom",
|
||||
navigationBarTitleText: "个人信息"
|
||||
});
|
||||
|
||||
@@ -1,3 +1,214 @@
|
||||
.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";
|
||||
|
||||
const sections: SecondaryPageSection[] = [
|
||||
{
|
||||
title: "资料编辑",
|
||||
items: [
|
||||
{ label: "头像", value: "支持后续替换" },
|
||||
{ label: "昵称", value: "张天爱" },
|
||||
{ label: "手机号", value: "135****2598" },
|
||||
{ label: "修改密码", value: "待接入" }
|
||||
]
|
||||
type ProfileState = {
|
||||
avatar: string;
|
||||
nickname: string;
|
||||
phone: string;
|
||||
email: string;
|
||||
wechatBound: boolean;
|
||||
};
|
||||
|
||||
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() {
|
||||
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 (
|
||||
<SecondaryPage
|
||||
eyebrow="PROFILE"
|
||||
title="个人信息"
|
||||
description="这里先保留资料编辑骨架,后续接入真实账号体系时可以直接补上头像上传、昵称编辑和密码修改。"
|
||||
sections={sections}
|
||||
footerTip="当前页面以结构预留为主,暂不提交真实修改。"
|
||||
/>
|
||||
<View className="profile-page" style={pageStyle}>
|
||||
<View className="profile-page__nav">
|
||||
<View className="profile-page__back" onClick={handleBack}>
|
||||
<View className="profile-page__back-icon" />
|
||||
</View>
|
||||
|
||||
<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": [
|
||||
"@tarojs/taro",
|
||||
"wechat-miniprogram"
|
||||
"miniprogram-api-typings"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
|
||||
Reference in New Issue
Block a user