Commit 5b57105a authored by tao.huang's avatar tao.huang

feat: 新增编辑宝宝

parent eaf80157
<template>
<Layer
:modelValue="modelValue"
:title="title"
:showCancel="true"
:showConfirm="true"
@update:modelValue="$emit('update:modelValue', $event)"
@confirm="onConfirm"
@cancel="onCancel"
>
<template>
<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="/static/check.png"
/>
</view>
</view>
</template>
</Layer>
</template>
<script setup>
import { ref, watch, computed } from "vue";
import Layer from "./Layer.vue";
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-list {
display: flex;
flex-wrap: wrap;
gap: 24rpx 20rpx;
}
.multi-select-tag {
display: flex;
align-items: center;
padding: 0 32rpx;
height: 56rpx;
background: #fff;
border-radius: 28rpx;
color: #222;
font-size: 28rpx;
margin-bottom: 24rpx;
position: relative;
border: 1rpx solid #eee;
.check-icon {
width: 28rpx;
height: 28rpx;
margin-left: 8rpx;
}
&.selected {
background: #e9d3b1;
color: #b88a3a;
border: 1rpx solid #b88a3a;
}
}
</style>
\ No newline at end of file
......@@ -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"
......@@ -203,8 +203,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,17 +223,17 @@ 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 onStatusChange(v) {
......@@ -258,7 +266,6 @@ function handleBabyInfoConfirm() {
fetus: fetus.value,
status: status.value,
});
visible.value = false;
}
function openAgreement() {
// 跳转协议页面
......
......@@ -82,6 +82,7 @@
:name="item.name"
:placeholder="item.placeholder"
v-model="formData[item.name]"
:maxlength="item.maxLength"
/>
<!-- 选择器类型 -->
<picker-custom
......@@ -94,15 +95,25 @@
:onStatusChange="onDateStatusChange"
>
<view class="form-input-box">
<view class="form-input">{{
formData[item.name] || item.placeholder
}}</view>
<view class="form-input">{{ getLabelByValue(item, formData[item.name]) || item.placeholder }}</view>
<image
class="form-input-icon"
:src="$baseUrl + 'person/icon_arrow_yellow_right.png'"
/>
</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'"
......@@ -151,22 +162,41 @@
保存
</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";
const userStore = useUserStore();
const pageType = ref('add');
const pageStatus = ref({
formStatus: 0, // 0: 隐藏 1: 展开, 2: 收起
btnStatus: true, // 是否显示提交按钮
});
// 0备孕 1孕中 2出生
const formData = ref({
currentBaby: "备孕",
currentBaby: 2,
nickName: "",
dateOfBirth: "",
dateOfBirth2: "",
gender: "",
babyIndex: "",
eatFunc: "",
......@@ -177,9 +207,25 @@ const formData = ref({
avatarUrl: "",
});
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"],
孕中: [
0: ["currentBaby", "dateOfBirth", "specialAttention"],
1: [
"currentBaby",
"dateOfBirth2",
"yz",
......@@ -187,7 +233,7 @@ const FormMap = {
"contentLike",
"specialAttention",
],
已出生: [
2: [
"currentBaby",
"nickName",
"dateOfBirth",
......@@ -205,6 +251,21 @@ const formItemFilter = (item) => {
return FormMap[formData.value.currentBaby].includes(item.name);
};
const MAX_NICKNAME_LENGTH = 10;
const contentLikeOptions = [
'奶粉选择', '奶粉成分', '奶粉冲泡', '宝宝奶量', '生长发育', '常见疾病', '体检疫苗', '饮食营养补充', '辅食营养', '亲子关系', '早教游戏'
];
const productLikeOptions = [
'飞鹤星飞帆', '星飞帆卓睿', '星飞帆卓耀', '星飞帆卓初', '星飞帆臻稚卓蓓', '星飞帆淳芮有机'
];
const specialAttentionOptions = [
'过敏', '长肉', '肠道', '消化', '脑发育'
];
const channelOptions = [
'电商(京东/天猫)', '母婴店'
];
const formItems = [
{
label: "当前状态",
......@@ -212,14 +273,16 @@ const formItems = [
required: true,
placeholder: "请选择当前状态",
type: "picker",
range: ["已出生", "孕中", "备孕"],
range: babyStageMap.map(i => i.label),
mode: "custom",
},
{
label: "宝宝昵称",
name: "nickName",
required: true,
placeholder: "请输入宝宝昵称",
placeholder: "暂无昵称",
type: "input",
maxLength: MAX_NICKNAME_LENGTH,
},
{
label: "宝宝出生日期",
......@@ -228,6 +291,7 @@ const formItems = [
placeholder: "请选择出生日期",
type: "picker",
mode: "date",
defaultToday: true,
},
{
label: "预产日期",
......@@ -241,27 +305,28 @@ const formItems = [
label: "宝宝胎数",
name: "babyIndex",
required: true,
placeholder: "请选择胎数",
placeholder: "未选择",
type: "picker",
range: ["单胎", "二胎", "多胎"],
range: babyTypeMap.map(i => i.label),
mode: "custom",
disabled: false,
},
{
label: "性别",
name: "gender",
required: true,
placeholder: "请选择性别",
placeholder: "未选择",
type: "picker",
range: ["男", "女"],
range: babyGenderMap.map(i => i.label),
mode: "custom",
},
{
label: "喂养方式",
name: "eatFunc",
required: false,
placeholder: "请选择喂养方式",
placeholder: "未选择",
type: "picker",
range: ["纯母乳", "混合", "奶粉"],
range: ["纯母乳", "混合喂养", "纯奶粉"],
mode: "custom",
},
{
......@@ -275,39 +340,55 @@ const formItems = [
label: "内容偏好",
name: "contentLike",
required: false,
placeholder: "请选择内容偏好",
type: "input",
placeholder: "未选择",
type: "multi-picker",
range: contentLikeOptions,
mode: "custom",
},
{
label: "产品使用偏好",
name: "productLike",
required: false,
placeholder: "请选择产品使用偏好",
type: "input",
placeholder: "未选择",
type: "multi-picker",
range: productLikeOptions,
mode: "custom",
},
{
label: "特殊关注信息",
name: "specialAttention",
required: false,
placeholder: "请输入特殊关注信息",
type: "input",
placeholder: "未选择",
type: "multi-picker",
range: specialAttentionOptions,
mode: "custom",
},
{
label: "常购买渠道",
name: "channel",
required: false,
placeholder: "请选择常购买渠道",
placeholder: "未选择",
type: "picker",
range: ["电商", "母婴店", "超市"],
range: channelOptions,
mode: "custom",
},
];
const getPickerIndex = (item) => {
const val = formData.value[item.name];
if (item.name === 'currentBaby') {
const idx = babyStageMap.findIndex(i => i.value === val);
return idx > -1 ? idx : 0;
}
if (item.name === 'babyIndex') {
const idx = babyTypeMap.findIndex(i => i.value === val);
return idx > -1 ? idx : 0;
}
if (item.name === 'gender') {
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,32 +397,36 @@ 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 === 'currentBaby') {
formData.value[name] = babyStageMap[e].value;
return;
}
if (name === 'babyIndex') {
formData.value[name] = babyTypeMap[e].value;
return;
}
if (name === 'gender') {
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];
}
};
......@@ -349,7 +434,6 @@ const onRadioChange = (e, name) => {
formData.value[name] = e.detail.value;
};
const onSubmit = (e) => {
// 校验与提交逻辑
console.log("提交数据", formData.value);
};
const handleReturn = () => {
......@@ -380,6 +464,83 @@ function onDateStatusChange(status) {
formData.value.currentBaby = status;
}
}
// getLabelByValue 支持所有映射字段
const getLabelByValue = (item, value) => {
if (item.name === 'currentBaby') {
return babyStageMap.find(i => i.value === value)?.label || '';
}
if (item.name === 'babyIndex') {
return babyTypeMap.find(i => i.value === value)?.label || '';
}
if (item.name === 'gender') {
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 : '';
};
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';
if (pageType.value === 'edit') {
const baby = userStore.babyInfo;
formData.value.currentBaby = baby.babyStage ?? 2;
formData.value.nickName = baby.babyName || '';
formData.value.dateOfBirth = baby.content?.babyBirthday || '';
formData.value.dateOfBirth2 = baby.content?.dueDate || '';
formData.value.babyIndex = baby.content?.babyType ?? 1;
formData.value.gender = baby.content?.babyGender ?? 'M';
formData.value.eatFunc = baby.content?.feedingType || '';
formData.value.contentLike = baby.content?.contentPreference || '';
formData.value.productLike = baby.content?.productPreference || '';
formData.value.specialAttention = baby.content?.followInfo || '';
formData.value.channel = baby.content?.purchaseChannel || '';
formData.value.avatarUrl = baby.babyAvatar || '';
} else {
Object.assign(formData.value, {
currentBaby: 2,
nickName: '',
dateOfBirth: '',
dateOfBirth2: '',
gender: '',
babyIndex: '',
eatFunc: '',
contentLike: '',
productLike: '',
specialAttention: '',
channel: '',
avatarUrl: '',
});
}
});
</script>
<style lang="less" scoped>
......
......@@ -47,3 +47,32 @@ 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);
}
}
......@@ -20,7 +20,7 @@
bindload=""
/>
</view>
<button
v-if="!cfgStatus.isRegister"
type="primary"
......@@ -33,7 +33,6 @@
<view
class="user-info"
:style="{ 'min-height': cfgStatus.showDetail ? '343rpx' : '180rpx' }"
@click="handleRegister"
>
<view class="user-header">
<view class="avatar-container" @click="handleEditProfile">
......@@ -127,15 +126,18 @@
</template>
<script setup>
import { ref, onMounted, getCurrentInstance } from "vue";
import { ref, onMounted, getCurrentInstance, computed } 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 +175,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,36 +209,33 @@ const navigateTo = (url) => {
// 编辑个人资料
const handleEditProfile = () => {
const userStore = useUserStore();
if (!userStore.userInfo || JSON.stringify(userStore.userInfo) === "{}") {
return;
}
navigateTo("/pages/person/person");
navigateTo(`/pages/person/person?type=${userStore.babyInfo?.allBabyBaseInfo?.length == 0 ? 'add' : 'edit'}`);
};
const handleRegister = () => {
// 判断是否已注册
if (!cfgStatus.value.isRegister) {
// 调用登录接口
// console.log("phoneButton", phoneButton.value);
// phoneButton.value.triggerEvent("tap");
const onRegisterConfirm = throttleTap(async (data) => {
const reqData = {
id: userStore?.babyInfo?.id,
babyStage: data.status,
};
if (userStore?.babyInfo?.babyType != 0) {
reqData.babyGender = data.gender;
reqData.babyType = data.fetus;
}
return;
};
console.log("reqData:", reqData);
const onRegisterConfirm = async (data) => {
console.log("onRegisterConfirm:", data);
showRegisterLayer.value = false;
const userStore = useUserStore();
await Promise.all[(userStore.loadUserInfo(), userStore.loadBabyInfo())];
initData();
};
}, 5000);
// 获取用户信息
const initData = async () => {
const userStore = useUserStore();
if (!userStore.userInfo || JSON.stringify(userStore.userInfo) == "{}") {
cfgStatus.value.isRegister = false;
cfgStatus.value.showDetail = false;
......@@ -306,7 +260,7 @@ const initData = async () => {
const getRealtimePhoneNumber = async (e) => {
console.log("获取手机号码", e);
const userStore = useUserStore();
if (e.detail.errMsg !== "getPhoneNumber:ok") {
uni.showToast({
title: "请授权使用手机号",
......
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