shinnosuke
5 天以前 e52934f6527027bd6008c2ade61c3083159efd4a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
<template>
    <view class="page">
        <scroll-view class="scroll-page" scroll-y @scroll="handleScroll">
            <view class="hero">
                <image class="hero-image" :src="homeImage('home-top.png')" mode="aspectFill"></image>
                <view class="hero-gradient"></view>
                <view v-if="homeVideoUrl != ''" class="play-button" @tap="playVideo"><text>向东看</text></view>
            </view>
 
            <view class="city-panel">
                <view class="city-heading"><text>才聚东区,共富未来</text><view class="city-label" @tap="openIntro"><text>幸福东区</text><image src="/static/home-assets/arrow-right.svg"></image></view></view>
                <view class="city-grid">
                    <view class="city-card city-intro" @tap="openIntro"><image class="city-bg" :src="homeImage('city-intro.jpg')" mode="aspectFill"></image><text class="intro-name">东区简介</text><view class="intro-button"><text>查看更多</text><image src="/static/home-assets/arrow-right.svg"></image></view></view>
                    <view class="city-side"><view class="city-card city-potential"><image class="city-bg" :src="homeImage('city-potential.jpg')" mode="aspectFill"></image><text>潜力城</text></view><view class="city-card city-golden"><image class="city-bg" :src="homeImage('city-golden.jpg')" mode="aspectFill"></image><text>黄金城</text></view></view>
                    <view class="city-side"><view class="city-card city-industry"><image class="city-bg" :src="homeImage('city-industry.jpg')" mode="aspectFill"></image><text>兴业城</text></view><view class="city-card city-livable"><image class="city-bg" :src="homeImage('city-livable.png')" mode="aspectFill"></image><text>宜居城</text></view></view>
                </view>
            </view>
 
            <view class="section-head"><view class="section-title"><text>人才资讯</text></view><text class="more" @tap="goNews">查看更多</text></view>
            <view v-if="homeLoading" class="news-state"><text>资讯加载中...</text></view>
            <view v-else-if="homeError" class="news-state" @tap="loadHome"><text>{{homeError}}</text><text class="news-retry">点击重试</text></view>
            <view v-else>
                <view class="news-featured" @tap="openNews(featuredNews.id)"><image v-if="featuredNews.coverUrl != ''" :src="featuredNews.coverUrl" mode="aspectFill"></image><view class="news-shade"></view><view class="top-badge"><text>置顶</text></view><view class="featured-copy"><text>{{featuredNews.title}}</text><text>{{featuredNews.date}}</text></view></view>
                <view class="news-list"><view class="news-row" v-for="item in news" :key="item.id" @tap="openNews(item.id)"><text class="news-name">{{item.title}}</text><view class="news-meta"><text v-if="item.isNew" class="new-badge">最新</text><text>{{item.date}}</text></view></view></view>
            </view>
            <view class="news-more" @tap="goNews"><text>查看更多</text></view>
            <view class="content-bottom"></view>
        </scroll-view>
 
        <view class="sticky-nav" :class="isStickyNav ? 'is-solid' : ''">
            <view class="sticky-nav-bg"></view>
            <view class="safe-space" :style="'height:' + navTop + 'px'"></view>
            <view class="nav-row" :style="'height:' + navHeight + 'px;padding-right:' + navRight + 'px'"><view class="nav-title"><text>攀枝花市东区人才服务平台</text></view><view class="capsule-reserve" :style="'width:' + navWidth + 'px'"></view></view>
        </view>
 
        <view class="tab-bar">
            <view class="tab active"><view class="tab-icon-wrap active-icon"><image src="/static/home-assets/home.svg"></image></view><text>首页</text></view>
            <view class="tab" @tap="goBenefits"><view class="tab-icon-wrap"><image src="/static/home-assets/benefits.svg"></image></view><text>人才权益</text></view>
            <view class="tab card-tab" @tap="goCard"><view class="card-icon"><image src="/static/home-assets/card.png"></image><text class="card-label">人才卡</text></view></view>
            <view class="tab" @tap="goPolicy"><view class="tab-icon-wrap"><image src="/static/home-assets/policy.svg"></image></view><text>人才政策</text></view>
            <view class="tab" @tap="goProfile"><view class="tab-icon-wrap"><image src="/static/home-assets/profile.svg"></image></view><text>个人中心</text></view>
        </view>
    </view>
</template>
 
<script setup lang="uts">
    import { isApiConfigured } from '../../config/api.uts'
    import { getAppHome } from '../../services/portal.uts'
    import { resolveApiUrl } from '../../services/http.uts'
 
    /**
     * 首页图片放在服务器上(`/uploads/home/…`),代码里只存相对路径:
     * 换图不用改代码、也不用重新上传体验版,和后台域名一起走环境配置。
     */
    const homeImage = (name: string): string => resolveApiUrl('/uploads/home/' + name)
 
    const navTop = ref(28)
    const navHeight = ref(32)
    const navWidth = ref(88)
    const navRight = ref(10)
    const isStickyNav = ref(false)
    const homeLoading = ref(false)
    const homeError = ref('')
    const homeVideoUrl = ref('')
    const miniProgramUrl = ref('')
    // 后台可配置目标小程序 AppID,配置后"幸福东区"等入口直接用小程序跳转,不再走 web-view。
    const miniProgramAppId = ref('')
    const miniProgramPath = ref('')
 
    onLoad(() => {
        // #ifdef MP-WEIXIN
        const capsule = uni.getMenuButtonBoundingClientRect()
        const systemInfo = uni.getSystemInfoSync()
        navTop.value = capsule.top
        navHeight.value = capsule.height
        navWidth.value = capsule.width
        navRight.value = systemInfo.windowWidth - capsule.right
        // #endif
        loadHome()
    })
 
    const featuredNews = ref({ id: 'news-20260603-senior-talent', title: '攀枝花市东区事业单位2026年直接考核招聘副高职称及以上人才公告', date: '2026-06-03 09:19', coverUrl: '' })
    const news = ref([
        { id: 'news-20260601-score-review', title: '关于攀枝花市东区事业单位2026年上半年公开考试招聘工作人员东区岗位加分情况、笔试总成绩及岗位排名、面试资格复审有关事宜的公告', date: '2026-06-01 16:36', isNew: true },
        { id: 'news-20260529-teacher-review', title: '攀枝花市2026年上半年公开考试招聘中小学教师东区岗位面试资格复审及领取面试通知书公告', date: '2026-05-29 10:42', isNew: false },
        { id: 'news-20260522-talent-notice', title: '关于攀枝花市东区2026年春季综合类、教育系统事业单位引才有关事宜的公告', date: '2026-05-22 11:01', isNew: false }
    ])
 
    const formatNoticeDate = (value: any): string => {
        if (value == null) return ''
        const text = '' + value
        return text.length >= 16 ? text.substring(0, 16).replace('T', ' ') : text.replace('T', ' ')
    }
    const loadHome = () => {
        if (!isApiConfigured()) return
        homeLoading.value = true
        homeError.value = ''
        getAppHome((data: any) => {
            homeLoading.value = false
            homeVideoUrl.value = data != null && data.homeVideoUrl != null ? data.homeVideoUrl as string : ''
            miniProgramUrl.value = data != null && data.miniProgramUrl != null ? data.miniProgramUrl as string : ''
            miniProgramAppId.value = data != null && data.miniProgramAppId != null ? data.miniProgramAppId as string : ''
            miniProgramPath.value = data != null && data.miniProgramPath != null ? data.miniProgramPath as string : ''
            const articles: any[] = data != null && data.news instanceof Array ? data.news as any[] : []
            if (articles.length == 0) return
            const first: any = articles[0]
            featuredNews.value = { id: '' + first.id, title: first.title != null ? first.title as string : '人才资讯', date: formatNoticeDate(first.publishTime), coverUrl: first.coverUrl != null && first.coverUrl != '' ? resolveApiUrl(first.coverUrl as string) : '' }
            const nextItems: any[] = []
            for (let index = 1; index < articles.length; index++) {
                const item: any = articles[index]
                nextItems.push({ id: '' + item.id, title: item.title != null ? item.title as string : '人才资讯', date: formatNoticeDate(item.publishTime), isNew: index == 1 })
            }
            news.value = nextItems
        }, (error) => {
            homeLoading.value = false
            homeError.value = error.message
        })
    }
 
    const goBenefits = () => uni.reLaunch({ url: '/pages/benefits/benefits' })
    const goCard = () => uni.reLaunch({ url: '/pages/card/card' })
    const goPolicy = () => uni.reLaunch({ url: '/pages/policy/policy' })
    const goNews = () => uni.navigateTo({ url: '/pages/news/news' })
    const goProfile = () => uni.reLaunch({ url: '/pages/profile/profile' })
    const playVideo = () => {
        if (homeVideoUrl.value == '') {
            uni.showToast({ title: '首页视频暂未配置', icon: 'none' })
            return
        }
        uni.navigateTo({ url: '/pages/home-video/home-video?url=' + encodeURIComponent(resolveApiUrl(homeVideoUrl.value)) })
    }
    const openIntro = () => {
        if (miniProgramAppId.value != '') {
            uni.navigateToMiniProgram({
                appId: miniProgramAppId.value,
                path: miniProgramPath.value,
                fail: () => {
                    uni.showToast({ title: '跳转小程序失败,请稍后重试', icon: 'none' })
                }
            })
            return
        }
        if (miniProgramUrl.value == '') {
            uni.showToast({ title: '首页跳转链接暂未配置', icon: 'none' })
            return
        }
        uni.navigateTo({ url: '/pages/external-webview/external-webview?source=homeMiniProgram' })
    }
    const openNews = (newsId: string) => uni.navigateTo({ url: '/pages/news-detail/news-detail?newsId=' + encodeURIComponent(newsId) })
    onPullDownRefresh(() => {
        loadHome()
        setTimeout(() => uni.stopPullDownRefresh(), 300)
    })
    const handleScroll = (event: any) => {
        const shouldShow = event.detail.scrollTop > 64
        if (isStickyNav.value != shouldShow) {
            isStickyNav.value = shouldShow
        }
    }
</script>
 
<style>
    .page { height: 100%; background-color: #FFFFFF; }
    .scroll-page { height: calc(100% - 126rpx); height: calc(100% - 126rpx - env(safe-area-inset-bottom)); background-color: #FFFFFF; }
    .hero { height: 468rpx; background-color: #FFFFFF; position: relative; overflow: hidden; border-bottom-left-radius: 48rpx; border-bottom-right-radius: 48rpx; }
    .hero-image { position: absolute; left: 0; top: 0; width: 100%; height: 468rpx; }
    .hero-gradient { position: absolute; z-index: 1; top: 0; left: 0; right: 0; height: 300rpx; background-image: linear-gradient(180deg, rgba(244,90,42,.88) 0%, rgba(255,130,60,.42) 62%, rgba(255,166,80,0) 100%); }
    .safe-space { height: 28px; position: relative; z-index: 2; }
    .nav-row { height: 32px; padding: 0 32rpx; position: relative; z-index: 2; flex-direction: row; align-items: center; justify-content: space-between; }
    .sticky-nav { position: absolute; z-index: 30; top: 0; left: 0; right: 0; padding-bottom: 20rpx; }
    .sticky-nav-bg { position: absolute; z-index: 0; top: 0; left: 0; right: 0; bottom: 0; opacity: 0; background-image: linear-gradient(180deg, #F26A38 0%, #EE542B 100%); transition: opacity 280ms ease-out; }
    .sticky-nav.is-solid .sticky-nav-bg { opacity: 1; }
    .nav-title { position: absolute; z-index: 1; left: 30rpx; top: 10rpx; height: 100%; flex-direction: row; align-items: center; justify-content: flex-start; }
    .nav-title text { color: #FFFFFF; font-family: 'FoodCalligraphyFull', 'FoodCalligraphy'; font-size: 38rpx; font-weight: 700; opacity: .95; }
    .capsule-reserve { width: 88px; height: 100%; flex-shrink: 0; }
    .play-button { position: absolute; z-index: 2; bottom: 20rpx; left: 50%; margin-left: -78rpx; width: 156rpx; height: 56rpx; border-radius: 28rpx; background-image: linear-gradient(180deg, #FFB75A 0%, #FF6A24 100%); border-width: 2rpx; border-color: rgba(255,255,255,.82); box-shadow: 0 7rpx 14rpx rgba(234,92,26,.26); flex-direction: row; justify-content: center; align-items: center; }
    .play-button text { color: #FFFFFF; font-size: 23rpx; }
 
    .city-panel { margin: -10rpx 16rpx 38rpx; padding: 22rpx 0 0; overflow: visible; background-color: #FFFFFF; border-radius: 26rpx; position: relative; box-shadow: 0 -8rpx 14rpx rgba(255, 103, 39, .27); }
    .city-heading { height: 58rpx; margin: 0 22rpx; flex-direction: row; justify-content: space-between; align-items: center; }
    .city-heading > text { color: #F16624; font-size: 35rpx; font-weight: 700; letter-spacing: 1rpx; background-image: linear-gradient(180deg, #F45A2A 0%, #FFB257 100%); background-clip: text; -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
    .city-label { margin-right: 48rpx; padding: 10rpx 13rpx; border-radius: 18rpx; background-image: linear-gradient(180deg, #FFB85E 0%, #FF6726 100%); box-shadow: 0 5rpx 10rpx rgba(239,101,34,.22); flex-direction: row; align-items: center; } .city-label text { color: #FFFFFF; font-size: 20rpx; } .city-label image { width: 20rpx; height: 20rpx; margin-left: 7rpx; }
    .city-grid { box-sizing: border-box; height: 292rpx; margin-top: 18rpx; padding: 22rpx; overflow: visible; background-image: linear-gradient(180deg, #FF5F2D 0%, #FFB36B 100%); border-bottom-left-radius: 26rpx; border-bottom-right-radius: 26rpx; flex-direction: row; } .city-card { overflow: hidden; border-radius: 17rpx; position: relative; justify-content: flex-end; background-image: linear-gradient(135deg, #E96C3D 0%, #F8AA56 100%); }
    .city-bg { position: absolute; left: 0; top: 0; width: 100%; height: 100%; }
    .city-intro { width: 242rpx; height: 308rpx; box-sizing: border-box; margin-right: 14rpx; padding: 22rpx; } .intro-name { position: absolute; z-index: 1; top: 50%; left: 0; right: 0; transform: translateY(-50%); color: #FFFFFF; font-size: 37rpx; font-weight: 700; text-align: center; } .intro-button { position: relative; z-index: 1; align-self: center; box-sizing: border-box; width: 146rpx; height: 42rpx; margin-top: 13rpx; padding: 0 12rpx; border-radius: 21rpx; background-image: linear-gradient(90deg, rgba(255,207,138,.92) 0%, rgba(255,101,36,.68) 100%); border-width: 1rpx; border-color: rgba(255,255,255,.42); flex-direction: row; align-items: center; justify-content: space-between; } .intro-button text { color: #FFFFFF; font-size: 19rpx; } .intro-button image { width: 19rpx; height: 19rpx; margin-left: 8rpx; }
    .city-side { height: 308rpx; flex: 1; margin-right: 14rpx; justify-content: space-between; } .city-side:last-child { margin-right: 0; } .city-side .city-card { height: 147rpx; padding: 0; } .city-side .city-card > text { position: absolute; z-index: 1; left: 0; right: 0; bottom: 0; padding: 10rpx 12rpx; color: #FFFFFF; font-size: 22rpx; font-weight: 700; text-shadow: 0 2rpx 8rpx rgba(0,0,0,.5); }
 
    .section-head { height: 93rpx; padding: 0 31rpx; flex-direction: row; align-items: center; justify-content: space-between; } .section-title { flex-direction: row; align-items: center; } .section-title text { color: #353535; font-size: 31rpx; font-weight: 700; } .more { color: #5C5C5C; font-size: 22rpx; }
    .news-state { min-height: 150rpx; margin: 0 31rpx; justify-content: center; align-items: center; color: #8B8B8B; font-size: 24rpx; } .news-retry { margin-top: 12rpx; color: #F16624; }
    .news-featured { height: 205rpx; margin: 0 19rpx; border-radius: 18rpx; overflow: hidden; position: relative; background-image: linear-gradient(135deg, #F26A38 0%, #F9A551 100%); } .news-featured > image { width: 100%; height: 100%; } .news-shade { position: absolute; left: 0; right: 0; bottom: 0; height: 118rpx; background-color: rgba(0,0,0,.44); } .top-badge { position: absolute; left: 20rpx; top: 18rpx; padding: 7rpx 14rpx; border-radius: 12rpx; background-color: #FFF3D4; } .top-badge text { color: #F2722E; font-size: 20rpx; font-weight: 700; } .featured-copy { position: absolute; left: 20rpx; right: 20rpx; bottom: 17rpx; } .featured-copy text:first-child { display: block; color: #FFFFFF; font-size: 24rpx; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .featured-copy text:last-child { display: block; margin-top: 8rpx; color: #FFFFFF; opacity: .85; font-size: 19rpx; }
    .news-list { margin: 0 31rpx; } .news-row { min-height: 116rpx; padding: 22rpx 0 16rpx; border-bottom-width: 1rpx; border-bottom-color: #EEEEEE; } .news-name { color: #363636; font-size: 25rpx; line-height: 36rpx; } .news-meta { margin-top: 12rpx; flex-direction: row; align-items: center; color: #98A2B3; font-size: 19rpx; } .new-badge { margin-right: 14rpx; padding: 4rpx 10rpx; border-radius: 12rpx; background-color: #FFF0D9; color: #FE7930; font-size: 18rpx; }
    .news-more { height: 87rpx; justify-content: center; align-items: center; color: #545454; font-size: 22rpx; } .content-bottom { height: 22rpx; }
    .tab-bar { height: 126rpx; height: calc(126rpx + constant(safe-area-inset-bottom)); height: calc(126rpx + env(safe-area-inset-bottom)); padding: 8rpx 14rpx; padding-bottom: calc(8rpx + constant(safe-area-inset-bottom)); padding-bottom: calc(8rpx + env(safe-area-inset-bottom)); box-sizing: border-box; overflow: visible; background-color: #FFFFFF; border-top-width: 1rpx; border-top-color: #F0F0F0; flex-direction: row; justify-content: space-between; } .tab { width: 20%; padding-top: 4rpx; align-items: center; justify-content: flex-start; color: #708099; font-size: 24rpx; } .tab-icon-wrap { width: 66rpx; height: 66rpx; margin-bottom: 13rpx; border-radius: 33rpx; justify-content: center; align-items: center; } .tab-icon-wrap image { width: 42rpx; height: 42rpx; } .active-icon { background-color: #FFF1F1; } .tab.active { color: #FF6030; font-weight: 700; } .card-tab { margin-top: -16rpx; padding-top: 0; justify-content: flex-start; } .card-icon { width: 160rpx; height: 160rpx; position: relative; justify-content: center; align-items: center; } .card-icon image { width: 160rpx; height: 160rpx; } .card-label { position: absolute; left: 0; right: 0; bottom: 42rpx; z-index: 1; color: #FFFFFF; font-size: 18rpx; font-weight: 700; text-align: center; }
    /* 人才资讯:提高标题与时间的阅读层级 */
    .featured-copy text:first-child { font-size: 28rpx; }
    .featured-copy text:last-child { font-size: 22rpx; }
    .news-name { font-size: 30rpx; line-height: 42rpx; }
    .news-meta { margin-top: 14rpx; font-size: 22rpx; }
    .new-badge { font-size: 20rpx; }
</style>