|
|
@@ -455,12 +455,12 @@
|
|
|
</el-input>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 用户列表 -->
|
|
|
- <div class="relation-list">
|
|
|
+ <!-- 用户列表(一页5条,滚动到底自动加载) -->
|
|
|
+ <div ref="relationListScrollRef" class="relation-list" @scroll="onRelationListScroll">
|
|
|
<div v-for="user in filteredRelationList" :key="user.id" class="relation-item">
|
|
|
<div class="user-info-row">
|
|
|
<div class="user-avatar-small">
|
|
|
- <img v-if="user.image" :src="user.avatar" :alt="user.name" />
|
|
|
+ <img v-if="user.avatar" :src="user.avatar" :alt="user.name" />
|
|
|
<el-icon v-else :size="40">
|
|
|
<Avatar />
|
|
|
</el-icon>
|
|
|
@@ -470,7 +470,7 @@
|
|
|
{{ user.name }}
|
|
|
</div>
|
|
|
<div class="user-desc">
|
|
|
- {{ user.blurb }}
|
|
|
+ {{ user.description }}
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -491,8 +491,18 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
+ <!-- 触底加载哨兵(IntersectionObserver 检测可见时加载更多) -->
|
|
|
+ <div v-if="relationHasMore" ref="relationLoadMoreSentinelRef" class="relation-load-sentinel" />
|
|
|
+
|
|
|
+ <!-- 加载更多按钮(点击或滚到底都会触发) -->
|
|
|
+ <div v-if="relationHasMore" class="relation-load-more">
|
|
|
+ <el-button :loading="relationLoading" text type="primary" @click="loadMoreRelationList">
|
|
|
+ {{ relationLoading ? "加载中..." : "点击或滚动到底加载更多" }}
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
<!-- 空状态 -->
|
|
|
- <el-empty v-if="filteredRelationList.length === 0" description="暂无数据" />
|
|
|
+ <el-empty v-if="!relationLoading && filteredRelationList.length === 0" description="暂无数据" />
|
|
|
</div>
|
|
|
</div>
|
|
|
</el-dialog>
|
|
|
@@ -699,7 +709,7 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts" name="myDynamic">
|
|
|
-import { ref, reactive, computed, onMounted } from "vue";
|
|
|
+import { ref, reactive, computed, onMounted, nextTick, watch } from "vue";
|
|
|
import { useRouter } from "vue-router";
|
|
|
import { ElMessage, ElMessageBox } from "element-plus";
|
|
|
import {
|
|
|
@@ -732,6 +742,7 @@ import {
|
|
|
getMyFollowed,
|
|
|
addTransferCount,
|
|
|
getMyUserFans,
|
|
|
+ getMyStoreFans,
|
|
|
blockUser,
|
|
|
likeDynamicNew,
|
|
|
unlikeDynamicNew,
|
|
|
@@ -841,6 +852,52 @@ const relationDialogVisible = ref(false);
|
|
|
const relationActiveTab = ref("friend");
|
|
|
const relationSearch = ref("");
|
|
|
const relationList = ref<RelationUser[]>([]);
|
|
|
+const relationPage = ref(1);
|
|
|
+const relationPageSize = 5;
|
|
|
+const relationTotal = ref(0);
|
|
|
+const relationLoading = ref(false);
|
|
|
+const relationHasMore = computed(() => relationList.value.length < relationTotal.value && !relationLoading.value);
|
|
|
+const relationListScrollRef = ref<HTMLElement | null>(null);
|
|
|
+const relationScrollTargets: HTMLElement[] = [];
|
|
|
+
|
|
|
+const onRelationListScroll = (e: Event) => {
|
|
|
+ const el = e.target as HTMLElement;
|
|
|
+ if (!el || !relationHasMore.value) return;
|
|
|
+ const { scrollTop, scrollHeight, clientHeight } = el;
|
|
|
+ const distanceToBottom = scrollHeight - scrollTop - clientHeight;
|
|
|
+ if (distanceToBottom < 80) {
|
|
|
+ loadMoreRelationList();
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const bindRelationScroll = () => {
|
|
|
+ nextTick(() => {
|
|
|
+ setTimeout(() => {
|
|
|
+ const listEl = relationListScrollRef.value;
|
|
|
+ if (!listEl) return;
|
|
|
+ unbindRelationScroll();
|
|
|
+ listEl.addEventListener("scroll", onRelationListScroll, { passive: true });
|
|
|
+ relationScrollTargets.push(listEl);
|
|
|
+ const dialogBody = listEl.closest(".el-dialog")?.querySelector(".el-dialog__body") as HTMLElement | null;
|
|
|
+ if (dialogBody && dialogBody !== listEl) {
|
|
|
+ dialogBody.addEventListener("scroll", onRelationListScroll, { passive: true });
|
|
|
+ relationScrollTargets.push(dialogBody);
|
|
|
+ }
|
|
|
+ }, 100);
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const unbindRelationScroll = () => {
|
|
|
+ relationScrollTargets.splice(0, relationScrollTargets.length).forEach(el => {
|
|
|
+ el.removeEventListener("scroll", onRelationListScroll);
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+watch(relationDialogVisible, visible => {
|
|
|
+ if (visible) bindRelationScroll();
|
|
|
+ else unbindRelationScroll();
|
|
|
+});
|
|
|
+
|
|
|
//点击本地草稿
|
|
|
const handleDraftClick = () => {
|
|
|
router.push({
|
|
|
@@ -1721,59 +1778,128 @@ const handleRelationTabClick = (tab: any) => {
|
|
|
}
|
|
|
};
|
|
|
//好友列表
|
|
|
-const handleFriendList = async () => {
|
|
|
+const mapRecordToRelationUser = (item: any, defaultStatus: "following" | "mutual" | "none" = "none"): RelationUser => ({
|
|
|
+ id: item.id || item.userId,
|
|
|
+ name: item.userName || item.nickname || item.name || "用户",
|
|
|
+ avatar: item.userImage || item.avatar || item.headImg || "",
|
|
|
+ description: item.description || item.bio || item.signature || "",
|
|
|
+ relationStatus: item.isFollowThis === 1 ? ("mutual" as const) : defaultStatus,
|
|
|
+ phoneId: item.phoneId || item.fansId || ""
|
|
|
+});
|
|
|
+
|
|
|
+const handleFriendList = async (append = false) => {
|
|
|
+ if (!append) {
|
|
|
+ relationPage.value = 1;
|
|
|
+ relationList.value = [];
|
|
|
+ }
|
|
|
const phone = userStore.userInfo?.phone || "";
|
|
|
const fansId = phone.startsWith("store_") ? phone : `store_${phone}`;
|
|
|
- const res: any = await getMutualAttention({
|
|
|
- page: 1,
|
|
|
- size: 1000,
|
|
|
- fansId: fansId,
|
|
|
- name: relationSearch.value || ""
|
|
|
- });
|
|
|
- if (res.code == 200) {
|
|
|
- const dataList = res.data?.records || [];
|
|
|
- relationList.value = dataList;
|
|
|
+ relationLoading.value = true;
|
|
|
+ try {
|
|
|
+ const res: any = await getMutualAttention({
|
|
|
+ page: relationPage.value,
|
|
|
+ size: relationPageSize,
|
|
|
+ fansId,
|
|
|
+ name: relationSearch.value || ""
|
|
|
+ });
|
|
|
+ if (res.code == 200) {
|
|
|
+ const data = res.data;
|
|
|
+ const dataList = data?.records || data?.list || data || [];
|
|
|
+ const mapped = dataList.map((item: any) => mapRecordToRelationUser(item, "mutual"));
|
|
|
+ if (append) relationList.value.push(...mapped);
|
|
|
+ else relationList.value = mapped;
|
|
|
+ const currentLen = relationList.value.length;
|
|
|
+ if (dataList.length === 0) relationTotal.value = currentLen;
|
|
|
+ else if (typeof data?.total === "number" && currentLen >= data.total) relationTotal.value = data.total;
|
|
|
+ else relationTotal.value = currentLen + 1;
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ relationLoading.value = false;
|
|
|
}
|
|
|
};
|
|
|
-//关注列表
|
|
|
-const handleFollowList = async () => {
|
|
|
+
|
|
|
+const handleFollowList = async (append = false) => {
|
|
|
+ if (!append) {
|
|
|
+ relationPage.value = 1;
|
|
|
+ relationList.value = [];
|
|
|
+ }
|
|
|
const phone = userStore.userInfo?.phone || "";
|
|
|
const fansId = phone.startsWith("store_") ? phone : `store_${phone}`;
|
|
|
- const res: any = await getMyFollowed({
|
|
|
- page: 1,
|
|
|
- size: 1000,
|
|
|
- fansId: fansId,
|
|
|
- name: relationSearch.value || ""
|
|
|
- });
|
|
|
- if (res.code === 200) {
|
|
|
- const dataList = res.data?.records || res.data?.list || res.data || [];
|
|
|
- relationList.value = dataList;
|
|
|
+ relationLoading.value = true;
|
|
|
+ try {
|
|
|
+ const res: any = await getMyFollowed({
|
|
|
+ page: relationPage.value,
|
|
|
+ size: relationPageSize,
|
|
|
+ fansId,
|
|
|
+ name: relationSearch.value || ""
|
|
|
+ });
|
|
|
+ if (res.code === 200) {
|
|
|
+ const data = res.data;
|
|
|
+ const dataList = data?.records || data?.list || data || [];
|
|
|
+ const mapped = dataList.map((item: any) => mapRecordToRelationUser(item, "following"));
|
|
|
+ if (append) relationList.value.push(...mapped);
|
|
|
+ else relationList.value = mapped;
|
|
|
+ const currentLen = relationList.value.length;
|
|
|
+ if (dataList.length === 0) relationTotal.value = currentLen;
|
|
|
+ else if (typeof data?.total === "number" && currentLen >= data.total) relationTotal.value = data.total;
|
|
|
+ else relationTotal.value = currentLen + 1;
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ relationLoading.value = false;
|
|
|
}
|
|
|
};
|
|
|
-//粉丝列表
|
|
|
-const handleFansList = async () => {
|
|
|
+
|
|
|
+const handleFansList = async (append = false) => {
|
|
|
+ if (!append) {
|
|
|
+ relationPage.value = 1;
|
|
|
+ relationList.value = [];
|
|
|
+ }
|
|
|
const phone = userStore.userInfo?.phone || "";
|
|
|
const fansId = phone.startsWith("store_") ? phone : `store_${phone}`;
|
|
|
- const res: any = await getMyUserFans({
|
|
|
- page: 1,
|
|
|
- size: 1000,
|
|
|
- fansId: fansId,
|
|
|
- name: relationSearch.value || ""
|
|
|
- });
|
|
|
- if (res.code === 200) {
|
|
|
- const dataList = res.data?.records || res.data?.list || res.data || [];
|
|
|
- relationList.value = dataList.map((item: any) => ({
|
|
|
- id: item.id || item.userId,
|
|
|
- name: item.userName || item.nickname || item.name || "用户",
|
|
|
- avatar: item.userImage || item.avatar || item.headImg || "",
|
|
|
- description: item.description || item.bio || item.signature || "欢迎来这里",
|
|
|
- // 粉丝列表需要判断是否互相关注
|
|
|
- relationStatus: item.isFollowThis === 1 ? ("mutual" as const) : ("none" as const),
|
|
|
- phoneId: item.phoneId || item.fansId || "" // 保存 phoneId 用于后续操作
|
|
|
- }));
|
|
|
+ const name = relationSearch.value || "";
|
|
|
+ const listParams = { page: append ? relationPage.value : 1, size: append ? relationPageSize : 1000, fansId, name };
|
|
|
+ relationLoading.value = true;
|
|
|
+ try {
|
|
|
+ let dataList: any[] = [];
|
|
|
+ if (!append) {
|
|
|
+ const res1: any = await getMyStoreFans(listParams);
|
|
|
+ const res2: any = await getMyUserFans(listParams);
|
|
|
+ const records1 = res1?.code === 200 ? res1.data?.records || res1.data?.list || res1.data || [] : [];
|
|
|
+ const records2 = res2?.code === 200 ? res2.data?.records || res2.data?.list || res2.data || [] : [];
|
|
|
+ dataList = [...records1, ...records2];
|
|
|
+ const createdId = localGet("createdId") || userStore.userInfo?.storeId || "";
|
|
|
+ if (createdId) dataList = dataList.filter((item: any) => String(item.id) !== String(createdId));
|
|
|
+ } else {
|
|
|
+ const res: any = await getMyUserFans(listParams);
|
|
|
+ if (res.code === 200) {
|
|
|
+ const data = res.data;
|
|
|
+ dataList = data?.records || data?.list || data || [];
|
|
|
+ const createdId = localGet("createdId") || userStore.userInfo?.storeId || "";
|
|
|
+ if (createdId) dataList = dataList.filter((item: any) => String(item.id) !== String(createdId));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!append || dataList.length > 0) {
|
|
|
+ const mapped = dataList.map((item: any) => mapRecordToRelationUser(item, "none"));
|
|
|
+ if (append) relationList.value.push(...mapped);
|
|
|
+ else relationList.value = mapped;
|
|
|
+ const currentLen = relationList.value.length;
|
|
|
+ if (dataList.length === 0) relationTotal.value = currentLen;
|
|
|
+ else if (!append) relationTotal.value = currentLen;
|
|
|
+ else relationTotal.value = currentLen + 1;
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ relationLoading.value = false;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+const loadMoreRelationList = () => {
|
|
|
+ if (!relationHasMore.value) return;
|
|
|
+ relationPage.value++;
|
|
|
+ if (relationActiveTab.value === "friend") handleFriendList(true);
|
|
|
+ else if (relationActiveTab.value === "follow") handleFollowList(true);
|
|
|
+ else handleFansList(true);
|
|
|
+};
|
|
|
+
|
|
|
// 搜索关系列表
|
|
|
const handleRelationSearch = () => {
|
|
|
if (relationActiveTab.value === "friend") {
|
|
|
@@ -1831,6 +1957,9 @@ const handleUnfollow = async (user: RelationUser) => {
|
|
|
const handleCloseRelationDialog = () => {
|
|
|
relationSearch.value = "";
|
|
|
relationList.value = [];
|
|
|
+ relationPage.value = 1;
|
|
|
+ relationTotal.value = 0;
|
|
|
+ unbindRelationScroll();
|
|
|
};
|
|
|
|
|
|
// 打开添加好友对话框
|
|
|
@@ -2760,18 +2889,27 @@ onMounted(() => {
|
|
|
:deep(.el-dialog) {
|
|
|
// 关系对话框
|
|
|
.relation-dialog-content {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ max-height: 70vh;
|
|
|
+ overflow: hidden;
|
|
|
.el-tabs {
|
|
|
+ flex-shrink: 0;
|
|
|
margin-bottom: 16px;
|
|
|
.el-tabs__header {
|
|
|
margin-bottom: 16px;
|
|
|
}
|
|
|
}
|
|
|
.search-box {
|
|
|
+ flex-shrink: 0;
|
|
|
margin-bottom: 16px;
|
|
|
}
|
|
|
.relation-list {
|
|
|
- max-height: 400px;
|
|
|
- overflow-y: auto;
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ max-height: 320px;
|
|
|
+ overflow: hidden auto;
|
|
|
+ -webkit-overflow-scrolling: touch;
|
|
|
.relation-item {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
@@ -2827,6 +2965,10 @@ onMounted(() => {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+ .relation-load-more {
|
|
|
+ padding: 12px 0;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|