681 lines
16 KiB
Vue
681 lines
16 KiB
Vue
<template>
|
||
<view class="page">
|
||
<scroll-view
|
||
scroll-y
|
||
class="page-scroll"
|
||
refresher-enabled
|
||
:refresher-triggered="refreshing"
|
||
@refresherrefresh="handleRefresh"
|
||
@scrolltolower="loadMore"
|
||
>
|
||
<common-hero title="挑战杯人才库" :use-image-bg="true"></common-hero>
|
||
<view class="hero-card hero-card--green">
|
||
<view class="hero-title">创新竞赛历史人才数据库</view>
|
||
<view class="hero-desc">
|
||
按姓名、学校、人才类型、参赛项目和比赛级别查看历届挑战杯人才线索,支持复制详情链接用于进一步跟踪。
|
||
</view>
|
||
<view class="hero-meta">
|
||
<view class="meta-item">
|
||
<view class="meta-value">{{ list.length }}</view>
|
||
<view class="meta-label">当前已加载</view>
|
||
</view>
|
||
<view class="meta-item">
|
||
<view class="meta-value">{{ levelOptions.length }}</view>
|
||
<view class="meta-label">级别筛选</view>
|
||
</view>
|
||
<view class="meta-item">
|
||
<view class="meta-value">{{ talentTypeCount }}</view>
|
||
<view class="meta-label">人才类型</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="filter-card">
|
||
<view class="search-box">
|
||
<uv-input
|
||
v-model.trim="filters.keywords"
|
||
class="search-input"
|
||
placeholder="姓名/项目/学校/奖项"
|
||
confirm-type="search"
|
||
@confirm="applyFilters"
|
||
:maxlength="-1"
|
||
border="none"
|
||
/>
|
||
<view class="search-btn" @tap="applyFilters">查询</view>
|
||
</view>
|
||
|
||
<view class="field-row">
|
||
<uv-input
|
||
v-model.trim="filters.schoolName"
|
||
class="field-input"
|
||
placeholder="参赛学校"
|
||
confirm-type="search"
|
||
@confirm="applyFilters"
|
||
:maxlength="-1"
|
||
border="none"
|
||
/>
|
||
</view>
|
||
|
||
<view class="field-row">
|
||
<uv-input
|
||
v-model.trim="filters.talentType"
|
||
class="field-input"
|
||
placeholder="人才类型"
|
||
confirm-type="search"
|
||
@confirm="applyFilters"
|
||
:maxlength="-1"
|
||
border="none"
|
||
/>
|
||
</view>
|
||
|
||
<view class="picker-row picker-row--single">
|
||
<picker class="picker-item" mode="selector" :range="levelLabels" :value="levelIndex"
|
||
@change="handleLevelChange">
|
||
<view class="picker-trigger">{{ filters.matchLevel || '比赛级别' }}</view>
|
||
</picker>
|
||
</view>
|
||
|
||
<view class="action-row">
|
||
<view class="action-btn action-btn--primary action-btn--green" @tap="applyFilters">查询</view>
|
||
<view class="action-btn" @tap="resetFilters">重置</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="loading && !list.length" class="state-card">
|
||
<view class="state-title">正在加载人才库...</view>
|
||
<view class="state-desc">请稍候,正在同步挑战杯历史人才数据。</view>
|
||
</view>
|
||
|
||
<view v-else-if="loadError && !list.length" class="state-card state-card--error">
|
||
<view class="state-title">加载失败</view>
|
||
<view class="state-desc">{{ loadError }}</view>
|
||
<view class="state-action state-action--green" @tap="loadData(true)">重新加载</view>
|
||
</view>
|
||
|
||
<view v-else-if="!list.length" class="state-card">
|
||
<view class="state-title">暂无匹配人才</view>
|
||
<view class="state-desc">请调整搜索条件后重新查询。</view>
|
||
</view>
|
||
|
||
<view v-else class="card-list">
|
||
<view v-for="item in list" :key="item.id || item.sourceId" class="data-card">
|
||
<view class="card-top">
|
||
<view class="avatar-cover" :style="getCoverStyle(item.coverImage)">
|
||
<view class="avatar-text">{{ getAvatarText(item.personName) }}</view>
|
||
</view>
|
||
<view class="card-main">
|
||
<view class="card-title-row">
|
||
<view class="card-title">{{ item.personName || '未命名人才' }}</view>
|
||
<view class="tag-chip tag-chip--green">{{ item.talentType || '未分类' }}</view>
|
||
</view>
|
||
<view class="card-school">{{ item.schoolName || '未知学校' }}</view>
|
||
<view class="card-project">{{ item.projectName || '未录入项目名称' }}</view>
|
||
<view class="tag-row">
|
||
<view class="tag-chip">{{ item.awardName || '未录入奖项' }}</view>
|
||
<view class="tag-chip tag-chip--blue">{{ item.matchLevel || '未知级别' }}</view>
|
||
<view class="tag-chip tag-chip--soft">{{ item.matchTerm || '未知届次' }}</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="info-grid">
|
||
<view class="info-item">
|
||
<view class="info-label">来源ID</view>
|
||
<view class="info-value">{{ item.sourceId || '-' }}</view>
|
||
</view>
|
||
<view class="info-item">
|
||
<view class="info-label">来源页码</view>
|
||
<view class="info-value">{{ item.pageNo || '-' }}</view>
|
||
</view>
|
||
<view class="info-item">
|
||
<view class="info-label">同步时间</view>
|
||
<view class="info-value">{{ formatDateTime(item.lastSyncTime) }}</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="card-footer">
|
||
<view class="footer-link footer-link--green" @tap="copyLink(item.detailUrl)">复制详情链接</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="list.length" class="load-more">
|
||
<view v-if="loadingMore" class="load-more-text">正在加载更多...</view>
|
||
<view v-else-if="hasMore" class="load-more-text" @tap="loadMore">点击加载更多</view>
|
||
<view v-else class="load-more-text load-more-text--end">没有更多数据了</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import CommonHero from '../../components/common-hero/common-hero.vue'
|
||
import {pageTzbTalentList} from './tzb-database-service'
|
||
|
||
const DEFAULT_FILTERS = () => ({
|
||
keywords: '',
|
||
schoolName: '',
|
||
talentType: '',
|
||
matchLevel: ''
|
||
})
|
||
|
||
export default {
|
||
components: {
|
||
CommonHero
|
||
},
|
||
data() {
|
||
return {
|
||
loading: false,
|
||
loadingMore: false,
|
||
refreshing: false,
|
||
loadError: '',
|
||
page: 1,
|
||
limit: 10,
|
||
total: 0,
|
||
hasMore: true,
|
||
list: [],
|
||
filters: DEFAULT_FILTERS(),
|
||
levelOptions: ['国赛', '省赛', '校赛']
|
||
}
|
||
},
|
||
computed: {
|
||
levelLabels() {
|
||
return ['全部级别', ...this.levelOptions]
|
||
},
|
||
levelIndex() {
|
||
if (!this.filters.matchLevel) {
|
||
return 0
|
||
}
|
||
const index = this.levelOptions.findIndex((item) => item === this.filters.matchLevel)
|
||
return index >= 0 ? index + 1 : 0
|
||
},
|
||
talentTypeCount() {
|
||
const set = new Set(this.list.map((item) => item.talentType).filter(Boolean))
|
||
return set.size || '-'
|
||
}
|
||
},
|
||
onLoad() {
|
||
this.loadData(true)
|
||
},
|
||
methods: {
|
||
showToast(title) {
|
||
uni.showToast({
|
||
title,
|
||
icon: 'none'
|
||
})
|
||
},
|
||
formatDateTime(value) {
|
||
if (!value) {
|
||
return '-'
|
||
}
|
||
const date = new Date(String(value).replace(/-/g, '/'))
|
||
if (Number.isNaN(date.getTime())) {
|
||
return value
|
||
}
|
||
const pad = (item) => String(item).padStart(2, '0')
|
||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
|
||
},
|
||
getCoverStyle(url) {
|
||
if (url) {
|
||
return `background-image: linear-gradient(rgba(15, 23, 42, 0.12), rgba(15, 23, 42, 0.24)), url(${url});`
|
||
}
|
||
return 'background: linear-gradient(135deg, #34d399 0%, #10b981 100%);'
|
||
},
|
||
getAvatarText(name) {
|
||
return String(name || '人才').slice(-2)
|
||
},
|
||
handleLevelChange(event) {
|
||
const index = Number(event.detail.value || 0)
|
||
this.filters.matchLevel = index === 0 ? '' : this.levelOptions[index - 1]
|
||
},
|
||
handleRefresh() {
|
||
this.refreshing = true
|
||
this.loadData(true)
|
||
},
|
||
applyFilters() {
|
||
this.loadData(true)
|
||
},
|
||
resetFilters() {
|
||
this.filters = DEFAULT_FILTERS()
|
||
this.loadData(true)
|
||
},
|
||
async loadData(reset = false) {
|
||
if (this.loading || this.loadingMore) {
|
||
if (reset) {
|
||
this.refreshing = false
|
||
}
|
||
return
|
||
}
|
||
const nextPage = reset ? 1 : this.page
|
||
if (!reset && !this.hasMore) {
|
||
return
|
||
}
|
||
if (reset) {
|
||
this.loading = true
|
||
this.loadError = ''
|
||
} else {
|
||
this.loadingMore = true
|
||
}
|
||
try {
|
||
const params = {
|
||
page: nextPage,
|
||
limit: this.limit
|
||
}
|
||
if (this.filters.keywords) params.keywords = this.filters.keywords
|
||
if (this.filters.schoolName) params.schoolName = this.filters.schoolName
|
||
if (this.filters.talentType) params.talentType = this.filters.talentType
|
||
if (this.filters.matchLevel) params.matchLevel = this.filters.matchLevel
|
||
const result = await pageTzbTalentList(params)
|
||
const nextList = (result && result.list) || []
|
||
const count = Number((result && result.count) || 0)
|
||
this.total = count
|
||
this.page = nextPage + 1
|
||
this.list = reset ? nextList : this.list.concat(nextList)
|
||
this.hasMore = this.list.length < count
|
||
} catch (error) {
|
||
this.loadError = (error && error.message) || '人才库加载失败'
|
||
this.showToast(this.loadError)
|
||
} finally {
|
||
this.loading = false
|
||
this.loadingMore = false
|
||
this.refreshing = false
|
||
}
|
||
},
|
||
loadMore() {
|
||
if (!this.loading && !this.loadingMore && this.hasMore) {
|
||
this.loadData(false)
|
||
}
|
||
},
|
||
copyLink(url) {
|
||
if (!url) {
|
||
this.showToast('暂无详情链接')
|
||
return
|
||
}
|
||
uni.setClipboardData({
|
||
data: url,
|
||
success: () => {
|
||
this.showToast('详情链接已复制')
|
||
}
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.page {
|
||
min-height: 100vh;
|
||
background: radial-gradient(circle at top left, rgba(16, 185, 129, 0.14), transparent 28%),
|
||
linear-gradient(180deg, #f5fffb 0%, #fffdf8 100%);
|
||
}
|
||
|
||
.page-scroll {
|
||
height: 100vh;
|
||
//padding: 28rpx 24rpx 40rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.hero-card,
|
||
.filter-card,
|
||
.state-card,
|
||
.data-card {
|
||
border-radius: 30rpx;
|
||
box-shadow: 0 14rpx 34rpx rgba(15, 23, 42, 0.06);
|
||
}
|
||
|
||
.hero-card {
|
||
padding: 34rpx 36rpx;
|
||
border-radius: 28rpx;
|
||
background: rgba(255, 255, 255, 0.5);
|
||
box-shadow: 0 4rpx 12rpx 0 rgba(174, 174, 174, 0.4);
|
||
backdrop-filter: blur(24rpx);
|
||
-webkit-backdrop-filter: blur(24rpx);
|
||
border: 1rpx solid rgba(255, 255, 255, 0.5);
|
||
}
|
||
|
||
.hero-card--green .hero-badge {
|
||
background: rgba(20,150,242,.1);
|
||
}
|
||
|
||
.hero-badge {
|
||
display: inline-flex;
|
||
padding: 10rpx 18rpx;
|
||
border-radius: 999rpx;
|
||
font-size: 22rpx;
|
||
color: #1496f2;
|
||
}
|
||
|
||
.hero-title {
|
||
margin-top: 8rpx;
|
||
padding-left: 10rpx;
|
||
font-size: 30rpx;
|
||
line-height: 1.35;
|
||
font-weight: 700;
|
||
color: #18a0f7;
|
||
border-left: 5px solid #0f94ef;
|
||
}
|
||
|
||
.hero-desc {
|
||
margin-top: 20rpx;
|
||
font-size: 24rpx;
|
||
line-height: 1.8;
|
||
color: #7f7f7f;
|
||
}
|
||
|
||
.hero-meta {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
gap: 16rpx;
|
||
margin-top: 26rpx;
|
||
}
|
||
|
||
.meta-item {
|
||
padding: 18rpx 16rpx;
|
||
border-radius: 22rpx;
|
||
background: rgba(255, 255, 255, 0.12);
|
||
}
|
||
|
||
.meta-value {
|
||
font-size: 32rpx;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.meta-label {
|
||
margin-top: 6rpx;
|
||
font-size: 22rpx;
|
||
color: rgba(247, 255, 251, 0.84);
|
||
}
|
||
|
||
.filter-card {
|
||
margin-top: 22rpx;
|
||
padding: 24rpx;
|
||
background: rgba(255, 255, 255, 0.94);
|
||
}
|
||
|
||
.search-box,
|
||
.field-input,
|
||
.picker-trigger {
|
||
background: #f7fffb;
|
||
border: 1rpx solid rgba(16, 185, 129, 0.12);
|
||
}
|
||
|
||
.search-box {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
padding: 0 18rpx;
|
||
height: 84rpx;
|
||
border-radius: 999rpx;
|
||
}
|
||
|
||
:deep(.search-input.uv-input) {
|
||
flex: 1;
|
||
height: 100%;
|
||
padding: 0 !important;
|
||
background: transparent !important;
|
||
border: 0 !important;
|
||
}
|
||
|
||
:deep(.search-input.uv-input .uv-input__content__field-wrapper__field) {
|
||
font-size: 24rpx;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.search-btn {
|
||
flex-shrink: 0;
|
||
font-size: 24rpx;
|
||
font-weight: 600;
|
||
color: #0f9f71;
|
||
}
|
||
|
||
.field-row,
|
||
.picker-row,
|
||
.action-row {
|
||
margin-top: 16rpx;
|
||
}
|
||
|
||
:deep(.field-input.uv-input) {
|
||
width: 100%;
|
||
height: 84rpx;
|
||
padding: 0 20rpx !important;
|
||
border-radius: 22rpx;
|
||
box-sizing: border-box;
|
||
background: #f7fffb !important;
|
||
border: 1rpx solid rgba(16, 185, 129, 0.12) !important;
|
||
}
|
||
|
||
:deep(.field-input.uv-input .uv-input__content__field-wrapper__field) {
|
||
font-size: 24rpx;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.picker-row--single {
|
||
display: block;
|
||
}
|
||
|
||
.picker-item {
|
||
display: block;
|
||
}
|
||
|
||
.picker-trigger {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 84rpx;
|
||
border-radius: 22rpx;
|
||
font-size: 24rpx;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.action-row {
|
||
display: flex;
|
||
gap: 14rpx;
|
||
}
|
||
|
||
.action-btn {
|
||
flex: 1;
|
||
height: 82rpx;
|
||
border-radius: 22rpx;
|
||
background: #ecfdf5;
|
||
color: #0f9f71;
|
||
font-size: 24rpx;
|
||
font-weight: 600;
|
||
line-height: 82rpx;
|
||
text-align: center;
|
||
}
|
||
|
||
.action-btn--primary {
|
||
color: #fff;
|
||
}
|
||
|
||
.action-btn--green {
|
||
background: linear-gradient(135deg, #34d399, #10b981);
|
||
}
|
||
|
||
.state-card {
|
||
margin-top: 22rpx;
|
||
padding: 32rpx 28rpx;
|
||
background: rgba(255, 255, 255, 0.94);
|
||
}
|
||
|
||
.state-card--error {
|
||
background: rgba(247, 255, 251, 0.98);
|
||
border: 1rpx solid rgba(16, 185, 129, 0.16);
|
||
}
|
||
|
||
.state-title {
|
||
font-size: 30rpx;
|
||
font-weight: 700;
|
||
color: #1f2329;
|
||
}
|
||
|
||
.state-desc {
|
||
margin-top: 12rpx;
|
||
font-size: 24rpx;
|
||
line-height: 1.7;
|
||
color: #6c7480;
|
||
}
|
||
|
||
.state-action {
|
||
margin-top: 18rpx;
|
||
font-size: 24rpx;
|
||
font-weight: 600;
|
||
color: #2563eb;
|
||
}
|
||
|
||
.state-action--green {
|
||
color: #0f9f71;
|
||
}
|
||
|
||
.card-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 18rpx;
|
||
margin-top: 22rpx;
|
||
}
|
||
|
||
.data-card {
|
||
padding: 24rpx;
|
||
background: rgba(255, 255, 255, 0.95);
|
||
}
|
||
|
||
.card-top {
|
||
display: flex;
|
||
gap: 18rpx;
|
||
}
|
||
|
||
.avatar-cover {
|
||
flex-shrink: 0;
|
||
width: 144rpx;
|
||
height: 144rpx;
|
||
border-radius: 50%;
|
||
background-size: cover;
|
||
background-position: center;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.avatar-text {
|
||
font-size: 34rpx;
|
||
font-weight: 700;
|
||
color: #fff;
|
||
}
|
||
|
||
.card-main {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.card-title-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.card-title {
|
||
font-size: 30rpx;
|
||
font-weight: 700;
|
||
line-height: 1.5;
|
||
color: #0f172a;
|
||
}
|
||
|
||
.card-school,
|
||
.card-project {
|
||
margin-top: 8rpx;
|
||
font-size: 24rpx;
|
||
line-height: 1.6;
|
||
color: #64748b;
|
||
}
|
||
|
||
.card-project {
|
||
color: #1f2937;
|
||
}
|
||
|
||
.tag-row {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 10rpx;
|
||
margin-top: 14rpx;
|
||
}
|
||
|
||
.tag-chip {
|
||
padding: 8rpx 14rpx;
|
||
border-radius: 999rpx;
|
||
background: #fff4e6;
|
||
color: #c76b11;
|
||
font-size: 20rpx;
|
||
}
|
||
|
||
.tag-chip--green {
|
||
background: #e9fbf5;
|
||
color: #0f9f71;
|
||
}
|
||
|
||
.tag-chip--blue {
|
||
background: #eef5ff;
|
||
color: #2563eb;
|
||
}
|
||
|
||
.tag-chip--soft {
|
||
background: #f3f4f6;
|
||
color: #6b7280;
|
||
}
|
||
|
||
.info-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
gap: 14rpx;
|
||
margin-top: 18rpx;
|
||
}
|
||
|
||
.info-item {
|
||
padding: 18rpx;
|
||
border-radius: 22rpx;
|
||
background: #f7fffb;
|
||
}
|
||
|
||
.info-label {
|
||
font-size: 20rpx;
|
||
color: #8b94a3;
|
||
}
|
||
|
||
.info-value {
|
||
margin-top: 8rpx;
|
||
font-size: 23rpx;
|
||
line-height: 1.6;
|
||
color: #1f2937;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.card-footer {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
margin-top: 18rpx;
|
||
}
|
||
|
||
.footer-link {
|
||
font-size: 24rpx;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.footer-link--green {
|
||
color: #0f9f71;
|
||
}
|
||
|
||
.load-more {
|
||
padding: 28rpx 0 12rpx;
|
||
text-align: center;
|
||
}
|
||
|
||
.load-more-text {
|
||
font-size: 22rpx;
|
||
color: #64748b;
|
||
}
|
||
|
||
.load-more-text--end {
|
||
color: #94a3b8;
|
||
}
|
||
</style>
|