feat: 完成前端业务闭环与后端联调
This commit is contained in:
@@ -109,14 +109,27 @@
|
||||
</template>
|
||||
</view>
|
||||
<AppToast :visible="feedbackVisible" :message="feedbackMessage" />
|
||||
<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 { computed, reactive, ref } from "vue";
|
||||
import { onLoad, onUnload } from "@dcloudio/uni-app";
|
||||
import { onBackPress, onLoad, onUnload } from "@dcloudio/uni-app";
|
||||
import AppAvatar from "@/components/AppAvatar.vue";
|
||||
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";
|
||||
@@ -129,11 +142,16 @@ import {
|
||||
} from "@/services/api/request-controller.js";
|
||||
import { profileApi } from "@/services/api/profile-service.js";
|
||||
import { getRequestErrorMessage } from "@/services/api/request-error-message.js";
|
||||
import { createDiscardConfirmation } from "@/utils/discard-confirmation.js";
|
||||
import {
|
||||
isImagePickCancelled,
|
||||
pickAndUploadImage,
|
||||
} from "@/utils/media-upload.js";
|
||||
import { finishPage, returnTo } from "@/utils/navigation/gateway.js";
|
||||
import {
|
||||
finishPage,
|
||||
handleBackPress,
|
||||
runBackGuard,
|
||||
} from "@/utils/navigation/gateway.js";
|
||||
|
||||
const form = reactive({
|
||||
nickName: "",
|
||||
@@ -162,11 +180,18 @@ const saveError = ref("");
|
||||
const committedProfilePayload = ref("");
|
||||
const feedbackVisible = ref(false);
|
||||
const feedbackMessage = ref("");
|
||||
const discardVisible = ref(false);
|
||||
const profileReadController = createRequestController();
|
||||
const avatarUploadController = createRequestController();
|
||||
const profileSaveController = createRequestController();
|
||||
let feedbackTimer = null;
|
||||
let isPageActive = true;
|
||||
const discardConfirmation = createDiscardConfirmation((visible) => {
|
||||
discardVisible.value = visible;
|
||||
});
|
||||
const requestDiscardConfirmation = discardConfirmation.request;
|
||||
const confirmDiscard = discardConfirmation.confirm;
|
||||
const cancelDiscard = discardConfirmation.cancel;
|
||||
const sexOptions = Object.freeze([
|
||||
Object.freeze({ value: "", label: "请选择" }),
|
||||
...PROFILE_SEX_OPTIONS,
|
||||
@@ -181,6 +206,16 @@ const avatarMessage = computed(() => {
|
||||
if (avatarFileName.value) return `已选择 ${avatarFileName.value}`;
|
||||
return avatarPreviewUrl.value ? "当前头像已设置" : "当前使用默认头像";
|
||||
});
|
||||
const profileSnapshot = computed(() => JSON.stringify({
|
||||
nickName: form.nickName,
|
||||
realName: form.realName,
|
||||
sex: form.sex,
|
||||
birthday: form.birthday,
|
||||
email: form.email,
|
||||
avatar: avatarId.value || original.avatar || null,
|
||||
}));
|
||||
const originalSnapshot = computed(() => JSON.stringify(original));
|
||||
const isDirty = computed(() => profileSnapshot.value !== originalSnapshot.value);
|
||||
|
||||
const showFeedback = (message) => {
|
||||
feedbackMessage.value = message;
|
||||
@@ -291,6 +326,15 @@ const saveProfile = async () => {
|
||||
});
|
||||
if (!isPageActive) return;
|
||||
committedProfilePayload.value = payloadFingerprint;
|
||||
for (const field of ["nickName", "realName", "sex", "birthday", "email"]) {
|
||||
if (Object.prototype.hasOwnProperty.call(payload, field)) {
|
||||
original[field] = payload[field];
|
||||
}
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(payload, "avatar")) {
|
||||
original.avatar = payload.avatar;
|
||||
avatarId.value = null;
|
||||
}
|
||||
}
|
||||
await finishPage(
|
||||
"M01",
|
||||
@@ -307,18 +351,24 @@ const saveProfile = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const backToProfile = () => {
|
||||
if (saving.value || uploading.value) return;
|
||||
return returnTo("M01");
|
||||
};
|
||||
const backToProfile = () => runBackGuard({
|
||||
transientOpen: discardVisible.value,
|
||||
dirty: isDirty.value,
|
||||
submitting: saving.value || uploading.value,
|
||||
"close-transient": cancelDiscard,
|
||||
"block-submitting": () => true,
|
||||
"confirm-discard": requestDiscardConfirmation,
|
||||
});
|
||||
|
||||
onLoad(loadProfile);
|
||||
onBackPress((event) => handleBackPress(event, backToProfile));
|
||||
onUnload(() => {
|
||||
isPageActive = false;
|
||||
profileReadController.abort();
|
||||
avatarUploadController.abort();
|
||||
profileSaveController.abort();
|
||||
if (feedbackTimer) clearTimeout(feedbackTimer);
|
||||
discardConfirmation.dispose();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -336,7 +386,7 @@ onUnload(() => {
|
||||
}
|
||||
.page-content {
|
||||
flex: 1;
|
||||
padding: 24rpx 30rpx 72rpx;
|
||||
padding: 24rpx 30rpx calc(72rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
.state-card,
|
||||
.form-panel {
|
||||
@@ -435,7 +485,7 @@ onUnload(() => {
|
||||
.picker-value {
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
min-height: 68rpx;
|
||||
min-height: 80rpx;
|
||||
color: $ink;
|
||||
font-size: clamp(14px, 23rpx, 17px);
|
||||
text-align: right;
|
||||
|
||||
Reference in New Issue
Block a user