feat(closed-loop): complete end-to-end closed loops across miniapp, backend, and admin
这个提交包含在:
@@ -244,7 +244,7 @@ export function formatOrderData(raw) {
|
||||
if (statusKey === '待支付' || statusKey === '待付款') statusKey = 'PENDING_PAY';
|
||||
else if (statusKey === '已到场核销' || statusKey === '履约中' || statusKey === '服务中') statusKey = 'IN_SERVICE';
|
||||
else if (statusKey === '已完成') statusKey = 'COMPLETED';
|
||||
else if (statusKey === '争议仲裁中' || statusKey === '退款中' || statusKey === '已全额退款') statusKey = 'REFUNDING';
|
||||
else if (statusKey === '争议仲裁中' || statusKey === '退款中' || statusKey === '已退款' || statusKey === '已全额退款') statusKey = 'REFUNDING';
|
||||
else if (statusKey === '已取消') statusKey = 'CANCELLED';
|
||||
|
||||
const companionAvatar = raw.expertAvatar || raw.expert_avatar || raw.avatar ||
|
||||
@@ -266,6 +266,7 @@ export function formatOrderData(raw) {
|
||||
quantity: Number(raw.durationHours || raw.duration_hours || raw.quantity || 2),
|
||||
totalAmount: Number(raw.amount || raw.totalAmount || 0),
|
||||
status: statusKey,
|
||||
rawStatus: raw.status || '履约中',
|
||||
meetVenue: raw.location || raw.meetVenue || '北京市公开商圈',
|
||||
expenseBurden: raw.expenseBurden || raw.expense_burden || 'AA制各自付',
|
||||
payType: raw.payType || raw.pay_type || 'WECHAT',
|
||||
@@ -418,6 +419,7 @@ export async function getPosts(category, userId) {
|
||||
|
||||
return {
|
||||
id: p.id,
|
||||
userId: p.userId || p.user_id || 0,
|
||||
author: p.author || '神秘同好',
|
||||
avatar: p.avatar || 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=150',
|
||||
tag: p.tag || '同城认证',
|
||||
@@ -433,7 +435,10 @@ export async function getPosts(category, userId) {
|
||||
liked: finalLiked || isLocallyLiked,
|
||||
comments: Number(p.comments) || 0,
|
||||
views: Number(p.views) || 1,
|
||||
category: p.category || 'partner'
|
||||
category: p.category || 'partner',
|
||||
applyCount: Number(p.applyCount) || 0,
|
||||
pendingApplyCount: Number(p.pendingApplyCount) || 0,
|
||||
myApplyStatus: p.myApplyStatus || null
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -1039,3 +1044,48 @@ export async function getDistributionRecords(userId) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定上级推荐人邀请码 (如 PW1001)
|
||||
*/
|
||||
export async function bindInviteCode(userId, inviteCode) {
|
||||
const uid = normalizeUserId(userId);
|
||||
const res = await http.post('/distribution/bind', { userId: uid, inviteCode });
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定友圈动态的搭子上车申请列表 (供动态房主查看与审批)
|
||||
*/
|
||||
export async function getPostApplies(postId) {
|
||||
try {
|
||||
const res = await http.get(`/posts/${postId}/applies`);
|
||||
return res || [];
|
||||
} catch (err) {
|
||||
console.warn('[API] 获取搭子上车申请列表异常:', err.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 房主或管理员处置搭子上车申请 (ACCEPTED | REJECTED)
|
||||
*/
|
||||
export async function handlePostApply(applyId, status) {
|
||||
const res = await http.post(`/post-applies/${applyId}/handle`, { status });
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户提交的违规举报工单与处理结果反馈
|
||||
*/
|
||||
export async function getMyReports(userId) {
|
||||
const uid = normalizeUserId(userId);
|
||||
try {
|
||||
const res = await http.get(`/reports?reporterId=${uid}`);
|
||||
return res || [];
|
||||
} catch (err) {
|
||||
console.warn('[API] 获取我的举报记录异常:', err.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -82,21 +82,29 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="author-actions">
|
||||
<button
|
||||
v-if="isPostOwner(post)"
|
||||
class="btn-delete-post btn-active"
|
||||
@click.stop="handleDeletePost(post)"
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
<template v-if="isPostOwner(post)">
|
||||
<button
|
||||
class="join-btn btn-active"
|
||||
style="margin-right: 12rpx;"
|
||||
@click.stop="handleManageApplies(post)"
|
||||
>
|
||||
申请({{ post.applyCount || 0 }})
|
||||
</button>
|
||||
<button
|
||||
class="btn-delete-post btn-active"
|
||||
@click.stop="handleDeletePost(post)"
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
</template>
|
||||
<button
|
||||
v-else
|
||||
class="join-btn btn-active"
|
||||
:class="{ 'btn-applied': post.hasApplied }"
|
||||
:disabled="post.hasApplied"
|
||||
:class="{ 'btn-applied': post.hasApplied && post.myApplyStatus !== 'REJECTED' }"
|
||||
:disabled="post.hasApplied && post.myApplyStatus !== 'REJECTED'"
|
||||
@click="handleJoinParty(post)"
|
||||
>
|
||||
{{ post.hasApplied ? '已申请' : (post.actionText || '聊一聊') }}
|
||||
{{ post.myApplyStatus === 'ACCEPTED' ? '已同意上车' : (post.myApplyStatus === 'REJECTED' ? '重新申请' : (post.hasApplied ? '待房主确认' : (post.actionText || '聊一聊'))) }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
@@ -389,7 +397,7 @@ import SkeletonScreen from '@/components/SkeletonScreen.vue';
|
||||
import { ICONS } from '@/common/icons.js';
|
||||
import { getCurrentThemeKey, applyThemeToNativeUI } from '@/common/theme.js';
|
||||
import { getNavBarInfo } from '@/common/nav.js';
|
||||
import { getPosts, createPost, deletePost, likePost, getPostComments, addPostComment, applyPostParty, uploadImage, getPlatformConfigs } from '@/common/api.js';
|
||||
import { getPosts, createPost, deletePost, likePost, getPostComments, addPostComment, applyPostParty, getPostApplies, handlePostApply, uploadImage, getPlatformConfigs } from '@/common/api.js';
|
||||
|
||||
const isLoading = ref(true);
|
||||
const currentThemeKey = ref(getCurrentThemeKey());
|
||||
@@ -473,12 +481,14 @@ const loadRemotePosts = async () => {
|
||||
if (!Array.isArray(localAppliedIds)) localAppliedIds = [];
|
||||
|
||||
posts.value = list.map(item => {
|
||||
const isApplied = localAppliedIds.includes(String(item.id));
|
||||
const serverStatus = item.myApplyStatus || null;
|
||||
const isApplied = serverStatus ? (serverStatus !== 'REJECTED') : localAppliedIds.includes(String(item.id));
|
||||
return {
|
||||
...item,
|
||||
myApplyStatus: serverStatus || (isApplied ? 'PENDING' : null),
|
||||
liked: item.liked !== undefined ? item.liked : localLikedIds.includes(String(item.id)),
|
||||
hasApplied: isApplied,
|
||||
actionText: isApplied ? '已申请' : (item.actionText || '加入车队')
|
||||
actionText: serverStatus === 'ACCEPTED' ? '已同意上车' : (isApplied ? '待房主确认' : (item.actionText || '加入车队'))
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -763,10 +773,59 @@ const handleDeletePost = (post) => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleManageApplies = async (post) => {
|
||||
uni.showLoading({ title: '加载申请列表...' });
|
||||
try {
|
||||
const list = await getPostApplies(post.id);
|
||||
uni.hideLoading();
|
||||
if (!list || list.length === 0) {
|
||||
uni.showToast({ title: '暂无搭子上车申请', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
const itemList = list.slice(0, 6).map(a => {
|
||||
const name = a.applicant_name || a.applicantName || '同城搭子';
|
||||
const st = a.status === 'ACCEPTED' ? '已同意' : (a.status === 'REJECTED' ? '已婉拒' : '待确认');
|
||||
return `${name} [${st}] - 点击审批`;
|
||||
});
|
||||
uni.showActionSheet({
|
||||
itemList,
|
||||
success: (res) => {
|
||||
const target = list[res.tapIndex];
|
||||
if (!target) return;
|
||||
const name = target.applicant_name || target.applicantName || '同城搭子';
|
||||
uni.showModal({
|
||||
title: `审批搭子申请`,
|
||||
content: `申请人:${name}\n请选择是否同意该搭子加入车队/同城局:`,
|
||||
confirmText: '同意上车',
|
||||
cancelText: '婉拒申请',
|
||||
confirmColor: '#10B981',
|
||||
success: async (mRes) => {
|
||||
const newStatus = mRes.confirm ? 'ACCEPTED' : (mRes.cancel ? 'REJECTED' : null);
|
||||
if (!newStatus) return;
|
||||
try {
|
||||
await handlePostApply(target.id, newStatus);
|
||||
uni.showToast({
|
||||
title: newStatus === 'ACCEPTED' ? '已同意搭子上车' : '已婉拒该申请',
|
||||
icon: 'success'
|
||||
});
|
||||
loadRemotePosts();
|
||||
} catch (err) {
|
||||
uni.showToast({ title: err.message || '操作失败', icon: 'none' });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: '加载申请列表失败', icon: 'none' });
|
||||
}
|
||||
};
|
||||
|
||||
const handleJoinParty = (post) => {
|
||||
if (post.hasApplied) {
|
||||
if (post.hasApplied && post.myApplyStatus !== 'REJECTED') {
|
||||
uni.showToast({
|
||||
title: '已提交过约伴申请',
|
||||
title: post.myApplyStatus === 'ACCEPTED' ? '房主已同意您的上车申请' : '已提交过申请,请等待房主确认',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
@@ -792,7 +851,8 @@ const handleJoinParty = (post) => {
|
||||
} catch (_) {}
|
||||
|
||||
post.hasApplied = true;
|
||||
post.actionText = '已申请';
|
||||
post.myApplyStatus = 'PENDING';
|
||||
post.actionText = '待房主确认';
|
||||
let localAppliedIds = uni.getStorageSync('COMMUNITY_APPLIED_POST_IDS') || [];
|
||||
if (!Array.isArray(localAppliedIds)) localAppliedIds = [];
|
||||
const strId = String(post.id);
|
||||
|
||||
@@ -73,6 +73,15 @@
|
||||
|
||||
<!-- 3. 状态三:未认证 / 重新申请 (NOT_APPLIED / REJECTED) -->
|
||||
<view class="form-container" v-else>
|
||||
<!-- 审核驳回反馈横幅 (当后台驳回时展示具体原因并引导修改重提) -->
|
||||
<view class="notice-banner reject-banner" v-if="certStatus === CERT_STATUS.REJECTED">
|
||||
<image class="banner-icon-svg" :src="ICONS.alert" mode="aspectFit" />
|
||||
<view class="banner-content">
|
||||
<text class="banner-title">上次入驻审核未通过,请修改后重新提交</text>
|
||||
<text class="banner-desc">驳回原因:{{ rejectReason || '提交的资质或语音未通过人工核验,请核对后重新提交' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 平台合规与安全保障提示横幅 -->
|
||||
<view class="notice-banner">
|
||||
<image class="banner-icon-svg" :src="ICONS.shield" mode="aspectFit" />
|
||||
@@ -410,17 +419,41 @@ const contactService = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const rejectReason = ref('');
|
||||
|
||||
const syncRemoteCertStatus = async () => {
|
||||
try {
|
||||
const cachedUser = uni.getStorageSync('PW_USER_INFO') || {};
|
||||
const uid = Number(cachedUser.id) || 1001;
|
||||
const profile = await getUserProfile(uid);
|
||||
if (profile && profile.certStatus !== undefined) {
|
||||
const remoteCert = Number(profile.certStatus);
|
||||
setExpertCertStatus(remoteCert);
|
||||
certStatus.value = remoteCert;
|
||||
rejectReason.value = profile.rejectReason || '';
|
||||
uni.setStorageSync('PW_USER_INFO', {
|
||||
...cachedUser,
|
||||
...profile
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// 保持本地缓存兜底
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
currentThemeKey.value = getCurrentThemeKey();
|
||||
applyThemeToNativeUI(currentThemeKey.value);
|
||||
certStatus.value = getExpertCertStatus();
|
||||
loadInitialConfigs();
|
||||
syncRemoteCertStatus();
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
currentThemeKey.value = getCurrentThemeKey();
|
||||
applyThemeToNativeUI(currentThemeKey.value);
|
||||
certStatus.value = getExpertCertStatus();
|
||||
syncRemoteCertStatus();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -443,6 +476,19 @@ onShow(() => {
|
||||
padding: 24rpx;
|
||||
margin-bottom: 28rpx;
|
||||
|
||||
&.reject-banner {
|
||||
background: rgba(244, 63, 94, 0.12);
|
||||
border-color: rgba(244, 63, 94, 0.35);
|
||||
|
||||
.banner-title {
|
||||
color: #FDA4AF;
|
||||
}
|
||||
|
||||
.banner-desc {
|
||||
color: #FECDD3;
|
||||
}
|
||||
}
|
||||
|
||||
.banner-icon-svg {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
|
||||
@@ -115,7 +115,7 @@ import AppTabBar from '@/components/CustomTabBar.vue';
|
||||
import { ICONS } from '@/common/icons.js';
|
||||
import { getCurrentThemeKey, applyThemeToNativeUI } from '@/common/theme.js';
|
||||
import { isExpertCertified } from '@/common/role.js';
|
||||
import { createOrder, getOrdersPool, getExpertStats, claimOrder } from '@/common/api.js';
|
||||
import { createOrder, getOrdersPool, getExpertStats, claimOrder, updateCompanionProfile } from '@/common/api.js';
|
||||
import { getNavBarInfo } from '@/common/nav.js';
|
||||
|
||||
const currentThemeKey = ref(getCurrentThemeKey());
|
||||
@@ -124,7 +124,7 @@ const expert = ref({
|
||||
nickname: '接单大神',
|
||||
avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=300'
|
||||
});
|
||||
const isOnline = ref(true);
|
||||
const isOnline = ref(uni.getStorageSync('EXPERT_RECEIVING_STATUS') !== '0');
|
||||
const activeCategory = ref('all');
|
||||
const navInfo = ref(getNavBarInfo());
|
||||
|
||||
@@ -282,8 +282,14 @@ const filteredOrders = computed(() => {
|
||||
return orders.value;
|
||||
});
|
||||
|
||||
const toggleOnlineStatus = () => {
|
||||
const toggleOnlineStatus = async () => {
|
||||
isOnline.value = !isOnline.value;
|
||||
uni.setStorageSync('EXPERT_RECEIVING_STATUS', isOnline.value ? '1' : '0');
|
||||
try {
|
||||
await updateCompanionProfile(expert.value.id || 101, {
|
||||
status: isOnline.value ? '接单中' : '休息中'
|
||||
});
|
||||
} catch (_) {}
|
||||
uni.showToast({
|
||||
title: isOnline.value ? '已开启在线接单状态' : '已设置为打烊休息',
|
||||
icon: isOnline.value ? 'success' : 'none'
|
||||
|
||||
@@ -404,6 +404,8 @@ import {
|
||||
getOrdersPool,
|
||||
getExpertStats,
|
||||
claimOrder,
|
||||
completeOrder as completeOrderApi,
|
||||
updateCompanionProfile,
|
||||
getExpertRevenueTrends,
|
||||
getExpertServices,
|
||||
updateExpertServiceStatus,
|
||||
@@ -583,9 +585,13 @@ const activeOrders = ref([
|
||||
}
|
||||
]);
|
||||
|
||||
const toggleReceivingStatus = () => {
|
||||
const toggleReceivingStatus = async () => {
|
||||
isReceivingOrders.value = !isReceivingOrders.value;
|
||||
const newStatusStr = isReceivingOrders.value ? '接单中' : '休息中';
|
||||
uni.setStorageSync('EXPERT_RECEIVING_STATUS', isReceivingOrders.value ? '1' : '0');
|
||||
try {
|
||||
await updateCompanionProfile(expert.value.id || 101, { status: newStatusStr });
|
||||
} catch (_) {}
|
||||
uni.showToast({
|
||||
title: isReceivingOrders.value ? '已开启在线接单通道!' : '已打烊停止接单',
|
||||
icon: 'none'
|
||||
@@ -792,12 +798,16 @@ const chatWithBuyer = () => {
|
||||
const completeOrder = (id) => {
|
||||
uni.showModal({
|
||||
title: '确认提交服务验收',
|
||||
content: '服务已完成?提交后买家确认无误或 24 小时内未异议将自动解冻订单资金打入钱包。',
|
||||
content: '服务已完成?提交后将完成服务结算并自动解冻订单资金打入您的佣金钱包。',
|
||||
confirmColor: '#10B981',
|
||||
success: (res) => {
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
await completeOrderApi(id);
|
||||
} catch (_) {}
|
||||
activeOrders.value = activeOrders.value.filter(o => o.id !== id);
|
||||
uni.showToast({ title: '已申请验收结算!', icon: 'success' });
|
||||
uni.showToast({ title: '已验收结算并入账!', icon: 'success' });
|
||||
loadWorkspaceOrders();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<text class="countdown-txt">{{ getOrderCountdown(order) }}</text>
|
||||
</view>
|
||||
<view class="status-pill" :class="order.status">
|
||||
{{ getStatusText(order.status) }}
|
||||
{{ getStatusText(order.status, order) }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -465,12 +465,14 @@ const goToExplore = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const getStatusText = (status) => {
|
||||
const getStatusText = (status, order) => {
|
||||
if (order && order.rawStatus === '已退款') return '已全额退款';
|
||||
if (order && order.rawStatus === '争议仲裁中') return '争议仲裁中';
|
||||
const map = {
|
||||
PENDING_PAY: '待付款',
|
||||
IN_SERVICE: '服务中',
|
||||
COMPLETED: '已完成',
|
||||
REFUNDING: '退款中',
|
||||
REFUNDING: '售后处理中',
|
||||
CANCELLED: '已取消',
|
||||
};
|
||||
return map[status] || status;
|
||||
|
||||
@@ -154,6 +154,12 @@
|
||||
<text class="row-label">平台消息与通知</text>
|
||||
<image class="row-arrow-svg" :src="ICONS.chevronRight" mode="aspectFit" />
|
||||
</view>
|
||||
<view class="list-row btn-active" @click="openMyReportsModal">
|
||||
<image class="row-svg" :src="ICONS.alert" mode="aspectFit" />
|
||||
<text class="row-label">我的举报与维权反馈</text>
|
||||
<text class="row-sub-tag" v-if="myReportsList.length > 0">{{ myReportsList.length }}条记录</text>
|
||||
<image class="row-arrow-svg" :src="ICONS.chevronRight" mode="aspectFit" />
|
||||
</view>
|
||||
<view class="list-row btn-active" @click="openFaq">
|
||||
<image class="row-svg" :src="ICONS.messageSquare" mode="aspectFit" />
|
||||
<text class="row-label">帮助中心与常见问题</text>
|
||||
@@ -321,6 +327,50 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 我的举报与维权进度反馈弹窗 -->
|
||||
<view class="profile-edit-modal-mask" v-if="showReportsModal" @click="showReportsModal = false">
|
||||
<view class="profile-edit-modal withdraw-records-modal-box" @click.stop>
|
||||
<view class="modal-header">
|
||||
<text class="modal-title">我的举报与维权进度</text>
|
||||
<view class="btn-close btn-active" @click="showReportsModal = false">
|
||||
<image class="close-svg" :src="ICONS.close" mode="aspectFit" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view scroll-y class="records-modal-scroll" v-if="myReportsList.length > 0">
|
||||
<view v-for="rpt in myReportsList" :key="rpt.id" class="wd-card-row">
|
||||
<view class="wd-row-top">
|
||||
<text class="wd-no">对象:{{ rpt.targetName || rpt.targetId }} · {{ rpt.reason }}</text>
|
||||
<text
|
||||
class="wd-badge"
|
||||
:class="{
|
||||
pending: rpt.status === '待处理',
|
||||
success: rpt.status === '已核实处罚',
|
||||
rejected: rpt.status === '已驳回'
|
||||
}"
|
||||
>
|
||||
{{ rpt.status || '待处理' }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="wd-row-center" v-if="rpt.description">
|
||||
<text class="wd-ch">举报说明:{{ rpt.description }}</text>
|
||||
</view>
|
||||
<view class="wd-row-bot">
|
||||
<text class="wd-time">{{ rpt.createdAt }}</text>
|
||||
<text class="wd-reason" v-if="rpt.handleNote || rpt.handleResult">
|
||||
平台反馈:{{ rpt.handleNote || rpt.handleResult }}
|
||||
</text>
|
||||
<text class="wd-time" v-else>平台安全专员正在核实处理中</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="empty-records-box" v-else>
|
||||
<image class="empty-icon-svg" :src="ICONS.shield" mode="aspectFit" />
|
||||
<text class="empty-txt">暂无举报与维权记录</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
<!-- 底部自定义导航栏 (动态自适应发单人4项 / 接单人3项) -->
|
||||
@@ -340,15 +390,33 @@ import { ICONS } from '@/common/icons.js';
|
||||
import { getCurrentThemeKey, setTheme, THEME_KEYS, applyThemeToNativeUI } from '@/common/theme.js';
|
||||
import { getCurrentRole, setCurrentRole, ROLE_TYPES, toggleRole as toggleUserRole, isExpertCertified, getExpertCertStatus, setExpertCertStatus, CERT_STATUS } from '@/common/role.js';
|
||||
import { getNavBarInfo } from '@/common/nav.js';
|
||||
import { getUserProfile, rechargeBalance, withdrawCommission, getUserFinanceRecords, updateUserProfile, uploadImage, getRechargeTiers, getPlatformConfigs, applyExpertCert, getUserWithdrawals, getFavorites, removeFavorite, getDistributionTeam, getDistributionRecords } from '@/common/api.js';
|
||||
import { getUserProfile, rechargeBalance, withdrawCommission, getUserFinanceRecords, updateUserProfile, uploadImage, getRechargeTiers, getPlatformConfigs, applyExpertCert, getUserWithdrawals, getFavorites, removeFavorite, getDistributionTeam, getDistributionRecords, getMyReports } from '@/common/api.js';
|
||||
|
||||
const DEFAULT_GUEST_AVATAR = 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=200';
|
||||
const isLoggedIn = ref(true);
|
||||
const showLoginModal = ref(false);
|
||||
|
||||
// 我的收藏
|
||||
// 我的收藏与我的举报反馈
|
||||
const favoriteList = ref([]);
|
||||
const loadingFavorites = ref(false);
|
||||
const showReportsModal = ref(false);
|
||||
const myReportsList = ref([]);
|
||||
|
||||
const loadMyReports = async () => {
|
||||
const uid = user.value.id || uni.getStorageSync('userId') || 1001;
|
||||
try {
|
||||
const list = await getMyReports(uid);
|
||||
if (Array.isArray(list)) {
|
||||
myReportsList.value = list;
|
||||
}
|
||||
} catch (_) {}
|
||||
};
|
||||
|
||||
const openMyReportsModal = async () => {
|
||||
if (!ensureLoggedIn()) return;
|
||||
await loadMyReports();
|
||||
showReportsModal.value = true;
|
||||
};
|
||||
|
||||
// 系统与隐私设置
|
||||
const privacyHidePhone = ref(uni.getStorageSync('privacy_hide_phone') === '1');
|
||||
@@ -546,6 +614,16 @@ const loadUserProfile = async (targetId) => {
|
||||
...profile,
|
||||
avatar: safeAvatar
|
||||
};
|
||||
// 实时同步服务端大神认证审核状态至本地 Storage,确保后台审核通过/驳回立即生效
|
||||
if (profile.certStatus !== undefined) {
|
||||
setExpertCertStatus(Number(profile.certStatus));
|
||||
} else if (profile.isExpert || (profile.role && profile.role.includes('大神'))) {
|
||||
setExpertCertStatus(CERT_STATUS.APPROVED);
|
||||
}
|
||||
uni.setStorageSync('PW_USER_INFO', {
|
||||
...(uni.getStorageSync('PW_USER_INFO') || {}),
|
||||
...user.value
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('[User] 加载用户信息异常:', err.message);
|
||||
@@ -566,6 +644,7 @@ onMounted(() => {
|
||||
uni.$on('login-success', onLoginSuccess);
|
||||
loadUserProfile();
|
||||
loadPlatformConfigs();
|
||||
loadMyReports();
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
@@ -579,6 +658,7 @@ onShow(() => {
|
||||
privacyHidePhone.value = uni.getStorageSync('privacy_hide_phone') === '1';
|
||||
if (isLoggedIn.value) {
|
||||
loadFavorites();
|
||||
loadMyReports();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -611,10 +691,17 @@ const handleSwitchTheme = () => {
|
||||
const toggleRole = () => {
|
||||
if (!ensureLoggedIn()) return;
|
||||
if (!isExpertCertified()) {
|
||||
const curCert = getExpertCertStatus();
|
||||
const isPending = curCert === CERT_STATUS.PENDING;
|
||||
const isRejected = curCert === CERT_STATUS.REJECTED;
|
||||
uni.showModal({
|
||||
title: '大神实名认证提醒',
|
||||
content: '当前账号尚未完成大神实名入驻认证,无法切换至接单模式。是否立即申请认证?',
|
||||
confirmText: '立即认证',
|
||||
title: isPending ? '入驻资质审核中' : (isRejected ? '入驻审核未通过' : '大神实名认证提醒'),
|
||||
content: isPending
|
||||
? '您的大神实名入驻申请正在由平台运营人工核验中,审核通过后即可切换至接单模式。是否查看审核进度?'
|
||||
: isRejected
|
||||
? `上次申请未通过:${user.value.rejectReason || '请核对身份与资质信息'}。是否立即修改并重新提交?`
|
||||
: '当前账号尚未完成大神实名入驻认证,无法切换至接单模式。是否立即申请认证?',
|
||||
confirmText: isPending ? '查看进度' : (isRejected ? '重新提交' : '立即认证'),
|
||||
cancelText: '稍后再说',
|
||||
confirmColor: '#10B981',
|
||||
success: (res) => {
|
||||
|
||||
@@ -85,6 +85,14 @@
|
||||
<button class="btn-poster-action secondary btn-active" @click="copyInviteLink">复制邀请链接</button>
|
||||
<button class="btn-poster-action primary btn-active" @click="savePosterImage">保存海报至相册</button>
|
||||
</view>
|
||||
|
||||
<view class="bind-inviter-card" style="margin-top: 24rpx; background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.08); border-radius: 20rpx; padding: 20rpx 24rpx; display: flex; align-items: center; justify-content: space-between;">
|
||||
<text style="font-size: 24rpx; color: #94A3B8;">{{ user.inviterId ? ('已绑定上级邀请人:PW' + user.inviterId) : '填写好友邀请码绑定上下级关系' }}</text>
|
||||
<view v-if="!user.inviterId" style="display: flex; align-items: center; gap: 12rpx;">
|
||||
<input v-model="bindCodeInput" placeholder="如 PW2001" placeholder-class="field-placeholder" style="width: 180rpx; height: 56rpx; background: rgba(0,0,0,0.25); border-radius: 12rpx; padding: 0 16rpx; font-size: 24rpx; color: #F8FAFC;" />
|
||||
<button class="btn-active" style="margin: 0; height: 56rpx; line-height: 56rpx; padding: 0 24rpx; font-size: 24rpx; border-radius: 12rpx; background: #6366F1; color: #FFFFFF;" @click="handleBindInviteCode">绑定</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Tab 2: 下级团队列表 -->
|
||||
@@ -209,7 +217,8 @@ import {
|
||||
getUserProfile,
|
||||
getDistributionTeam,
|
||||
getDistributionRecords,
|
||||
withdrawCommission
|
||||
withdrawCommission,
|
||||
bindInviteCode
|
||||
} from '@/common/api.js';
|
||||
import { ICONS } from '@/common/icons.js';
|
||||
import { getCurrentThemeKey, applyThemeToNativeUI } from '@/common/theme.js';
|
||||
@@ -218,6 +227,7 @@ import { getNavBarInfo } from '@/common/nav.js';
|
||||
const currentThemeKey = ref(getCurrentThemeKey());
|
||||
const navInfo = ref(getNavBarInfo());
|
||||
const isRefreshing = ref(false);
|
||||
const bindCodeInput = ref('');
|
||||
|
||||
const defaultAvatar = 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=200';
|
||||
|
||||
@@ -324,6 +334,24 @@ const savePosterImage = () => {
|
||||
uni.showToast({ title: '海报已保存至相册', icon: 'success' });
|
||||
};
|
||||
|
||||
const handleBindInviteCode = async () => {
|
||||
const code = bindCodeInput.value.trim();
|
||||
if (!code) {
|
||||
uni.showToast({ title: '请输入好友邀请码', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const uid = user.value.id || 2006;
|
||||
const res = await bindInviteCode(uid, code);
|
||||
user.value.inviterId = res.inviterId;
|
||||
bindCodeInput.value = '';
|
||||
uni.showToast({ title: `成功绑定上级:${res.inviterName || code}`, icon: 'success' });
|
||||
loadData();
|
||||
} catch (err) {
|
||||
uni.showToast({ title: err.message || '绑定失败', icon: 'none' });
|
||||
}
|
||||
};
|
||||
|
||||
const handleFillAllCommission = () => {
|
||||
const comm = Number(user.value.commission || 0);
|
||||
if (comm <= 0) {
|
||||
|
||||
在新工单中引用
屏蔽一个用户