Files
qiming/qiming-mobile/pages/index/agent-list-content/agent-list-content.uvue

380 lines
10 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<!-- 内容区域 -->
<scroll-view
class="flex-1 h-full"
scroll-y="true"
@scrolltolower="handleLoadMore"
:lower-threshold="50"
:refresher-enabled="true"
:refresher-triggered="refreshing"
@refresherrefresh="handleRefresh"
:show-scrollbar="false"
>
<template v-if="recentAgentList?.length">
<recent-used-agent-item
v-for="info in recentAgentList"
:key="info.id"
:icon="info.icon"
:name="info.name"
:description="info.description"
:modifiedTime="formatTimeAgo(info.modified)"
:showModifiedTime="isLoggedIn"
:paymentRequired="info.paymentRequired && enableSubscription === 1"
:subscribed="info.subscribed"
@click="handleAgentClick(info)"
@longpress="isLoggedIn ? handleDeleteUserUsedAgent(info) : () => {}"
/>
</template>
<!-- loading状态或刷新状态时隐藏空状态 -->
<template v-else-if="!loading && !refreshing">
<view class="empty-state h-full">
<image
:src="noData"
class="empty-image"
:alt="t('Mobile.Common.noDataImageAlt')"
/>
<text class="empty-text">{{ t("Mobile.Common.noData") }}</text>
</view>
</template>
<!-- 刷新状态时隐藏加载状态 -->
<template v-else-if="!refreshing">
<view class="loading-state">
<text class="loading-text">{{ t("Mobile.Page.loadingMore") }}</text>
</view>
</template>
<!-- 加载状态时隐藏没有更多数据状态 -->
<template v-if="showNoMore">
<view class="no-more-state">
<text class="no-more-text">{{ t("Mobile.Common.noMoreData") }}</text>
</view>
</template>
</scroll-view>
</template>
<script setup lang="uts">
import { SUCCESS_CODE } from "@/constants/codes.constants.uts";
import type { AgentInfo } from "@/types/interfaces/agent";
import {
apiUserUsedAgentList,
apiUserUsedAgentDelete,
} from "@/servers/agentDev";
import { apiPublishedAgentList } from "@/servers/square";
import type {
SquarePublishedListParams,
SquarePublishedItemInfo,
} from "@/types/interfaces/square";
import type { Page, RequestResponse } from "@/types/interfaces/request";
import RecentUsedAgentItem from "@/components/recent-used-agent-item/recent-used-agent-item.uvue";
import noData from "@/static/assets/no_data.png";
import { formatTimeAgo } from "@/utils/common";
import { USER_NO_LOGIN, REDIRECT_LOGIN } from "@/constants/codes.constants";
import { AgentComponentTypeEnum } from "@/types/enums/agent";
import { useI18n } from "@/utils/i18n";
import { apiTenantConfig } from "@/servers/account";
import { TENANT_CONFIG_INFO } from "@/constants/home.constants";
import type { TenantConfigInfo } from "@/types/interfaces/login";
const { t } = useI18n();
// Props 定义
interface Props {
isLoggedIn: boolean;
}
const props = defineProps<Props>();
// Emits 定义 - 只保留需要父组件处理的事件
const emit = defineEmits<{
(e: "agent-click", info: AgentInfo): void;
}>();
// 最近使用智能体列表
const recentAgentList = ref<AgentInfo[]>([]);
// 加载状态
const loading = ref<boolean>(false);
// 刷新状态
const refreshing = ref<boolean>(false);
// 是否还有更多
const hasMore = ref<boolean>(false);
// 当前页码
const currentPage = ref<number>(1);
// 租户信息
const tenantConfigInfo = ref<TenantConfigInfo | null>(null);
// 租户订阅限制强受控标志
const enableSubscription = computed((): number => {
return tenantConfigInfo.value?.enableSubscription ?? 0;
});
// 是否显示没有更多数据
const showNoMore = computed(() => {
return (
!loading.value && !hasMore.value && recentAgentList.value?.length > 8
);
});
// 查询用户最近使用过的智能体列表
const fetchUserUsedAgentList = async (
page: number = 1,
pageSize: number = 20,
) => {
try {
const res = await apiUserUsedAgentList({
pageIndex: page,
size: pageSize,
});
loading.value = false;
const { code, data } = res || {};
if (code === SUCCESS_CODE) {
const newList = data || [];
if (page > 1) {
// 追加数据
const existingList = recentAgentList.value || [];
recentAgentList.value = [...existingList, ...newList];
} else {
// 替换数据
recentAgentList.value = newList;
}
// 是否有更多数据, 如果数据长度大于0则有更多数据
hasMore.value = newList?.length > 0;
currentPage.value = page;
return data;
}
} catch (error) {
return false;
}
};
// 获取公开的智能体列表(未登录时使用)
const fetchPublishedAgentList = async () => {
try {
const params: SquarePublishedListParams = {
targetType: AgentComponentTypeEnum.Agent,
targetSubType: "ChatBot",
page: 1,
pageSize: 10,
category: "",
kw: "",
};
const res = await apiPublishedAgentList(params);
loading.value = false;
const { code, data } = res as unknown as RequestResponse<
Page<SquarePublishedItemInfo>
>;
if (code === SUCCESS_CODE) {
const newList = data.records || [];
// 转换为 AgentInfo 格式
const agentList: AgentInfo[] = newList.map(
(item: SquarePublishedItemInfo) =>
({
id: item.id,
agentId: item.targetId,
name: item.name,
icon: item.icon,
description: item.description,
agentType: item.agentType,
paymentRequired: item.paymentRequired,
subscribed: item.subscribed,
}) as AgentInfo,
);
recentAgentList.value = agentList;
// 只显示第一页,所以没有更多数据
hasMore.value = false;
currentPage.value = 1;
return agentList;
}
} catch (error) {
return false;
}
};
// 删除最近使用的智能体
const handleDeleteUserUsedAgent = (info: AgentInfo) => {
uni.showModal({
title: t("Mobile.AgentList.confirmDeleteAgent"),
content: info?.name,
success: (res) => {
// 确认删除
if (res.confirm) {
deleteUserUsedAgent(info?.agentId);
}
},
});
};
// 删除用户最近智能体使用记录
const deleteUserUsedAgent = async (agentId: number) => {
const res = await apiUserUsedAgentDelete(agentId);
const { code, message } = res || {};
if (code === SUCCESS_CODE) {
recentAgentList.value = recentAgentList.value.filter(
(item) => item.agentId !== agentId,
);
} else if (code !== USER_NO_LOGIN && code !== REDIRECT_LOGIN) {
uni.showToast({
title: message || t("Mobile.AgentList.deleteFailed"),
icon: "none",
});
}
};
// 下拉刷新
const handleRefresh = async () => {
if (refreshing.value) {
return;
}
refreshing.value = true;
currentPage.value = 1;
hasMore.value = true;
// 根据是否登录调用不同的 API
const fetchPromise = props.isLoggedIn
? fetchUserUsedAgentList()
: fetchPublishedAgentList();
fetchPromise.finally(() => {
// 添加延迟让loading效果更明显
setTimeout(() => {
refreshing.value = false;
}, 500);
});
};
// 加载更多
const handleLoadMore = () => {
if (hasMore.value && !loading.value) {
// 下一页页码
const nextPage = currentPage.value + 1;
fetchUserUsedAgentList(nextPage);
}
};
// 点击智能体 - 发射事件给父组件处理
const handleAgentClick = (info: AgentInfo) => {
emit("agent-click", info);
};
// 获取租户配置
const fetchTenantConfig = async () => {
const { code, data } = await apiTenantConfig();
if (code === SUCCESS_CODE) {
tenantConfigInfo.value = data;
// 缓存租户信息
uni.setStorageSync(TENANT_CONFIG_INFO, JSON.stringify(data));
}
};
// 初始化租户配置数据
const initTenantConfig = async () => {
// 1. 首先尝试从本地缓存读取
const tenantConfigInfoString = await uni.getStorageSync(TENANT_CONFIG_INFO);
if (tenantConfigInfoString) {
tenantConfigInfo.value = JSON.parse(tenantConfigInfoString);
}
// 2. 然后必须发起网络强同步请求,以获得最新的 enableSubscription 状态
await fetchTenantConfig();
};
// 初始化加载数据
const loadData = async () => {
loading.value = true;
// 强同步更新最新的租户配置,防止进入首页列表时计费状态渲染不正确
await initTenantConfig();
const list = props.isLoggedIn
? await fetchUserUsedAgentList(1, 20 * currentPage.value)
: await fetchPublishedAgentList();
};
onMounted(()=>{
loadData();
})
onPageShow(()=>{
loadData();
})
// 暴露方法给父组件
defineExpose({
loadData,
recentAgentList,
});
</script>
<style lang="scss" scoped>
/* 空状态 */
.h-full {
height: 100%;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.empty-image {
width: 170rpx;
height: 170rpx;
}
.empty-text {
font-size: 28rpx;
color: #999;
margin-top: 32rpx;
}
}
/* 加载状态 */
.loading-state {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
.loading-text {
font-size: 28rpx;
color: #666;
display: flex;
align-items: center;
flex-direction: row;
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
&::before {
content: "";
width: 32rpx;
height: 32rpx;
border: 4rpx solid #e8e8e8;
border-top: 4rpx solid #8b5cf6;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-right: 16rpx;
}
}
}
/* 没有更多数据状态 */
.no-more-state {
display: flex;
align-items: center;
justify-content: center;
padding: 40rpx;
.no-more-text {
font-size: 24rpx;
color: #999;
}
}
</style>