完成50%
This commit is contained in:
@@ -2,56 +2,102 @@
|
||||
<template>
|
||||
<view class="profile-edit-page" :class="{ 'profile-state--ready': profileState === 'ready', 'profile-state--saving': profileState === 'saving' }">
|
||||
<ModulePageBackground module="profile" />
|
||||
<view class="page-layer"><PageHeader title="个人资料" /></view>
|
||||
<view class="page-layer"><PageHeader title="个人资料" custom-back @back="requestBack" /></view>
|
||||
<view class="page-content page-layer">
|
||||
<view class="profile-avatar-card">
|
||||
<image class="avatar-seal" src="/static/assets/foundation/transparent/brand-seal.png" mode="aspectFit" />
|
||||
<view class="avatar-copy"><text>{{ profileForm.nickname || "未填写昵称" }}</text><text>头像将在相册权限接入后支持上传</text></view>
|
||||
<view class="avatar-copy"><text>{{ profileForm.nickName || "未填写昵称" }}</text><text>头像将在上传接口批次接入</text></view>
|
||||
<view class="text-action" role="button" aria-label="选择头像" @click="chooseAvatar">选择头像</view>
|
||||
</view>
|
||||
|
||||
<view class="form-panel">
|
||||
<view class="form-row"><text>昵称</text><input v-model.trim="profileForm.nickname" maxlength="20" aria-label="昵称" placeholder="请输入昵称" /></view>
|
||||
<text v-if="errors.nickname" class="field-error">{{ errors.nickname }}</text>
|
||||
<view class="form-row"><text>真实姓名</text><input v-model.trim="profileForm.realName" maxlength="20" aria-label="真实姓名" placeholder="请输入真实姓名" /></view>
|
||||
<view class="form-row"><text>常住地区</text><input v-model.trim="profileForm.region" maxlength="40" aria-label="常住地区" placeholder="省 / 市 / 区县" /></view>
|
||||
<view class="textarea-row"><text>个人简介</text><textarea v-model.trim="profileForm.bio" auto-height maxlength="300" aria-label="个人简介" placeholder="介绍你的家族身份或经历" /></view>
|
||||
<text class="counter">{{ profileForm.bio.length }}/300</text>
|
||||
<text v-if="errors.bio" class="field-error">{{ errors.bio }}</text>
|
||||
<view class="form-row"><text>昵称</text><input v-model.trim="profileForm.nickName" maxlength="30" aria-label="昵称" placeholder="请输入昵称" @input="errors.nickName = ''" /></view>
|
||||
<text v-if="errors.nickName" class="field-error">{{ errors.nickName }}</text>
|
||||
<view class="form-row"><text>真实姓名</text><input v-model.trim="profileForm.realName" maxlength="30" aria-label="真实姓名" placeholder="请输入真实姓名" /></view>
|
||||
<view class="form-row"><text>邮箱</text><input v-model.trim="profileForm.email" maxlength="100" aria-label="邮箱" placeholder="选填,用于接收通知" @input="errors.email = ''" /></view>
|
||||
<text v-if="errors.email" class="field-error">{{ errors.email }}</text>
|
||||
</view>
|
||||
<AppButton block :disabled="profileState === 'saving'" :label="profileState === 'saving' ? '正在保存' : '保存资料'" @click="saveProfile" />
|
||||
<AppButton block :disabled="profileState === 'saving'" :label="profileState === 'saving' ? '正在校验' : '生成本地校验预览'" @click="saveProfile" />
|
||||
</view>
|
||||
<AppToast :visible="toastVisible" :message="toastMessage" />
|
||||
<AppDialog
|
||||
:visible="discardVisible"
|
||||
:close-on-mask="false"
|
||||
eyebrow="放弃确认"
|
||||
title="放弃当前填写?"
|
||||
message="尚未提交服务器的资料将从当前页面清除。"
|
||||
confirm-text="确认放弃"
|
||||
cancel-text="继续填写"
|
||||
show-cancel
|
||||
@confirm="confirmDiscard"
|
||||
@cancel="cancelDiscard"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onUnmounted, reactive, ref } from "vue";
|
||||
import { computed, reactive, ref } from "vue";
|
||||
import { onBackPress, onUnload } from "@dcloudio/uni-app";
|
||||
import AppButton from "@/components/AppButton.vue";
|
||||
import AppDialog from "@/components/AppDialog.vue";
|
||||
import AppToast from "@/components/AppToast.vue";
|
||||
import ModulePageBackground from "@/components/ModulePageBackground.vue";
|
||||
import PageHeader from "@/components/PageHeader.vue";
|
||||
import { currentUser } from "@/data/mock.js";
|
||||
import { createDiscardConfirmation } from "@/utils/discard-confirmation.js";
|
||||
import {
|
||||
handleBackPress,
|
||||
runBackGuard,
|
||||
} from "@/utils/navigation.js";
|
||||
|
||||
const profileForm = reactive({ nickname: "汤文清", realName: "汤文清", region: "河南省 周口市", bio: "热心参与家谱资料整理。" });
|
||||
const errors = reactive({ nickname: "", bio: "" });
|
||||
const profileForm = reactive({ nickName: currentUser.name, realName: currentUser.name, email: "" });
|
||||
const errors = reactive({ nickName: "", email: "" });
|
||||
const profileState = ref("ready");
|
||||
const toastVisible = ref(false);
|
||||
const toastMessage = ref("");
|
||||
const discardVisible = ref(false);
|
||||
let timer = null;
|
||||
const formSnapshot = computed(() => JSON.stringify(profileForm));
|
||||
const baseline = ref(formSnapshot.value);
|
||||
const isDirty = computed(() => formSnapshot.value !== baseline.value);
|
||||
const discardConfirmation = createDiscardConfirmation((visible) => {
|
||||
discardVisible.value = visible;
|
||||
});
|
||||
const requestDiscardConfirmation = discardConfirmation.request;
|
||||
const confirmDiscard = discardConfirmation.confirm;
|
||||
const cancelDiscard = discardConfirmation.cancel;
|
||||
const showToast = (message) => { toastMessage.value = message; toastVisible.value = true; clearTimeout(timer); timer = setTimeout(() => (toastVisible.value = false), 1800); };
|
||||
const chooseAvatar = () => showToast("头像选择将在相册权限接入后开放");
|
||||
const validateProfile = () => {
|
||||
errors.nickname = profileForm.nickname ? "" : "请填写昵称";
|
||||
errors.bio = profileForm.bio.length > 300 ? "个人简介不能超过 300 字" : "";
|
||||
return !errors.nickname && !errors.bio;
|
||||
errors.nickName = profileForm.nickName ? "" : "请填写昵称";
|
||||
errors.email = !profileForm.email || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(profileForm.email)
|
||||
? ""
|
||||
: "请输入正确的邮箱地址";
|
||||
return !errors.nickName && !errors.email;
|
||||
};
|
||||
const saveProfile = () => {
|
||||
if (!validateProfile() || profileState.value === "saving") return;
|
||||
profileState.value = "saving";
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => { profileState.value = "ready"; showToast("个人资料已保存"); }, 500);
|
||||
timer = setTimeout(() => {
|
||||
profileState.value = "ready";
|
||||
showToast("本地校验通过,尚未提交服务器");
|
||||
}, 500);
|
||||
};
|
||||
onUnmounted(() => clearTimeout(timer));
|
||||
const requestBack = () =>
|
||||
runBackGuard({
|
||||
transientOpen: discardVisible.value,
|
||||
dirty: isDirty.value,
|
||||
submitting: profileState.value === "saving",
|
||||
"close-transient": cancelDiscard,
|
||||
"block-submitting": () => true,
|
||||
"confirm-discard": requestDiscardConfirmation,
|
||||
});
|
||||
onBackPress((event) => handleBackPress(event, requestBack));
|
||||
onUnload(() => {
|
||||
clearTimeout(timer);
|
||||
discardConfirmation.dispose();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -70,11 +116,8 @@ onUnmounted(() => clearTimeout(timer));
|
||||
.text-action { min-height: 72rpx; display: flex; align-items: center; color: $brand-red; font-size: 22rpx; }
|
||||
.form-panel { margin-top: 22rpx; padding: 20rpx 34rpx 30rpx; box-sizing: border-box; }
|
||||
.form-row { display: grid; grid-template-columns: 150rpx minmax(0,1fr); min-height: 92rpx; align-items: center; border-bottom: 1px solid rgba(181,137,63,.42); gap: 18rpx; }
|
||||
.form-row > text, .textarea-row > text { color: $ink; font-size: 24rpx; font-weight: 700; }
|
||||
.form-row > text { color: $ink; font-size: 24rpx; font-weight: 700; }
|
||||
.form-row input { width: auto; min-width: 0; min-height: 70rpx; color: $ink; font-size: 24rpx; text-align: right; }
|
||||
.textarea-row { padding-top: 24rpx; }
|
||||
.textarea-row textarea { display: block; width: auto; min-width: 0; min-height: 150rpx; margin-top: 14rpx; color: $ink; font-size: 24rpx; line-height: 1.6; }
|
||||
.counter { display: block; color: $ink-muted; font-size: 20rpx; text-align: right; }
|
||||
.field-error { display: block; padding-top: 7rpx; color: #b42318; font-size: 21rpx; text-align: right; }
|
||||
.page-content > .app-button { margin-top: 26rpx; }
|
||||
@media(max-width:340px){.page-content{padding-right:22rpx;padding-left:22rpx}.profile-avatar-card{grid-template-columns:72rpx minmax(0,1fr);padding-right:26rpx;padding-left:26rpx}.text-action{grid-column:2}.avatar-seal{width:68rpx;max-height:78rpx}.form-row{grid-template-columns:126rpx minmax(0,1fr)}}
|
||||
|
||||
Reference in New Issue
Block a user