Commit cc38a55d authored by 王炽's avatar 王炽

Merge branch 'dev' into 20250528-dev-king

parents 1c5b2ace a8156271
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"
}
});
......@@ -15,7 +15,7 @@ const {
// request.js
// 通常可以吧 baseUrl 单独放在一个 js 文件了
const baseUrl = "http://172.16.230.108:7777/pmall";
const baseUrl = "http://172.16.224.178:7777/pmall";
const request = (options = {}) => {
// 在这里可以对请求头进行一些设置
......@@ -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完成登录
......@@ -36,4 +38,10 @@ export const fetchAutoPhone = (data) => api.post('/c/login/authPhone', data);
* 获取用户积分信息
* @returns
*/
export const fetchMemberInfo = () => api.get('/c/user/memberInfo');
\ No newline at end of file
export const fetchMemberInfo = () => api.get('/c/user/memberInfo');
/* * 更新宝宝信息
* @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>
This diff is collapsed.
......@@ -5,8 +5,11 @@ import {
fetchBabyInfo,
fetchMemberInfo,
fetchAutoPhone,
fetchBabyInfoById,
updateBabyInfo,
} from "../api/user.js";
import { useGlobalStore } from "./global.js";
import { showLoading, hideLoading } from "../utils/index.js";
const globalStore = useGlobalStore();
......@@ -35,6 +38,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}
......@@ -134,5 +154,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?.content?.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,30 @@ 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?.memberId || userStore?.userInfo?.memberId == "not_login") {
cfgStatus.value.isRegister = false;
cfgStatus.value.showDetail = false;
return;
......@@ -306,7 +266,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 +289,9 @@ const handleChangeBaby = () => {
function onSelectBaby(baby) {
// 处理宝宝切换逻辑
showBabySwitcher.value = false;
userStore.changeBabySelected(baby.id);
console.log("onSelectBaby", baby);
}
......@@ -340,6 +303,12 @@ function onAddBaby() {
// 页面加载
onMounted(() => {
console.log("onMounted");
initData();
});
watch([() => userStore.userInfo, () => userStore.babyInfo], () => {
console.log("userInfo/babyInfo变化", userStore.userInfo, userStore.babyInfo);
initData();
});
......
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