Commit 03055b1d authored by 黄韬's avatar 黄韬

Merge branch '20250528-dev-ht' into 'dev'

20250528 dev ht

See merge request sparkprojects/20250528_FHQ1!11
parents 760d19ba 9b5946a1
import requestModule from "./request.js";
const { api } = requestModule;
/**
* 通过此接口完成手机号授权,注册新用户
* @param {*} data : {phoneEncryptedData, phoneIv, code, codeLogin}
* @returns
*/
export const uploadImage = (file64) =>
api.post("/c/upload/image", {
img64: file64,
}, {
headers: {
"Content-Type": "application/json"
}
});
......@@ -39,13 +39,13 @@ const request = (options = {}) => {
if (data.statusCode !== HTTP_STATUS.SUCCESS) {
uni.showToast({
title: data.errMsg,
icon: 'error'
icon: 'none'
});
reject(data);
} else if (!data.data?.ok) {
uni.showToast({
title: data.data?.message,
icon: 'error'
icon: 'none'
});
reject(data.data);
} else {
......
......@@ -14,6 +14,8 @@ export const fetchUserInfo = () => api.get('/c/user/memberInfo');
* @returns
*/
export const fetchBabyInfo = () => api.get('/c/user/babyInfo');
export const fetchBabyInfoById = (id) => api.get('/c/user/babyInfo', { id });
export const getGestationalWeeks = (dueDate) => api.get('/c/user/calGestationalWeeks', { dueDate });
/**
* 根据wx.login接口返回的code完成登录
......@@ -30,4 +32,11 @@ export const autoLoginByCode = (code) => api.get('/c/login/autologin', {
* @param {*} data : {phoneEncryptedData, phoneIv, code, codeLogin}
* @returns
*/
export const fetchAutoPhone = (data) => api.post('/c/login/authPhone', data);
\ No newline at end of file
export const fetchAutoPhone = (data) => api.post('/c/login/authPhone', data);
/**
* 更新宝宝信息
* @param {*} data
* @returns
*/
export const updateBabyInfo = (data) => api.post('/c/user/saveBaby', data);
<template>
<Layer
:modelValue="modelValue"
:customHeader="true"
:showCancel="false"
:showConfirm="false"
@update:modelValue="$emit('update:modelValue', $event)"
@confirm="onConfirm"
@cancel="onCancel"
>
<template>
<view class="multi-select-title">{{ title }}</view>
<view class="multi-select-list">
<view
v-for="opt in options"
:key="opt"
class="multi-select-tag"
:class="{ selected: selectedSet.has(opt) }"
@click="toggle(opt)"
>
<text>{{ opt }}</text>
<image
v-if="selectedSet.has(opt)"
class="check-icon"
:src="$baseUrl + 'person/icon_gou.png'"
/>
</view>
</view>
<view class="multi-select-btn" @click="onConfirm"> 保存 </view>
</template>
</Layer>
</template>
<script setup>
import { ref, watch, computed, getCurrentInstance } from "vue";
import Layer from "./Layer.vue";
const { proxy } = getCurrentInstance();
const $baseUrl = proxy.$baseUrl;
const props = defineProps({
modelValue: Boolean,
title: String,
options: { type: Array, default: () => [] },
modelSelected: { type: Array, default: () => [] },
max: Number,
});
const emit = defineEmits([
"update:modelValue",
"update:selected",
"confirm",
"cancel",
]);
const selected = ref([...props.modelSelected]);
watch(
() => props.modelSelected,
(val) => (selected.value = [...val])
);
const selectedSet = computed(() => new Set(selected.value));
function toggle(opt) {
if (selectedSet.value.has(opt)) {
selected.value = selected.value.filter((v) => v !== opt);
} else {
if (!props.max || selected.value.length < props.max) {
selected.value = [...selected.value, opt];
}
}
emit("update:selected", selected.value);
}
function onConfirm() {
emit("confirm", selected.value);
emit("update:modelValue", false);
}
function onCancel() {
emit("cancel");
emit("update:modelValue", false);
}
</script>
<style lang="less" scoped>
.multi-select-btn {
width: 686rpx;
height: 94rpx;
background: #d3a358;
color: #fff;
border-radius: 46rpx;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto;
margin-top: 24rpx;
font-size: 32rpx;
}
.multi-select-title {
font-size: 32rpx;
color: #222;
font-weight: bold;
margin-bottom: 24rpx;
}
.multi-select-list {
display: flex;
flex-wrap: wrap;
gap: 15rpx 20rpx;
}
.multi-select-tag {
display: flex;
align-items: center;
padding: 0 24rpx;
height: 56rpx;
background: #ffffff;
border-radius: 28rpx;
color: #1d1e25;
font-size: 24rpx;
position: relative;
.check-icon {
width: 19rpx;
height: 19rpx;
position: absolute;
right: 2rpx;
top: 3rpx;
display: block;
}
&.selected {
background: #efe7da;
}
}
</style>
\ No newline at end of file
......@@ -21,7 +21,7 @@
<picker-view
class="picker-layer-view"
mask-style="background: rgb(246, 248, 250); z-index: 0;"
indicator-style="border-radius: 10px; height: 50px;background:#ffffff; z-index:0"
indicator-style="border-radius: 10px; height: 50px; background:#ffffff; z-index:0"
:value="pickerValue"
@change="onChange"
>
......@@ -55,9 +55,9 @@
</picker-view-column>
</template>
<template v-else>
<picker-view-column v-for="(col, colIdx) in columns" :key="colIdx">
<picker-view-column>
<view
v-for="(item, idx) in col"
v-for="(item, idx) in columns[0]"
:key="idx"
class="picker-layer-item"
>
......@@ -309,6 +309,7 @@ function onChange(e) {
overflow: hidden;
display: flex;
flex-direction: column;
position: relative;
}
.picker-layer-header {
display: flex;
......@@ -350,6 +351,7 @@ function onChange(e) {
.picker-layer-view {
flex: 1;
width: 100%;
height: 100%;
}
.picker-layer-item {
height: 100rpx;
......
......@@ -39,7 +39,7 @@
<PickerCustom
mode="selector"
:disabled="status == 0"
:range="genderOptions"
:range="genderOptions.map((item) => item.label)"
:value="genderIndex"
:onPickerChange="onGenderChange"
:onLayerVisibleChange="onLayerVisibleChange"
......@@ -64,7 +64,7 @@
<PickerCustom
mode="selector"
:disabled="status == 0"
:range="fetusOptions"
:range="fetusOptions.map((item) => item.label)"
:value="fetusIndex"
:onPickerChange="onFetusChange"
:onLayerVisibleChange="onLayerVisibleChange"
......@@ -110,12 +110,7 @@
<view class="register-baby-info-agreement">
<image
class="register-baby-info-agreement-icon"
:src="$baseUrl + 'registerLayer/privacy_agree.png'"
mode="aspectFit"
/>
<image
class="register-baby-info-userInfo-icon"
:src="$baseUrl + 'registerLayer/user_agree.png'"
:src="$baseUrl + 'registerLayer/registerAgree.png'"
mode="aspectFit"
/>
<image
......@@ -132,20 +127,9 @@
mode="aspectFit"
@click="checked.option1 = !checked.option1"
/>
<image
v-if="!checked.option2"
class="register-baby-info-agreement-checkbox checkbox-checked2"
:src="$baseUrl + 'registerLayer/circle_no.png'"
mode="aspectFit"
@click="checked.option2 = !checked.option2"
/>
<image
v-else
class="register-baby-info-agreement-checkbox checkbox-checked2"
:src="$baseUrl + 'registerLayer/circle_yes.png'"
mode="aspectFit"
@click="checked.option2 = !checked.option2"
/>
<view class="register-baby-info-hot" @click="openAgreement" data-type="member" />
<view class="register-baby-info-hot2" @click="openAgreement" data-type="privacy" />
</view>
<view
class="register-baby-info-btn"
......@@ -159,8 +143,12 @@
</template>
<script setup>
import { ref, watch, computed } from "vue";
import { showLoading, hideLoading } from "../utils";
import { useUserStore } from "../stores/user.js";
import Layer from "./Layer.vue";
import PickerCustom from "./PickerCustom.vue";
import { throttleTap } from "../utils/index.js";
const userStore = useUserStore();
const props = defineProps({
modelValue: Boolean,
......@@ -193,7 +181,7 @@ function handleCancel() {
const checked = ref({
option1: false,
option2: false,
option2: true,
});
// 宝宝信息相关
......@@ -203,8 +191,16 @@ const fetus = ref("");
// 0备孕 1孕中 2出生
const status = ref(-1);
const genderOptions = ["男", "女", "未知"];
const fetusOptions = ["一胎", "二胎", "三胎"];
const genderOptions = [
{ label: "男", value: "M" },
{ label: "女", value: "F" },
{ label: "未知", value: "O" },
];
const fetusOptions = [
{ label: "一胎", value: 1 },
{ label: "二胎", value: 2 },
{ label: "三胎", value: 3 },
];
// date picker
const dateValue = ref([50, 0, 0]); // 默认选中今年1月1日
......@@ -215,19 +211,33 @@ function onDateChange(val) {
}
// gender picker
const genderIndex = ref(0);
const genderDisplay = computed(() => gender.value);
const genderDisplay = computed(() =>
gender.value ? genderOptions[genderIndex.value].label : ""
);
function onGenderChange(idx) {
genderIndex.value = idx;
gender.value = genderOptions[idx];
gender.value = genderOptions[idx].value;
}
// fetus picker
const fetusIndex = ref(0);
const fetusDisplay = computed(() => fetus.value);
const fetusDisplay = computed(() =>
fetus.value ? fetusOptions[fetusIndex.value].label : ""
);
function onFetusChange(idx) {
fetusIndex.value = idx;
fetus.value = fetusOptions[idx];
fetus.value = fetusOptions[idx].value;
}
function openAgreement(e) {
const type = e.currentTarget.dataset.type;
if (type === "member") {
uni.navigateTo({ url: "/pages/webview/webview?type=MEMBER_URL" });
} else if (type === "privacy") {
uni.navigateTo({ url: "/pages/webview/webview?type=PRIVACY_URL" });
}
}
function onStatusChange(v) {
status.value = v;
}
......@@ -248,22 +258,35 @@ const isBtnActive = computed(() => {
}
});
function handleBabyInfoConfirm() {
const handleBabyInfoConfirm = throttleTap(async () => {
if (!isBtnActive.value) {
return;
}
emit("confirm", {
date: date.value,
gender: gender.value,
fetus: fetus.value,
status: status.value,
});
visible.value = false;
}
function openAgreement() {
// 跳转协议页面
uni.navigateTo({ url: "/pages/agreement/index" });
}
showLoading();
const req = {
babyStage: status.value,
};
if (status.value !== 0) {
req.dueDate = date.value;
req.babyGender = gender.value;
req.babyType = fetus.value;
}
const res = await userStore.createBabyInfo(req);
hideLoading();
if (res.success) {
emit("confirm", {
date: date.value,
gender: gender.value,
fetus: fetus.value,
status: status.value,
});
}
}, 5000);
function toggleStatus(val) {
if (status.value === val) {
......@@ -385,7 +408,7 @@ function toggleStatus(val) {
margin: 24rpx 0 32rpx 0;
width: 100%;
position: relative;
height: 109rpx;
height: 69rpx;
.register-baby-info-agreement-icon {
width: 574rpx;
......@@ -442,5 +465,20 @@ function toggleStatus(val) {
align-items: center;
justify-content: center;
}
.register-baby-info-hot {
position: absolute;
top: -10rpx;
left: 220rpx;
width: 256rpx;
height: 40rpx;
}
.register-baby-info-hot2 {
position: absolute;
top: -10rpx;
left: 480rpx;
width: 160rpx;
height: 40rpx;
}
</style>
......@@ -11,6 +11,7 @@
class="banner_upload"
:src="$baseUrl + 'person/customer.png'"
mode="aspectFit"
@click="handleUploadBackground"
/>
<view class="person-header">
......@@ -34,7 +35,7 @@
open-type="chooseAvatar"
@chooseavatar="onChooseAvatar"
>
<image class="person-avatar-img" :src="formData.avatarUrl"></image>
<image class="person-avatar-img" :src="formData.babyAvatar"></image>
</button>
</view>
......@@ -55,7 +56,7 @@
.filter(formItemFilter)
.slice(
0,
formData.currentBaby == '已出生' && pageStatus.formStatus == 2
formData.babyStage == 2 && pageStatus.formStatus == 2
? 6
: formItems.length
)"
......@@ -82,7 +83,14 @@
:name="item.name"
:placeholder="item.placeholder"
v-model="formData[item.name]"
:maxlength="item.maxLength"
/>
<!-- 只读展示类型 -->
<view v-else-if="item.type === 'display'" class="form-input-box">
<view class="form-input" style="color: #222">{{
formData[item.name] || item.placeholder
}}</view>
</view>
<!-- 选择器类型 -->
<picker-custom
v-else-if="item.type === 'picker'"
......@@ -95,7 +103,7 @@
>
<view class="form-input-box">
<view class="form-input">{{
formData[item.name] || item.placeholder
getLabelByValue(item, formData[item.name]) || item.placeholder
}}</view>
<image
class="form-input-icon"
......@@ -103,6 +111,25 @@
/>
</view>
</picker-custom>
<!-- 多选弹窗类型 -->
<view
v-else-if="item.type === 'multi-picker'"
@click="handleMultiPickerOpen(item)"
>
<view class="form-input-box">
<view class="form-input">
{{
(formData[item.name] &&
formData[item.name].split(",").join("、")) ||
item.placeholder
}}
</view>
<image
class="form-input-icon"
:src="$baseUrl + 'person/icon_arrow_yellow_right.png'"
/>
</view>
</view>
<!-- 单选类型 -->
<radio-group
v-else-if="item.type === 'radio'"
......@@ -124,7 +151,7 @@
</block>
<view
v-if="pageStatus.formStatus != 0"
v-if="formData.babyStage == 2"
class="form-bottom-btn"
@click="handleFormBottomBtn"
>
......@@ -151,87 +178,165 @@
保存
</button>
</form>
<MultiSelectLayer
v-if="multiPickerStatus && currentMultiPickerName"
v-model="multiPickerStatus"
:title="
formItems.find((i) => i.name === currentMultiPickerName)?.label +
'(多选)'
"
:options="formItems.find((i) => i.name === currentMultiPickerName)?.range"
:modelSelected="multiPickerSelected[currentMultiPickerName]"
@confirm="handleMultiPickerConfirm"
@cancel="handleMultiPickerCancel"
/>
</view>
</template>
<script setup>
import { ref, watch } from "vue";
import { ref, watch, onMounted } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import { useUserStore } from "../../stores/user.js";
import PickerCustom from "../../components/PickerCustom.vue";
import MultiSelectLayer from "../../components/MultiSelectLayer.vue";
import { uploadImage } from "../../api/common.js";
import { showLoading, hideLoading } from "../../utils/index.js";
import { updateBabyInfo, getGestationalWeeks } from "../../api/user.js";
const userStore = useUserStore();
const pageType = ref("add");
const babyId = ref("");
const pageStatus = ref({
formStatus: 0, // 0: 隐藏 1: 展开, 2: 收起
btnStatus: true, // 是否显示提交按钮
});
// 0备孕 1孕中 2出生
const formData = ref({
currentBaby: "备孕",
nickName: "",
dateOfBirth: "",
gender: "",
babyIndex: "",
eatFunc: "",
contentLike: "",
productLike: "",
specialAttention: "",
channel: "",
avatarUrl: "",
babyStage: 2,
babyName: "",
babyBirthday: "",
dueDate: "",
babyGender: "",
babyType: "",
feedingType: "",
contentPreference: "",
productPreference: "",
followInfo: "",
purchaseChannel: "",
babyAvatar: "",
backgroundImg: "",
});
const babyTypeMap = [
{ label: "一胎", value: 1 },
{ label: "二胎", value: 2 },
{ label: "三胎", value: 3 },
];
const babyStageMap = [
{ label: "备孕", value: 0 },
{ label: "孕中", value: 1 },
{ label: "已出生", value: 2 },
];
const babyGenderMap = [
{ label: "男", value: "M" },
{ label: "女", value: "F" },
{ label: "其他", value: "O" },
];
const FormMap = {
备孕: ["currentBaby", "dateOfBirth", "specialAttention"],
孕中: [
"currentBaby",
"dateOfBirth2",
"yz",
"babyIndex",
"contentLike",
"specialAttention",
0: ["babyStage", "contentPreference", "followInfo"],
1: [
"babyStage",
"dueDate",
"gestationalWeeks",
"babyType",
"contentPreference",
"followInfo",
],
已出生: [
"currentBaby",
"nickName",
"dateOfBirth",
"babyIndex",
"gender",
"eatFunc",
"contentLike",
"productLike",
"specialAttention",
"channel",
2: [
"babyStage",
"babyName",
"babyBirthday",
"babyType",
"babyGender",
"feedingType",
"contentPreference",
"productPreference",
"followInfo",
"purchaseChannel",
],
};
const formItemFilter = (item) => {
return FormMap[formData.value.currentBaby].includes(item.name);
return FormMap[formData.value.babyStage].includes(item.name);
};
const MAX_babyName_LENGTH = 10;
watch(
() => formData.value.babyStage,
(newVal) => {
pageStatus.value.formStatus = newVal == 2 ? 2 : 0;
}
);
const contentLikeOptions = [
"奶粉选择",
"奶粉成分",
"奶粉冲泡",
"宝宝奶量",
"生长发育",
"常见疾病",
"体检疫苗",
"饮食营养补充",
"辅食营养",
"亲子关系",
"早教游戏",
];
const productLikeOptions = [
"飞鹤星飞帆",
"星飞帆卓睿",
"星飞帆卓耀",
"星飞帆卓初",
"星飞帆臻稚卓蓓",
"星飞帆淳芮有机",
];
const specialAttentionOptions = ["过敏", "长肉", "肠道", "消化", "脑发育"];
const channelOptions = ["电商(京东/天猫)", "母婴店"];
const formItems = [
{
label: "当前状态",
name: "currentBaby",
name: "babyStage",
required: true,
placeholder: "请选择当前状态",
type: "picker",
range: ["已出生", "孕中", "备孕"],
range: babyStageMap.map((i) => i.label),
mode: "custom",
},
{
label: "宝宝昵称",
name: "nickName",
name: "babyName",
required: true,
placeholder: "请输入宝宝昵称",
placeholder: "暂无昵称",
type: "input",
maxLength: MAX_babyName_LENGTH,
},
{
label: "宝宝出生日期",
name: "dateOfBirth",
name: "babyBirthday",
required: true,
placeholder: "请选择出生日期",
type: "picker",
mode: "date",
defaultToday: true,
},
{
label: "预产日期",
name: "dateOfBirth2",
name: "dueDate",
required: true,
placeholder: "请选择预产日期",
type: "picker",
......@@ -239,75 +344,92 @@ const formItems = [
},
{
label: "宝宝胎数",
name: "babyIndex",
name: "babyType",
required: true,
placeholder: "请选择胎数",
placeholder: "未选择",
type: "picker",
range: ["单胎", "二胎", "多胎"],
range: babyTypeMap.map((i) => i.label),
mode: "custom",
disabled: false,
},
{
label: "性别",
name: "gender",
name: "babyGender",
required: true,
placeholder: "请选择性别",
placeholder: "未选择",
type: "picker",
range: ["男", "女"],
range: babyGenderMap.map((i) => i.label),
mode: "custom",
},
{
label: "喂养方式",
name: "eatFunc",
name: "feedingType",
required: false,
placeholder: "请选择喂养方式",
placeholder: "未选择",
type: "picker",
range: ["纯母乳", "混合", "奶粉"],
range: ["纯母乳", "混合喂养", "纯奶粉"],
mode: "custom",
},
{
label: "孕周",
name: "yz",
name: "gestationalWeeks",
required: false,
placeholder: "请选择",
type: "input",
placeholder: "",
type: "display",
},
{
label: "内容偏好",
name: "contentLike",
name: "contentPreference",
required: false,
placeholder: "请选择内容偏好",
type: "input",
placeholder: "未选择",
type: "multi-picker",
range: contentLikeOptions,
mode: "custom",
},
{
label: "产品使用偏好",
name: "productLike",
name: "productPreference",
required: false,
placeholder: "请选择产品使用偏好",
type: "input",
placeholder: "未选择",
type: "multi-picker",
range: productLikeOptions,
mode: "custom",
},
{
label: "特殊关注信息",
name: "specialAttention",
name: "followInfo",
required: false,
placeholder: "请输入特殊关注信息",
type: "input",
placeholder: "未选择",
type: "multi-picker",
range: specialAttentionOptions,
mode: "custom",
},
{
label: "常购买渠道",
name: "channel",
name: "purchaseChannel",
required: false,
placeholder: "请选择常购买渠道",
placeholder: "未选择",
type: "picker",
range: ["电商", "母婴店", "超市"],
range: channelOptions,
mode: "custom",
},
];
const getPickerIndex = (item) => {
const val = formData.value[item.name];
if (item.name === "babyStage") {
const idx = babyStageMap.findIndex((i) => i.value === val);
return idx > -1 ? idx : 0;
}
if (item.name === "babyType") {
const idx = babyTypeMap.findIndex((i) => i.value === val);
return idx > -1 ? idx : 0;
}
if (item.name === "babyGender") {
const idx = babyGenderMap.findIndex((i) => i.value === val);
return idx > -1 ? idx : 0;
}
if (item.mode === "date") {
// val 形如 '2024-06-01'
if (typeof val === "string" && val.match(/^\d{4}-\d{2}-\d{2}$/)) {
const [year, month, day] = val.split("-").map(Number);
const currentYear = new Date().getFullYear();
......@@ -316,41 +438,112 @@ const getPickerIndex = (item) => {
const dayIdx = day - 1;
return [yearIdx, monthIdx, dayIdx];
}
// 默认选中今年1月1日
const currentYear = new Date().getFullYear();
return [currentYear - 1970, 0, 0];
} else if (Array.isArray(item.range?.[0])) {
// 多列自定义选择
if (Array.isArray(val)) {
return val.map((v, col) => item.range[col].indexOf(v));
}
// 默认选中每列第一个
return item.range.map(() => 0);
} else {
// 单列自定义选择
return val ? item.range.indexOf(val) : 0;
}
};
const onPickerChange = (e, name) => {
const item = formItems.find((i) => i.name === name);
// 日期选择(mode="date"),e 为字符串
if (name === "babyStage") {
formData.value[name] = babyStageMap[e].value;
return;
}
if (name === "babyType") {
formData.value[name] = babyTypeMap[e].value;
return;
}
if (name === "babyGender") {
formData.value[name] = babyGenderMap[e].value;
return;
}
if (item.mode === "date") {
formData.value[name] = e;
} else if (Array.isArray(e)) {
// 多列自定义选择
formData.value[name] = e.map((idx, col) => item.range[col][idx]).join(" ");
} else {
// 单列自定义选择
formData.value[name] = item.range[e];
}
};
const onRadioChange = (e, name) => {
formData.value[name] = e.detail.value;
};
const onSubmit = (e) => {
// 校验与提交逻辑
console.log("提交数据", formData.value);
const onSubmit = async (e) => {
// 获取当前阶段需要校验的字段
const fieldsToValidate = FormMap[formData.value.babyStage].filter(
(field) => field !== "babyStage"
);
console.log("fieldsToValidate", formData.value);
// 获取必填的表单项
const requiredFields = formItems
.filter((item) => fieldsToValidate.includes(item.name) && item.required)
.map((item) => ({
name: item.name,
label: item.label,
}));
// 校验必填项
for (const field of requiredFields) {
if (!formData.value[field.name]) {
uni.showToast({
title: `请填写${field.label}`,
icon: "none",
});
return;
}
}
const data = {
dueDate: formData.value.dueDate,
babyType: formData.value.babyType,
contentPreference: formData.value.contentPreference,
followInfo: formData.value.followInfo,
babyName: formData.value.babyName,
babyBirthday: formData.value.babyBirthday,
feedingType: formData.value.feedingType,
productPreference: formData.value.productPreference,
purchaseChannel: formData.value.purchaseChannel,
babyStage: formData.value.babyStage,
babyAvatar: formData.value.babyAvatar,
backgroundImg: formData.value.backgroundImg,
babyGender: formData.value.babyGender,
};
if (babyId.value) {
data.id = babyId.value;
}
console.log("提交数据", data);
showLoading();
const res = await updateBabyInfo(data);
hideLoading();
if (res.success) {
uni.showToast({
title: "提交成功",
icon: "success",
});
uni.navigateBack();
showLoading();
await Promise.all([userStore.loadBabyInfo(), userStore.loadUserInfo()]);
hideLoading();
} else {
uni.showToast({
title: res.message,
icon: "none",
});
}
};
const handleReturn = () => {
uni.navigateBack();
......@@ -360,26 +553,156 @@ const handleFormBottomBtn = () => {
pageStatus.value.formStatus = pageStatus.value.formStatus == 1 ? 2 : 1;
};
const onChooseAvatar = (e) => {
formData.value.avatarUrl = e.detail.avatarUrl;
const onChooseAvatar = async (e) => {
const fs = uni.getFileSystemManager();
const base64 =
"data:image/jpeg;base64," + fs.readFileSync(e.detail.avatarUrl, "base64");
const res = await uploadImage(base64);
if (res.success) {
formData.value.babyAvatar = res.data.url;
} else {
uni.showToast({
title: res.message,
icon: "none",
});
}
};
watch(
() => formData.value.currentBaby,
(newVal) => {
if (newVal === "已出生") {
pageStatus.value.formStatus = 2;
} else {
pageStatus.value.formStatus = 0;
}
const handleUploadBackground = async (e) => {
// 唤起图片选择器
uni.chooseImage({
count: 1,
sizeType: ["original", "compressed"],
sourceType: ["album", "camera"],
success: async (res) => {
const tempFilePath = res.tempFilePaths[0];
const fs = uni.getFileSystemManager();
const base64 = "data:image/jpeg;base64," + fs.readFileSync(tempFilePath);
showLoading();
const uploadRes = await uploadImage(base64);
hideLoading();
if (uploadRes.success) {
formData.value.backgroundImg = uploadRes.data.url;
} else {
uni.showToast({
title: uploadRes.message,
icon: "none",
});
}
},
});
};
function onDateStatusChange(status) {
formData.value.babyStage = status;
}
// getLabelByValue 支持所有映射字段
const getLabelByValue = (item, value) => {
if (item.name === "babyStage") {
return babyStageMap.find((i) => i.value === value)?.label || "";
}
);
if (item.name === "babyType") {
return babyTypeMap.find((i) => i.value === value)?.label || "";
}
if (item.name === "babyGender") {
return babyGenderMap.find((i) => i.value === value)?.label || "";
}
return value;
};
const getValueByLabel = (map, label) => {
const found = map.find((i) => i.label === label);
return found ? found.value : "";
};
function onDateStatusChange(status) {
if (status === '已出生' || status === '孕中') {
formData.value.currentBaby = status;
const multiPickerStatus = ref(false); // 控制弹窗显示
const currentMultiPickerName = ref(""); // 当前弹窗对应的item.name
const multiPickerSelected = ref({}); // 存储每个multi-picker的选中项
function handleMultiPickerOpen(item) {
currentMultiPickerName.value = item.name;
multiPickerStatus.value = true;
// 初始化选中项
let val = formData.value[item.name];
if (typeof val === "string") {
val = val ? val.split(",") : [];
}
multiPickerSelected.value[item.name] = val || [];
}
function handleMultiPickerConfirm(selected) {
if (!currentMultiPickerName.value) return;
formData.value[currentMultiPickerName.value] = selected.join(",");
multiPickerStatus.value = false;
currentMultiPickerName.value = "";
}
function handleMultiPickerCancel() {
multiPickerStatus.value = false;
currentMultiPickerName.value = "";
}
onLoad((options) => {
pageType.value = options.type || "add";
babyId.value = options.id || "";
if (pageType.value === "edit") {
const baby = userStore.babyInfo;
formData.value.babyStage = baby.babyStage ?? 2;
formData.value.babyName = baby.babyName || "";
formData.value.babyBirthday = baby.content?.babyBirthday || "";
formData.value.dueDate = baby.content?.dueDate || "";
formData.value.babyType = baby.content?.babyType || "";
formData.value.babyGender = baby.content?.babyGender || "";
formData.value.feedingType = baby.content?.feedingType || "";
formData.value.contentPreference = baby.content?.contentPreference || "";
formData.value.productPreference = baby.content?.productPreference || "";
formData.value.followInfo = baby.content?.followInfo || "";
formData.value.purchaseChannel = baby.content?.purchaseChannel || "";
formData.value.babyAvatar = baby.babyAvatar || "";
formData.value.backgroundImg = baby.content?.backgroundImg || "";
formData.value.gestationalWeeks = baby.content?.gestationalWeeks || "";
} else {
Object.assign(formData.value, {
babyStage: 2,
babyName: "",
babyBirthday: "",
dueDate: "",
babyGender: "",
babyType: "",
feedingType: "",
contentPreference: "",
productPreference: "",
followInfo: "",
purchaseChannel: "",
babyAvatar: "",
backgroundImg: "",
gestationalWeeks: "",
});
}
if (formData.value.babyStage == 2) {
pageStatus.value.formStatus = 2;
}
});
// 监听 babyStage 和 dueDate
watch(
() => [formData.value.babyStage, formData.value.dueDate],
async ([stage, dueDate], [oldStage, oldDueDate]) => {
if (stage === 1) {
// 1. 没有孕周
// 2. 预产期变化
if (!formData.value.gestationalWeeks || dueDate !== oldDueDate) {
if (dueDate) {
const res = await getGestationalWeeks(dueDate);
if (res.success) {
formData.value.gestationalWeeks = res.data.gestationalWeeks;
}
}
}
}
}
);
</script>
<style lang="less" scoped>
......
......@@ -4,8 +4,11 @@ import {
fetchUserInfo,
fetchBabyInfo,
fetchAutoPhone,
fetchBabyInfoById,
updateBabyInfo,
} from "../api/user.js";
import { useGlobalStore } from "./global.js";
import { showLoading, hideLoading } from "../utils/index.js";
const globalStore = useGlobalStore();
......@@ -33,6 +36,23 @@ export const useUserStore = defineStore("userInfo", {
this.babyInfo = babyInfo;
},
async changeBabySelected(babyId) {
// 更新选中状态
showLoading();
const { data } = await fetchBabyInfoById(babyId);
console.log("babyInfo", data);
if (data?.memberId !== "not_login") {
this.babyInfo = data;
}
hideLoading();
},
saveBabyInfo(babyInfo) {
this.babyInfo.allBabyBaseInfo.push(babyInfo);
},
/**
* 用户手机号验证的回调方法,用于获取encryptedData、iv、code,然后调用fetchAutoPhone接口完成手机号授权
* @param {Object} data : {encryptedData, iv, code}
......@@ -120,5 +140,16 @@ export const useUserStore = defineStore("userInfo", {
},
});
},
async createBabyInfo(babyInfo) {
const res = await updateBabyInfo(babyInfo);
if (res.success) {
this.loadBabyInfo();
this.loadUserInfo();
return true;
} else {
return false;
}
},
},
});
......@@ -47,3 +47,43 @@ export function jump({ type, url, extra = {} }) {
console.error('不支持的跳转类型')
}
}
/**
* 防连点函数
* @param {Function} fn 需要防连点的函数
* @param {number} [delay=1000] 防连点时间间隔(ms)
* @returns {Function} 包装后的防连点函数
*/
export function throttleTap(fn, delay = 1000) {
let timer = null;
let lastTime = 0;
return function (...args) {
const now = Date.now();
if (now - lastTime < delay) {
// 小于间隔时间,阻止执行
console.log('防连点')
return;
}
lastTime = now;
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
}
}
export function showLoading() {
uni.showLoading({
title: '加载中',
mask: true,
});
}
export function hideLoading() {
uni.hideLoading();
}
......@@ -13,14 +13,16 @@
<view class="bg-container">
<image
class="bg-img"
:src="$baseUrl + 'my/cover_white_bg.png'"
:src="
babyInfo?.content?.backgroundImg || $baseUrl + 'my/cover_white_bg.png'
"
mode="aspectFit"
lazy-load="false"
binderror=""
bindload=""
/>
</view>
<button
v-if="!cfgStatus.isRegister"
type="primary"
......@@ -33,13 +35,14 @@
<view
class="user-info"
:style="{ 'min-height': cfgStatus.showDetail ? '343rpx' : '180rpx' }"
@click="handleRegister"
>
<view class="user-header">
<view class="avatar-container" @click="handleEditProfile">
<image
class="avatar"
:src="babyInfo.babyAvatar || $baseUrl + 'common/default_avatar.png'"
:src="
babyInfo?.babyAvatar || $baseUrl + 'common/default_avatar.png'
"
mode="aspectFill"
/>
</view>
......@@ -52,7 +55,13 @@
<view class="user-detail">
<view class="user-detail-nickname" @click="handleChangeBaby">
<text class="nickname">{{ babyInfo.babyName || "暂无昵称" }}</text>
<text class="nickname">{{
babyInfo?.babyStage === 0
? "备孕"
: babyInfo?.babyStage === 1
? "孕中"
: babyInfo.babyName || "暂无昵称"
}}</text>
<image
class="user-detail-nickname-icon"
:src="$baseUrl + 'registerLayer/icon_arrow_yellow.png'"
......@@ -63,14 +72,14 @@
<view class="integral-account">
<text class="integral-account-text"> 积分账户: </text>
<text class="integral-account-value">
{{ babyInfo.points || "---" }}</text
{{ babyInfo.points || "0" }}</text
>
</view>
<BabySwitcher
v-if="showBabySwitcher"
:show="showBabySwitcher"
:babyList="babyInfo.allBabyBaseInfo"
:babyList="babyInfo.allBabyBaseInfo || []"
:addIcon="$baseUrl + 'my/baby_add_btn.png'"
@close="showBabySwitcher = false"
@select="onSelectBaby"
......@@ -127,15 +136,18 @@
</template>
<script setup>
import { ref, onMounted, getCurrentInstance } from "vue";
import { ref, onMounted, getCurrentInstance, computed, watch } from "vue";
import RegisterLayer from "../components/RegisterLayer.vue";
import BabySwitcher from "../components/BabySwitcher.vue";
import WheelSelector from "../components/WheelSelector.vue";
import { WHEEL_OPTIONS_YL, WHEEL_OPTIONS_YZ } from "@/cfg";
import { useUserStore } from "@/stores/user";
import { throttleTap } from "@/utils";
const { proxy } = getCurrentInstance();
const $baseUrl = proxy.$baseUrl;
const userStore = useUserStore();
const cfgStatus = ref({
openBabyCardDesc: false,
showDetail: false,
......@@ -173,52 +185,7 @@ const toolList = ref([
},
]);
const babyInfo = ref({
babyName: "et",
babySkill: "occaecat",
babyAge: "2月龄",
babyAvatar: "sint ad sunt anim",
points: "23223",
babyStage: 1,
content: {
dueDate: "esse ea dolor id ipsum",
gestationalWeeks: "culpa",
babyType: "irure",
contentPreference: "occaecat commodo dolore",
followInfo: "est qui ea occaecat ad",
babyName: "Lorem",
babyBirthday: "est mollit do consectetur",
babyGender: "consectetur",
birthWeight: "dolore pariatur dolor",
birthHeight: "fugiat consectetur incididunt",
feedingType: "ipsum labore dolor in Lorem",
productPreference: "id voluptate",
purchaseChannel: "enim veniam in elit",
id: 2212796.2919538617,
memberBabyId: -77291739.14081913,
babyAvatar: "nisi Duis est",
backgroundImg: "laborum in esse ut deserunt",
},
allBabyBaseInfo: [
{
babyName: "",
babyType: 19188017.35935314,
babyStage: "veniam Ut sunt cupidatat",
id: "officia pariatur proident",
typeName: "eu",
selected: false,
},
{
babyName: "",
babyType: 34899428.44859558,
babyStage: "incididunt",
id: "non",
typeName: "dolor irure id cupidatat",
selected: true,
},
],
babyGender: "in voluptate ut",
});
const babyInfo = computed(() => userStore?.babyInfo || {});
const showRegisterLayer = ref(false);
const showBabySwitcher = ref(false);
......@@ -252,37 +219,34 @@ const navigateTo = (url) => {
// 编辑个人资料
const handleEditProfile = () => {
const userStore = useUserStore();
if (!userStore.userInfo || JSON.stringify(userStore.userInfo) === "{}") {
return;
}
navigateTo("/pages/person/person");
};
const handleRegister = () => {
// 判断是否已注册
if (!cfgStatus.value.isRegister) {
// 调用登录接口
// console.log("phoneButton", phoneButton.value);
// phoneButton.value.triggerEvent("tap");
}
const type =
userStore.babyInfo?.allBabyBaseInfo?.length == 0 ? "add" : "edit";
const babyId = userStore.babyInfo?.allBabyBaseInfo.find(
(item) => item.selected
)?.id;
return;
if (type === "edit") {
navigateTo(`/pages/person/person?type=${type}&id=${babyId}`);
} else {
navigateTo(`/pages/person/person?type=${type}`);
}
};
const onRegisterConfirm = async (data) => {
console.log("onRegisterConfirm:", data);
const onRegisterConfirm = (data) => {
showRegisterLayer.value = false;
const userStore = useUserStore();
await Promise.all[(userStore.loadUserInfo(), userStore.loadBabyInfo())];
initData();
};
// 获取用户信息
const initData = async () => {
const userStore = useUserStore();
if (!userStore.userInfo || JSON.stringify(userStore.userInfo) == "{}") {
if (
!userStore.userInfo ||
JSON.stringify(userStore.userInfo) == "{}" ||
userStore.userInfo.memberId == "not_login"
) {
cfgStatus.value.isRegister = false;
cfgStatus.value.showDetail = false;
return;
......@@ -306,7 +270,7 @@ const initData = async () => {
const getRealtimePhoneNumber = async (e) => {
console.log("获取手机号码", e);
const userStore = useUserStore();
if (e.detail.errMsg !== "getPhoneNumber:ok") {
uni.showToast({
title: "请授权使用手机号",
......@@ -329,6 +293,9 @@ const handleChangeBaby = () => {
function onSelectBaby(baby) {
// 处理宝宝切换逻辑
showBabySwitcher.value = false;
userStore.changeBabySelected(baby.id);
console.log("onSelectBaby", baby);
}
......@@ -343,6 +310,10 @@ onMounted(() => {
initData();
});
watch(babyInfo, () => {
initData();
});
// 定义页面配置
defineExpose({});
</script>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment