chore: initialize qiming workspace repository
This commit is contained in:
@@ -0,0 +1,691 @@
|
||||
<template>
|
||||
<view class="container page-container relative border-b">
|
||||
<!-- 导航栏 -->
|
||||
<custom-nav-bar :title="title">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<template v-slot:left>
|
||||
<text class="iconfont icon-Search font-48" @click="handleSearch"></text>
|
||||
</template>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<template v-slot:right>
|
||||
<text class="iconfont icon-Search font-48" @click="handleSearch"></text>
|
||||
</template>
|
||||
<!-- #endif -->
|
||||
</custom-nav-bar>
|
||||
|
||||
<!-- 分类导航 -->
|
||||
<view class="category-nav border-b">
|
||||
<scroll-view
|
||||
class="category-scroll"
|
||||
direction="horizontal"
|
||||
:scroll-with-animation="true"
|
||||
:scroll-into-view="scrollIntoView"
|
||||
:show-scrollbar="false"
|
||||
>
|
||||
<view
|
||||
:id="category.key"
|
||||
v-for="(category, index) in categories"
|
||||
:key="category.key"
|
||||
:class="`category-item ${activeCategory === category.key ? 'active' : ''}`"
|
||||
@click="handleCategoryClick(category.key, index)"
|
||||
>
|
||||
<text class="category-text">{{
|
||||
translateText(category.label)
|
||||
}}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 滚动部分 -->
|
||||
<view class="scroll-section">
|
||||
<swiper
|
||||
class="h-full"
|
||||
:current="activeSwiperIndex"
|
||||
@change="handleSwiperChange"
|
||||
:indicator-dots="false"
|
||||
:circular="false"
|
||||
:duration="300"
|
||||
easing-function="easeOut"
|
||||
>
|
||||
<template v-for="(category, index) in categories" :key="category.key">
|
||||
<swiper-item>
|
||||
<!-- 只渲染当前页和相邻页,减少渲染压力 -->
|
||||
<view v-if="shouldRenderCategory(index)" class="h-full">
|
||||
<scroll-view
|
||||
class="h-full"
|
||||
@scrolltolower="handleLoadMore"
|
||||
:refresher-enabled="true"
|
||||
:refresher-triggered="refreshingMap[category.key] || false"
|
||||
@refresherrefresh="handleRefresh"
|
||||
:show-scrollbar="false"
|
||||
scroll-with-animation
|
||||
>
|
||||
<template v-if="(agentListMap[category.key] || []).length">
|
||||
<template
|
||||
v-for="info in agentListMap[category.key]"
|
||||
:key="info.id"
|
||||
>
|
||||
<template v-if="agentType === 'PageApp'">
|
||||
<page-card
|
||||
:coverImg="info.coverImg"
|
||||
:name="info.name"
|
||||
:avatar="info.publishUser?.avatar"
|
||||
:userName="
|
||||
info.publishUser?.nickName ||
|
||||
info.publishUser?.userName
|
||||
"
|
||||
:created="info.created"
|
||||
:userCount="info.statistics?.userCount"
|
||||
:collectCount="info.statistics?.collectCount"
|
||||
:collect="info.collect"
|
||||
@click="handleAgentClick(info)"
|
||||
@collect="handleToggleCollect(info, category.key)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<agent-component
|
||||
:icon="info.icon"
|
||||
:name="info.name"
|
||||
:avatar="info.publishUser?.avatar"
|
||||
:userName="
|
||||
info.publishUser?.nickName ||
|
||||
info.publishUser?.userName
|
||||
"
|
||||
:description="info.description"
|
||||
:userCount="info.statistics?.userCount"
|
||||
:convCount="info.statistics?.convCount"
|
||||
:collectCount="info.statistics?.collectCount"
|
||||
:collect="info.collect"
|
||||
:paymentRequired="info.paymentRequired && enableSubscription === 1"
|
||||
:subscribed="info.subscribed"
|
||||
@click="handleAgentClick(info)"
|
||||
@collect="handleToggleCollect(info, category.key)"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- loading状态或刷新状态时隐藏空状态 -->
|
||||
<template
|
||||
v-else-if="
|
||||
!loadingMap[category.key] && !refreshingMap[category.key]
|
||||
"
|
||||
>
|
||||
<view class="empty-state">
|
||||
<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="!refreshingMap[category.key]">
|
||||
<view class="loading-state">
|
||||
<text class="loading-text">{{
|
||||
t("Mobile.Page.loadingMore")
|
||||
}}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 加载状态时隐藏没有更多数据状态 -->
|
||||
<template
|
||||
v-if="
|
||||
!loadingMap[category.key] &&
|
||||
!hasMoreMap[category.key] &&
|
||||
(agentListMap[category.key] || []).length > 6
|
||||
"
|
||||
>
|
||||
<view class="no-more-state">
|
||||
<text class="no-more-text">{{
|
||||
t("Mobile.Common.noMoreData")
|
||||
}}</text>
|
||||
</view>
|
||||
</template>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<!-- 未加载的页面显示占位 -->
|
||||
<view v-else class="h-full placeholder-view"></view>
|
||||
</swiper-item>
|
||||
</template>
|
||||
</swiper>
|
||||
</view>
|
||||
|
||||
<!-- 登录弹窗 -->
|
||||
<auth-login-popup ref="loginPopupRef" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { CONST_ALL } from "@/constants/common.constants";
|
||||
import { SquarePublishedItemInfo } from "@/types/interfaces/square";
|
||||
import { apiPublishedCategoryList } from "@/servers/square";
|
||||
import { SUCCESS_CODE } from "@/constants/codes.constants";
|
||||
import { SquareCategoryInfo } from "@/types/interfaces/square";
|
||||
import { SquareAgentTypeEnum } from "@/types/enums/square";
|
||||
import { AgentComponentTypeEnum } from "@/types/enums/agent";
|
||||
import { apiPublishedAgentList } from "@/servers/square";
|
||||
import { SquarePublishedListParams } from "@/types/interfaces/square";
|
||||
import { Page } from "@/types/interfaces/request";
|
||||
import { RequestResponse } from "@/types/interfaces/request";
|
||||
import AgentComponent from "@/components/agent-component/agent-component.uvue";
|
||||
import PageCard from "@/components/page-card/page-card.uvue";
|
||||
import noData from "@/static/assets/no_data.png";
|
||||
import { TENANT_CONFIG_INFO } from "@/constants/home.constants";
|
||||
import { apiTenantConfig } from "@/servers/account";
|
||||
import { getCurrentPagePath } from "@/utils/commonBusiness";
|
||||
import CustomNavBar from "@/components/custom-nav-bar/custom-nav-bar.uvue";
|
||||
import { apiUnCollectAgent, apiCollectAgent } from "@/servers/agentDev";
|
||||
import { USER_NO_LOGIN, REDIRECT_LOGIN } from "@/constants/codes.constants";
|
||||
import { useAuthInterceptor } from "@/hooks/useAuthInterceptor";
|
||||
import AuthLoginPopup from "@/components/auth-login-popup/auth-login-popup.uvue";
|
||||
import { useI18n, translateText } from "@/utils/i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
agentType: "ChatBot" | "PageApp";
|
||||
title: string;
|
||||
}>(),
|
||||
{
|
||||
agentType: "ChatBot",
|
||||
title: "Mobile.Nav.agent",
|
||||
},
|
||||
);
|
||||
|
||||
// 使用登录拦截 composable
|
||||
const {
|
||||
loginPopupRef,
|
||||
handleAgentClick: baseHandleAgentClick,
|
||||
checkAuthAndShowPopup,
|
||||
} = useAuthInterceptor();
|
||||
|
||||
// 当前分类
|
||||
const activeCategory = ref<string>(CONST_ALL);
|
||||
// 当前swiper索引
|
||||
const activeSwiperIndex = ref<number>(0);
|
||||
// 分类列表
|
||||
const categories = ref<{ key: string; label: string; active: boolean }[]>([]);
|
||||
// 智能体列表
|
||||
const agentListMap = ref<{ [key: string]: SquarePublishedItemInfo[] }>({});
|
||||
// 加载状态
|
||||
const loadingMap = ref<{ [key: string]: boolean }>({});
|
||||
// 刷新状态
|
||||
const refreshingMap = ref<{ [key: string]: boolean }>({});
|
||||
// 页码
|
||||
const pageMap = ref<{ [key: string]: number }>({});
|
||||
// 是否还有更多
|
||||
const hasMoreMap = ref<{ [key: string]: boolean }>({});
|
||||
// 搜索关键词
|
||||
const searchKeyword = ref<string>("");
|
||||
// 滚动到对应的分类标签
|
||||
const scrollIntoView = ref<string>("");
|
||||
// 搜索定时器
|
||||
const searchTimeoutRef = ref<number | null>(null);
|
||||
// 租户信息
|
||||
const tenantConfigInfo = ref<TenantConfigInfo>(null);
|
||||
|
||||
// 租户订阅限制强受控标志
|
||||
const enableSubscription = computed((): number => {
|
||||
return tenantConfigInfo.value?.enableSubscription ?? 0;
|
||||
});
|
||||
// 已渲染的分类索引集合(用于懒加载优化)
|
||||
const renderedCategoryIndexes = ref<Set<number>>(new Set([0]));
|
||||
|
||||
// // 新增标志变量,用于区分首次加载
|
||||
// const isFirstLoad = ref<boolean>(true)
|
||||
|
||||
/**
|
||||
* 判断是否应该渲染该分类
|
||||
* 只渲染当前页和相邻的前后各1页,减少DOM节点数量
|
||||
*/
|
||||
const shouldRenderCategory = (index: number): boolean => {
|
||||
const currentIndex = activeSwiperIndex.value;
|
||||
// 渲染当前页、前一页、后一页
|
||||
const shouldRender = Math.abs(index - currentIndex) <= 1;
|
||||
if (shouldRender) {
|
||||
renderedCategoryIndexes.value.add(index);
|
||||
}
|
||||
// 已经渲染过的页面保持渲染状态,避免频繁创建销毁
|
||||
return renderedCategoryIndexes.value.has(index);
|
||||
};
|
||||
|
||||
// 获取用户配置
|
||||
const fetchTenantConfig = async () => {
|
||||
const { code, data } = await apiTenantConfig();
|
||||
if (code === SUCCESS_CODE) {
|
||||
tenantConfigInfo.value = data;
|
||||
uni.setNavigationBarTitle({
|
||||
title: data?.siteName,
|
||||
});
|
||||
// 缓存租户信息
|
||||
uni.setStorageSync(TENANT_CONFIG_INFO, JSON.stringify(data));
|
||||
}
|
||||
};
|
||||
|
||||
// 获取分类列表
|
||||
const fetchCategories = async () => {
|
||||
const response = await apiPublishedCategoryList();
|
||||
const { code, data } = response as unknown as RequestResponse<
|
||||
SquareCategoryInfo[]
|
||||
>;
|
||||
if (code === SUCCESS_CODE) {
|
||||
// 过滤掉游戏分类 - 微信小程序(属平台未允许内容)违反《微信小程序平台运营规范常见拒绝情形3.4》
|
||||
//#ifdef MP-WEIXIN
|
||||
data?.forEach((item) => {
|
||||
if (item.type === "PageApp") {
|
||||
item.children = item?.children.filter((child) => {
|
||||
return child.key !== "Game";
|
||||
});
|
||||
}
|
||||
});
|
||||
//#endif
|
||||
// 智能体需要单独处理
|
||||
let filterType = props.agentType;
|
||||
if (props.agentType === "ChatBot") {
|
||||
filterType = SquareAgentTypeEnum.Agent;
|
||||
}
|
||||
|
||||
// 获取智能体分类列表
|
||||
const agentCategoryList =
|
||||
data.find((item: SquareCategoryInfo) => item.type === filterType)
|
||||
?.children || [];
|
||||
// 转换为分类列表
|
||||
const categoryData = agentCategoryList.map(
|
||||
(item: SquareCategoryInfo) => ({
|
||||
key: item.key,
|
||||
label: item.label,
|
||||
active: false,
|
||||
}),
|
||||
);
|
||||
categories.value = [
|
||||
{ key: CONST_ALL, label: "Mobile.Common.all", active: true },
|
||||
...categoryData,
|
||||
];
|
||||
} else if (code !== USER_NO_LOGIN && code !== REDIRECT_LOGIN) {
|
||||
uni.showToast({
|
||||
title: t("Mobile.AgentList.fetchCategoryFailed"),
|
||||
icon: "error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 获取智能体列表
|
||||
const fetchAgentList = async (
|
||||
category: string = CONST_ALL,
|
||||
pageNum: number = 1,
|
||||
keyword: string = searchKeyword.value,
|
||||
) => {
|
||||
if (loadingMap.value[category]) return;
|
||||
// 设置加载状态 - 使用直接赋值而非解构,减少响应式更新
|
||||
loadingMap.value[category] = true;
|
||||
const params: SquarePublishedListParams = {
|
||||
targetType: AgentComponentTypeEnum.Agent,
|
||||
targetSubType: props.agentType,
|
||||
page: pageNum,
|
||||
pageSize: 10,
|
||||
category: category === CONST_ALL ? "" : category,
|
||||
kw: keyword,
|
||||
};
|
||||
// 获取智能体列表
|
||||
const response = await apiPublishedAgentList(params);
|
||||
const { code, data, message } = response as unknown as RequestResponse<
|
||||
Page<SquarePublishedItemInfo>
|
||||
>;
|
||||
// 成功
|
||||
if (code === SUCCESS_CODE) {
|
||||
// 过滤掉游戏分类 - 微信小程序(属平台未允许内容)违反《微信小程序平台运营规范常见拒绝情形3.4》
|
||||
//#ifdef MP-WEIXIN
|
||||
data.records = data.records?.filter((item) => {
|
||||
return item.category !== "Game";
|
||||
});
|
||||
//#endif
|
||||
const newList = data.records || [];
|
||||
if (pageNum > 1) {
|
||||
// 追加数据
|
||||
const existingList = agentListMap.value[category] || [];
|
||||
agentListMap.value[category] = [...existingList, ...newList];
|
||||
} else {
|
||||
// 替换数据
|
||||
agentListMap.value[category] = newList;
|
||||
}
|
||||
hasMoreMap.value[category] = data.pages > pageNum;
|
||||
pageMap.value[category] = pageNum;
|
||||
} else if (code !== USER_NO_LOGIN && code !== REDIRECT_LOGIN) {
|
||||
uni.showToast({
|
||||
title: message || t("Mobile.AgentList.fetchAgentFailed"),
|
||||
icon: "error",
|
||||
});
|
||||
}
|
||||
loadingMap.value[category] = false;
|
||||
};
|
||||
|
||||
const initData = async () => {
|
||||
// 获取分类列表
|
||||
fetchCategories();
|
||||
// 1. 首先尝试从本地缓存读取,以实现秒开级的初始渲染,防止 siteName 等标题闪烁
|
||||
const tenantConfigInfoString = await uni.getStorageSync(TENANT_CONFIG_INFO);
|
||||
if (tenantConfigInfoString) {
|
||||
tenantConfigInfo.value = JSON.parse(tenantConfigInfoString);
|
||||
uni.setNavigationBarTitle({
|
||||
title: tenantConfigInfo.value?.siteName,
|
||||
});
|
||||
}
|
||||
// 2. 然后必须发起强同步网络请求更新最新状态,防止缓存过期导致计费状态显示不准确
|
||||
await fetchTenantConfig();
|
||||
};
|
||||
|
||||
onPageShow(() => {
|
||||
// isFirstLoad.value = true
|
||||
initData();
|
||||
// 预加载第一个分类的数据
|
||||
fetchAgentList();
|
||||
});
|
||||
|
||||
// onTabItemTap(async () => {
|
||||
// if (isFirstLoad.value) {
|
||||
// isFirstLoad.value = false
|
||||
// return
|
||||
// }
|
||||
// // 预加载第一个分类的数据
|
||||
// fetchAgentList(activeCategory.value)
|
||||
// })
|
||||
|
||||
onUnload(() => {
|
||||
if (searchTimeoutRef.value) {
|
||||
clearTimeout(searchTimeoutRef.value);
|
||||
}
|
||||
});
|
||||
|
||||
// 点击智能体分类标签
|
||||
const handleCategoryClick = (key: string, index: number) => {
|
||||
// 当前分类
|
||||
activeCategory.value = key;
|
||||
// 当前swiper索引
|
||||
activeSwiperIndex.value = index;
|
||||
// 滚动到对应的分类标签
|
||||
scrollIntoView.value = key;
|
||||
|
||||
// 如果该分类还没有数据,则加载数据
|
||||
if (!agentListMap.value[key] || agentListMap.value[key].length === 0) {
|
||||
pageMap.value[key] = 1;
|
||||
hasMoreMap.value[key] = true;
|
||||
fetchAgentList(key);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理Swiper切换
|
||||
const handleSwiperChange = (e: UniSwiperChangeEvent) => {
|
||||
const index = e.detail.current;
|
||||
const category = categories.value[index];
|
||||
if (category && category.key !== activeCategory.value) {
|
||||
// 当前分类
|
||||
activeCategory.value = category.key;
|
||||
// 当前swiper索引
|
||||
activeSwiperIndex.value = index;
|
||||
|
||||
// 滚动到对应的分类标签
|
||||
const id = categories.value[index].key;
|
||||
scrollIntoView.value = id;
|
||||
|
||||
// 如果该分类还没有数据,则加载数据
|
||||
if (
|
||||
!agentListMap.value[category.key] ||
|
||||
agentListMap.value[category.key].length === 0
|
||||
) {
|
||||
pageMap.value[category.key] = 1;
|
||||
hasMoreMap.value[category.key] = true;
|
||||
fetchAgentList(category.key);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 处理搜索
|
||||
const handleSearch = async () => {
|
||||
// 检查登录状态,如果未登录则显示登录弹窗
|
||||
const isLoggedIn = await checkAuthAndShowPopup();
|
||||
if (!isLoggedIn) {
|
||||
return;
|
||||
}
|
||||
const currentUrl = getCurrentPagePath();
|
||||
uni.navigateTo({
|
||||
url:
|
||||
`/subpackages/pages/agent-search/agent-search?type=${props.agentType}&backUrl=` +
|
||||
encodeURIComponent(currentUrl),
|
||||
});
|
||||
};
|
||||
|
||||
// 跳转到智能体详情页
|
||||
const handleAgentClick = (agentInfo: SquarePublishedItemInfo) => {
|
||||
// 使用登录拦截逻辑处理点击
|
||||
baseHandleAgentClick({
|
||||
targetId: agentInfo.targetId,
|
||||
name: agentInfo.name,
|
||||
agentType: agentInfo.agentType,
|
||||
icon: agentInfo.icon,
|
||||
description: agentInfo.description,
|
||||
});
|
||||
};
|
||||
|
||||
// 处理收藏与取消收藏
|
||||
const handleToggleCollect = async (
|
||||
agentInfo: SquarePublishedItemInfo,
|
||||
category: string,
|
||||
) => {
|
||||
const { targetId, collect } = agentInfo;
|
||||
// 获取当前分类列表
|
||||
const existingList = agentListMap.value[category] || [];
|
||||
// 更新列表收藏状态,并更新统计信息
|
||||
const updatedList = existingList.map((item: SquarePublishedItemInfo) => {
|
||||
if (item.targetId === targetId) {
|
||||
const collectCount = item.statistics?.collectCount || 0;
|
||||
const newCollectCount = collect ? collectCount - 1 : collectCount + 1;
|
||||
return {
|
||||
...item,
|
||||
collect: !collect,
|
||||
statistics: {
|
||||
...item.statistics,
|
||||
collectCount: newCollectCount,
|
||||
},
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
agentListMap.value[category] = updatedList;
|
||||
// 更新数据库收藏状态
|
||||
if (collect) {
|
||||
const { code } = await apiUnCollectAgent(targetId);
|
||||
if (code !== SUCCESS_CODE) {
|
||||
agentListMap.value[category] = existingList;
|
||||
uni.showToast({
|
||||
title: t("Mobile.AgentList.unCollectFailed"),
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const { code } = await apiCollectAgent(targetId);
|
||||
if (code !== SUCCESS_CODE) {
|
||||
agentListMap.value[category] = existingList;
|
||||
uni.showToast({
|
||||
title: t("Mobile.AgentList.collectFailed"),
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 下拉刷新
|
||||
const handleRefresh = () => {
|
||||
const currentCategory = activeCategory.value;
|
||||
if (refreshingMap.value[currentCategory]) {
|
||||
return;
|
||||
}
|
||||
refreshingMap.value[currentCategory] = true;
|
||||
pageMap.value[currentCategory] = 1;
|
||||
hasMoreMap.value[currentCategory] = true;
|
||||
fetchAgentList(currentCategory).finally(() => {
|
||||
// 添加延迟,让loading效果更明显
|
||||
setTimeout(() => {
|
||||
refreshingMap.value[currentCategory] = false;
|
||||
}, 500);
|
||||
});
|
||||
};
|
||||
|
||||
// 加载更多
|
||||
const handleLoadMore = () => {
|
||||
const currentPage = pageMap.value[activeCategory.value] || 1;
|
||||
const currentHasMore = hasMoreMap.value[activeCategory.value] !== false;
|
||||
const currentLoading = loadingMap.value[activeCategory.value];
|
||||
|
||||
if (currentHasMore && !currentLoading) {
|
||||
fetchAgentList(activeCategory.value, currentPage + 1);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background-color: #fff;
|
||||
/* #ifdef MP-WEIXIN */
|
||||
// padding-top: env(safe-area-inset-top);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
/* #endif */
|
||||
|
||||
.icon-Search {
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
|
||||
.category-nav {
|
||||
padding: 0 32rpx 16rpx 32rpx;
|
||||
|
||||
/* 分类导航 */
|
||||
.category-scroll {
|
||||
scroll-behavior: smooth;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
height: 80rpx;
|
||||
display: flex;
|
||||
// align-items: center;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.category-item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
height: 80rpx;
|
||||
padding: 0 32rpx;
|
||||
border-radius: 16rpx;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
user-select: none;
|
||||
color: #5e6470;
|
||||
font-size: 32rpx;
|
||||
font-weight: 400;
|
||||
|
||||
&.active {
|
||||
background-color: #f2f2ff;
|
||||
|
||||
.category-text {
|
||||
color: #5147ff;
|
||||
}
|
||||
}
|
||||
|
||||
.category-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 400;
|
||||
color: #5e6470;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 滚动部分 */
|
||||
.scroll-section {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 160rpx 40rpx;
|
||||
text-align: center;
|
||||
|
||||
.empty-image {
|
||||
width: 172rpx;
|
||||
height: 172rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin-top: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40rpx;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.loading-text::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;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* 没有更多数据状态 */
|
||||
.no-more-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40rpx;
|
||||
}
|
||||
|
||||
.no-more-text {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 占位视图 */
|
||||
.placeholder-view {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user