feat: 资源库和社区分页加载、网络超时错误提示优化
This commit is contained in:
@@ -51,7 +51,11 @@ request.interceptors.response.use(
|
|||||||
async (error: AxiosError) => {
|
async (error: AxiosError) => {
|
||||||
const detail = (error.response?.data as any)?.detail
|
const detail = (error.response?.data as any)?.detail
|
||||||
let msg = '请求失败,请稍后重试'
|
let msg = '请求失败,请稍后重试'
|
||||||
if (typeof detail === 'string') {
|
if (error.code === 'ECONNABORTED' || error.message?.includes('timeout')) {
|
||||||
|
msg = '请求超时,AI 生成可能需要较长时间,请稍后重试'
|
||||||
|
} else if (!error.response) {
|
||||||
|
msg = '网络连接失败,请检查网络后重试'
|
||||||
|
} else if (typeof detail === 'string') {
|
||||||
msg = detail
|
msg = detail
|
||||||
} else if (Array.isArray(detail)) {
|
} else if (Array.isArray(detail)) {
|
||||||
msg = detail.map((e: any) => e.msg || JSON.stringify(e)).join('; ')
|
msg = detail.map((e: any) => e.msg || JSON.stringify(e)).join('; ')
|
||||||
|
|||||||
@@ -81,6 +81,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="comHasMore && !loading && filteredPosts.length > 0" class="com-load-more">
|
||||||
|
<button type="button" :disabled="comLoadingMore" @click="loadMorePosts">
|
||||||
|
{{ comLoadingMore ? '加载中...' : '加载更多' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div v-if="!loading && filteredPosts.length === 0" class="empty-state">
|
<div v-if="!loading && filteredPosts.length === 0" class="empty-state">
|
||||||
<el-icon :size="48" color="#CBD5E1"><ChatDotRound /></el-icon>
|
<el-icon :size="48" color="#CBD5E1"><ChatDotRound /></el-icon>
|
||||||
<p>{{ emptyText }}</p>
|
<p>{{ emptyText }}</p>
|
||||||
@@ -163,6 +168,10 @@ const userStore = useUserStore()
|
|||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const posts = ref<any[]>([])
|
const posts = ref<any[]>([])
|
||||||
const rankedPosts = ref<any[]>([])
|
const rankedPosts = ref<any[]>([])
|
||||||
|
const comPageSize = 20
|
||||||
|
const comCurrentPage = ref(1)
|
||||||
|
const comHasMore = ref(false)
|
||||||
|
const comLoadingMore = ref(false)
|
||||||
const hotTags = ['互动课件', '教学动画', '课堂游戏', 'AI工具', '公开课', '单元复习', '实验模拟']
|
const hotTags = ['互动课件', '教学动画', '课堂游戏', 'AI工具', '公开课', '单元复习', '实验模拟']
|
||||||
const postTypes = [{ value: 'discussion', label: '案例' }, { value: 'share', label: '模板' }, { value: 'help', label: '需求' }]
|
const postTypes = [{ value: 'discussion', label: '案例' }, { value: 'share', label: '模板' }, { value: 'help', label: '需求' }]
|
||||||
const scopeOptions = [
|
const scopeOptions = [
|
||||||
@@ -267,23 +276,49 @@ function pathFromContentRef(ref: string) {
|
|||||||
return '/courseware/create'
|
return '/courseware/create'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchPostPage(page: number): Promise<any[]> {
|
||||||
|
const base = activeScope.value === 'all' ? {} : { scope: activeScope.value }
|
||||||
|
const res: any = await getPosts({ ...base, skip: (page - 1) * comPageSize, limit: comPageSize })
|
||||||
|
return Array.isArray(res) ? res : []
|
||||||
|
}
|
||||||
|
|
||||||
async function loadData() {
|
async function loadData() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
comCurrentPage.value = 1
|
||||||
try {
|
try {
|
||||||
const params = activeScope.value === 'all' ? undefined : { scope: activeScope.value }
|
const [items, ranking] = await Promise.all([
|
||||||
const [res, ranking] = await Promise.all([
|
fetchPostPage(1),
|
||||||
getPosts(params),
|
|
||||||
getPostRanking({ limit: 5 }),
|
getPostRanking({ limit: 5 }),
|
||||||
]) as any[]
|
]) as any[]
|
||||||
posts.value = Array.isArray(res) ? res : []
|
posts.value = Array.isArray(items) ? items : []
|
||||||
rankedPosts.value = Array.isArray(ranking) ? ranking : []
|
rankedPosts.value = Array.isArray(ranking) ? ranking : []
|
||||||
|
comHasMore.value = posts.value.length >= comPageSize
|
||||||
} catch {
|
} catch {
|
||||||
posts.value = []
|
posts.value = []
|
||||||
rankedPosts.value = []
|
rankedPosts.value = []
|
||||||
|
comHasMore.value = false
|
||||||
}
|
}
|
||||||
finally { loading.value = false }
|
finally { loading.value = false }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadMorePosts() {
|
||||||
|
if (comLoadingMore.value || !comHasMore.value) return
|
||||||
|
comLoadingMore.value = true
|
||||||
|
try {
|
||||||
|
const nextPage = comCurrentPage.value + 1
|
||||||
|
const items = await fetchPostPage(nextPage)
|
||||||
|
if (items.length > 0) {
|
||||||
|
posts.value = [...posts.value, ...items]
|
||||||
|
comCurrentPage.value = nextPage
|
||||||
|
}
|
||||||
|
comHasMore.value = items.length >= comPageSize
|
||||||
|
} catch {
|
||||||
|
ElMessage.warning('加载更多失败,请稍后重试')
|
||||||
|
} finally {
|
||||||
|
comLoadingMore.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function syncScopeFromRoute() {
|
function syncScopeFromRoute() {
|
||||||
const scope = route.query.scope
|
const scope = route.query.scope
|
||||||
const nextScope = scopeOptions.some(item => item.value === scope) ? String(scope) : 'all'
|
const nextScope = scopeOptions.some(item => item.value === scope) ? String(scope) : 'all'
|
||||||
@@ -490,4 +525,8 @@ watch(() => route.query.scope, () => {
|
|||||||
.post-card { grid-template-columns: 1fr; }
|
.post-card { grid-template-columns: 1fr; }
|
||||||
.post-actions { flex-direction: column; }
|
.post-actions { flex-direction: column; }
|
||||||
}
|
}
|
||||||
|
.com-load-more { display: flex; justify-content: center; padding: 20px 0; }
|
||||||
|
.com-load-more button { border: 1px solid #d1e7dd; border-radius: 22px; background: rgba(255,255,255,.85); color: #00543d; padding: 8px 36px; font-size: 14px; font-weight: 600; cursor: pointer; transition: all .2s; }
|
||||||
|
.com-load-more button:hover:not(:disabled) { background: #00543d; color: #fff; }
|
||||||
|
.com-load-more button:disabled { opacity: .6; cursor: not-allowed; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -74,6 +74,12 @@
|
|||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<div v-if="hasMore && !loading && displayResources.length > 0" class="load-more-wrap">
|
||||||
|
<button type="button" class="load-more-btn" :disabled="loadingMore" @click="loadMore">
|
||||||
|
{{ loadingMore ? '加载中...' : '加载更多' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="!loading && displayResources.length === 0" class="empty-state">
|
<div v-if="!loading && displayResources.length === 0" class="empty-state">
|
||||||
<el-icon><FolderOpened /></el-icon>
|
<el-icon><FolderOpened /></el-icon>
|
||||||
<strong>没有找到相关资源</strong>
|
<strong>没有找到相关资源</strong>
|
||||||
@@ -208,6 +214,10 @@ const router = useRouter()
|
|||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const resources = ref<any[]>([])
|
const resources = ref<any[]>([])
|
||||||
|
const pageSize = 20
|
||||||
|
const currentPage = ref(1)
|
||||||
|
const hasMore = ref(false)
|
||||||
|
const loadingMore = ref(false)
|
||||||
const showUpload = ref(false)
|
const showUpload = ref(false)
|
||||||
const showDetail = ref(false)
|
const showDetail = ref(false)
|
||||||
const showEdit = ref(false)
|
const showEdit = ref(false)
|
||||||
@@ -616,32 +626,59 @@ async function handleCreateResource() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchPage(page: number): Promise<any[]> {
|
||||||
|
if ((filters.resource_type === 'favorite' || filters.resource_type === 'mine') && !ensureResourceLogin('请先登录后再查看个人资源')) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
const params = {
|
||||||
|
resource_type: filters.resource_type === 'favorite' || filters.resource_type === 'mine' ? '' : filters.resource_type,
|
||||||
|
subject: filters.subject,
|
||||||
|
keyword: filters.keyword,
|
||||||
|
sort: filters.sort,
|
||||||
|
skip: (page - 1) * pageSize,
|
||||||
|
limit: pageSize,
|
||||||
|
}
|
||||||
|
const res: any = filters.resource_type === 'favorite'
|
||||||
|
? await getFavoriteResources(params)
|
||||||
|
: filters.resource_type === 'mine'
|
||||||
|
? await getMyResources(params)
|
||||||
|
: await getResources(params)
|
||||||
|
return Array.isArray(res) ? res : []
|
||||||
|
}
|
||||||
|
|
||||||
async function loadData() {
|
async function loadData() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
currentPage.value = 1
|
||||||
try {
|
try {
|
||||||
if ((filters.resource_type === 'favorite' || filters.resource_type === 'mine') && !ensureResourceLogin('请先登录后再查看个人资源')) {
|
const items = await fetchPage(1)
|
||||||
resources.value = []
|
resources.value = items
|
||||||
return
|
hasMore.value = items.length >= pageSize
|
||||||
}
|
|
||||||
const params = {
|
|
||||||
resource_type: filters.resource_type === 'favorite' || filters.resource_type === 'mine' ? '' : filters.resource_type,
|
|
||||||
subject: filters.subject,
|
|
||||||
keyword: filters.keyword,
|
|
||||||
sort: filters.sort,
|
|
||||||
}
|
|
||||||
const res: any = filters.resource_type === 'favorite'
|
|
||||||
? await getFavoriteResources(params)
|
|
||||||
: filters.resource_type === 'mine'
|
|
||||||
? await getMyResources(params)
|
|
||||||
: await getResources(params)
|
|
||||||
resources.value = Array.isArray(res) ? res : []
|
|
||||||
} catch {
|
} catch {
|
||||||
resources.value = []
|
resources.value = []
|
||||||
|
hasMore.value = false
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadMore() {
|
||||||
|
if (loadingMore.value || !hasMore.value) return
|
||||||
|
loadingMore.value = true
|
||||||
|
try {
|
||||||
|
const nextPage = currentPage.value + 1
|
||||||
|
const items = await fetchPage(nextPage)
|
||||||
|
if (items.length > 0) {
|
||||||
|
resources.value = [...resources.value, ...items]
|
||||||
|
currentPage.value = nextPage
|
||||||
|
}
|
||||||
|
hasMore.value = items.length >= pageSize
|
||||||
|
} catch {
|
||||||
|
ElMessage.warning('加载更多失败,请稍后重试')
|
||||||
|
} finally {
|
||||||
|
loadingMore.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function unpublishResource(res: any) {
|
async function unpublishResource(res: any) {
|
||||||
if (typeof res.id !== 'number') return
|
if (typeof res.id !== 'number') return
|
||||||
if (!ensureResourceLogin('请先登录后再下架资源')) return
|
if (!ensureResourceLogin('请先登录后再下架资源')) return
|
||||||
@@ -772,6 +809,10 @@ watch(() => [route.query.keyword, route.query.tab], () => {
|
|||||||
.empty-state .el-icon { font-size: 32px; color: #9aaba4; }
|
.empty-state .el-icon { font-size: 32px; color: #9aaba4; }
|
||||||
.empty-state strong { color: #173f35; }
|
.empty-state strong { color: #173f35; }
|
||||||
.empty-state button { margin-top: 6px; border: none; border-radius: 18px; background: #00543d; color: #fff; padding: 8px 18px; font-weight: 800; cursor: pointer; }
|
.empty-state button { margin-top: 6px; border: none; border-radius: 18px; background: #00543d; color: #fff; padding: 8px 18px; font-weight: 800; cursor: pointer; }
|
||||||
|
.load-more-wrap { display: flex; justify-content: center; padding: 24px 0 8px; }
|
||||||
|
.load-more-btn { border: 1px solid #cfe0d3; border-radius: 24px; background: rgba(255,255,255,.8); color: #00543d; padding: 10px 40px; font-size: 15px; font-weight: 700; cursor: pointer; transition: all .2s; }
|
||||||
|
.load-more-btn:hover:not(:disabled) { background: #00543d; color: #fff; border-color: #00543d; }
|
||||||
|
.load-more-btn:disabled { opacity: .6; cursor: not-allowed; }
|
||||||
@media (max-width: 1380px) { .resource-grid { grid-template-columns: repeat(3, minmax(240px, 1fr)); } }
|
@media (max-width: 1380px) { .resource-grid { grid-template-columns: repeat(3, minmax(240px, 1fr)); } }
|
||||||
@media (max-width: 1080px) { .resource-grid { grid-template-columns: repeat(2, minmax(220px, 1fr)); } .filter-panel { align-items: stretch; flex-direction: column; } .filter-line { justify-content: space-between; } }
|
@media (max-width: 1080px) { .resource-grid { grid-template-columns: repeat(2, minmax(220px, 1fr)); } .filter-panel { align-items: stretch; flex-direction: column; } .filter-line { justify-content: space-between; } }
|
||||||
@media (max-width: 760px) { .resource-plaza { padding: 16px; } .resource-hero { align-items: flex-start; flex-direction: column; } .filter-line { align-items: stretch; flex-direction: column; } .search-box { width: 100%; } .resource-grid, .detail-actions, .upload-grid { grid-template-columns: 1fr; } }
|
@media (max-width: 760px) { .resource-plaza { padding: 16px; } .resource-hero { align-items: flex-start; flex-direction: column; } .filter-line { align-items: stretch; flex-direction: column; } .search-box { width: 100%; } .resource-grid, .detail-actions, .upload-grid { grid-template-columns: 1fr; } }
|
||||||
|
|||||||
Reference in New Issue
Block a user