这个两个ApproverSettingMain<template> <view :class="[...
Creato il: 10 gennaio 2026
Risposto usando GPT-5.2 Thinking da Chat01
Creato il: 10 gennaio 2026
Risposto usando GPT-5.2 Thinking da Chat01
这个两个ApproverSettingMain<template>
<view :class="['app-container', theme]">
<view class="content">
<view class="overlay">
<!-- Header -->
<view class="header" :style="{ paddingTop: statusBarHeight + 'px', height: headerBarHeight + 'px' }">
<view class="header-inner">
<view class="left-box" @click="navigateBack">
<uni-icons type="left" color="#000000" size="26" />
</view>
</template> <script setup> import { ref, inject } from "vue"; import { onLoad, onShow } from "@dcloudio/uni-app"; import { getStatusBarHeight, getTitleBarHeight } from "@/utils/system.js"; import ajax from "@/api/ajax.js"; import { storeToRefs } from "pinia"; import { useMainStore } from "@/store/index.js"; const store = useMainStore(); const { theme } = storeToRefs(store); const statusBarHeight = ref(getStatusBarHeight()); const headerBarHeight = ref(getTitleBarHeight()); const castingTeamId = ref(""); const members = ref([]); const approvers = ref([]); let webProjectUrl = inject("webProjectUrl"); function formatAvatar(url) { if (!url) return "/static/images/default-avatar.png"; return webProjectUrl ? webProjectUrl + url : url; } function navigateBack() { uni.navigateBack(); } function getConsumerId() { const s = store || {}; const fromStore = s?.userInfo?.consumerId || s?.consumerId; if (fromStore) return String(fromStore); const id1 = uni.getStorageSync("consumerId"); if (id1) return String(id1); const u = uni.getStorageSync("userInfo"); if (u && typeof u === "object" && u.consumerId) return String(u.consumerId); if (u && typeof u === "string") { try { const obj = JSON.parse(u); if (obj?.consumerId) return String(obj.consumerId); } catch (e) {} } return ""; } function canRemove(m) { if (m.owner) return false; return true; } async function fetchMembers() { if (!castingTeamId.value) return; const res = await ajax .post({ url: "castingTeamConsumerList", data: { castingTeamId: castingTeamId.value, rowStartIdx: 0, rowCount: 200, }, method: "post", }) .catch(() => ({})); console.log(res,'里面的审批人列表') consumerArray const arr = res?.data?.consumerArray || res?.data?.castingTeamConsumerArray || res?.data?.castingTeamConsumerObject?.consumerArray || res?.data || []; const list = Array.isArray(arr) ? arr : []; members.value = list.map((x) => ({ consumerId: x.consumerId ?? x.id ?? "", nickname: x.nickname ?? "", avatarUrl: x.avatarUrl ?? "", owner: !!x.owner, manager: !!x.manager, leaveApprover: !!x.leaveApprover, })); approvers.value = members.value.filter((x) => x.leaveApprover); } async function updateApprover({ addIds = [], removeIds = [] }) { if (!castingTeamId.value) return false; // castingTeam:修改用 id(你后端文档里写的) const data = { id: castingTeamId.value }; if (addIds.length) data.leaveApproverId = addIds; if (removeIds.length) data.cancelLeaveApproverId = removeIds; const res = await ajax .post({ url: "castingTeam", data, method: "post", }) .catch(() => ({})); return !!(res?.data?.id || res?.data || res?.id); } async function removeApprover(m) { uni.showModal({ title: "提示", content: "确定移除该审批人?", success: async (r) => { if (!r.confirm) return; const ok = await updateApprover({ removeIds: [m.consumerId] }); if (ok) { uni.showToast({ title: "已移除", icon: "none" }); await fetchMembers(); } else { uni.showToast({ title: "移除失败", icon: "none" }); } }, }); } function goAdd() { uni.navigateTo({ url: "/pages/sociAlize/Coffeegroup/application/CheckinSettings/Settings/ApproverAddMember?castingTeamId=" + encodeURIComponent(castingTeamId.value), }); } onLoad(async (option) => { castingTeamId.value = option?.castingTeamId ? decodeURIComponent(option.castingTeamId) : ""; await fetchMembers(); }); onShow(async () => { // 从添加页回来刷新 if (castingTeamId.value) await fetchMembers(); }); </script> <style lang="scss" scoped> @import "@/assets/styles/global.scss"; .content { min-height: 100vh; position: relative; background: #f7f7f7; } .overlay { position: absolute; inset: 0; background: #f7f7f7; } .header { width: 94%; margin: 0 auto; } .header-inner { position: relative; display: flex; align-items: center; justify-content: center; height: 100%; } .left-box, .right-box { position: absolute; top: 0; bottom: 0; width: 120rpx; display: flex; align-items: center; } .left-box { left: 0; justify-content: flex-start; } .right-box { right: 0; justify-content: flex-end; } .title-text { font-size: 32rpx; font-weight: 600; color: #111; } .done-text { font-size: 28rpx; color: #d5962a; font-weight: 600; } .wrap { width: 94%; margin: 18rpx auto 0; } .list-card { background: #fff; border-radius: 12rpx; overflow: hidden; } .item { height: 110rpx; padding: 0 18rpx; display: flex; align-items: center; border-bottom: 1px solid #f0f0f0; box-sizing: border-box; } .item:last-child { border-bottom: none; } .avatar { width: 72rpx; height: 72rpx; border-radius: 50%; background: #eee; margin-right: 16rpx; } .name { flex: 1; font-size: 28rpx; color: #111; } .action { font-size: 28rpx; font-weight: 600; color: #d5962a; } .add-row { height: 90rpx; display: flex; align-items: center; justify-content: flex-end; padding: 0 18rpx; border-top: 1px solid #f0f0f0; } .note { margin-top: 18rpx; } .note-text { font-size: 24rpx; color: #999; line-height: 36rpx; } </style>和ApproverAddMember页面<template>text<view class="center-box"> <text class="title-text">审批人添加</text> </view> <!-- UI 图右上角是“完成”,这里就是返回 --> <view class="right-box" @click="navigateBack"> <text class="done-text">完成</text> </view> </view> </view> <view class="wrap"> <view class="list-card"> <view v-for="m in approvers" :key="m.consumerId" class="item"> <image class="avatar" :src="formatAvatar(m.avatarUrl)" mode="aspectFill" /> <text class="name">{{ m.nickname || "用户名称" }}</text> <text v-if="canRemove(m)" class="action remove" @click="removeApprover(m)">移除</text> </view> <view class="add-row" @click="goAdd"> <text class="action add">添加</text> </view> </view> <view class="note"> <text class="note-text">注释:添加了审批人后该审批人可以直接同意组员的请假、补卡申请</text> </view> </view> </view> </view> </view>
</template> <script setup> import { ref, computed, inject } from "vue"; import { onLoad } from "@dcloudio/uni-app"; import { getStatusBarHeight, getTitleBarHeight } from "@/utils/system.js"; import ajax from "@/api/ajax.js"; import { storeToRefs } from "pinia"; import { useMainStore } from "@/store/index.js"; const store = useMainStore(); const { theme } = storeToRefs(store); const statusBarHeight = ref(getStatusBarHeight()); const headerBarHeight = ref(getTitleBarHeight()); const castingTeamId = ref(""); let webProjectUrl = inject("webProjectUrl"); function formatAvatar(url) { if (!url) return "/static/images/default-avatar.png"; return webProjectUrl ? webProjectUrl + url : url; } const keyword = ref(""); const members = ref([]); const loading = ref(false); const rowStartIdx = ref(0); const rowCount = ref(30); const hasMore = ref(true); const loadMoreStatus = computed(() => { if (loading.value && members.value.length > 0) return "loading"; if (!hasMore.value) return "noMore"; return "more"; }); const filteredMembers = computed(() => { const k = (keyword.value || "").trim(); if (!k) return members.value; return members.value.filter((m) => (m.nickname || "").includes(k)); }); function navigateBack() { uni.navigateBack(); } async function fetchMembers(reset = false) { if (!castingTeamId.value) return; if (loading.value) return; if (reset) { rowStartIdx.value = 0; hasMore.value = true; members.value = []; } if (!hasMore.value) return; loading.value = true; const res = await ajax .post({ url: "castingTeamConsumerList", data: { castingTeamId: castingTeamId.value, rowStartIdx: rowStartIdx.value, rowCount: rowCount.value, }, method: "post", }) .catch(() => ({})); const arr = res?.data?.consumerArray || res?.data?.castingTeamConsumerArray || res?.data?.castingTeamConsumerObject?.consumerArray || res?.data || []; const list = Array.isArray(arr) ? arr : []; const normalized = list.map((x) => ({ consumerId: x.consumerId ?? x.id ?? "", nickname: x.nickname ?? "", avatarUrl: x.avatarUrl ?? "", owner: !!x.owner, manager: !!x.manager, leaveApprover: !!x.leaveApprover, })); members.value = members.value.concat(normalized); if (normalized.length < rowCount.value) { hasMore.value = false; } else { rowStartIdx.value += rowCount.value; } loading.value = false; } async function loadMore() { if (loading.value || !hasMore.value) return; await fetchMembers(false); } async function addApprover(m) { if (m.leaveApprover) return; let option = { id: castingTeamId.value, leaveApproverId: [m.consumerId], } console.log(option, '里面的参数来源') const res = await ajax.post({ url: "castingTeam", data: option, method: "post", }) .catch(() => ({})); console.log(res, '里面的数据来源') const ok = !!(res?.data?.id || res?.data || res?.id); if (ok) { uni.showToast({ title: "已添加", icon: "none" }); // 直接在本地标记,保持 UI 即时反馈 const idx = members.value.findIndex((x) => x.consumerId === m.consumerId); if (idx !== -1) members.value[idx].leaveApprover = true; } else { uni.showToast({ title: "添加失败", icon: "none" }); } } onLoad(async (option) => { castingTeamId.value = option?.castingTeamId ? decodeURIComponent(option.castingTeamId) : ""; await fetchMembers(true); }); </script> <style lang="scss" scoped> @import "@/assets/styles/global.scss"; .content { min-height: 100vh; position: relative; background: #f7f7f7; } .overlay { position: absolute; inset: 0; background: #f7f7f7; } .header { width: 94%; margin: 0 auto; } .header-inner { position: relative; display: flex; align-items: center; justify-content: center; height: 100%; } .left-box { position: absolute; left: 0; top: 0; bottom: 0; width: 120rpx; display: flex; align-items: center; justify-content: flex-start; } .title-text { font-size: 32rpx; font-weight: 600; color: #111; } .wrap { width: 94%; margin: 18rpx auto 0; } .search-bar { height: 88rpx; background: #fff; border-radius: 12rpx; padding: 0 16rpx; box-sizing: border-box; display: flex; align-items: center; gap: 12rpx; } .search-label { font-size: 26rpx; color: #111; } .search-input { flex: 1; font-size: 26rpx; color: #111; } .list { margin-top: 16rpx; height: calc(100vh - 220rpx); background: #fff; border-radius: 12rpx; overflow: hidden; } .item { height: 110rpx; padding: 0 18rpx; display: flex; align-items: center; border-bottom: 1px solid #f0f0f0; box-sizing: border-box; } .item:last-child { border-bottom: none; } .avatar { width: 72rpx; height: 72rpx; border-radius: 50%; background: #eee; margin-right: 16rpx; } .info { flex: 1; min-width: 0; } .name { font-size: 28rpx; color: #111; } .action { font-size: 28rpx; font-weight: 600; } .action.add { color: #d5962a; } .action.added { color: #bbb; font-weight: 500; } .loadmore-wrap { padding: 10rpx 0 18rpx; } </style>在根据ui图和啊api9.castingTeam 组维护、加入、新建text<view :class="['app-container', theme]"> <view class="content"> <view class="overlay"> <!-- Header --> <view class="header" :style="{ paddingTop: statusBarHeight + 'px', height: headerBarHeight + 'px' }"> <view class="header-inner"> <view class="left-box" @click="navigateBack"> <uni-icons type="left" color="#000000" size="26" /> </view> <view class="center-box"> <text class="title-text">添加</text> </view> </view> </view> <!-- 搜索 + 列表 --> <view class="wrap"> <view class="search-bar"> <text class="search-label">联系人</text> <input class="search-input" v-model="keyword" placeholder=" " placeholder-style="color:#C8C8C8;" confirm-type="search" /> <uni-icons type="search" size="18" color="#999" /> </view> <scroll-view class="list" scroll-y @scrolltolower="loadMore"> <view v-for="m in filteredMembers" :key="m.consumerId" class="item"> <image class="avatar" :src="formatAvatar(m.avatarUrl)" mode="aspectFill" /> <view class="info"> <text class="name">{{ m.nickname || "组备注" }}</text> </view> <text v-if="!m.leaveApprover" class="action add" @click="addApprover(m)">添加</text> <text v-else class="action added">已添加</text> </view> <view class="loadmore-wrap"> <uni-load-more :status="loadMoreStatus" /> </view> </scroll-view> </view> </view> </view> </view>
入口参数
String id, 修改用
String parentId, 父组id
String title, 名称
String notice, 公告
String[] consumerIds, 加入组的用户
String ownerId,所有者id。新建时用,更换所有者时候用
String cancelOwnerId,取消管理员id,变更管理员时用
String[] managerIds,管理员id,变更管理员时用
String[] cancelManagerIds, 取消管理员id,变更管理员时用
String[] cancelLeaveApproverId, 取消审批者id, 变更审批者用
String[] leaveApproverId, 审批者id, 变更审批者用
返回值
id 组id
20.leaveAuditListt组审批人列表
入口参数
String castingTeamId, 组id
String consumerId, 审批人id
Integer rowStartIdx,起始行
Integer rowCount行数
返回值
adminArray
consumerId 用户id
nickname 群昵称
avatarUrl 头像
owner 是否是拥有者
manager 是否是管理员
leaveApprover 是否是审批者
在根据重新写两个完整的页面包括滑动不能出现白边和已经添加就显示已添加在头部加上用户名和头像
下面给你把 ApproverSettingMain(审批人添加) 和 ApproverAddMember(添加) 两个页面按你给的 UI + API(9.castingTeam、20.leaveAuditList、castingTeamConsumerList)重新写成完整可用版本,并且做到:
你只要把下面两个
.vue文件直接替换即可(路由地址按你项目路径保留了你原来的写法)。
vue<template> <view :class="['page', theme]"> <!-- 顶部栏 --> <view class="header" :style="{ paddingTop: statusBarHeight + 'px', height: headerBarHeight + 'px' }"> <view class="header-inner"> <view class="left" @click="navigateBack"> <uni-icons type="left" color="#000" size="26" /> </view> <view class="center"> <text class="title">审批人添加</text> </view> <view class="right" @click="navigateBack"> <text class="done">完成</text> </view> </view> </view> <!-- 主体滚动区:用 scroll-view + flex:1,避免页面滚动露白 --> <scroll-view class="scroll" scroll-y> <view class="wrap"> <view class="list"> <!-- 头部:显示拥有者(优先) 或 当前用户 --> <view v-if="headerUser" class="item"> <image class="avatar" :src="formatAvatar(headerUser.avatarUrl)" mode="aspectFill" /> <text class="name">{{ headerUser.nickname || "用户名称" }}</text> </view> <!-- 审批人列表(不含 owner) --> <view v-for="m in approverList" :key="m.consumerId" class="item"> <image class="avatar" :src="formatAvatar(m.avatarUrl)" mode="aspectFill" /> <text class="name">{{ m.nickname || "用户名称" }}</text> <text v-if="canRemove(m)" class="action remove" @click="removeApprover(m)">移除</text> </view> <!-- 添加入口 --> <view class="add-row" @click="goAdd"> <text class="action add">添加</text> </view> </view> <view class="note"> <text class="note-text"> 注释:添加了审批人后该审批人可以直接同意组员的请假、补卡申请 </text> </view> </view> </scroll-view> </view> </template> <script setup> import { ref, inject, computed } from "vue"; import { onLoad, onShow } from "@dcloudio/uni-app"; import { getStatusBarHeight, getTitleBarHeight } from "@/utils/system.js"; import ajax from "@/api/ajax.js"; import { storeToRefs } from "pinia"; import { useMainStore } from "@/store/index.js"; const store = useMainStore(); const { theme } = storeToRefs(store); const statusBarHeight = ref(getStatusBarHeight()); const headerBarHeight = ref(getTitleBarHeight()); const castingTeamId = ref(""); let webProjectUrl = inject("webProjectUrl"); // 拥有者/头部用户 const ownerUser = ref(null); // 审批人列表(含 owner 时也可能返回 owner) const approversRaw = ref([]); /** 头像处理 */ function formatAvatar(url) { if (!url) return "/static/images/default-avatar.png"; return webProjectUrl ? webProjectUrl + url : url; } function navigateBack() { uni.navigateBack(); } /** 当前登录用户 consumerId(用于 leaveAuditList 入参 consumerId) */ function getConsumerId() { const s = store || {}; const fromStore = s?.userInfo?.consumerId || s?.consumerId; if (fromStore) return String(fromStore); const id1 = uni.getStorageSync("consumerId"); if (id1) return String(id1); const u = uni.getStorageSync("userInfo"); if (u && typeof u === "object" && u.consumerId) return String(u.consumerId); if (u && typeof u === "string") { try { const obj = JSON.parse(u); if (obj?.consumerId) return String(obj.consumerId); } catch (e) {} } return ""; } /** 头部展示用户:优先 owner,其次当前用户 */ const headerUser = computed(() => { if (ownerUser.value) return ownerUser.value; const u = store?.userInfo || {}; if (u?.consumerId || u?.nickname || u?.avatarUrl) { return { consumerId: u.consumerId || "", nickname: u.nickname || u.name || "用户名称", avatarUrl: u.avatarUrl || "", owner: true, leaveApprover: true, }; } return null; }); /** 只展示“可移除审批人”:过滤掉 owner */ const approverList = computed(() => { const list = approversRaw.value || []; return list.filter((x) => x.leaveApprover && !x.owner); }); /** owner 不可移除 */ function canRemove(m) { return !m.owner; } /** 20.leaveAuditList:组审批人列表 */ async function fetchApprovers() { if (!castingTeamId.value) return; const res = await ajax .post({ url: "leaveAuditList", data: { castingTeamId: castingTeamId.value, consumerId: getConsumerId(), rowStartIdx: 0, rowCount: 200, }, method: "post", }) .catch(() => ({})); const arr = res?.data?.adminArray || res?.data?.consumerArray || res?.data?.castingTeamConsumerArray || res?.data?.castingTeamConsumerObject?.consumerArray || res?.data || []; const list = Array.isArray(arr) ? arr : []; const normalized = list.map((x) => ({ consumerId: String(x.consumerId ?? x.id ?? ""), nickname: x.nickname ?? "", avatarUrl: x.avatarUrl ?? "", owner: !!x.owner, manager: !!x.manager, leaveApprover: !!x.leaveApprover, })); // owner 优先取第一个 owner ownerUser.value = normalized.find((x) => x.owner) || null; approversRaw.value = normalized.filter((x) => x.leaveApprover); } /** 9.castingTeam:变更审批人(移除) */ async function updateApprover({ addIds = [], removeIds = [] }) { if (!castingTeamId.value) return false; const data = { id: castingTeamId.value }; if (addIds.length) data.leaveApproverId = addIds; if (removeIds.length) data.cancelLeaveApproverId = removeIds; const res = await ajax .post({ url: "castingTeam", data, method: "post", }) .catch(() => ({})); return !!(res?.data?.id || res?.data || res?.id); } async function removeApprover(m) { uni.showModal({ title: "提示", content: "确定移除该审批人?", success: async (r) => { if (!r.confirm) return; const ok = await updateApprover({ removeIds: [m.consumerId] }); if (ok) { uni.showToast({ title: "已移除", icon: "none" }); await fetchApprovers(); } else { uni.showToast({ title: "移除失败", icon: "none" }); } }, }); } function goAdd() { uni.navigateTo({ url: "/pages/sociAlize/Coffeegroup/application/CheckinSettings/Settings/ApproverAddMember?castingTeamId=" + encodeURIComponent(castingTeamId.value), }); } onLoad(async (option) => { castingTeamId.value = option?.castingTeamId ? decodeURIComponent(option.castingTeamId) : ""; await fetchApprovers(); }); onShow(async () => { if (castingTeamId.value) await fetchApprovers(); }); </script> <style lang="scss" scoped> /* 关键:防止 iOS 下滑动露白 */ :global(page) { background-color: #f7f7f7; } :global(body) { background-color: #f7f7f7; } .page { min-height: 100vh; background: #f7f7f7; display: flex; flex-direction: column; overflow: hidden; } .header { background: #f7f7f7; padding-left: 3%; padding-right: 3%; } .header-inner { position: relative; height: 100%; display: flex; align-items: center; justify-content: center; } .left, .right { position: absolute; top: 0; bottom: 0; width: 140rpx; display: flex; align-items: center; } .left { left: 0; justify-content: flex-start; } .right { right: 0; justify-content: flex-end; } .title { font-size: 32rpx; font-weight: 600; color: #111; } .done { font-size: 28rpx; font-weight: 600; color: #d5962a; } .scroll { flex: 1; height: 0; /* flex + scroll-view 必须配 height:0 */ } .wrap { width: 94%; margin: 18rpx auto 24rpx; padding-bottom: env(safe-area-inset-bottom); } .list { background: #fff; border-radius: 12rpx; overflow: hidden; } .item { height: 110rpx; padding: 0 18rpx; display: flex; align-items: center; border-bottom: 1px solid #f0f0f0; box-sizing: border-box; } .item:last-child { border-bottom: none; } .avatar { width: 72rpx; height: 72rpx; border-radius: 50%; background: #eee; margin-right: 16rpx; } .name { flex: 1; font-size: 28rpx; color: #111; } .action { font-size: 28rpx; font-weight: 600; } .remove { color: #d5962a; } .add-row { height: 90rpx; display: flex; align-items: center; justify-content: flex-end; padding: 0 18rpx; border-top: 1px solid #f0f0f0; } .add { color: #d5962a; } .note { margin-top: 18rpx; } .note-text { font-size: 24rpx; color: #999; line-height: 36rpx; } </style>
vue<template> <view :class="['page', theme]"> <!-- 顶部栏 --> <view class="header" :style="{ paddingTop: statusBarHeight + 'px', height: headerBarHeight + 'px' }"> <view class="header-inner"> <view class="left" @click="navigateBack"> <uni-icons type="left" color="#000" size="26" /> </view> <view class="center"> <text class="title">添加</text> </view> </view> </view> <view class="body"> <!-- 头部:已添加审批人(头像+名字) --> <view v-if="selectedApprovers.length" class="selected-card"> <view class="selected-title">已添加</view> <scroll-view class="selected-scroll" scroll-x> <view class="selected-item" v-for="p in selectedApprovers" :key="p.consumerId"> <image class="selected-avatar" :src="formatAvatar(p.avatarUrl)" mode="aspectFill" /> <text class="selected-name">{{ p.nickname || "用户名称" }}</text> </view> </scroll-view> </view> <!-- 搜索 --> <view class="search-bar"> <text class="search-label">联系人</text> <input class="search-input" v-model="keyword" placeholder=" " placeholder-style="color:#C8C8C8;" confirm-type="search" /> <uni-icons type="search" size="18" color="#999" /> </view> <!-- 列表 --> <scroll-view class="list" scroll-y @scrolltolower="loadMore"> <view v-for="m in filteredMembers" :key="m.consumerId" class="item"> <image class="avatar" :src="formatAvatar(m.avatarUrl)" mode="aspectFill" /> <view class="info"> <text class="name">{{ m.nickname || "组备注" }}</text> </view> <text v-if="!m.leaveApprover" class="action add" @click="addApprover(m)">添加</text> <text v-else class="action added">已添加</text> </view> <view class="loadmore-wrap"> <uni-load-more :status="loadMoreStatus" /> </view> </scroll-view> </view> </view> </template> <script setup> import { ref, computed, inject } from "vue"; import { onLoad } from "@dcloudio/uni-app"; import { getStatusBarHeight, getTitleBarHeight } from "@/utils/system.js"; import ajax from "@/api/ajax.js"; import { storeToRefs } from "pinia"; import { useMainStore } from "@/store/index.js"; const store = useMainStore(); const { theme } = storeToRefs(store); const statusBarHeight = ref(getStatusBarHeight()); const headerBarHeight = ref(getTitleBarHeight()); const castingTeamId = ref(""); let webProjectUrl = inject("webProjectUrl"); function formatAvatar(url) { if (!url) return "/static/images/default-avatar.png"; return webProjectUrl ? webProjectUrl + url : url; } function navigateBack() { uni.navigateBack(); } /** 顶部已添加审批人 */ const selectedApprovers = ref([]); /** 搜索关键字 */ const keyword = ref(""); /** 成员列表分页 */ const members = ref([]); const loading = ref(false); const rowStartIdx = ref(0); const rowCount = ref(30); const hasMore = ref(true); const loadMoreStatus = computed(() => { if (loading.value && members.value.length > 0) return "loading"; if (!hasMore.value) return "noMore"; return "more"; }); const filteredMembers = computed(() => { const k = (keyword.value || "").trim(); if (!k) return members.value; return members.value.filter((m) => (m.nickname || "").includes(k)); }); function getConsumerId() { const s = store || {}; const fromStore = s?.userInfo?.consumerId || s?.consumerId; if (fromStore) return String(fromStore); const id1 = uni.getStorageSync("consumerId"); if (id1) return String(id1); const u = uni.getStorageSync("userInfo"); if (u && typeof u === "object" && u.consumerId) return String(u.consumerId); if (u && typeof u === "string") { try { const obj = JSON.parse(u); if (obj?.consumerId) return String(obj.consumerId); } catch (e) {} } return ""; } /** 20.leaveAuditList:取已添加审批人(用于头部展示) */ async function fetchSelectedApprovers() { if (!castingTeamId.value) return; const res = await ajax .post({ url: "leaveAuditList", data: { castingTeamId: castingTeamId.value, consumerId: getConsumerId(), rowStartIdx: 0, rowCount: 200, }, method: "post", }) .catch(() => ({})); const arr = res?.data?.adminArray || res?.data?.consumerArray || res?.data || []; const list = Array.isArray(arr) ? arr : []; selectedApprovers.value = list .map((x) => ({ consumerId: String(x.consumerId ?? x.id ?? ""), nickname: x.nickname ?? "", avatarUrl: x.avatarUrl ?? "", owner: !!x.owner, leaveApprover: !!x.leaveApprover, })) .filter((x) => x.leaveApprover); } /** castingTeamConsumerList:全成员列表(带 leaveApprover 标记) */ async function fetchMembers(reset = false) { if (!castingTeamId.value) return; if (loading.value) return; if (reset) { rowStartIdx.value = 0; hasMore.value = true; members.value = []; } if (!hasMore.value) return; loading.value = true; const res = await ajax .post({ url: "castingTeamConsumerList", data: { castingTeamId: castingTeamId.value, rowStartIdx: rowStartIdx.value, rowCount: rowCount.value, }, method: "post", }) .catch(() => ({})); const arr = res?.data?.consumerArray || res?.data?.castingTeamConsumerArray || res?.data?.castingTeamConsumerObject?.consumerArray || res?.data || []; const list = Array.isArray(arr) ? arr : []; const normalized = list.map((x) => ({ consumerId: String(x.consumerId ?? x.id ?? ""), nickname: x.nickname ?? "", avatarUrl: x.avatarUrl ?? "", owner: !!x.owner, manager: !!x.manager, leaveApprover: !!x.leaveApprover, })); members.value = members.value.concat(normalized); if (normalized.length < rowCount.value) { hasMore.value = false; } else { rowStartIdx.value += rowCount.value; } loading.value = false; } async function loadMore() { if (loading.value || !hasMore.value) return; await fetchMembers(false); } /** 9.castingTeam:添加审批人 */ async function addApprover(m) { if (m.leaveApprover) return; const res = await ajax .post({ url: "castingTeam", data: { id: castingTeamId.value, leaveApproverId: [m.consumerId], }, method: "post", }) .catch(() => ({})); const ok = !!(res?.data?.id || res?.data || res?.id); if (!ok) { uni.showToast({ title: "添加失败", icon: "none" }); return; } uni.showToast({ title: "已添加", icon: "none" }); // 1) 本地标记“已添加” const idx = members.value.findIndex((x) => x.consumerId === m.consumerId); if (idx !== -1) members.value[idx].leaveApprover = true; // 2) 顶部已添加刷新(更稳) await fetchSelectedApprovers(); } onLoad(async (option) => { castingTeamId.value = option?.castingTeamId ? decodeURIComponent(option.castingTeamId) : ""; // 先拉已添加,再拉成员列表 await fetchSelectedApprovers(); await fetchMembers(true); }); </script> <style lang="scss" scoped> /* 关键:防止 iOS 下滑动露白 */ :global(page) { background-color: #f7f7f7; } :global(body) { background-color: #f7f7f7; } .page { min-height: 100vh; background: #f7f7f7; display: flex; flex-direction: column; overflow: hidden; } .header { background: #f7f7f7; padding-left: 3%; padding-right: 3%; } .header-inner { position: relative; height: 100%; display: flex; align-items: center; justify-content: center; } .left { position: absolute; left: 0; top: 0; bottom: 0; width: 140rpx; display: flex; align-items: center; justify-content: flex-start; } .title { font-size: 32rpx; font-weight: 600; color: #111; } .body { flex: 1; height: 0; width: 94%; margin: 18rpx auto 0; display: flex; flex-direction: column; padding-bottom: env(safe-area-inset-bottom); } /* 顶部已添加审批人 */ .selected-card { background: #fff; border-radius: 12rpx; padding: 16rpx 16rpx 10rpx; margin-bottom: 16rpx; overflow: hidden; } .selected-title { font-size: 24rpx; color: #999; margin-bottom: 10rpx; } .selected-scroll { white-space: nowrap; } .selected-item { display: inline-flex; flex-direction: column; align-items: center; margin-right: 18rpx; width: 110rpx; } .selected-avatar { width: 72rpx; height: 72rpx; border-radius: 50%; background: #eee; } .selected-name { margin-top: 8rpx; font-size: 22rpx; color: #111; width: 110rpx; text-align: center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } /* 搜索栏 */ .search-bar { height: 88rpx; background: #fff; border-radius: 12rpx; padding: 0 16rpx; box-sizing: border-box; display: flex; align-items: center; gap: 12rpx; } .search-label { font-size: 26rpx; color: #111; } .search-input { flex: 1; font-size: 26rpx; color: #111; } /* 列表 */ .list { margin-top: 16rpx; flex: 1; height: 0; /* 关键:避免滚动露白 */ background: #fff; border-radius: 12rpx; overflow: hidden; } .item { height: 110rpx; padding: 0 18rpx; display: flex; align-items: center; border-bottom: 1px solid #f0f0f0; box-sizing: border-box; } .item:last-child { border-bottom: none; } .avatar { width: 72rpx; height: 72rpx; border-radius: 50%; background: #eee; margin-right: 16rpx; } .info { flex: 1; min-width: 0; } .name { font-size: 28rpx; color: #111; } .action { font-size: 28rpx; font-weight: 600; } .action.add { color: #d5962a; } .action.added { color: #bbb; font-weight: 500; } .loadmore-wrap { padding: 10rpx 0 18rpx; } </style>
如果你这边 leaveAuditList 实际返回字段不是 adminArray(比如是 consumerArray 或别的),我在代码里已经做了多种 fallback;你只要把控制台打印一下真实结构,我还能帮你把解析再收敛得更干净。