shinnosuke
2026-09-16 c5a63165aca6468157e0b4807d511e828de2076a
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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
<template>
    <view class="page">
        <view class="nav-shell">
            <view class="safe-space" :style="'height:' + navTop + 'px'"></view>
            <view class="nav-row" :style="'height:' + navHeight + 'px;padding-right:' + navRight + 'px'">
                <view class="back" @tap="goBack"><image src="/static/news-assets/arrow-left.svg" mode="aspectFit"></image></view>
                <text class="nav-title">资讯详情</text>
                <view class="capsule-reserve" :style="'width:' + navWidth + 'px'"></view>
            </view>
            <view class="nav-bottom-space"></view>
        </view>
 
        <scroll-view class="article-scroll" scroll-y>
            <view v-if="isLoading" class="state-panel">
                <view class="loading-spinner"></view>
                <text class="state-title">正在加载资讯</text>
                <text class="state-desc">请稍候</text>
            </view>
            <view v-else-if="loadFailed" class="state-panel">
                <view class="state-symbol"><text>!</text></view>
                <text class="state-title">资讯内容加载失败</text>
                <text class="state-desc">{{loadErrorMessage == '' ? '请检查网络后重新加载' : loadErrorMessage}}</text>
                <view class="retry-button" @tap="retryLoad"><text>重新加载</text></view>
            </view>
            <view v-else-if="contentHtml.trim() == ''" class="state-panel">
                <view class="state-symbol is-empty"><text>—</text></view>
                <text class="state-title">暂无资讯内容</text>
                <text class="state-desc">该资讯可能已下架或正文暂未发布</text>
            </view>
            <view v-else>
                <view class="article-head">
                    <text class="article-title">{{articleTitle}}</text>
                    <view class="article-meta"><view class="meta-left"><text class="publisher">{{publisher}}</text><text class="publish-time">{{publishTime}}</text></view><view class="views"><image src="/static/news-assets/eye.svg" mode="aspectFit"></image><text>{{viewCount}}</text></view></view>
                </view>
                <view class="article-body">
                    <rich-text class="rich-content" :nodes="contentHtml"></rich-text>
                    <view class="bottom-space"></view>
                </view>
            </view>
        </scroll-view>
    </view>
</template>
 
<script setup lang="uts">
    import { isApiConfigured } from '../../config/api.uts'
    import { safeBack } from '../../utils/navigation.uts'
    import { sanitizeRichText } from '../../services/business.uts'
    import { getNewsDetail } from '../../services/portal.uts'
 
    const navTop = ref(28)
    const navHeight = ref(32)
    const navWidth = ref(88)
    const navRight = ref(10)
    const articleTitle = ref('')
    const publisher = ref('')
    const publishTime = ref('')
    const viewCount = ref('')
    const contentHtml = ref('')
    const isLoading = ref(true)
    const loadFailed = ref(false)
    const loadErrorMessage = ref('')
    const previewState = ref('success')
    const newsId = ref('')
    const demoContentHtml = '<div style="color:#747F8F;font-size:16px;line-height:1.9;word-break:break-all;">'
        + '<p style="margin:0 0 14px;">根据《关于攀枝花市事业单位2026年上半年公开考试招聘工作人员的公告》规定,现将本次东区岗位考生加分情况、笔试总成绩及岗位排名、面试资格复审相关事宜公告如下。</p>'
        + '<p style="margin:18px 0 6px;color:#687585;font-weight:600;">一、加分情况</p>'
        + '<p style="margin:0 0 14px;">此次事业单位公开考试招聘东区岗位政策性加分情况见附件1。</p>'
        + '<p style="margin:18px 0 6px;color:#687585;font-weight:600;">二、笔试总成绩和岗位排名</p>'
        + '<p style="margin:0 0 14px;">一般事业单位岗位笔试总成绩按以下原则计算:</p>'
        + '<p style="margin:0 0 14px;">笔试总成绩=《公共基础知识》×50%+《综合能力测试》×50%+政策性加分</p>'
        + '<p style="margin:0 0 14px;">请考生自行查询本人成绩及排名(具体见附件2),应聘人员笔试总成绩(含政策性加分)低于合格分数线的不能进入下一环节。</p>'
        + '<p style="margin:0 0 14px;">笔试考试中任一科目存在缺考、零分或违纪的考生,不进行笔试排名。</p>'
        + '<p style="margin:18px 0 6px;color:#687585;font-weight:600;">三、笔试成绩平均分及合格分数线</p>'
        + '<p style="margin:0 0 14px;">笔试成绩合格分数线为此次参加我市事业单位公招考试相同科目所有参考人员有效笔试成绩的平均分×90%。</p>'
        + '<p style="margin:0 0 14px;">笔试科目《公共基础知识》和《综合能力测试》笔试成绩平均分为54.43分,笔试成绩合格分数线为48.99分。</p>'
        + '<p style="margin:18px 0 6px;color:#687585;font-weight:600;">四、资格复审</p>'
        + '<p style="margin:0;">资格复审时间、地点及相关材料要求以公告附件及后续通知为准,请考生保持通讯畅通。</p>'
        + '</div>'
    const seniorTalentContentHtml = '<div style="color:#747F8F;font-size:16px;line-height:1.9;word-break:break-all;">'
        + '<p style="margin:0 0 14px;">根据事业单位公开招聘有关规定,攀枝花市东区面向社会直接考核招聘副高职称及以上人才,现将有关事项公告如下。</p>'
        + '<p style="margin:18px 0 6px;color:#687585;font-weight:600;">一、招聘对象</p>'
        + '<p style="margin:0 0 14px;">符合岗位条件、具有副高级及以上专业技术职称的专业人才。</p>'
        + '<p style="margin:18px 0 6px;color:#687585;font-weight:600;">二、报名与考核</p>'
        + '<p style="margin:0;">报名时间、岗位条件、资格审查及考核安排以正式公告附件为准,请报名人员保持联系方式畅通。</p>'
        + '</div>'
    const teacherReviewContentHtml = '<div style="color:#747F8F;font-size:16px;line-height:1.9;word-break:break-all;">'
        + '<p style="margin:0 0 14px;">根据攀枝花市2026年上半年公开考试招聘中小学教师工作安排,现公布东区岗位面试资格复审及面试通知书领取事项。</p>'
        + '<p style="margin:18px 0 6px;color:#687585;font-weight:600;">一、资格复审</p>'
        + '<p style="margin:0 0 14px;">进入资格复审人员须按规定携带身份证、学历学位证书、教师资格证及岗位要求的其他材料。</p>'
        + '<p style="margin:18px 0 6px;color:#687585;font-weight:600;">二、面试通知书</p>'
        + '<p style="margin:0;">资格复审合格人员按现场通知领取面试通知书,并按规定时间和地点参加面试。</p>'
        + '</div>'
    const talentNoticeContentHtml = '<div style="color:#747F8F;font-size:16px;line-height:1.9;word-break:break-all;">'
        + '<p style="margin:0 0 14px;">为做好2026年春季综合类、教育系统事业单位引才工作,现将资格确认、材料提交及后续安排公告如下。</p>'
        + '<p style="margin:18px 0 6px;color:#687585;font-weight:600;">有关安排</p>'
        + '<p style="margin:0;">请进入后续环节的人员按通知要求准备材料,并持续关注东区人才工作相关公告。</p>'
        + '</div>'
    const medicalCheckContentHtml = '<div style="color:#747F8F;font-size:16px;line-height:1.9;word-break:break-all;">'
        + '<p style="margin:0 0 14px;">根据2026年春季综合类、教育系统事业单位引才工作安排,现将体检人员、时间及注意事项公告如下。</p>'
        + '<p style="margin:18px 0 6px;color:#687585;font-weight:600;">体检要求</p>'
        + '<p style="margin:0 0 14px;">参加体检人员须携带本人有效身份证件,按通知时间到指定地点集合。</p>'
        + '<p style="margin:18px 0 6px;color:#687585;font-weight:600;">注意事项</p>'
        + '<p style="margin:0;">体检前请注意休息并保持空腹,未按规定参加体检的,按相关招聘规定处理。</p>'
        + '</div>'
 
    onLoad((options: any) => {
        // #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
        if (options != null && options['previewState'] != null) previewState.value = options['previewState'] as string
        if (options != null && options['newsId'] != null) newsId.value = decodeURIComponent(options['newsId'] as string)
        loadArticle()
    })
 
    const applyArticle = (id: string) : boolean => {
        publisher.value = '东区发布'
        if (id == 'news-20260603-senior-talent') {
            articleTitle.value = '攀枝花市东区事业单位2026年直接考核招聘副高职称及以上人才公告'
            publishTime.value = '2026-06-03 09:19'
            viewCount.value = '1268'
            contentHtml.value = seniorTalentContentHtml
            return true
        }
        if (id == 'news-20260601-score-review') {
            articleTitle.value = '关于攀枝花市东区事业单位2026年上半年公开考试招聘工作人员东区岗位加分情况、笔试总成绩及岗位排名、面试资格复审有关事宜的公告'
            publishTime.value = '2026-06-01 16:36'
            viewCount.value = '888'
            contentHtml.value = demoContentHtml
            return true
        }
        if (id == 'news-20260529-teacher-review') {
            articleTitle.value = '攀枝花市2026年上半年公开考试招聘中小学教师东区岗位面试资格复审及领取面试通知书公告'
            publishTime.value = '2026-05-29 10:42'
            viewCount.value = '736'
            contentHtml.value = teacherReviewContentHtml
            return true
        }
        if (id == 'news-20260522-talent-notice') {
            articleTitle.value = '关于攀枝花市东区2026年春季综合类、教育系统事业单位引才有关事宜的公告'
            publishTime.value = '2026-05-22 11:01'
            viewCount.value = '654'
            contentHtml.value = talentNoticeContentHtml
            return true
        }
        if (id == 'news-20260522-medical-check') {
            articleTitle.value = '关于攀枝花市东区2026年春季综合类、教育系统事业单位引才体检有关事宜的公告'
            publishTime.value = '2026-05-22 11:01'
            viewCount.value = '592'
            contentHtml.value = medicalCheckContentHtml
            return true
        }
        if (id == 'news-20260518-service') {
            articleTitle.value = '东区人才综合服务窗口办事指南'
            publishTime.value = '2026-05-18 09:30'
            viewCount.value = '486'
            contentHtml.value = demoContentHtml
            return true
        }
        if (id == 'news-20260512-policy') {
            articleTitle.value = '2026年度东区人才政策申报工作提示'
            publishTime.value = '2026-05-12 14:20'
            viewCount.value = '421'
            contentHtml.value = demoContentHtml
            return true
        }
        if (id == 'news-20260508-training') {
            articleTitle.value = '东区青年人才能力提升培训报名通知'
            publishTime.value = '2026-05-08 10:05'
            viewCount.value = '368'
            contentHtml.value = demoContentHtml
            return true
        }
        if (id == 'news-20260428-event') {
            articleTitle.value = '东区人才交流活动安排公告'
            publishTime.value = '2026-04-28 16:10'
            viewCount.value = '315'
            contentHtml.value = demoContentHtml
            return true
        }
        return false
    }
 
    const loadArticle = () => {
        isLoading.value = true
        loadFailed.value = false
        loadErrorMessage.value = ''
        articleTitle.value = ''
        publisher.value = ''
        publishTime.value = ''
        viewCount.value = ''
        contentHtml.value = ''
        const isRemoteNewsId = /^\d+$/.test(newsId.value)
        if (isApiConfigured() && isRemoteNewsId) {
            getNewsDetail(newsId.value, (data: any) => {
                articleTitle.value = data != null && data.title != null ? data.title as string : ''
                publisher.value = data != null && data.source != null ? data.source as string : '东区发布'
                if (data != null && data.author != null && (data.author as string).length > 0) publisher.value = data.author as string
                const publishValue = data != null && data.publishTime != null ? '' + data.publishTime : ''
                publishTime.value = publishValue.length >= 16 ? publishValue.substring(0, 16).replace('T', ' ') : publishValue.replace('T', ' ')
                viewCount.value = data != null && data.viewCount != null ? '' + data.viewCount : '0'
                contentHtml.value = sanitizeRichText(data != null && data.content != null ? data.content as string : '')
                isLoading.value = false
            }, (error) => {
                isLoading.value = false
                loadFailed.value = true
                loadErrorMessage.value = error.message
                console.error('资讯详情请求失败', error)
            })
            return
        }
        if (apiEnvironment != 'development') {
            isLoading.value = false
            loadFailed.value = true
            loadErrorMessage.value = '资讯内容加载失败'
            return
        }
        setTimeout(() => {
            if (previewState.value == 'error') {
                isLoading.value = false
                loadFailed.value = true
                return
            }
            if (previewState.value != 'empty') {
                applyArticle(newsId.value)
                contentHtml.value = sanitizeRichText(contentHtml.value)
            }
            isLoading.value = false
        }, 350)
    }
    const retryLoad = () => {
        previewState.value = 'success'
        loadArticle()
    }
    const goBack = () => safeBack('/pages/news/news')
</script>
 
<style>
    .page { display: flex; flex-direction: column; width: 100%; height: 100vh; background-color: #FFFFFF; }
    .nav-shell { flex-shrink: 0; background-image: linear-gradient(180deg, #FFF0B8 0%, #FFF8E4 58%, #FFFFFF 100%); }
    .safe-space { height: 28px; }
    .nav-row { display: flex; position: relative; height: 32px; align-items: center; justify-content: flex-end; box-sizing: border-box; }
    .back { display: flex; position: absolute; z-index: 2; left: 24rpx; top: 0; width: 72rpx; height: 100%; justify-content: center; align-items: center; }
    .back image { width: 38rpx; height: 38rpx; }
    .nav-title { position: absolute; left: 190rpx; right: 190rpx; color: #171717; font-size: 35rpx; font-weight: 700; text-align: center; white-space: nowrap; }
    .capsule-reserve { width: 88px; height: 100%; flex-shrink: 0; }
    .nav-bottom-space { height: 34rpx; }
    .article-scroll { flex: 1; height: 0; background-color: #FFFFFF; }
    .article-head { padding: 0 34rpx 25rpx; border-bottom-width: 1rpx; border-bottom-color: #F0F0F0; }
    .article-title { display: block; color: #272727; font-size: 32rpx; font-weight: 700; line-height: 47rpx; }
    .article-meta { display: flex; height: 48rpx; margin-top: 22rpx; flex-direction: row; align-items: center; justify-content: space-between; }
    .meta-left { display: flex; flex-direction: row; align-items: center; } .publisher { padding: 5rpx 9rpx; border-radius: 5rpx; background-color: #FFF8E9; color: #F2A22C; font-size: 19rpx; } .publish-time { margin-left: 12rpx; color: #A5A8AE; font-size: 22rpx; }
    .views { display: flex; flex-direction: row; align-items: center; color: #A5A8AE; font-size: 20rpx; } .views image { width: 28rpx; height: 28rpx; margin-right: 8rpx; }
    .article-body { padding: 23rpx 35rpx 0; }
    .rich-content { display: block; width: 100%; color: #747F8F; font-size: 27rpx; line-height: 51rpx; overflow: hidden; }
    .state-panel { display: flex; min-height: 640rpx; box-sizing: border-box; padding: 120rpx 48rpx 80rpx; flex-direction: column; align-items: center; }
    .loading-spinner { width: 48rpx; height: 48rpx; box-sizing: border-box; border-width: 5rpx; border-style: solid; border-color: #FFE0C3; border-top-color: #F28B2F; border-radius: 50%; animation: article-loading 760ms linear infinite; }
    .state-symbol { display: flex; width: 78rpx; height: 78rpx; border-radius: 50%; background-color: #FFF2E8; justify-content: center; align-items: center; }
    .state-symbol text { color: #EF7C31; font-size: 40rpx; font-weight: 600; }
    .state-symbol.is-empty { background-color: #F2F4F7; }
    .state-symbol.is-empty text { color: #A6ADB7; }
    .state-title { margin-top: 28rpx; color: #424A55; font-size: 29rpx; font-weight: 600; }
    .state-desc { margin-top: 13rpx; color: #9AA1AA; font-size: 23rpx; text-align: center; line-height: 34rpx; }
    .retry-button { display: flex; width: 190rpx; height: 68rpx; margin-top: 34rpx; border-radius: 35rpx; background-image: linear-gradient(90deg, #FF9A32 0%, #FF6D21 100%); justify-content: center; align-items: center; }
    .retry-button text { color: #FFFFFF; font-size: 25rpx; }
    @keyframes article-loading { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
    .bottom-space { height: calc(40rpx + env(safe-area-inset-bottom)); }
</style>