hookehuyr

feat(user): 添加用户性别字段支持

refactor(EditFamily): 添加familyId字段用于家庭信息更新
style(ShareButton): 调整按钮间距和样式
fix(auth): 修正测试环境openid配置
docs(PosterCheckin): 更新海报关卡描述显示
feat(profile): 在添加和编辑资料页面增加性别选择功能
......@@ -26,6 +26,7 @@ const Api = {
* @returns {string} response.data.user.avatar_url - 用户头像URL
* @returns {string} response.data.user.birth_date - 用户生日
* @returns {boolean} response.data.user.wheelchair_needed - 是否需要轮椅
* @returns {number} response.data.user.gender - 用户性别 (0=女, 1=男)
*/
export const getUserProfileAPI = (params) => fn(fetch.get(Api.GET_PROFILE, params));
......@@ -36,5 +37,6 @@ export const getUserProfileAPI = (params) => fn(fetch.get(Api.GET_PROFILE, param
* @param {string} params.birth_date - 用户生日
* @param {string} params.avatar_url - 用户头像URL
* @param {boolean} params.wheelchair_needed - 是否需要轮椅
* @param {number} params.gender - 用户性别 (0=女, 1=男)
*/
export const updateUserProfileAPI = (params) => fn(fetch.post(Api.UPDATE_PROFILE, params));
......
<!--
* @Date: 2025-09-03 14:34:58
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-09-08 11:16:47
* @LastEditTime: 2025-09-08 11:50:44
* @FilePath: /lls_program/src/components/ShareButton/index.vue
* @Description: 文件描述
-->
......@@ -54,7 +54,7 @@ const handleSharePoster = () => {
z-index: 1000;
display: flex;
flex-direction: column;
gap: 16rpx;
gap: 28rpx;
align-items: center;
}
......@@ -68,7 +68,7 @@ const handleSharePoster = () => {
font: inherit !important;
position: relative !important;
display: inline-block !important;
// 应用自定义样式
color: white !important;
padding: 12rpx 20rpx !important;
......@@ -93,13 +93,13 @@ const handleSharePoster = () => {
transform: scale(0.95) !important;
background: rgba(0, 0, 0, 0.8) !important;
}
// 去除微信小程序button的默认样式
&::after {
border: none !important;
content: none !important;
}
// 去除button默认的点击效果
&.button-hover {
background: rgba(0, 0, 0, 0.8) !important;
......
......@@ -34,6 +34,20 @@
<DateIcon size="20" color="#888" />
</view>
</view>
<text class="text-red-500 text-xs mt-1 block">注意:出生年月填写后不可修改,请仔细确认</text>
</view>
<!-- gender -->
<view class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2">性别</label>
<view class="bg-white rounded-xl p-4 border border-gray-200" @click="showGenderPicker = true">
<view class="flex justify-between items-center">
<text :class="{'text-gray-400': !formData.gender_text, 'text-gray-900': formData.gender_text}" class="text-base">
{{ formData.gender_text || '请选择' }}
</text>
<Right size="16" color="#888" />
</view>
</view>
</view>
<!-- wheelchair_needed -->
......@@ -77,6 +91,16 @@
></nut-picker>
</nut-popup>
<nut-popup v-model:visible="showGenderPicker" position="bottom">
<nut-picker
v-model="genderValue"
:columns="genderColumns"
@confirm="onGenderConfirm"
@cancel="showGenderPicker = false"
title="选择性别"
></nut-picker>
</nut-popup>
<nut-image-preview v-model:show="previewVisible" :images="previewImages" />
</view>
</template>
......@@ -100,13 +124,15 @@ const formData = reactive({
birth_date: '',
wheelchair_needed: null, // 0 for No, 1 for Yes
wheelchair_text: '',
gender: null, // 0 for 女, 1 for 男
gender_text: '',
});
/**
* @description 检查表单是否有效
*/
const isFormValid = computed(() => {
return formData.nickname && formData.birth_date && formData.wheelchair_needed !== null;
return formData.nickname && formData.birth_date && formData.wheelchair_needed !== null && formData.gender !== null;
});
// --- Date Picker ---
......@@ -153,6 +179,33 @@ const wheelchairColumns = ref([
{ text: '否', value: 0 },
]);
// --- Gender Picker ---
/**
* @description 控制性别选择器显示
*/
const showGenderPicker = ref(false);
/**
* @description 当前选中的性别选项
*/
const genderValue = ref([]);
/**
* @description 性别选择器选项
*/
const genderColumns = ref([
{ text: '女', value: 0 },
{ text: '男', value: 1 },
]);
/**
* @description 确认选择性别选项
* @param {object} param0 - 包含 selectedValue 和 selectedOptions 的对象
*/
const onGenderConfirm = ({ selectedValue, selectedOptions }) => {
formData.gender = selectedValue[0];
formData.gender_text = selectedOptions.map((option) => option.text).join('');
showGenderPicker.value = false;
};
/**
* @description 确认选择轮椅选项
* @param {object} param0 - 包含 selectedValue 和 selectedOptions 的对象
......
<!--
* @Date: 2025-08-27 17:44:53
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-09-04 17:39:21
* @LastEditTime: 2025-09-08 11:55:03
* @FilePath: /lls_program/src/pages/EditFamily/index.vue
* @Description: 文件描述
-->
......@@ -176,6 +176,7 @@ const defaultFamilyCoverSvg = 'https://cdn.ipadbiz.cn/lls_prog/images/default-fa
// 获取接口信息
import { editFamilyAPI, getFamilyInfoAPI } from '@/api/family';
const familyId = ref('');
const familyName = ref('');
const familyIntro = ref('');
const selectedDistrict = ref(null);
......@@ -221,6 +222,7 @@ onMounted(async () => {
Taro.setNavigationBarTitle({ title: '编辑家庭' });
const { code, data } = await getFamilyInfoAPI();
if (code) {
familyId.value = data.id;
familyName.value = data.name;
familyIntro.value = data.note;
districtValue.value = [+data.county];
......@@ -474,6 +476,7 @@ const handleSaveFamily = async () => {
// 调用API保存家庭信息
const { code } = await editFamilyAPI({
id: familyId.value,
name: familyName.value,
note: familyIntro.value,
county: selectedDistrict.value,
......@@ -481,7 +484,7 @@ const handleSaveFamily = async () => {
avatar_url: familyAvatar.value,
});
if (code) {
showToast('家庭信息保存成功', 'success');
showToast('保存成功', 'success');
setTimeout(() => {
Taro.navigateBack();
......
......@@ -26,12 +26,26 @@
<!-- birth_date -->
<view class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2">出生年月</label>
<view class="bg-white rounded-xl p-4 border border-gray-200" @click="showDatePicker = true">
<view class="bg-gray-100 rounded-xl p-4 border border-gray-200">
<view class="flex justify-between items-center">
<text :class="{'text-gray-400': !formData.birth_date, 'text-gray-900': formData.birth_date}" class="text-base">
<text class="text-gray-500 text-base">
{{ formData.birth_date || '-/-/-/' }}
</text>
<DateIcon size="20" color="#888" />
<DateIcon size="20" color="#ccc" />
</view>
</view>
<text class="text-gray-400 text-xs mt-1 block">出生年月不可修改</text>
</view>
<!-- gender -->
<view class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2">性别</label>
<view class="bg-white rounded-xl p-4 border border-gray-200" @click="showGenderPicker = true">
<view class="flex justify-between items-center">
<text :class="{'text-gray-400': !formData.gender_text, 'text-gray-900': formData.gender_text}" class="text-base">
{{ formData.gender_text || '请选择' }}
</text>
<Right size="16" color="#888" />
</view>
</view>
</view>
......@@ -77,6 +91,16 @@
></nut-picker>
</nut-popup>
<nut-popup v-model:visible="showGenderPicker" position="bottom">
<nut-picker
v-model="genderValue"
:columns="genderColumns"
@confirm="onGenderConfirm"
@cancel="showGenderPicker = false"
title="选择性别"
></nut-picker>
</nut-popup>
<nut-image-preview v-model:show="previewVisible" :images="previewImages" />
</view>
</template>
......@@ -100,6 +124,8 @@ const formData = reactive({
birth_date: '',
wheelchair_needed: null, // 0 for No, 1 for Yes
wheelchair_text: '',
gender: null, // 0 for 女, 1 for 男
gender_text: '',
});
// --- Date Picker ---
......@@ -146,6 +172,33 @@ const wheelchairColumns = ref([
{ text: '否', value: false },
]);
// --- Gender Picker ---
/**
* @description 控制性别选择器显示
*/
const showGenderPicker = ref(false);
/**
* @description 当前选中的性别选项
*/
const genderValue = ref([]);
/**
* @description 性别选择器选项
*/
const genderColumns = ref([
{ text: '女', value: 0 },
{ text: '男', value: 1 },
]);
/**
* @description 确认选择性别选项
* @param {object} param0 - 包含 selectedValue 和 selectedOptions 的对象
*/
const onGenderConfirm = ({ selectedValue, selectedOptions }) => {
formData.gender = selectedValue[0];
formData.gender_text = selectedOptions.map((option) => option.text).join('');
showGenderPicker.value = false;
};
/**
* @description 确认选择轮椅选项
* @param {object} param0 - 包含 selectedValue 和 selectedOptions 的对象
......@@ -289,6 +342,11 @@ onMounted(async () => {
wheelchairValue.value = [formData.wheelchair_needed];
formData.wheelchair_text = formData.wheelchair_needed ? '是' : '否';
}
// 设置性别选择器的值和显示文本
if (formData.gender !== null && formData.gender !== undefined) {
genderValue.value = [formData.gender];
formData.gender_text = formData.gender === 0 ? '女' : '男';
}
}
});
</script>
......
......@@ -207,7 +207,7 @@ const posterList = ref([
},
level: {
logo: 'https://cdn.ipadbiz.cn/lls_prog/images/%E6%B5%B7%E6%8A%A5%E5%8F%B3%E4%B8%8B%E8%A7%92icon.png',
name: '第一关卡'
name: '卡点: 上海市第一百货商店'
},
qrcode: 'https://cdn.ipadbiz.cn/space/068a790496c87cb8d2ed6e551401c544.png',
qrcodeDesc: '长按识别,来,我们一起打卡!'
......@@ -364,25 +364,25 @@ const posterConfig = computed(() => {
zIndex: 2
},
// 家庭描述
{
x: 40,
y: 250,
text: currentMockData.value.family.description,
fontSize: 28,
color: '#ffffff',
textAlign: 'left',
shadowColor: 'rgba(0, 0, 0, 0.6)',
shadowOffsetX: 2,
shadowOffsetY: 2,
shadowBlur: 4,
zIndex: 2
},
// 关卡描述
// {
// x: 40,
// y: 250,
// text: currentMockData.value.family.description,
// fontSize: 28,
// color: '#ffffff',
// textAlign: 'left',
// shadowColor: 'rgba(0, 0, 0, 0.6)',
// shadowOffsetX: 2,
// shadowOffsetY: 2,
// shadowBlur: 4,
// zIndex: 2
// },
// 小程序码描述
{
x: 280,
y: 1125,
text: currentMockData.value.level.name,
fontSize: 35,
text: currentMockData.value.qrcodeDesc,
fontSize: 28,
color: '#333333',
lineHeight: 40,
lineNum: 2,
......@@ -390,19 +390,19 @@ const posterConfig = computed(() => {
textAlign: 'left',
zIndex: 1
},
// 小程序码描述
// 关卡描述
{
x: 280,
y: 1200,
text: currentMockData.value.qrcodeDesc,
fontSize: 28,
y: 1180,
text: currentMockData.value.level.name,
fontSize: 32,
color: '#333333',
lineHeight: 40,
lineNum: 2,
width: 400,
textAlign: 'left',
zIndex: 1
}
},
],
blocks: [
// 下半部分白色背景
......
<!--
* @Date: 2022-09-19 14:11:06
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-09-06 22:13:30
* @LastEditTime: 2025-09-07 01:37:03
* @FilePath: /lls_program/src/pages/auth/index.vue
* @Description: 文件描述
-->
......@@ -42,12 +42,12 @@ export default {
// 测试环境下传递openid,正式环境不传递
if (process.env.NODE_ENV === 'development') {
// requestData.openid = 'h-008';
requestData.openid = 'h-008';
// requestData.openid = 'h-009';
// requestData.openid = 'h-010';
// requestData.openid = 'h-011';
// requestData.openid = 'h-012';
requestData.openid = 'h-013';
// requestData.openid = 'h-013';
// requestData.openid = 'oWbdFvkD5VtloC50wSNR9IWiU2q8';
// requestData.openid = 'oex8h5QZnZJto3ttvO6swSvylAQo';
}
......