hookehuyr

feat: 实现用户和家庭相关接口集成及功能优化

- 集成用户信息获取和更新接口
- 实现家庭创建、加入和查询功能
- 优化微信运动授权逻辑
- 统一上传接口响应处理
- 修复多个页面表单验证逻辑
......@@ -13,7 +13,6 @@ declare module 'vue' {
GlassCard: typeof import('./src/components/GlassCard.vue')['default']
NavBar: typeof import('./src/components/navBar.vue')['default']
NutActionSheet: typeof import('@nutui/nutui-taro')['ActionSheet']
NutBacktop: typeof import('@nutui/nutui-taro')['Backtop']
NutButton: typeof import('@nutui/nutui-taro')['Button']
NutDatePicker: typeof import('@nutui/nutui-taro')['DatePicker']
NutImagePreview: typeof import('@nutui/nutui-taro')['ImagePreview']
......
/*
* @Date: 2024-01-01 00:00:00
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-09-02 20:09:52
* @FilePath: /lls_program/src/api/family.js
* @Description: 家庭相关接口
*/
import { fn, fetch } from './fn';
const Api = {
SEARCH_BY_PASSPHRASE: '/srv/?a=family&t=search_by_passphrase',
LIST_MY_FAMILIES: '/srv/?a=family&t=list_my_families',
GET_DASHBOARD: '/srv/?a=family&t=get_dashboard',
ADD_FAMILY: '/srv/?a=family&t=add',
JOIN_FAMILY: '/srv/?a=family&t=join',
DEL_MEMBER: '/srv/?a=family&t=del_member',
}
/**
* @description: 根据口令搜索家庭
* @param {Object} params - 请求参数
* @param {string} params.passphrase - 家训口令
* @param {number} [params.page=0] - 页码,从0开始
* @param {number} [params.limit=10] - 每页数量
* @returns {Promise} 返回匹配的家庭列表
* @returns {Object} response - 响应对象
* @returns {number} response.code - 响应状态码
* @returns {string} response.msg - 响应消息
* @returns {Array} response.data - 家庭列表
* @returns {number} response.data[].id - 家庭ID
* @returns {string} response.data[].name - 家庭名称
* @returns {string} response.data[].avatar_url - 家庭头像
*/
export const searchFamilyByPassphraseAPI = (params) => fn(fetch.get(Api.SEARCH_BY_PASSPHRASE, params));
/**
* @description: 获取我的家庭列表
* @returns {Promise} 返回当前用户创建和加入的所有家庭列表
* @returns {Object} response - 响应对象
* @returns {string} response.code - 响应状态码
* @returns {string} response.msg - 响应消息
* @returns {Array} response.data - 家庭列表
* @returns {number} response.data[].id - 家庭ID
* @returns {string} response.data[].name - 家庭名称
* @returns {string} response.data[].avatar_url - 家庭头像
* @returns {boolean} response.data[].is_my - 是否是我创建的家庭
*/
export const getMyFamiliesAPI = () => fn(fetch.get(Api.LIST_MY_FAMILIES));
/**
* @description: 获取家庭首页数据
* @param {Object} [params] - 请求参数
* @param {string} [params.family_id] - 家庭ID,默认是上次选中的家庭
* @returns {Promise} 返回家庭首页聚合数据
* @returns {Object} response - 响应对象
* @returns {number} response.code - 响应状态码
* @returns {string} response.msg - 响应消息
* @returns {Object} response.data - 家庭首页数据
* @returns {Object} response.data.family - 家庭信息
* @returns {number} response.data.family.id - 家庭ID
* @returns {string} response.data.family.name - 家庭名称
* @returns {string} response.data.family.note - 家庭描述
* @returns {string} response.data.family.avatar_url - 家庭头像
* @returns {number} response.data.family.total_points - 家庭总积分
* @returns {number} response.data.family_today_step - 家庭今日总步数
* @returns {number} response.data.my_today_step - 我今天的步数
* @returns {Array} response.data.pending_points - 等待汇总到家庭的积分
* @returns {number} response.data.pending_points[].id - 积分ID
* @returns {string} response.data.pending_points[].title - 积分标题
* @returns {string} response.data.pending_points[].points - 积分数量
* @returns {string} response.data.pending_points[].source_type - 积分来源
* @returns {string} response.data.pending_points[].note - 积分说明
* @returns {Array} response.data.step_ranking - 步数排名
* @returns {string} response.data.step_ranking[].user_id - 用户ID
* @returns {string} response.data.step_ranking[].role - 角色
* @returns {string} response.data.step_ranking[].avatar_url - 头像
* @returns {string} response.data.step_ranking[].today_step - 用户今日步数
*/
export const getFamilyDashboardAPI = (params) => fn(fetch.get(Api.GET_DASHBOARD, params));
/**
* @description: 创建家庭
* @param {Object} params - 请求参数
* @param {string} params.name - 家庭名称
* @param {string} params.county - 所在区县
* @param {string} params.passphrase - 家训口令
* @param {string} params.avatar_url - 家庭头像
* @param {string} params.note - 家庭介绍
* @returns {Promise} 返回创建的家庭ID
* @returns {Object} response - 响应对象
* @returns {string} response.code - 响应状态码
* @returns {string} response.msg - 响应消息
* @returns {Object} response.data - 响应数据
* @returns {number} response.data.family_id - 家庭ID
*/
export const createFamilyAPI = (params) => fn(fetch.post(Api.ADD_FAMILY, params));
/**
* @description: 加入家庭
* @param {Object} params - 请求参数
* @param {number} params.family_id - 家庭ID
* @param {string} params.role - 角色(如:儿子、女儿等)
* @returns {Promise} 返回加入结果
* @returns {Object} response - 响应对象
* @returns {string} response.code - 响应状态码
* @returns {string} response.msg - 响应消息
*/
export const joinFamilyAPI = (params) => fn(fetch.post(Api.JOIN_FAMILY, params));
/**
* @description: 退出或移出家庭成员
* @param {Object} params - 请求参数
* @param {number} params.family_id - 家庭ID
* @param {number} [params.member_user_id] - 成员用户ID(主动退出时不传,创建者移出时传递)
* @returns {Promise} 返回操作结果
* @returns {Object} response - 响应对象
* @returns {string} response.code - 响应状态码
* @returns {string} response.msg - 响应消息
*/
export const deleteFamilyMemberAPI = (params) => fn(fetch.post(Api.DEL_MEMBER, params));
/*
* @Date: 2024-01-01 00:00:00
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-09-02 20:33:00
* @FilePath: /lls_program/src/api/user.js
* @Description: 用户相关接口
*/
import { fn, fetch } from './fn';
const Api = {
GET_PROFILE: '/srv/?a=user&t=get_profile',
UPDATE_PROFILE: '/srv/?a=user&t=update_profile',
}
/**
* @description: 获取个人信息
* @returns {Promise} 返回用户详细个人资料
* @returns {Object} response - 响应对象
* @returns {number} response.code - 响应状态码
* @returns {string} response.msg - 响应消息
* @returns {Object} response.data - 响应数据
* @returns {Object} response.data.user - 用户信息对象
* @returns {number} response.data.user.id - 用户ID
* @returns {string} response.data.user.openid - 微信openid
* @returns {string} response.data.user.nickname - 用户昵称
* @returns {string} response.data.user.avatar_url - 用户头像URL
* @returns {string} response.data.user.birth_date - 用户生日
* @returns {boolean} response.data.user.wheelchair_needed - 是否需要轮椅
*/
export const getUserProfileAPI = (params) => fn(fetch.get(Api.GET_PROFILE, params));
/**
* @description: 更新个人信息
* @param {Object} params - 请求参数
* @param {string} params.nickname - 用户昵称
* @param {string} params.birth_date - 用户生日
* @param {string} params.avatar_url - 用户头像URL
* @param {boolean} params.wheelchair_needed - 是否需要轮椅
*/
export const updateUserProfileAPI = (params) => fn(fetch.post(Api.UPDATE_PROFILE, params));
......@@ -47,8 +47,9 @@ const isLoading = ref(false)
/**
* @description 检查微信运动授权状态
* @param {boolean} shouldGetData - 是否在授权后获取步数数据
*/
const checkAuthStatus = async () => {
const checkAuthStatus = async (shouldGetData = false) => {
try {
const authSetting = await Taro.getSetting()
const hasWeRunAuth = authSetting.authSetting['scope.werun']
......@@ -58,8 +59,8 @@ const checkAuthStatus = async () => {
// 通知父组件授权状态变化
emit('auth-change', isAuthorized.value)
// 如果已授权,获取步数数据
if (isAuthorized.value) {
// 根据参数决定是否获取步数数据
if (isAuthorized.value && shouldGetData) {
await getWeRunData()
}
} catch (error) {
......@@ -147,10 +148,10 @@ const getWeRunData = async () => {
const { code, data } = await syncWxStepAPI({ encryptedData: weRunRes.encryptedData, iv: weRunRes.iv });
if (code) {
// 提示获取步数成功
Taro.showToast({
title: `获取步数成功`,
icon: 'none'
})
// Taro.showToast({
// title: `获取步数成功`,
// icon: 'none'
// })
console.warn('同步微信步数成功', data);
// todaySteps.value = decryptedData.todaySteps
isLoading.value = false
......@@ -187,9 +188,9 @@ defineExpose({
checkAuthStatus
})
// 组件挂载时检查授权状态
// 组件挂载时检查授权状态,但不获取步数数据
onMounted(() => {
checkAuthStatus()
checkAuthStatus(false)
})
</script>
......
......@@ -23,20 +23,20 @@
</view>
</view>
<!-- Birthday -->
<!-- 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="flex justify-between items-center">
<text :class="{'text-gray-400': !formData.birthday, 'text-gray-900': formData.birthday}" class="text-base">
{{ formData.birthday || '-/-/-/' }}
<text :class="{'text-gray-400': !formData.birth_date, 'text-gray-900': formData.birth_date}" class="text-base">
{{ formData.birth_date || '-/-/-/' }}
</text>
<DateIcon size="20" color="#888" />
</view>
</view>
</view>
<!-- Wheelchair -->
<!-- wheelchair_needed -->
<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="showWheelchairPicker = true">
......@@ -88,6 +88,8 @@ import { Date as DateIcon, Right } from '@nutui/icons-vue-taro';
import BASE_URL from '@/utils/config';
// 默认头像
const defaultAvatar = 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'
// 获取接口信息
import { updateUserProfileAPI } from '@/api/user';
/**
* @description 表单数据
......@@ -95,8 +97,8 @@ const defaultAvatar = 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'
const formData = reactive({
avatar_url: '',
nickname: '',
birthday: '',
wheelchair: null, // 0 for No, 1 for Yes
birth_date: '',
wheelchair_needed: null, // 0 for No, 1 for Yes
wheelchair_text: '',
});
......@@ -104,7 +106,7 @@ const formData = reactive({
* @description 检查表单是否有效
*/
const isFormValid = computed(() => {
return formData.nickname && formData.birthday && formData.wheelchair !== null;
return formData.nickname && formData.birth_date && formData.wheelchair_needed !== null;
});
// --- Date Picker ---
......@@ -130,11 +132,11 @@ const currentDate = ref(new Date());
* @param {object} param0 - 包含 selectedValue 的对象
*/
const onDateConfirm = ({ selectedValue }) => {
formData.birthday = selectedValue.join('-');
formData.birth_date = selectedValue.join('-');
showDatePicker.value = false;
};
// --- Wheelchair Picker ---
// --- wheelchair_needed Picker ---
/**
* @description 控制轮椅选择器显示
*/
......@@ -156,7 +158,7 @@ const wheelchairColumns = ref([
* @param {object} param0 - 包含 selectedValue 和 selectedOptions 的对象
*/
const onWheelchairConfirm = ({ selectedValue, selectedOptions }) => {
formData.wheelchair = selectedValue[0];
formData.wheelchair_needed = selectedValue[0];
formData.wheelchair_text = selectedOptions.map((option) => option.text).join('');
showWheelchairPicker.value = false;
};
......@@ -198,8 +200,8 @@ const changeAvatar = () => {
success: (uploadRes) => {
Taro.hideLoading();
const data = JSON.parse(uploadRes.data);
if (data.code === 0) {
formData.avatar_url = data.data.url;
if (data.code == 0) {
formData.avatar_url = data.data.src;
Taro.showToast({ title: '上传成功', icon: 'success' });
} else {
Taro.showToast({ title: data.msg || '上传失败', icon: 'none' });
......@@ -221,7 +223,7 @@ const changeAvatar = () => {
/**
* @description 保存用户信息
*/
const handleSave = () => {
const handleSave = async () => {
if (!isFormValid.value) {
Taro.showToast({
title: '请填写所有必填项',
......@@ -229,39 +231,21 @@ const handleSave = () => {
});
return;
}
console.log('Saving data:', formData);
Taro.showLoading({ title: '保存中...' });
// Mock save process
setTimeout(() => {
Taro.hideLoading();
// 保存用户信息
const { code, data } = await updateUserProfileAPI(formData);
if (code) {
Taro.showToast({
title: '保存成功',
icon: 'success',
duration: 1500,
complete: () => {
Taro.navigateBack();
},
});
}, 1000);
setTimeout(() => {
Taro.navigateBack();
}, 1500);
}
};
/**
* @description 页面加载时获取初始数据
*/
onMounted(() => {
// Mock fetching user data, in a real app, you would call an API
// const mockUserData = {
// avatar_url: 'https://img.yzcdn.cn/vant/cat.jpeg',
// nickname: '张三',
// birthday: '1990-05-15',
// wheelchair: 0,
// wheelchair_text: '否',
// };
// Object.assign(formData, mockUserData);
// Initialize pickers with current data
// currentDate.value = new Date(mockUserData.birthday);
// wheelchairValue.value = [mockUserData.wheelchair];
});
</script>
......
<!--
* @Date: 2025-08-27 17:44:53
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-09-02 16:29:48
* @LastEditTime: 2025-09-02 17:52:24
* @FilePath: /lls_program/src/pages/CreateFamily/index.vue
* @Description: 文件描述
-->
......@@ -23,7 +23,6 @@
class="w-full text-gray-600 focus:outline-none"
placeholder="请输入家庭名称(最多10个字)"
@blur="validateFamilyName"
maxlength="10"
/>
<view v-if="familyNameError" class="text-red-500 text-sm mt-2">{{ familyNameError }}</view>
</view>
......@@ -38,7 +37,6 @@
placeholder="请输入您家庭的特色、成员特点等家庭标签(最多100个字)"
:rows="2"
@blur="validateFamilyIntro"
maxlength="100"
/>
<view v-if="familyIntroError" class="text-red-500 text-sm mt-2">{{ familyIntroError }}</view>
</view>
......@@ -138,7 +136,7 @@
</view>
<!-- Submit Button -->
<view
@tap="isFormValid ? handleCreateFamily : null"
@tap="handleCreateFamily"
:class="[
'w-full py-3 text-white text-lg font-medium rounded-lg flex items-center justify-center',
isFormValid ? 'bg-blue-500' : 'bg-gray-300'
......@@ -174,10 +172,11 @@
<script setup>
import { ref, nextTick, computed } from 'vue';
import Taro from '@tarojs/taro';
import { Edit, Tips, Photograph, Right } from '@nutui/icons-vue-taro';
// import AppHeader from '../../components/AppHeader.vue';
import { Tips, Photograph, Right } from '@nutui/icons-vue-taro';
import BASE_URL from '@/utils/config';
import defaultFamilyCoverSvg from '@/assets/images/default-family-cover.png';
// 接口信息
import { createFamilyAPI } from '@/api/family';
const familyName = ref('');
const familyIntro = ref('');
......@@ -342,7 +341,7 @@ const uploadImage = (filePath) => {
let upload_data = JSON.parse(res.data);
Taro.hideLoading({
success: () => {
if (res.statusCode === 200) {
if (data.code == 0) {
familyAvatar.value = upload_data.data.src;
showToast('上传成功', 'success');
} else {
......@@ -460,19 +459,45 @@ const validateForm = () => {
/**
* 创建家庭
*/
const handleCreateFamily = () => {
const handleCreateFamily = async () => {
if (!validateForm()) {
return;
}
// 在实际应用中,这里会调用API创建家庭
// 目前仅作为演示跳转到仪表盘页面
showToast('家庭创建成功', 'success');
try {
// 显示加载中
Taro.showLoading({
title: '创建中...',
mask: true
});
setTimeout(() => {
Taro.navigateTo({
url: '/pages/Dashboard/index'
const { code, data, msg } = await createFamilyAPI({
name: familyName.value,
note: familyIntro.value,
county: selectedDistrict.value,
passphrase: familyMotto.value.join(''),
avatar_url: familyAvatar.value,
});
}, 1500);
Taro.hideLoading();
// 判断API调用是否成功
if (code) {
showToast('创建成功', 'success');
setTimeout(() => {
Taro.navigateTo({
url: '/pages/Dashboard/index'
});
}, 1500);
} else {
// 显示错误信息
showToast(msg || '创建失败,请重试', 'none');
}
} catch (error) {
Taro.hideLoading();
console.error('创建家庭失败:', error);
showToast('网络错误,请重试', 'none');
}
};
</script>
......
......@@ -205,6 +205,8 @@ import { useMediaPreview } from '@/composables/useMediaPreview';
import defaultFamilyCover from '@/assets/images/default-family-cover.png';
// 默认头像
const defaultAvatar = 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'
// 接口信息
import { getMyFamiliesAPI } from '@/api/family'
const todaySteps = ref(0);
const isWeRunAuthorized = ref(false);
......@@ -381,15 +383,25 @@ const initPageData = async () => {
familyCover.value = defaultFamilyCover
}
useDidShow(() => {
// TODO: 等待真实接口获取用户是否加入家庭
const hasJoinedFamily = Math.random() > 0.5; // Change to true to simulate having a family
if (!hasJoinedFamily) {
Taro.redirectTo({ url: '/pages/Welcome/index' });
useDidShow(async () => {
// 获取用户是否加入家庭
const { code, data } = await getMyFamiliesAPI()
if (code) {
// 如果没有加入家庭
if (!data.length) {
Taro.redirectTo({ url: '/pages/Welcome/index' });
return; // 直接返回,不执行后续逻辑
}
}
// 只有在用户已加入家庭的情况下才初始化页面数据
initPageData();
// 刷新微信步数数据(只有在已加入家庭时才获取)
if (weRunAuthRef.value) {
weRunAuthRef.value.checkAuthStatus(true);
}
// TODO: 真实情况下 需要重新获取积分列表, 测试随机积分获取状态, 接口有积分列表就显示出来, 没有就不显示
showTotalPointsOnly.value = Math.random() > 0.5 ? true : false;
// 总积分数量也要从接口获取传进去
......
<!--
* @Date: 2025-08-27 17:44:53
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-09-02 16:30:22
* @LastEditTime: 2025-09-02 20:00:30
* @FilePath: /lls_program/src/pages/EditFamily/index.vue
* @Description: 文件描述
-->
......@@ -324,7 +324,7 @@ const uploadImage = (filePath) => {
let upload_data = JSON.parse(res.data);
Taro.hideLoading({
success: () => {
if (res.statusCode === 200) {
if (data.code == 0) {
familyAvatar.value = upload_data.data.src;
showToast('上传成功', 'success');
} else {
......
......@@ -23,20 +23,20 @@
</view>
</view>
<!-- Birthday -->
<!-- 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="flex justify-between items-center">
<text :class="{'text-gray-400': !formData.birthday, 'text-gray-900': formData.birthday}" class="text-base">
{{ formData.birthday || '-/-/-/' }}
<text :class="{'text-gray-400': !formData.birth_date, 'text-gray-900': formData.birth_date}" class="text-base">
{{ formData.birth_date || '-/-/-/' }}
</text>
<DateIcon size="20" color="#888" />
</view>
</view>
</view>
<!-- Wheelchair -->
<!-- wheelchair_needed -->
<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="showWheelchairPicker = true">
......@@ -88,6 +88,8 @@ import { My, Date as DateIcon, Right } from '@nutui/icons-vue-taro';
import BASE_URL from '@/utils/config';
// 默认头像
const defaultAvatar = 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'
// 获取接口信息
import { getUserProfileAPI, updateUserProfileAPI } from '@/api/user';
/**
* @description 表单数据
......@@ -95,8 +97,8 @@ const defaultAvatar = 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'
const formData = reactive({
avatar_url: '',
nickname: '',
birthday: '',
wheelchair: null, // 0 for No, 1 for Yes
birth_date: '',
wheelchair_needed: null, // 0 for No, 1 for Yes
wheelchair_text: '',
});
......@@ -123,11 +125,11 @@ const currentDate = ref(new Date());
* @param {object} param0 - 包含 selectedValue 的对象
*/
const onDateConfirm = ({ selectedValue }) => {
formData.birthday = selectedValue.join('-');
formData.birth_date = selectedValue.join('-');
showDatePicker.value = false;
};
// --- Wheelchair Picker ---
// --- wheelchair_needed Picker ---
/**
* @description 控制轮椅选择器显示
*/
......@@ -140,8 +142,8 @@ const wheelchairValue = ref([]);
* @description 轮椅选择器选项
*/
const wheelchairColumns = ref([
{ text: '是', value: 1 },
{ text: '否', value: 0 },
{ text: '是', value: true },
{ text: '否', value: false },
]);
/**
......@@ -149,7 +151,7 @@ const wheelchairColumns = ref([
* @param {object} param0 - 包含 selectedValue 和 selectedOptions 的对象
*/
const onWheelchairConfirm = ({ selectedValue, selectedOptions }) => {
formData.wheelchair = selectedValue[0];
formData.wheelchair_needed = selectedValue[0];
formData.wheelchair_text = selectedOptions.map((option) => option.text).join('');
showWheelchairPicker.value = false;
};
......@@ -192,7 +194,7 @@ const changeAvatar = () => {
Taro.hideLoading();
const data = JSON.parse(uploadRes.data);
if (data.code === 0) {
formData.avatar_url = data.data.url;
formData.avatar_url = data.data.src;
Taro.showToast({ title: '上传成功', icon: 'success' });
} else {
Taro.showToast({ title: data.msg || '上传失败', icon: 'none' });
......@@ -224,7 +226,7 @@ const validateForm = () => {
return false;
}
if (!formData.birthday) {
if (!formData.birth_date) {
Taro.showToast({
title: '请选择出生年月',
icon: 'none'
......@@ -232,7 +234,7 @@ const validateForm = () => {
return false;
}
if (formData.wheelchair === null || formData.wheelchair === undefined) {
if (formData.wheelchair_needed === null || formData.wheelchair_needed === undefined) {
Taro.showToast({
title: '请选择是否需要轮椅出行',
icon: 'none'
......@@ -246,7 +248,7 @@ const validateForm = () => {
/**
* @description 保存用户信息
*/
const handleSave = () => {
const handleSave = async () => {
// 验证表单
if (!validateForm()) {
return;
......@@ -257,39 +259,37 @@ const handleSave = () => {
formData.avatar_url = defaultAvatar;
}
console.log('Saving data:', formData);
Taro.showLoading({ title: '保存中...' });
// Mock save process
setTimeout(() => {
Taro.hideLoading();
// 保存数据
const { code, data } = await updateUserProfileAPI(formData);
if (code) {
Taro.showToast({
title: '保存成功',
icon: 'success',
duration: 1500,
complete: () => {
Taro.navigateBack();
},
});
}, 1000);
setTimeout(() => {
Taro.navigateBack();
}, 1500);
}
};
/**
* @description 页面加载时获取初始数据
*/
onMounted(() => {
// Mock fetching user data, in a real app, you would call an API
const mockUserData = {
avatar_url: 'https://img.yzcdn.cn/vant/cat.jpeg',
nickname: '张三',
birthday: '1990-05-15',
wheelchair: 0,
wheelchair_text: '否',
};
Object.assign(formData, mockUserData);
// Initialize pickers with current data
currentDate.value = new Date(mockUserData.birthday);
wheelchairValue.value = [mockUserData.wheelchair];
onMounted(async () => {
// 获取用户信息
const { code, data } = await getUserProfileAPI();
if (code && data?.user) {
Object.assign(formData, data.user);
// 初始化勾选数据
if (formData.birth_date) {
currentDate.value = new Date(formData.birth_date);
}
// 设置轮椅选择器的值和显示文本
if (formData.wheelchair_needed !== null && formData.wheelchair_needed !== undefined) {
wheelchairValue.value = [formData.wheelchair_needed];
formData.wheelchair_text = formData.wheelchair_needed ? '是' : '否';
}
}
});
</script>
......
......@@ -191,7 +191,7 @@ const uploadImage = (filePath) => {
let upload_data = JSON.parse(res.data);
Taro.hideLoading({
success: () => {
if (res.statusCode === 200) {
if (data.code == 0) {
screenshots.value.push({
url: upload_data.data.src,
localPath: filePath
......
......@@ -117,17 +117,15 @@
<!-- 家庭头像 -->
<view class="w-12 h-12 rounded-full bg-gray-200 flex items-center justify-center overflow-hidden">
<image
v-if="family.avatar"
:src="family.avatar"
:src="family.avatar_url || defaultAvatar"
class="w-full h-full object-cover"
/>
<view v-else class="text-gray-500 text-xs">头像</view>
</view>
<!-- 家庭信息 -->
<view class="flex-1">
<view class="font-medium text-gray-900 mb-1">{{ family.name }}</view>
<view class="text-sm text-gray-600 line-clamp-2">{{ family.description }}</view>
<view class="text-sm text-gray-600 line-clamp-2">{{ family.note }}</view>
</view>
<!-- 选中状态 -->
......@@ -168,6 +166,10 @@
import { ref, computed, nextTick, onMounted, watch } from 'vue';
import Taro from '@tarojs/taro';
import { My, Check } from '@nutui/icons-vue-taro';
// 获取接口信息
import { searchFamilyByPassphraseAPI, joinFamilyAPI } from '@/api/family';
// 默认头像
const defaultAvatar = 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'
const mottoChars = ref(['', '', '', '']);
const selectedRole = ref('');
......@@ -239,42 +241,6 @@ const familyRoles = [
{ id: 'maternal-granddaughter', label: '外孙女' }
];
// Mock家庭数据
const generateMockFamilies = () => {
return [
{
id: '1',
name: '幸福之家',
description: '我们是一个温馨和睦的大家庭,每天一起运动健身,分享生活的美好时光。',
avatar: 'https://placehold.co/100x100/e2f3ff/0369a1?text=幸福&font=roboto'
},
{
id: '2',
name: '健康家园',
description: '注重健康生活,坚持每日步行锻炼,用积分兑换健康好礼。',
avatar: 'https://placehold.co/100x100/f0f9ff/0284c7?text=健康&font=roboto'
},
{
id: '3',
name: '快乐一家人',
description: '快乐是我们家的主旋律,一起运动,一起成长,一起享受生活。',
avatar: 'https://placehold.co/100x100/fef3c7/d97706?text=快乐&font=roboto'
},
{
id: '4',
name: '幸福一家人',
description: '快乐是我们家的主旋律,一起运动,一起成长,一起享受生活。',
avatar: 'https://placehold.co/100x100/fef3c7/d97706?text=快乐&font=roboto'
},
{
id: '5',
name: '幸福一家人',
description: '快乐是我们家的主旋律,一起运动,一起成长,一起享受生活。',
avatar: 'https://placehold.co/100x100/fef3c7/d97706?text=快乐&font=roboto'
},
]
};
const isComplete = computed(() => {
return mottoChars.value.every((char) => char) && selectedRole.value;
});
......@@ -359,7 +325,7 @@ watch(searchKeyword, () => {
})
// 确认加入家庭
const confirmJoinFamily = () => {
const confirmJoinFamily = async () => {
if (!selectedFamilyId.value) {
Taro.showToast({
title: '请选择一个家庭',
......@@ -370,14 +336,25 @@ const confirmJoinFamily = () => {
const selectedFamily = mockFamilies.value.find(f => f.id === selectedFamilyId.value)
console.log('确认加入家庭:', selectedFamily)
const joinFamily = await joinFamilyAPI({
family_id: selectedFamily.id,
role: selectedRole.value
})
if (joinFamily.code) {
// 关闭弹窗
closeFamilySelector()
// 关闭弹窗
closeFamilySelector()
Taro.showToast({
title: '加入成功',
icon: 'success'
})
// 跳转到Dashboard
Taro.redirectTo({
url: '/pages/Dashboard/index'
})
setTimeout(() => {
Taro.redirectTo({
url: '/pages/Dashboard/index'
})
}, 1500)
}
};
const handleJoinFamily = async () => {
......@@ -386,32 +363,50 @@ const handleJoinFamily = async () => {
const motto = mottoChars.value.join('')
try {
// TODO: 调用API查询家庭
// const families = await api.queryFamiliesByMotto(motto)
// 模拟API响应 - 生成mock数据
const families = generateMockFamilies()
console.log('查询家庭:', { motto, role: selectedRole.value, families })
if (families.length === 0) {
Taro.showToast({
title: '未找到匹配的家庭',
icon: 'none'
})
return
}
// 调用API查询家庭
const { code, data } = await searchFamilyByPassphraseAPI({
passphrase: motto,
page: 0,
limit: 9999
})
if (families.length === 1) {
// 只有一个家庭,直接跳转
console.log('直接加入家庭:', families[0])
Taro.redirectTo({
url: '/pages/Dashboard/index'
})
} else {
// 多个家庭,显示选择弹窗
mockFamilies.value = families
showFamilySelector.value = true
let families = [];
if (code) {
families = data;
console.log('查询家庭:', { motto, role: selectedRole.value, families })
if (families.length === 0) {
Taro.showToast({
title: '未找到匹配的家庭',
icon: 'none'
})
return
}
if (families.length === 1) {
// 只有一个家庭,直接加入
const joinFamily = await joinFamilyAPI({
family_id: families[0].id,
role: selectedRole.value
})
if (joinFamily.code) {
Taro.showToast({
title: '加入成功',
icon: 'success'
})
setTimeout(() => {
Taro.redirectTo({
url: '/pages/Dashboard/index'
})
}, 1500)
}
} else {
// 多个家庭,显示选择弹窗
showFamilySelector.value = true
mockFamilies.value = families
}
}
} catch (error) {
console.error('加入家庭失败:', error)
......
<!--
* @Date: 2025-08-27 17:47:46
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-09-02 14:55:52
* @LastEditTime: 2025-09-02 20:41:29
* @FilePath: /lls_program/src/pages/Profile/index.vue
* @Description: 文件描述
-->
......@@ -14,7 +14,7 @@
<!-- User profile section -->
<view class="px-4 pt-8 pb-6 flex items-center justify-between">
<view class="flex items-center">
<image :src="defaultAvatar" alt="User avatar" class="w-16 h-16 rounded-full border-2 border-white" />
<image :src="userInfo.avatarUrl || defaultAvatar" alt="User avatar" class="w-16 h-16 rounded-full border-2 border-white" />
<view class="ml-4">
<h1 class="text-xl font-bold text-white">{{ userInfo.nickName }}</h1>
</view>
......@@ -50,6 +50,8 @@ import BottomNav from '../../components/BottomNav.vue';
import { My, Shop3, Cart, Message, Tips, Right } from '@nutui/icons-vue-taro';
// 默认头像
const defaultAvatar = 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'
// 获取接口信息
import { getUserProfileAPI } from '@/api/user'
const menuItems = shallowRef([
{
......@@ -105,11 +107,14 @@ const goToEditProfile = () => {
Taro.navigateTo({ url: '/pages/EditProfile/index' });
};
const initPageData = () => {
const initPageData = async () => {
// 获取用户信息
userInfo.value = {
nickName: '用户昵称',
avatarUrl: defaultAvatar,
const { code, data } = await getUserProfileAPI()
if (code) {
userInfo.value = {
nickName: data?.user?.nickname || '',
avatarUrl: data?.user?.avatar_url || '',
}
}
}
......
......@@ -368,7 +368,7 @@ const uploadFileToServer = (filePath) => {
success: function (res) {
try {
let upload_data = JSON.parse(res.data);
if (res.statusCode === 200 && upload_data.data) {
if (data.code == 0 && upload_data.data) {
resolve(upload_data.data.src);
} else {
reject(new Error('服务器错误'));
......
<!--
* @Date: 2025-08-27 17:43:45
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-09-02 11:52:57
* @LastEditTime: 2025-09-02 18:11:12
* @FilePath: /lls_program/src/pages/Welcome/index.vue
* @Description: 文件描述
-->
......@@ -61,7 +61,7 @@
</view>
<!-- Action Buttons -->
<view class="space-y-4 mt-auto">
<view v-if="canCreateFamily" @tap="handleNavigate('/pages/CreateFamily/index')" class="w-full py-3.5 bg-blue-500 text-white text-lg font-medium rounded-full text-center">
<view @tap="handleNavigate('/pages/CreateFamily/index')" class="w-full py-3.5 bg-blue-500 text-white text-lg font-medium rounded-full text-center">
创建家庭
</view>
<view @tap="handleNavigate('/pages/JoinFamily/index')" class="w-full py-3.5 bg-white text-gray-800 text-lg font-medium rounded-full border border-gray-300 text-center" style="margin-bottom: 1rem;">
......@@ -75,44 +75,45 @@
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import Taro from '@tarojs/taro';
import { ref } from 'vue';
import Taro, { useDidShow } from '@tarojs/taro';
import BottomNav from '../../components/BottomNav.vue'; // 假设BottomNav组件已转换
import welcomeHomeImg from '../../assets/images/welcome_home.png';
// 获取接口信息
import { getUserProfileAPI } from '@/api/user';
// TODO: 等待真实接口获取用户年龄
const userAge = ref(null);
const userInfo = ref({
age: null,
name: null,
phone: null,
});
const canCreateFamily = computed(() => userAge.value >= 60);
const userInfo = ref({});
const canCreateFamily = ref(true);
const hasProfile = ref(false);
const navigateTo = (url) => {
Taro.navigateTo({ url });
};
onMounted(() => {
// 模拟获取用户的个人信息
const userInfo = {
age: '1890-01-01',
name: '张三',
phone: '13800000000',
};
userInfo.value = userInfo;
// userInfo.age 是年月日的形式需要转成年龄
userInfo.value.age = new Date().getFullYear() - new Date(userInfo.value.age).getFullYear();
userAge.value = userInfo.value.age;
console.warn(userAge.value);
useDidShow(async () => {
// 获取用户的个人信息
const { code, data } = await getUserProfileAPI();
if (code) {
userInfo.value = data?.user?.nickname ? data.user : {
avatar_url: null,
nickname: null,
birthday: null,
wheelchair: null,
wheelchair_text: null,
phone: null,
};
// userInfo.birthday 是年月日的形式需要转成年龄
userInfo.value.birthday = new Date().getFullYear() - new Date(userInfo.value.birth_date).getFullYear();
// 检查用户是否完善了个人信息
hasProfile.value = userInfo.value.nickname && userInfo.value.birth_date && userInfo.value.wheelchair_needed !== null;
userAge.value = userInfo.value.birthday;
}
});
const handleNavigate = (url) => {
// TODO: 模拟检查个人信息是否完善
const hasProfile = true; // 假设未完善
if (!hasProfile) {
// 检查个人信息是否完善
if (!hasProfile.value) {
Taro.showModal({
title: '提示',
content: '参加活动需要完善个人信息',
......@@ -125,7 +126,17 @@ const handleNavigate = (url) => {
},
});
} else {
navigateTo(url);
// 检查用户是否年满60岁
if (canCreateFamily.value) {
navigateTo(url);
} else {
Taro.showModal({
title: '提示',
content: '您需要年满60岁才能创建家庭',
cancelText: '关闭',
confirmText: '了解',
});
}
}
};
</script>
......
<!--
* @Date: 2022-09-19 14:11:06
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-09-01 13:57:13
* @LastEditTime: 2025-09-02 19:46:17
* @FilePath: /lls_program/src/pages/auth/index.vue
* @Description: 文件描述
-->
......@@ -34,13 +34,24 @@ export default {
Taro.showLoading({
title: '授权中',
})
request.post('/srv/?a=openid', {
// 根据环境判断是否传递openid参数
const requestData = {
code: res.code
// openid: '0002'
// openid: 'o5NFZ5cFQtLRy3aVHaZMLkjHFusI'
// openid: 'o5NFZ5TpgG4FwYursGCLjcUJH2ak'
// openid: 'o5NFZ5cqroPYwawCp8FEOxewtgnw'
})
};
// 测试环境下传递openid,正式环境不传递
if (process.env.NODE_ENV === 'development') {
// 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 = 'oWbdFvkD5VtloC50wSNR9IWiU2q8';
// requestData.openid = 'oex8h5QZnZJto3ttvO6swSvylAQo';
}
request.post('/srv/?a=openid', requestData)
.then(res => {
if (res.data.code) {
var cookie = res.cookies[0];
......