initial commit: giftlaw ni xue fa wo you li - natural resources session

This commit is contained in:
lcj
2026-09-03 10:12:08 +08:00
commit dac4260d73
239 changed files with 48949 additions and 0 deletions
+219
View File
@@ -0,0 +1,219 @@
<template>
<view class="content">
<image src="https://aliyunoss.hn12301.net/learnlaw/gift_zt_bj.jpg" mode="widthFix" style="width:750rpx;position:absolute;top:0;left:0;z-index:-1;"></image>
<view class="page-body">
<view class="loading-box" v-if="!loaded && !loadError">
<u-loading mode="circle" size="40"></u-loading>
<text class="loading-text">加载中...</text>
</view>
<view class="loading-box" v-if="loadError">
<text class="error-text">{{ loadError }}</text>
<button class="retry-btn" @tap="fetchRecords">重新加载</button>
</view>
<view class="empty-box" v-if="loaded && records.length === 0 && !loadError">
<text class="empty-icon">📝</text>
<text class="empty-text">暂无答题记录</text>
</view>
<template v-if="records.length > 0">
<view class="record-item" v-for="(item, i) in records" :key="item.id" @click="goDetail(item)">
<view class="record-index">
<text class="record-index-text">{{ i + 1 }}</text>
</view>
<view class="record-body">
<view class="record-row">
<text class="record-label">答题时间:</text>
<text class="record-value">{{ item.create_time }}</text>
</view>
<view class="record-row">
<text class="record-label">答对题数:</text>
<text class="record-value">{{ item.correct_question_num }} / {{ item.total_question_num }} 题</text>
</view>
<view class="record-row">
<text class="record-label">答题用时:</text>
<text class="record-value">{{ formatTime(item.use_time) }}</text>
</view>
</view>
</view>
</template>
</view>
</view>
</template>
<script>
export default {
data() {
return {
records: [],
loaded: false,
loadError: ''
}
},
onLoad() {
this.fetchRecords()
},
methods: {
fetchRecords() {
this.loadError = ''
const userId = uni.getStorageSync('userIdGift')
if (!userId) {
this.loadError = '暂未登录'
return
}
this.$u.api.getUserHisResultList({
wenjuanId: 1003,
userId: userId
}).then(res => {
if (res.code === 0) {
this.records = res.Data?.result_list || []
this.loaded = true
} else {
this.loadError = res.msg || '数据异常'
}
}).catch(err => {
console.error('获取答题记录失败', err)
this.loadError = '网络请求失败'
})
},
formatTime(sec) {
const m = Math.floor(sec / 60)
const s = sec % 60
return (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s
},
goDetail(item) {
uni.navigateTo({
url: '/pages/record-detail/record-detail?resultId=' + item.id
})
}
}
}
</script>
<style lang="scss">
page {
background-color: #7CC6EB;
}
.content {
position: relative;
min-height: 100vh;
min-height: 100dvh;
}
.page-body {
position: relative;
z-index: 1;
min-height: 100vh;
min-height: 100dvh;
padding: 40rpx 30rpx;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
.record-item {
background: rgba(255,255,255,0.95);
border-radius: 20rpx;
padding: 28rpx;
margin-bottom: 20rpx;
display: flex;
box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.04);
}
.record-index {
width: 44rpx;
height: 44rpx;
border-radius: 50%;
background: linear-gradient(135deg, #FD5401, #FF7A3D);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-right: 16rpx;
margin-top: 2rpx;
box-shadow: 0 2rpx 8rpx rgba(253,84,1,0.3);
}
.record-index-text {
font-size: 20rpx;
font-weight: 700;
color: #fff;
}
.record-body {
flex: 1;
min-width: 0;
}
.record-row {
display: flex;
align-items: baseline;
margin-top: 6rpx;
}
.record-row:first-child {
margin-top: 0;
}
.record-label {
font-size: 24rpx;
color: #999;
flex-shrink: 0;
}
.record-value {
font-size: 26rpx;
color: #333;
font-weight: 500;
}
.loading-box {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.loading-text {
font-size: 28rpx;
color: #fff;
margin-top: 20rpx;
}
.error-text {
font-size: 28rpx;
color: #fff;
text-align: center;
display: block;
}
.retry-btn {
margin-top: 30rpx;
width: 240rpx;
height: 72rpx;
line-height: 72rpx;
background: rgba(255, 255, 255, 0.9);
color: #FD5401;
border: none;
border-radius: 36rpx;
font-size: 28rpx;
}
.empty-box {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.empty-icon {
font-size: 80rpx;
margin-bottom: 16rpx;
}
.empty-text {
font-size: 26rpx;
color: rgba(255,255,255,0.7);
}
</style>
+404
View File
@@ -0,0 +1,404 @@
<template>
<view class="content">
<view class="page-body">
<view class="loading-box" v-if="questions.length === 0 && !loadError">
<u-loading mode="circle" size="40"></u-loading>
<text class="loading-text">加载题目中...</text>
</view>
<view class="loading-box" v-if="loadError">
<text class="error-text">{{ loadError }}</text>
<button class="retry-btn" @tap="fetchQuestions">重新加载</button>
</view>
<template v-if="questions.length > 0">
<image src="https://aliyunoss.hn12301.net/learnlaw/gift_zt_bj.jpg" mode="widthFix" style="width:750rpx;"></image>
<view style="position:absolute;top:200rpx;left:0;z-index:0;padding: 30rpx;" >
<view class="quiz-header">
<view class="timer-wrap">
<text class="timer-label">答题时间</text>
<text class="timer">{{ formatTime(usedSeconds) }}</text>
</view>
<text class="answered-count">已答 {{ answeredCount }} / {{ questions.length }}</text>
</view>
<view class="question-item" v-for="(q, qi) in questions" :key="q.question_id">
<view class="q-header">
<text class="q-index">{{ qi + 1 }}</text>
<text class="q-content">{{ q.question_content }}</text>
</view>
<view class="options">
<view
class="option"
v-for="(opt, oi) in q.answerList"
:key="opt.id"
:class="{ selected: answers[q.question_id] === oi }"
@tap="select(q.question_id, oi)"
>
<view class="option-radio" :class="{ checked: answers[q.question_id] === oi }">
<view v-if="answers[q.question_id] === oi" class="radio-dot"></view>
</view>
<text class="option-text">{{ opt.answer_content }}</text>
</view>
</view>
<view class="tips" v-if="answers[q.question_id] !== undefined">
<text class="tips-label">解析</text>
<text class="tips-text">{{ q.tips }}</text>
</view>
</view>
<button class="submit-btn" @tap="submit">提交</button>
</view>
</template>
</view>
</view>
</template>
<script>
export default {
data() {
return {
questions: [],
answers: {},
loadError: '',
startTime: 0,
usedSeconds: 0,
timer: null
}
},
computed: {
answeredCount() {
return Object.keys(this.answers).length
}
},
onLoad() {
this.fetchQuestions()
},
onUnload() {
this.stopTimer()
},
methods: {
fetchQuestions() {
this.loadError = ''
if (uni.getStorageSync('userIdGift')) {
this.doFetchQuestions()
return
}
const openId = uni.getStorageSync('openIdGift')
if (!openId) {
this.loadError = '未获取到用户信息,请返回首页重新进入'
return
}
this.$u.api.wxOpenidLogin({ wenjuan_id: 1003, open_id: openId }).then(loginRes => {
if (loginRes && loginRes.code === 0 && loginRes.Data && loginRes.Data.user_name && loginRes.Data.user_phone) {
uni.setStorageSync('isRegisteredGift', true)
uni.setStorageSync('userIdGift', loginRes.Data.user_id || loginRes.Data.uesr_id)
uni.setStorageSync('userNameGift', loginRes.Data.user_name)
uni.setStorageSync('userPhoneGift', loginRes.Data.user_phone)
}
if (!uni.getStorageSync('userIdGift')) {
this.loadError = '未获取到用户信息,请返回首页重新进入'
return
}
this.doFetchQuestions()
}).catch(err => {
console.error('同步用户信息失败', err)
this.loadError = '网络请求失败'
})
},
doFetchQuestions() {
const userId = uni.getStorageSync('userIdGift')
const userPhone = uni.getStorageSync('userPhoneGift')
this.$u.api.queryWenjuanQA({
wenjuan_id: 1003,
user_id: userId,
user_phone: userPhone
}).then(res => {
if (res.code === 0 && res.Data?.wenjuanTmList) {
this.questions = res.Data.wenjuanTmList
this.startTimer()
} else {
this.loadError = res.msg || '数据异常'
}
}).catch(err => {
console.error('获取题目失败', err)
this.loadError = '网络请求失败'
})
},
startTimer() {
this.startTime = Date.now()
this.timer = setInterval(() => {
this.usedSeconds = (Date.now() - this.startTime) / 1000
}, 100)
},
stopTimer() {
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
},
formatTime(sec) {
const m = Math.floor(sec / 60)
const sFloat = sec - m * 60
const s = Math.floor(sFloat)
const cs = Math.floor((sFloat - s) * 100)
return (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s + '.' + (cs < 10 ? '0' : '') + cs
},
select(qId, oi) {
this.$set(this.answers, qId, oi)
},
submit() {
if (this.answeredCount < this.questions.length) {
uni.showToast({ title: '还有题目未作答', icon: 'none' })
return
}
const elapsed = Math.round((Date.now() - this.startTime) / 1000 * 100) / 100
if (elapsed < 9) {
uni.showToast({ title: '操作繁忙,请重试', icon: 'none' })
return
}
this.stopTimer()
const userId = uni.getStorageSync('userIdGift')
const userPhone = uni.getStorageSync('userPhoneGift')
const details = this.questions.map(q => ({
question_id: q.question_id,
user_answer: q.answerList[this.answers[q.question_id]]?.answer_content || ''
}))
this.$u.api.submitQA({
user_id: userId,
user_phone: userPhone,
wenjuan_id: 1003,
use_time: elapsed,
total_question_num: this.questions.length,
channel: 1,
user_answer_details: details
}).then(res => {
if (res.code === 0) {
uni.setStorageSync('resultDataGift', {
...res.Data,
use_time: elapsed,
total_question_num: this.questions.length
})
uni.navigateTo({ url: '/pages/result/result' })
} else {
uni.showToast({ title: res.msg || '提交失败', icon: 'none' })
}
}).catch(() => {
uni.showToast({ title: '网络异常', icon: 'none' })
})
}
}
}
</script>
<style lang="scss">
page {
background-color: #7CC6EB;
}
.content {
position: relative;
display: flex;
flex-direction: column;
height: 100vh;
height: 100dvh;
}
.page-body {
flex: 1;
box-sizing: border-box;
display: flex;
flex-direction: column;
overflow: hidden;
}
.quiz-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
padding: 16rpx 24rpx;
background: #71BCFB66;
border-radius: 12rpx;
flex-shrink: 0;
}
.quiz-scroll {
flex: 1;
overflow: hidden;
position: relative;
}
.timer-wrap {
display: flex;
align-items: center;
gap: 10rpx;
}
.timer-label {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.8);
}
.timer {
font-size: 28rpx;
color: #fff;
font-weight: 600;
}
.answered-count {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.85);
}
.question-item {
background: rgba(255, 255, 255, 0.95);
border-radius: 20rpx;
padding: 32rpx 28rpx;
margin-bottom: 24rpx;
}
.q-header {
display: flex;
margin-bottom: 20rpx;
}
.q-index {
width: 44rpx;
height: 44rpx;
line-height: 44rpx;
text-align: center;
background: #FD5401;
color: #fff;
border-radius: 50%;
font-size: 24rpx;
font-weight: 600;
flex-shrink: 0;
margin-right: 16rpx;
margin-top: 4rpx;
}
.q-content {
font-size: 28rpx;
color: #333;
line-height: 1.6;
flex: 1;
}
.options {
display: flex;
flex-direction: column;
gap: 14rpx;
}
.option {
padding: 22rpx 20rpx;
display: flex;
align-items: center;
border: 2rpx solid #eee;
border-radius: 12rpx;
}
.option.selected {
border-color: #FD5401;
background: rgba(253, 84, 1, 0.05);
}
.option-radio {
width: 36rpx;
height: 36rpx;
border-radius: 50%;
border: 3rpx solid #CCC;
display: flex;
align-items: center;
justify-content: center;
margin-right: 16rpx;
flex-shrink: 0;
}
.option-radio.checked {
border-color: #FD5401;
}
.radio-dot {
width: 20rpx;
height: 20rpx;
border-radius: 50%;
background: #FD5401;
}
.option-text {
font-size: 27rpx;
color: #333;
line-height: 1.4;
}
.tips {
background: rgba(255, 247, 237, 0.95);
border-radius: 12rpx;
padding: 20rpx;
margin-top: 16rpx;
border-left: 6rpx solid #FD5401;
}
.tips-label {
font-size: 24rpx;
font-weight: 600;
color: #FD5401;
display: block;
margin-bottom: 6rpx;
}
.tips-text {
font-size: 24rpx;
color: #666;
line-height: 1.5;
}
.submit-btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
background: linear-gradient(135deg, #FD5401, #FF7A3D);
color: #fff;
font-size: 32rpx;
font-weight: 600;
border: none;
border-radius: 44rpx;
margin: 10rpx 0 40rpx;
box-shadow: 0 4rpx 16rpx rgba(253, 84, 1, 0.3);
}
.loading-box {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.loading-text {
font-size: 28rpx;
color: #fff;
margin-top: 20rpx;
}
.error-text {
font-size: 28rpx;
color: #fff;
text-align: center;
display: block;
}
.retry-btn {
margin-top: 30rpx;
width: 240rpx;
height: 72rpx;
line-height: 72rpx;
background: rgba(255, 255, 255, 0.9);
color: #FD5401;
border: none;
border-radius: 36rpx;
font-size: 28rpx;
}
</style>
+329
View File
@@ -0,0 +1,329 @@
<template>
<view>
<image src="https://aliyunoss.hn12301.net/learnlaw/gift_bj1_1.jpg" mode="widthFix"
style="width: 750rpx;position: absolute;top: 0rpx;"></image>
<image src="https://aliyunoss.hn12301.net/learnlaw/gift_dtcj1.png" @tap="goRegister"
style="width: 180rpx;position: absolute;top:1920rpx;left: 165rpx;" mode="widthFix"></image>
<view style="width: 630rpx;height: 200rpx;position: absolute;top: 635rpx;left: 60rpx;">
<view style="display: flex;align-items: center;width: 630rpx;justify-content: space-evenly;font-weight: bold;color:#503318;"><view class="tag"></view> {{ fiveStar ? '本轮答题赢好礼达成' : '本轮答题赢好礼未达成' }} <view class="tag1"></view></view>
<view style="display: flex;align-items: center;width: 630rpx;justify-content: space-evenly;">
<image :src="item<6 && fiveStar?'https://aliyunoss.hn12301.net/learnlaw/gift_yzg.png':'https://aliyunoss.hn12301.net/learnlaw/gift_mzg.png' " style="width: 120rpx;height: 120rpx;" v-for="item in 5" :key="item"></image>
</view>
<view style="width: 630rpx;display: flex;align-items: center;justify-content: center;margin-top: -10rpx;font-size: 24rpx;color: #503318;" @tap="goRegister">{{ subTitle }}</view>
</view>
<image src="https://aliyunoss.hn12301.net/learnlaw/gift_ftxx1.png" @tap="goStudy"
style="width: 180rpx;position: absolute;top:1920rpx;right:45rpx;" mode="widthFix"></image>
<view class="rules-btn" @tap="goRules">
<text class="rules-btn-text">活动规则</text>
</view>
<image src="https://aliyunoss.hn12301.net/learnlaw/gift_ckjpxq1.png" @tap="toPrize()"
style="width: 200rpx;position: absolute;top:1740rpx;right:275rpx;" mode="widthFix"></image>
<u-tabbar :list="tabbarList" :hideTabBar="true" activeColor="#E88300" inactiveColor="#606060" :borderTop="false"
height="120rpx" iconSize="48"></u-tabbar>
<view class="blocked-mask" v-if="blocked">
<text class="blocked-tip">{{ blockTip }}</text>
<text class="blocked-sub">本活动仅支持手机微信访问</text>
</view>
</view>
</template>
<script>
const APP_ID = 'wx71c218bd9ff0e2da'
const SCOPE = 'snsapi_base'
export default {
data() {
return {
fiveStar: false,
registered: false,
lvShow:false,
blocked: false,
blockTip: '',
tabbarList: [{
pagePath: "/pages/index/index",
iconPath: "/static/shouye.png",
selectedIconPath: "/static/shouye_select.png",
text: "首页"
},
{
pagePath: "/pages/records/records",
iconPath: "/static/zhongjiangjilu.png",
selectedIconPath: "/static/zhongjiangjilu_select.png",
text: "中奖记录"
},
{
pagePath: "/pages/profile/profile",
iconPath: "/static/wode.png",
selectedIconPath: "/static/wode_select.png",
text: "我的"
}
]
}
},
computed: {
subTitle() {
if (!this.registered) {
return '您未注册,点击参与挑战去注册'
}
return this.fiveStar ? '本轮您已进入抽奖池' : '本轮您未进入抽奖池,坚持答题参与抽奖'
}
},
onLoad() {
if (new Date().getTime() < new Date('2026/08/29 10:00:00')) {
this.lvShow = true
uni.redirectTo({
url:'/pages/jqqd/jqqd'
})
}
this.initLogin()
},
onShow() {
if (this.blocked) return
const openId = uni.getStorageSync('openIdGift')
const isRegisteredGift = uni.getStorageSync('isRegisteredGift')
const userId = uni.getStorageSync('userIdGift')
this.registered = !!isRegisteredGift
if (openId && (!isRegisteredGift || !userId)) {
this.loginWithOpenId(openId)
}
if (isRegisteredGift && userId) {
this.fetchUserResult()
}
},
methods: {
fetchUserResult() {
const userId = uni.getStorageSync('userIdGift')
if (!userId) return
this.$u.api.getUserResult({ wenjuanId: 1003, userId }).then(res => {
if (res.code !== 0 || !res.Data) return
this.fiveStar = Number(res.Data.correct_question_num) === 5
}).catch(err => {
console.error('获取答题结果失败', err)
})
},
initLogin() {
if (!this.isWechatBrowser()) {
this.block('请在微信中打开')
return
}
if (this.isPcWechat()) {
this.block('请使用手机微信打开')
return
}
let openId = uni.getStorageSync('openIdGift')
if (openId) {
this.loginWithOpenId(openId)
return
}
const code = this.getCodeFromUrl()
if (code) {
this.exchangeOpenId(code)
} else {
this.wechatAuth()
}
},
block(tip) {
this.blocked = true
this.blockTip = tip
},
isWechatBrowser() {
return /MicroMessenger/i.test(navigator.userAgent)
},
isPcWechat() {
return /MicroMessenger/i.test(navigator.userAgent) && /WindowsWechat|MacWechat/i.test(navigator.userAgent)
},
getCodeFromUrl() {
const match = window.location.href.match(/[?&]code=([^&#]+)/)
return match ? match[1] : null
},
cleanUrlCode() {
const cleanUrl = window.location.href.replace(/[?&]code=[^&#]*(&|#|$)/, (m, p1) => p1 === '&' ? '?' : (
p1 === '#' ? '#' : ''))
window.history.replaceState({}, '', cleanUrl)
},
wechatAuth() {
const redirectUri = encodeURIComponent(window.location.href.split('?')[0].split('#')[0])
const url =
`https://open.weixin.qq.com/connect/oauth2/authorize?appid=${APP_ID}&redirect_uri=${redirectUri}&response_type=code&scope=${SCOPE}&state=STATE#wechat_redirect`
window.location.href = url
},
exchangeOpenId(code) {
this.$u.api.getWxOpenId(code).then(res => {
if (res.code !== 0 || !res.Data || !res.Data.openid) return
const openId = res.Data.openid
uni.setStorageSync('openIdGift', openId)
this.cleanUrlCode()
return this.$u.api.wxOpenidLogin({ wenjuan_id: 1003, open_id: openId })
}).then(loginRes => {
if (loginRes && loginRes.code === 0 && loginRes.Data && loginRes.Data.user_name && loginRes.Data.user_phone) {
uni.setStorageSync('isRegisteredGift', true)
uni.setStorageSync('userIdGift', loginRes.Data.user_id || loginRes.Data.uesr_id)
uni.setStorageSync('userNameGift', loginRes.Data.user_name)
uni.setStorageSync('userPhoneGift', loginRes.Data.user_phone)
this.registered = true
this.fetchUserResult()
} else if (loginRes && !loginRes.Data) {
uni.setStorageSync('isRegisteredGift', false)
this.registered = false
}
}).catch(err => {
console.error('请求失败', err)
})
},
loginWithOpenId(openId) {
return this.$u.api.wxOpenidLogin({ wenjuan_id: 1003, open_id: openId }).then(loginRes => {
if (!loginRes.Data) {
uni.setStorageSync('isRegisteredGift', false)
this.registered = false
return
}
if (loginRes.code === 0 && loginRes.Data?.user_name && loginRes.Data?.user_phone) {
uni.setStorageSync('isRegisteredGift', true)
uni.setStorageSync('userIdGift', loginRes.Data.user_id || loginRes.Data.uesr_id)
uni.setStorageSync('userNameGift', loginRes.Data.user_name)
uni.setStorageSync('userPhoneGift', loginRes.Data.user_phone)
this.registered = true
this.fetchUserResult()
}
}).catch(err => {
console.error('登录请求失败', err)
})
},
toPrize() {
uni.navigateTo({
url: '/pages/prize/prize'
})
},
goStudy() {
uni.navigateTo({ url: '/pages/study/preview' })
},
goRules() {
uni.navigateTo({ url: '/pages/rules/rules' })
},
goPrize() {
uni.navigateTo({ url: '/pages/prize/prize' })
},
goRegister() {
if (uni.getStorageSync('isRegisteredGift')) {
if (uni.getStorageSync('userIdGift') && uni.getStorageSync('userPhoneGift')) {
uni.navigateTo({ url: '/pages/answer/answer' })
return
}
uni.showLoading({ title: '加载中...' })
this.loginWithOpenId(uni.getStorageSync('openIdGift')).then(() => {
uni.hideLoading()
if (uni.getStorageSync('userIdGift')) {
uni.navigateTo({ url: '/pages/answer/answer' })
} else {
uni.showToast({ title: '用户信息获取失败,请重试', icon: 'none' })
}
})
return
}
uni.navigateTo({ url: '/pages/register/register' })
}
}
}
</script>
<style lang="scss">
page {
background-color: #7CC6EB;
}
.page-body {
flex: 1;
}
.blocked-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 999;
background-color: #7CC6EB;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.blocked-tip {
font-size: 36rpx;
font-weight: 600;
color: #fff;
}
.blocked-sub {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.85);
margin-top: 16rpx;
}
.tag {
width: 48rpx;
height: 6rpx;
border-radius: 3rpx;
background: rgba(80, 51, 24, 0.45);
}
.tag1 {
width: 48rpx;
height: 6rpx;
border-radius: 3rpx;
background: rgba(80, 51, 24, 0.45);
}
.rules-btn {
position: absolute;
top: 950rpx;
right: 0;
display: flex;
align-items: center;
justify-content: center;
height: 60rpx;
padding: 0 26rpx;
background: linear-gradient(135deg, #FFC24D, #FF8A3D);
border-radius: 30rpx 0 0 30rpx;
box-shadow: 0 4rpx 14rpx rgba(255, 138, 61, 0.45);
}
.rules-btn:active {
opacity: 0.85;
}
.rules-btn-text {
font-size: 24rpx;
font-weight: 600;
color: #fff;
letter-spacing: 2rpx;
}
::v-deep .u-tabbar__content {
background: linear-gradient(to bottom, #FAFFC7, #B2FFC9) !important;
}
::v-deep .u-tabbar__content__item {
background: linear-gradient(to bottom, #FAFFC7, #B2FFC9) !important;
}
::v-deep .u-tabbar {
.u-border-top {
border-top: none !important;
}
}
::v-deep .u-icon__img {
object-fit: contain !important;
}
::v-deep .u-tabbar__content__item {
padding: 16rpx 0 !important;
}
</style>
+22
View File
@@ -0,0 +1,22 @@
<template>
<view>
<image src="https://aliyunoss.hn12301.net/learnlaw/gift_jqqd.jpg" style="width: 750rpx;" mode="widthFix"></image>
</view>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
}
}
</script>
<style>
</style>
+189
View File
@@ -0,0 +1,189 @@
<template>
<view class="content">
<view class="page-header">
<text class="header-back" @tap="goBack">‹</text>
<text class="header-title">用户隐私协议</text>
</view>
<scroll-view scroll-y class="doc-scroll">
<view class="doc-card">
<view class="doc-title">用户隐私协议</view>
<text class="doc-meta">主办方:湖南一二三零一文化旅游服务有限公司</text>
<text class="doc-meta">生效日期:自发布之日起</text>
<view class="sec">
<text class="sec-title">一、适用范围</text>
<text class="sec-text">本协议适用于"你学法我有礼——自然资源专场"普法答题抽奖活动(以下简称"本活动")过程中我们对您个人信息的收集、使用与保护。您勾选同意本协议即表示您已阅读、理解并同意本协议全部内容。</text>
</view>
<view class="sec">
<text class="sec-title">二、我们收集的信息</text>
<text class="sec-text">1. 注册信息:姓名、手机号码,用于活动参与资格确认与实名核验,短信验证码用于验证手机归属;</text>
<text class="sec-text">2. 微信授权信息:openid,用于关联您的身份与本活动答题、中奖记录;</text>
<text class="sec-text">3. 答题与积分数据:答题记录、对错结果、用时及积分,用于成绩结算、排名与抽奖;</text>
<text class="sec-text">4. 中奖及兑奖信息:中奖记录;兑奖时需填写身份证号码,仅用于核验兑奖资格及奖品发放。</text>
</view>
<view class="sec">
<text class="sec-title">三、信息的使用</text>
<text class="sec-text">1. 提供答题、积分计算、排名及开奖抽奖服务;</text>
<text class="sec-text">2. 验证用户身份及实名信息真实性,保障活动公平,防范刷量、冒领;</text>
<text class="sec-text">3. 完成兑奖核验,并在中奖时与您联系;</text>
<text class="sec-text">4. 满足法律法规及主管部门的监管要求。</text>
</view>
<view class="sec">
<text class="sec-title">四、信息的存储与保护</text>
<text class="sec-text">1. 您的个人信息存储于活动服务器,并采取加密传输、访问控制等安全措施;</text>
<text class="sec-text">2. 我们不会出售您的个人信息,亦不会向与本活动无关的第三方提供;除法律法规明确要求或司法、行政机关依法要求外,任何共享均经审慎评估;</text>
<text class="sec-text">3. 活动结束后,相关数据将依法律规定予以保留或删除。</text>
</view>
<view class="sec">
<text class="sec-title">五、信息的公示</text>
<text class="sec-text">为保障抽奖活动公开透明,中奖名单、积分排行等信息将依活动监管要求进行公示,公示内容将对姓名、手机号码等个人信息作脱敏处理。</text>
</view>
<view class="sec">
<text class="sec-title">六、您的权利</text>
<text class="sec-text">您可通过本协议所列联系方式向我们申请查询、更正、删除您的个人信息,或撤回您的同意。撤回同意不影响撤回前基于同意已进行的信息处理,但可能导致您无法继续参与本活动。</text>
</view>
<view class="sec">
<text class="sec-title">七、未成年人保护</text>
<text class="sec-text">本活动面向具备完全民事行为能力的成年人。未满十八周岁的未成年人请勿在本活动中提交个人信息,如需参与应取得监护人同意并在监护人陪同下进行。</text>
</view>
<view class="sec">
<text class="sec-title">八、协议更新</text>
<text class="sec-text">我们可能根据活动安排及法律法规变化适时更新本协议,更新后的协议将在活动页面公示后生效。若您继续参与活动,即表示您接受更新后的协议。</text>
</view>
<view class="sec">
<text class="sec-title">九、联系方式</text>
<text class="sec-text">如您对本协议或本活动个人信息保护事宜有任何疑问、意见或投诉,请通过以下方式联系我们:</text>
<text class="sec-text">主办方:湖南一二三零一文化旅游服务有限公司</text>
<text class="sec-text">联系电话:0731-88912301</text>
</view>
<view style="height: 40rpx;"></view>
</view>
</scroll-view>
<view class="page-footer">
<button class="agree-btn" @tap="agreeAndBack">已阅读并同意</button>
</view>
</view>
</template>
<script>
export default {
methods: {
goBack() {
uni.navigateBack()
},
agreeAndBack() {
const pages = getCurrentPages()
const prev = pages[pages.length - 2]
if (prev && prev.$vm) prev.$vm.agreed = true
uni.navigateBack()
}
}
}
</script>
<style lang="scss">
page {
background-color: #F5F5F5;
}
.content {
position: relative;
display: flex;
flex-direction: column;
height: 100vh;
height: 100dvh;
}
.page-header {
display: flex;
align-items: center;
padding: 20rpx 30rpx;
background: linear-gradient(135deg, #FD5401, #FF7A3D);
flex-shrink: 0;
}
.header-back {
font-size: 48rpx;
color: #fff;
line-height: 1;
margin-right: 16rpx;
padding: 0 10rpx;
}
.header-title {
font-size: 30rpx;
font-weight: 600;
color: #fff;
flex: 1;
}
.doc-scroll {
flex: 1;
overflow: hidden;
padding-bottom: 20rpx;
}
.doc-card {
background: #fff;
margin: 24rpx;
border-radius: 20rpx;
padding: 36rpx 30rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.doc-title {
font-size: 36rpx;
font-weight: 700;
color: #333;
text-align: center;
margin-bottom: 16rpx;
}
.doc-meta {
display: block;
font-size: 22rpx;
color: #999;
text-align: center;
line-height: 1.8;
}
.sec {
margin-top: 30rpx;
}
.sec-title {
display: block;
font-size: 28rpx;
font-weight: 700;
color: #FD5401;
margin-bottom: 10rpx;
}
.sec-text {
display: block;
font-size: 26rpx;
color: #555;
line-height: 1.7;
margin-bottom: 6rpx;
}
.page-footer {
flex-shrink: 0;
padding: 20rpx 30rpx 40rpx;
background: #fff;
border-top: 1rpx solid #f0f0f0;
}
.agree-btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
background: linear-gradient(135deg, #FD5401, #FF7A3D);
color: #fff;
font-size: 32rpx;
font-weight: 600;
border: none;
border-radius: 44rpx;
box-shadow: 0 4rpx 16rpx rgba(253, 84, 1, 0.3);
}
</style>
+28
View File
@@ -0,0 +1,28 @@
<template>
<view>
<image src="https://aliyunoss.hn12301.net/learnlaw/gift_jp.jpg" style="width: 750rpx;" mode="widthFix"></image>
<image src="https://aliyunoss.hn12301.net/learnlaw/gift_jp_an.png" style="width: 200rpx;position: absolute;left: 275rpx;top: 1600rpx;" mode="widthFix" @click="toIndex"></image>
<!-- <view style="width: 240rpx;height: 80rpx;position: absolute;top:3130rpx;left: 255rpx;" @click="toIndex()"></view> -->
</view>
</template>
<script>
export default {
data() {
return {
}
},
onLoad(options) {
},
methods:{
toIndex(){
uni.navigateBack()
}
}
}
</script>
<style lang="scss">
</style>
+270
View File
@@ -0,0 +1,270 @@
<template>
<view class="content">
<image src="https://aliyunoss.hn12301.net/learnlaw/gift_zt_bj.jpg" mode="scaleToFill" :style="{ width: '750rpx', height: bgHeight + 'px', position: 'absolute', top: 0, left: 0, zIndex: 0 }"></image>
<view class="page-body">
<view class="login-box" v-if="!isLoggedIn">
<text class="login-tip">注册后享受更多功能</text>
<button class="login-btn" @tap="goRegister">立即注册</button>
</view>
<template v-if="isLoggedIn">
<view class="user-card">
<view class="user-avatar">
<text class="avatar-text">{{ userName.charAt(0) }}</text>
</view>
<view class="user-info">
<text class="user-name">{{ userName }}</text>
<text class="user-phone">{{ maskedPhone }}</text>
</view>
</view>
<view class="menu-list">
<view class="menu-item" @tap="goMyPrizes">
<view class="menu-icon menu-icon-prize">
<text>🎁</text>
</view>
<text class="menu-text">我的奖品</text>
<text class="menu-arrow">›</text>
</view>
<view class="menu-item" @tap="goAnswerRecords">
<view class="menu-icon menu-icon-points">
<text>📊</text>
</view>
<text class="menu-text">答题记录</text>
<text class="menu-arrow">›</text>
</view>
</view>
</template>
</view>
<u-tabbar
:list="tabbarList"
:hideTabBar="true"
activeColor="#FD5401"
inactiveColor="#5D533D"
:borderTop="false"
height="120rpx"
iconSize="48"
></u-tabbar>
</view>
</template>
<script>
export default {
data() {
return {
bgHeight: 0,
tabbarList: [
{ pagePath: "/pages/index/index", iconPath: "/static/shouye.png", selectedIconPath: "/static/shouye_select.png", text: "首页" },
{ pagePath: "/pages/records/records", iconPath: "/static/zhongjiangjilu.png", selectedIconPath: "/static/zhongjiangjilu_select.png", text: "中奖记录" },
{ pagePath: "/pages/profile/profile", iconPath: "/static/wode.png", selectedIconPath: "/static/wode_select.png", text: "我的" }
],
isLoggedIn: false,
userName: '',
userPhone: ''
}
},
computed: {
maskedPhone() {
const p = this.userPhone
if (!p || p.length < 7) return p
return p.slice(0, 3) + '****' + p.slice(-4)
}
},
onLoad() {
const sys = uni.getSystemInfoSync()
const tabbarHeight = sys.windowWidth / 750 * 120
this.bgHeight = sys.windowHeight - tabbarHeight
},
onShow() {
this.checkLogin()
},
methods: {
checkLogin() {
const userId = uni.getStorageSync('userIdGift')
if (userId) {
this.isLoggedIn = true
this.userName = uni.getStorageSync('userNameGift') || ''
this.userPhone = uni.getStorageSync('userPhoneGift') || ''
} else {
this.isLoggedIn = false
}
},
goRegister() {
uni.navigateTo({ url: '/pages/register/register' })
},
goMyPrizes() {
const phone = uni.getStorageSync('userPhoneGift') || ''
window.location.href = 'https://goldnar.hntgov.net/cardholder/index.html#/?phone=' + phone
},
goAnswerRecords() {
uni.navigateTo({ url: '/pages/answer-record/answer-record' })
}
}
}
</script>
<style lang="scss">
page {
background-color: #6EC5E3;
}
.content {
position: relative;
display: flex;
flex-direction: column;
}
.page-body {
position: relative;
z-index: 1;
flex: 1;
padding: 160rpx 30rpx 20rpx;
box-sizing: border-box;
}
.login-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
}
.login-tip {
font-size: 28rpx;
color: rgba(255,255,255,0.85);
margin-bottom: 30rpx;
}
.login-btn {
width: 400rpx;
height: 80rpx;
line-height: 80rpx;
background: linear-gradient(135deg, #FD5401, #FF7A3D);
color: #fff;
font-size: 30rpx;
font-weight: 600;
border: none;
border-radius: 40rpx;
box-shadow: 0 4rpx 16rpx rgba(253,84,1,0.3);
}
.user-card {
background: rgba(255,255,255,0.95);
border-radius: 20rpx;
padding: 36rpx 30rpx;
display: flex;
align-items: center;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
}
.user-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background: linear-gradient(135deg, #FD5401, #FF7A3D);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-right: 24rpx;
}
.avatar-text {
font-size: 32rpx;
font-weight: 700;
color: #fff;
}
.user-info {
flex: 1;
min-width: 0;
}
.user-name {
font-size: 30rpx;
font-weight: 600;
color: #333;
display: block;
}
.user-phone {
font-size: 24rpx;
color: #999;
margin-top: 6rpx;
display: block;
}
.menu-list {
background: rgba(255,255,255,0.95);
border-radius: 20rpx;
overflow: hidden;
box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
}
.menu-item {
display: flex;
align-items: center;
padding: 30rpx 28rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.menu-item:last-child {
border-bottom: none;
}
.menu-icon {
width: 52rpx;
height: 52rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-right: 20rpx;
font-size: 28rpx;
}
.menu-icon-prize {
background: #fff7e6;
}
.menu-icon-points {
background: #e6f7ff;
}
.menu-text {
flex: 1;
font-size: 28rpx;
color: #333;
font-weight: 500;
}
.menu-arrow {
font-size: 36rpx;
color: #ccc;
flex-shrink: 0;
}
::v-deep .u-tabbar__content {
background: linear-gradient(to bottom, #FAFFC7, #B2FFC9) !important;
}
::v-deep .u-tabbar__content__item {
background: linear-gradient(to bottom, #FAFFC7, #B2FFC9) !important;
}
::v-deep .u-tabbar {
.u-border-top {
border-top: none !important;
}
}
::v-deep .u-icon__img {
object-fit: contain !important;
}
::v-deep .u-tabbar__content__item {
padding: 16rpx 0 !important;
}
</style>
+391
View File
@@ -0,0 +1,391 @@
<template>
<view class="content">
<view class="page-body">
<image src="https://aliyunoss.hn12301.net/learnlaw/gift_zt_bj.jpg" mode="widthFix" style="width:750rpx;position:absolute;top:0;left:0;z-index:-1;"></image>
<view class="login-box" v-if="!isLoggedIn">
<text class="login-tip">注册后可查看排行榜</text>
<button class="login-btn" @tap="goRegister">立即注册</button>
</view>
<template v-if="isLoggedIn">
<view class="header">
<view class="header-icon"><text>🏆</text></view>
<text class="header-title">排行榜</text>
<view class="header-line"></view>
<view class="my-rank" v-if="myRank">
<text class="my-rank-label">我的排名</text>
<text class="my-rank-value">第 {{ myRank }} 名</text>
<text class="my-rank-stats">{{ myScore }}分 · {{ myTime }}s</text>
</view>
</view>
<view class="rank-list" v-if="list.length > 0">
<view class="rank-item" v-for="(item, i) in list" :key="i">
<view class="rank-index" :class="'rank-' + (item.index || i + 1)">
<text v-if="(item.index || i + 1) <= 3">
<image :src="'https://aliyunoss.hn12301.net/sd/rank' + (i + 1) + '.png'" mode="widthFix" style="width:36rpx;"></image>
</text>
<text v-else class="rank-num">{{ item.index }}</text>
</view>
<view class="rank-info">
<text class="rank-name">{{ item.user_name }}</text>
<text class="rank-phone">{{ item.user_phone }}</text>
</view>
<view class="rank-stats">
<text class="rank-time">{{ item.total_time }}s</text>
<text class="rank-score">{{ item.total_score }}分</text>
</view>
</view>
</view>
<view class="load-more" v-if="hasMore">
<u-loading mode="circle" size="28"></u-loading>
<text class="load-text">加载更多...</text>
</view>
<view class="no-more" v-if="!hasMore && list.length > 0">
<text class="no-more-text">— 没有更多了 —</text>
</view>
<view class="empty-box" v-if="list.length === 0">
<text class="empty-icon">🏆</text>
<text class="empty-text">暂无排行榜数据</text>
</view>
</template>
</view>
<u-tabbar
:list="tabbarList"
:hideTabBar="true"
activeColor="#FD5401"
inactiveColor="#5D533D"
:borderTop="false"
height="120rpx"
iconSize="48"
></u-tabbar>
</view>
</template>
<script>
export default {
data() {
return {
tabbarList: [
{ pagePath: "/pages/index/index", iconPath: "/static/shouye.png", selectedIconPath: "/static/shouye_select.png", text: "首页" },
{ pagePath: "/pages/ranking/ranking", iconPath: "/static/paihangbang.png", selectedIconPath: "/static/paihangbang_select.png", text: "排行榜" },
{ pagePath: "/pages/records/records", iconPath: "/static/zhongjiangjilu.png", selectedIconPath: "/static/zhongjiangjilu_select.png", text: "中将记录" },
{ pagePath: "/pages/profile/profile", iconPath: "/static/wode.png", selectedIconPath: "/static/wode_select.png", text: "我的" }
],
isLoggedIn: false,
list: [],
pageNum: 1,
pageSize: 10,
hasMore: true,
loading: false,
myRank: null,
myScore: 0,
myTime: 0
}
},
onLoad() {
this.checkLogin()
},
onShow() {
this.checkLogin()
},
methods: {
checkLogin() {
const userId = uni.getStorageSync('userIdGift')
if (userId) {
this.isLoggedIn = true
this.pageNum = 1
this.hasMore = true
this.fetchList()
} else {
this.isLoggedIn = false
}
},
goRegister() {
uni.navigateTo({ url: '/pages/register/register' })
},
fetchList() {
if (this.loading || !this.hasMore) return
this.loading = true
const userId = uni.getStorageSync('userIdGift')
this.$u.api.getWenjuanPhb({
user_id: userId,
wenjuan_id: 1003,
pageNum: this.pageNum,
pageSize: this.pageSize
}).then(res => {
if (res.code === 0) {
const data = res.Data
if (this.pageNum === 1) {
this.list = data.phb || []
this.myRank = data.pm
this.myScore = data.score || 0
this.myTime = data.time || 0
} else {
this.list = this.list.concat(data.phb || [])
}
this.hasMore = (data.phb || []).length >= this.pageSize
} else {
uni.showToast({ title: res.msg || '加载失败', icon: 'none' })
}
}).catch(() => {
uni.showToast({ title: '网络异常', icon: 'none' })
}).finally(() => {
this.loading = false
})
}
}
}
</script>
<style lang="scss">
page {
background-color: #B3E9F3;
}
.content {
position: relative;
display: flex;
flex-direction: column;
}
.page-body {
position: relative;
z-index: 1;
flex: 1;
padding: 80rpx 30rpx 20rpx;
box-sizing: border-box;
}
.login-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
}
.login-tip {
font-size: 28rpx;
color: rgba(255,255,255,0.85);
margin-bottom: 30rpx;
}
.login-btn {
width: 400rpx;
height: 80rpx;
line-height: 80rpx;
background: linear-gradient(135deg, #FD5401, #FF7A3D);
color: #fff;
font-size: 30rpx;
font-weight: 600;
border: none;
border-radius: 40rpx;
box-shadow: 0 4rpx 16rpx rgba(253,84,1,0.3);
}
.header {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 30rpx;
}
.header-icon {
font-size: 52rpx;
margin-bottom: 10rpx;
}
.header-title {
font-size: 36rpx;
font-weight: 800;
color: #fff;
text-shadow: 0 2rpx 8rpx rgba(0,0,0,0.15);
letter-spacing: 4rpx;
}
.header-line {
width: 60rpx;
height: 4rpx;
background: rgba(255,255,255,0.5);
border-radius: 2rpx;
margin-top: 12rpx;
margin-bottom: 8rpx;
}
.my-rank {
display: flex;
align-items: center;
gap: 20rpx;
background: rgba(255,255,255,0.2);
border-radius: 16rpx;
padding: 16rpx 28rpx;
margin-top: 16rpx;
}
.my-rank-label {
font-size: 24rpx;
color: rgba(255,255,255,0.8);
}
.my-rank-value {
font-size: 28rpx;
font-weight: 700;
color: #fff;
}
.my-rank-stats {
font-size: 22rpx;
color: rgba(255,255,255,0.65);
margin-left: auto;
}
.rank-list {
background: rgba(255,255,255,0.95);
border-radius: 20rpx;
overflow: hidden;
box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
}
.rank-item {
display: flex;
align-items: center;
padding: 28rpx 24rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.rank-item:last-child {
border-bottom: none;
}
.rank-index {
width: 56rpx;
height: 56rpx;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-right: 16rpx;
border-radius: 50%;
background: #f5f5f5;
}
.rank-index.rank-1 {
background: #fff7e6;
}
.rank-index.rank-2 {
background: #f0f0f0;
}
.rank-index.rank-3 {
background: #fef6e7;
}
.rank-num {
font-size: 24rpx;
font-weight: 700;
color: #999;
}
.rank-info {
flex: 1;
min-width: 0;
}
.rank-name {
font-size: 28rpx;
color: #333;
font-weight: 500;
display: block;
}
.rank-phone {
font-size: 22rpx;
color: #bbb;
margin-top: 4rpx;
display: block;
}
.rank-stats {
text-align: right;
flex-shrink: 0;
margin-left: 16rpx;
}
.rank-time {
font-size: 22rpx;
color: #999;
display: block;
}
.rank-score {
font-size: 30rpx;
font-weight: 700;
color: #FD5401;
display: block;
margin-top: 4rpx;
}
.load-more {
display: flex;
align-items: center;
justify-content: center;
padding: 30rpx 0;
gap: 10rpx;
}
.load-text {
font-size: 24rpx;
color: rgba(255,255,255,0.7);
}
.no-more {
text-align: center;
padding: 24rpx 0;
}
.no-more-text {
font-size: 22rpx;
color: rgba(255,255,255,0.5);
}
.empty-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80rpx 0;
}
.empty-icon {
font-size: 80rpx;
margin-bottom: 16rpx;
}
.empty-text {
font-size: 26rpx;
color: rgba(255,255,255,0.7);
}
::v-deep .u-tabbar__content {
background: linear-gradient(to bottom, #FAFFC7, #B2FFC9) !important;
}
::v-deep .u-tabbar__content__item {
background: linear-gradient(to bottom, #FAFFC7, #B2FFC9) !important;
}
::v-deep .u-tabbar {
.u-border-top {
border-top: none !important;
}
}
::v-deep .u-icon__img {
object-fit: contain !important;
}
::v-deep .u-tabbar__content__item {
padding: 16rpx 0 !important;
}
</style>
+227
View File
@@ -0,0 +1,227 @@
<template>
<view class="content">
<image src="https://aliyunoss.hn12301.net/learnlaw/gift_zt_bj.jpg" mode="widthFix" style="width:750rpx;position:absolute;top:0;left:0;z-index:-1;"></image>
<view class="page-body">
<view class="loading-box" v-if="detailList.length === 0 && !loadError">
<u-loading mode="circle" size="40"></u-loading>
<text class="loading-text">加载中...</text>
</view>
<view class="loading-box" v-if="loadError">
<text class="error-text">{{ loadError }}</text>
<button class="retry-btn" @tap="fetchDetail">重新加载</button>
</view>
<template v-if="detailList.length > 0">
<view class="detail-item" v-for="(item, i) in detailList" :key="item.id">
<view class="detail-index" :class="item.is_correct ? 'correct' : 'wrong'">
<text class="detail-index-text">{{ i + 1 }}</text>
</view>
<view class="detail-body">
<text class="detail-q">{{ item.tiku_question_content }}</text>
<view class="detail-answer-row">
<text class="detail-label">你的答案:</text>
<text class="detail-value" :class="item.is_correct ? 'text-correct' : 'text-wrong'">{{ item.user_answer }}</text>
</view>
<view class="detail-answer-row" v-if="!item.is_correct">
<text class="detail-label">正确答案:</text>
<text class="detail-value text-correct-answer">{{ item.tiku_correct_answer }}</text>
</view>
<view class="tips" v-if="item.tips">
<text class="tips-label">解析</text>
<text class="tips-text">{{ item.tips }}</text>
</view>
</view>
</view>
</template>
</view>
</view>
</template>
<script>
export default {
data() {
return {
resultId: 0,
detailList: [],
loadError: ''
}
},
onLoad(options) {
this.resultId = options.resultId
this.fetchDetail()
},
methods: {
fetchDetail() {
this.loadError = ''
this.$u.api.getUserHisResultDetail({
resultId: this.resultId
}).then(res => {
if (res.code === 0 && Array.isArray(res.Data) && res.Data.length) {
this.detailList = res.Data
} else {
this.loadError = res.msg || '数据异常'
}
}).catch(err => {
console.error('获取答题详情失败', err)
this.loadError = '网络请求失败'
})
}
}
}
</script>
<style lang="scss">
page {
background-color: #7CC6EB;
}
.content {
position: relative;
min-height: 100vh;
min-height: 100dvh;
}
.page-body {
position: relative;
z-index: 1;
min-height: 100vh;
min-height: 100dvh;
padding: 40rpx 30rpx;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
.detail-item {
background: rgba(255,255,255,0.95);
border-radius: 20rpx;
padding: 28rpx;
margin-bottom: 20rpx;
display: flex;
box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.04);
}
.detail-index {
width: 44rpx;
height: 44rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-right: 16rpx;
margin-top: 2rpx;
}
.detail-index.correct {
background: linear-gradient(135deg, #52c41a, #73d13d);
box-shadow: 0 2rpx 8rpx rgba(82,196,26,0.3);
}
.detail-index.wrong {
background: linear-gradient(135deg, #f5222d, #ff4d4f);
box-shadow: 0 2rpx 8rpx rgba(245,34,45,0.3);
}
.detail-index-text {
font-size: 20rpx;
font-weight: 700;
color: #fff;
}
.detail-body {
flex: 1;
min-width: 0;
}
.detail-q {
font-size: 26rpx;
color: #333;
line-height: 1.5;
display: block;
margin-bottom: 10rpx;
}
.detail-answer-row {
display: flex;
align-items: baseline;
margin-top: 4rpx;
}
.detail-label {
font-size: 22rpx;
color: #999;
flex-shrink: 0;
}
.detail-value {
font-size: 24rpx;
font-weight: 500;
}
.text-correct {
color: #52c41a;
}
.text-wrong {
color: #f5222d;
}
.text-correct-answer {
color: #52c41a;
}
.tips {
background: rgba(255, 247, 237, 0.95);
border-radius: 12rpx;
padding: 20rpx;
margin-top: 16rpx;
border-left: 6rpx solid #FD5401;
}
.tips-label {
font-size: 24rpx;
font-weight: 600;
color: #FD5401;
display: block;
margin-bottom: 6rpx;
}
.tips-text {
font-size: 24rpx;
color: #666;
line-height: 1.5;
}
.loading-box {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.loading-text {
font-size: 28rpx;
color: #fff;
margin-top: 20rpx;
}
.error-text {
font-size: 28rpx;
color: #fff;
text-align: center;
display: block;
}
.retry-btn {
margin-top: 30rpx;
width: 240rpx;
height: 72rpx;
line-height: 72rpx;
background: rgba(255, 255, 255, 0.9);
color: #FD5401;
border: none;
border-radius: 36rpx;
font-size: 28rpx;
}
</style>
+504
View File
@@ -0,0 +1,504 @@
<template>
<view class="content">
<view class="page-body">
<image src="https://aliyunoss.hn12301.net/learnlaw/gift_zt_bj.jpg" mode="scaleToFill" :style="{ width: '750rpx', height: bgHeight + 'px', position: 'absolute', top: 0, left: 0, zIndex: -1 }"></image>
<view class="login-box" v-if="!isLoggedIn">
<text class="login-tip">注册后可查看中奖记录</text>
<button class="login-btn" @tap="goRegister">立即注册</button>
</view>
<template v-if="isLoggedIn">
<view class="header">
<view class="header-icon"><text>🏆</text></view>
<text class="header-title">中奖记录</text>
<view class="header-line"></view>
</view>
<view class="record-list" v-if="list.length > 0">
<view class="record-item" v-for="(item, i) in list" :key="i">
<view class="record-icon">
<text class="iconfont">🎁</text>
</view>
<view class="record-info">
<text class="record-prize">{{ item.title }}</text>
<text class="record-desc">{{ item.is_win_pc_desc }}</text>
<text class="record-status">{{ item.status_desc }}</text>
</view>
<view class="record-right">
<text class="record-time">{{ item.time }}</text>
<view class="btn-row">
<button class="winlist-btn" v-if="isEnded(item)" @tap="showWinList(item)">中奖名单</button>
<button class="redeem-btn" v-if="item.is_win_pc == 1" @tap="redeem(item)">兑奖</button>
</view>
</view>
</view>
</view>
<view class="empty-box" v-if="list.length === 0">
<text class="empty-icon">🏆</text>
<text class="empty-text">暂无中奖记录</text>
</view>
</template>
</view>
<view class="modal-mask" v-if="showWinModal" @tap="closeWinModal"></view>
<view class="modal win-modal" v-if="showWinModal">
<view class="modal-header">
<text class="modal-title">中奖名单</text>
<text class="modal-close" @tap="closeWinModal">✕</text>
</view>
<view class="win-list">
<view class="win-item" v-for="(w, wi) in winList" :key="wi">
<view class="win-level" :class="'win-level-' + w.win_level">
<text>{{ w.win_level_name }}</text>
</view>
<text class="win-name">{{ w.user_name }}</text>
<text class="win-phone">{{ w.user_phone }}</text>
</view>
<view class="win-empty" v-if="winList.length === 0">
<text>暂无中奖名单</text>
</view>
</view>
</view>
<u-tabbar
:list="tabbarList"
:hideTabBar="true"
activeColor="#FD5401"
inactiveColor="#5D533D"
:borderTop="false"
height="120rpx"
iconSize="48"
></u-tabbar>
</view>
</template>
<script>
export default {
data() {
return {
bgHeight: 0,
tabbarList: [
{ pagePath: "/pages/index/index", iconPath: "/static/shouye.png", selectedIconPath: "/static/shouye_select.png", text: "首页" },
{ pagePath: "/pages/records/records", iconPath: "/static/zhongjiangjilu.png", selectedIconPath: "/static/zhongjiangjilu_select.png", text: "中奖记录" },
{ pagePath: "/pages/profile/profile", iconPath: "/static/wode.png", selectedIconPath: "/static/wode_select.png", text: "我的" }
],
isLoggedIn: false,
list: [],
showWinModal: false,
winList: []
}
},
onLoad() {
const sys = uni.getSystemInfoSync()
const tabbarHeight = sys.windowWidth / 750 * 120
this.bgHeight = sys.windowHeight - tabbarHeight
},
onShow() {
this.checkLogin()
},
methods: {
checkLogin() {
const userId = uni.getStorageSync('userIdGift')
if (userId) {
this.isLoggedIn = true
this.fetchList(userId)
} else {
this.isLoggedIn = false
}
},
goRegister() {
uni.navigateTo({ url: '/pages/register/register' })
},
fetchList(userId) {
this.$u.api.getChouJiangResult({
user_id: userId,
wenjuan_id: 1003
}).then(res => {
if (res.code === 0) {
const data = res.Data || {}
this.list = data.list || data.records || data || []
if (!Array.isArray(this.list)) this.list = []
} else {
uni.showToast({ title: res.msg || '加载失败', icon: 'none' })
}
}).catch(() => {
uni.showToast({ title: '网络异常', icon: 'none' })
})
},
isEnded(item) {
return String(item.status_desc || '').indexOf('已结束') > -1
},
showWinList(item) {
this.winList = []
this.showWinModal = true
this.$u.api.getWenjuanWinDetailList({
wenjuan_id: 1003,
period: item.index
}).then(res => {
if (res.code === 0) {
this.winList = Array.isArray(res.Data) ? res.Data : []
} else {
uni.showToast({ title: res.msg || '加载失败', icon: 'none' })
}
}).catch(() => {
uni.showToast({ title: '网络异常', icon: 'none' })
})
},
closeWinModal() {
this.showWinModal = false
},
redeem(item) {
const level = Number(item.is_win_level)
uni.navigateTo({
url: '/pages/redeem/redeem?level=' + level + '&period=' + item.index
})
}
}
}
</script>
<style lang="scss">
page {
background-color: #6EC5E3;
}
.content {
position: relative;
display: flex;
flex-direction: column;
}
.page-body {
position: relative;
z-index: 1;
flex: 1;
padding: 220rpx 30rpx 20rpx;
box-sizing: border-box;
}
.login-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
}
.login-tip {
font-size: 28rpx;
color: rgba(255,255,255,0.85);
margin-bottom: 30rpx;
}
.login-btn {
width: 400rpx;
height: 80rpx;
line-height: 80rpx;
background: linear-gradient(135deg, #FD5401, #FF7A3D);
color: #fff;
font-size: 30rpx;
font-weight: 600;
border: none;
border-radius: 40rpx;
box-shadow: 0 4rpx 16rpx rgba(253,84,1,0.3);
}
.header {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 30rpx;
}
.header-icon {
font-size: 52rpx;
margin-bottom: 10rpx;
}
.header-title {
font-size: 36rpx;
font-weight: 800;
color: #fff;
text-shadow: 0 2rpx 8rpx rgba(0,0,0,0.15);
letter-spacing: 4rpx;
}
.header-line {
width: 60rpx;
height: 4rpx;
background: rgba(255,255,255,0.5);
border-radius: 2rpx;
margin-top: 12rpx;
}
.record-list {
background: rgba(255,255,255,0.95);
border-radius: 20rpx;
overflow: hidden;
box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
}
.record-item {
display: flex;
align-items: center;
padding: 28rpx 24rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.record-item:last-child {
border-bottom: none;
}
.record-icon {
width: 56rpx;
height: 56rpx;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-right: 16rpx;
background: #fff7e6;
border-radius: 50%;
}
.record-icon .iconfont {
font-size: 32rpx;
}
.record-info {
flex: 1;
min-width: 0;
}
.record-prize {
font-size: 28rpx;
color: #333;
font-weight: 500;
display: block;
}
.record-desc {
font-size: 22rpx;
color: #999;
margin-top: 4rpx;
display: block;
}
.record-status {
font-size: 22rpx;
color: #FD5401;
margin-top: 4rpx;
display: block;
}
.record-right {
display: flex;
flex-direction: column;
align-items: stretch;
flex-shrink: 0;
margin-left: 16rpx;
min-width: 260rpx;
}
.record-time {
font-size: 22rpx;
color: #bbb;
margin-bottom: 8rpx;
text-align: right;
width: 100%;
}
.btn-row {
display: flex;
align-items: center;
gap: 12rpx;
width: 100%;
}
.winlist-btn {
flex: 1;
height: 60rpx;
line-height: 60rpx;
background: linear-gradient(135deg, #E0B866, #F0D08A);
color: #fff;
font-size: 24rpx;
font-weight: 600;
border: none;
border-radius: 30rpx;
padding: 0 12rpx;
margin: 0;
letter-spacing: 0;
text-align: center;
}
.redeem-btn {
flex: 1;
height: 60rpx;
line-height: 60rpx;
background: linear-gradient(135deg, rgba(253,84,1,0.85), rgba(255,122,61,0.85));
color: #fff;
font-size: 24rpx;
font-weight: 600;
border: none;
border-radius: 30rpx;
box-shadow: 0 4rpx 12rpx rgba(253,84,1,0.3);
padding: 0 12rpx;
margin: 0;
letter-spacing: 0;
text-align: center;
}
.empty-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80rpx 0;
}
.empty-icon {
font-size: 80rpx;
margin-bottom: 16rpx;
}
.empty-text {
font-size: 26rpx;
color: rgba(255,255,255,0.7);
}
.modal-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
z-index: 100;
}
.modal {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 600rpx;
background: #fff;
border-radius: 24rpx;
z-index: 101;
overflow: hidden;
}
.modal-header {
display: flex;
align-items: center;
justify-content: center;
padding: 30rpx 30rpx 20rpx;
position: relative;
}
.modal-title {
font-size: 32rpx;
font-weight: 700;
color: #333;
}
.modal-close {
position: absolute;
right: 30rpx;
top: 30rpx;
font-size: 28rpx;
color: #999;
padding: 8rpx;
}
.win-modal {
max-height: 80vh;
display: flex;
flex-direction: column;
}
.win-list {
flex: 1;
max-height: 60vh;
overflow-y: auto;
padding: 0 30rpx 30rpx;
}
.win-item {
display: flex;
align-items: center;
padding: 22rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.win-item:last-child {
border-bottom: none;
}
.win-level {
width: 120rpx;
height: 44rpx;
line-height: 44rpx;
text-align: center;
border-radius: 22rpx;
font-size: 22rpx;
color: #fff;
flex-shrink: 0;
margin-right: 16rpx;
}
.win-level-1 {
background: linear-gradient(135deg, #E0B866, #F0D08A);
}
.win-level-2 {
background: linear-gradient(135deg, #B8B8B8, #D8D8D8);
}
.win-level-3 {
background: linear-gradient(135deg, #CD9B6A, #E0B88A);
}
.win-name {
flex: 1;
font-size: 28rpx;
color: #333;
font-weight: 500;
min-width: 0;
}
.win-phone {
font-size: 24rpx;
color: #999;
flex-shrink: 0;
margin-left: 16rpx;
}
.win-empty {
text-align: center;
font-size: 26rpx;
color: #999;
padding: 60rpx 0;
}
::v-deep .u-tabbar__content {
background: linear-gradient(to bottom, #FAFFC7, #B2FFC9) !important;
}
::v-deep .u-tabbar__content__item {
background: linear-gradient(to bottom, #FAFFC7, #B2FFC9) !important;
}
::v-deep .u-tabbar {
.u-border-top {
border-top: none !important;
}
}
::v-deep .u-icon__img {
object-fit: contain !important;
}
::v-deep .u-tabbar__content__item {
padding: 16rpx 0 !important;
}
</style>
+376
View File
@@ -0,0 +1,376 @@
<template>
<view class="content">
<view class="page-header">
<text class="header-back" @tap="goBack">‹</text>
<text class="header-title">兑奖</text>
</view>
<view class="page-wrap">
<view class="prize-card">
<view class="prize-title">{{ prizeText }}</view>
<view class="prize-box" v-if="prizeOptions.length === 0 && prizeImages.length > 0">
<image class="prize-main-img" :src="prizeImages[0]" mode="widthFix"></image>
<text class="prize-desc" v-if="prizeContent">{{ prizeContent }}</text>
</view>
<view class="prize-options" v-if="prizeOptions.length > 0">
<text class="field-label">请选择奖品(5选1)</text>
<view class="prize-grid">
<view class="prize-grid-item" v-for="(opt, oi) in prizeOptions" :key="oi"
:class="{ active: selectedOption === opt.key }"
@tap="selectOption(opt)">
<view class="prize-grid-img-wrap">
<image class="prize-grid-img" :src="opt.images[0]" mode="aspectFill"></image>
<view class="prize-grid-mark" :class="{ checked: selectedOption === opt.key }">
<text v-if="selectedOption === opt.key">✓</text>
</view>
</view>
<view class="prize-grid-name-row">
<text class="prize-grid-name">{{ opt.name }}</text>
</view>
</view>
</view>
</view>
</view>
<view class="form-card">
<view class="form-field">
<text class="field-label">姓名</text>
<input class="field-input" v-model="userName" placeholder="请输入姓名" placeholder-class="input-placeholder" />
</view>
<view class="form-field">
<text class="field-label">手机号码</text>
<view class="field-value-static">{{ userPhone }}</view>
</view>
<view class="form-field">
<text class="field-label">身份证号码</text>
<input class="field-input" v-model="idCard" placeholder="请输入身份证号码" placeholder-class="input-placeholder" />
</view>
</view>
<button class="submit-btn" @tap="submitRedeem">确认兑奖</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
level: 0,
period: '',
prizeText: '',
prizeContent: '',
prizeImages: [],
prizeOptions: [],
selectedOption: '',
userName: '',
userPhone: '',
idCard: ''
}
},
onLoad(options) {
this.level = Number(options.level) || 0
this.period = options.period || ''
this.userName = uni.getStorageSync('userNameGift') || ''
this.userPhone = uni.getStorageSync('userPhoneGift') || ''
if (this.level === 1) {
this.prizeText = '恭喜您中了一等奖'
this.prizeContent = '2026学法有礼一等奖'
this.prizeImages = ['https://aliyunoss.hn12301.net/learnlaw/gift_cztk.png']
} else if (this.level === 2) {
this.prizeText = '恭喜您中了二等奖'
this.prizeOptions = [
{ key: 'prize_option1', name: '永州卡', prize_type: 18, images: ['https://aliyunoss.hn12301.net/learnlaw/gift_yzk.png'] },
{ key: 'prize_option2', name: '郴州卡', prize_type: 19, images: ['https://aliyunoss.hn12301.net/learnlaw/gift_czk.png'] },
{ key: 'prize_option3', name: '怀化卡', prize_type: 20, images: ['https://aliyunoss.hn12301.net/learnlaw/gift_hhk.png'] },
{ key: 'prize_option4', name: '邵阳卡', prize_type: 21, images: ['https://aliyunoss.hn12301.net/learnlaw/gift_syk.png'] },
{ key: 'prize_option5', name: '益阳卡', prize_type: 22, images: ['https://aliyunoss.hn12301.net/learnlaw/gift_yyk.png'] }
]
} else if (this.level === 3) {
this.prizeText = '恭喜您中了三等奖'
this.prizeContent = '2026学法有礼三等奖'
this.prizeImages = ['https://aliyunoss.hn12301.net/learnlaw/gift_jxxxk.png']
} else {
this.prizeText = '恭喜您中奖'
}
},
methods: {
goBack() {
uni.navigateBack()
},
selectOption(opt) {
this.selectedOption = opt.key
},
submitRedeem() {
if (!this.userName) {
uni.showToast({ title: '请输入姓名', icon: 'none' })
return
}
if (this.prizeOptions.length > 0 && !this.selectedOption) {
uni.showToast({ title: '请选择奖品', icon: 'none' })
return
}
if (!this.idCard) {
uni.showToast({ title: '请输入身份证号码', icon: 'none' })
return
}
if (!/^\d{15}$|^\d{17}[\dXx]$/.test(this.idCard)) {
uni.showToast({ title: '身份证号码格式不正确', icon: 'none' })
return
}
let prizeType = null
if (this.level === 1) {
prizeType = 17
} else if (this.level === 2) {
const opt = this.prizeOptions.find(o => o.key === this.selectedOption)
prizeType = opt ? opt.prize_type : null
} else if (this.level === 3) {
prizeType = 23
}
this.$u.api.exchangePrize({
wenjuan_id: 1003,
period: this.period,
win_level: this.level,
user_id: uni.getStorageSync('userIdGift'),
prize_type: prizeType,
user_name: this.userName,
user_card_num: this.idCard,
address: ''
}).then(res => {
if (res.code === 0) {
uni.showToast({ title: '兑奖成功', icon: 'none' })
setTimeout(() => {
uni.navigateBack()
}, 1200)
} else {
uni.showToast({ title: res.msg || '兑奖失败', icon: 'none' })
}
}).catch(() => {
uni.showToast({ title: '网络异常', icon: 'none' })
})
}
}
}
</script>
<style lang="scss">
page {
background-color: #F5F5F5;
}
.content {
position: relative;
display: flex;
flex-direction: column;
min-height: 100vh;
min-height: 100dvh;
}
.page-header {
display: flex;
align-items: center;
padding: 20rpx 30rpx;
background: linear-gradient(135deg, #FD5401, #FF7A3D);
flex-shrink: 0;
}
.header-back {
font-size: 48rpx;
color: #fff;
line-height: 1;
margin-right: 16rpx;
padding: 0 10rpx;
}
.header-title {
font-size: 30rpx;
font-weight: 600;
color: #fff;
flex: 1;
}
.page-wrap {
padding: 24rpx 24rpx 60rpx;
box-sizing: border-box;
}
.prize-card {
background: #fff;
border-radius: 20rpx;
padding: 30rpx 24rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
margin-bottom: 24rpx;
}
.prize-title {
font-size: 32rpx;
font-weight: 700;
color: #333;
text-align: center;
margin-bottom: 24rpx;
}
.prize-box {
background: linear-gradient(135deg, #FDF6E8 0%, #FEF3E0 100%);
border: 2rpx solid #F5E3C8;
border-radius: 20rpx;
padding: 30rpx 24rpx 24rpx;
text-align: center;
position: relative;
overflow: hidden;
}
.prize-box::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 6rpx;
background: linear-gradient(to right, #FD5401, #E0B866, #FD5401);
}
.prize-main-img {
width: 400rpx;
border-radius: 12rpx;
}
.prize-desc {
font-size: 24rpx;
color: #8B6A3E;
display: block;
margin-top: 16rpx;
line-height: 1.6;
font-weight: 500;
text-align: center;
}
.prize-options {
margin-top: 8rpx;
}
.prize-grid {
display: flex;
flex-wrap: wrap;
}
.prize-grid-item {
width: 319rpx;
box-sizing: border-box;
border: 2rpx solid #eee;
border-radius: 14rpx;
background: #FEFCF8;
overflow: hidden;
margin-right: 16rpx;
margin-bottom: 16rpx;
}
.prize-grid-item:nth-child(2n) {
margin-right: 0;
}
.prize-grid-item.active {
border-color: #FD5401;
background: #FFF4EC;
box-shadow: 0 4rpx 12rpx rgba(253, 84, 1, 0.15);
}
.prize-grid-img-wrap {
position: relative;
width: 100%;
height: 210rpx;
}
.prize-grid-img {
width: 100%;
height: 100%;
}
.prize-grid-mark {
position: absolute;
top: 12rpx;
right: 12rpx;
width: 40rpx;
height: 40rpx;
line-height: 36rpx;
text-align: center;
border-radius: 50%;
background: rgba(255, 255, 255, 0.9);
border: 2rpx solid #ccc;
color: #fff;
font-size: 24rpx;
font-weight: 700;
box-sizing: border-box;
}
.prize-grid-mark.checked {
background: #FD5401;
border-color: #FD5401;
box-shadow: 0 2rpx 8rpx rgba(253, 84, 1, 0.4);
}
.prize-grid-name-row {
display: flex;
align-items: center;
justify-content: center;
padding: 16rpx 10rpx;
}
.prize-grid-name {
font-size: 26rpx;
color: #333;
font-weight: 600;
}
.form-card {
background: #fff;
border-radius: 20rpx;
padding: 10rpx 24rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
margin-bottom: 30rpx;
}
.form-field {
padding: 20rpx 0;
border-bottom: 1rpx solid #f5f5f5;
}
.form-field:last-child {
border-bottom: none;
}
.field-label {
font-size: 26rpx;
color: #666;
display: block;
margin-bottom: 10rpx;
}
.field-value-static {
font-size: 28rpx;
color: #333;
font-weight: 500;
padding: 8rpx 0;
}
.field-input {
font-size: 28rpx;
color: #333;
padding: 8rpx 0;
width: 100%;
}
.input-placeholder {
color: #ccc;
}
.submit-btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
background: linear-gradient(135deg, #FD5401, #FF7A3D);
color: #fff;
font-size: 32rpx;
font-weight: 600;
border: none;
border-radius: 44rpx;
box-shadow: 0 4rpx 16rpx rgba(253, 84, 1, 0.4);
}
</style>
+357
View File
@@ -0,0 +1,357 @@
<template>
<view class="register-page">
<view class="header">
<!-- <text class="header-title">注册信息</text> -->
<text class="header-desc">填写参与活动信息</text>
</view>
<view class="form">
<view class="field">
<text class="label">姓名</text>
<view class="input-wrap">
<text class="input-icon">👤</text>
<input class="input" v-model="userName" placeholder="请输入您的姓名" />
</view>
<view class="notice">
<view class="notice-icon">⚠️</view>
<text class="notice-text">为保障活动公平及奖品顺利发放,请使用本人实名信息注册,中奖后需凭有效身份信息核验兑奖。</text>
</view>
</view>
<view class="field">
<text class="label">手机号</text>
<view class="input-wrap">
<text class="input-icon">📱</text>
<input class="input" v-model="phone" placeholder="请输入手机号" type="number" maxlength="11" />
</view>
</view>
<view class="field">
<text class="label">验证码</text>
<view class="field-row">
<view class="input-wrap code-input-wrap">
<text class="input-icon">✉️</text>
<input class="input" v-model="smsCode" placeholder="请输入验证码" type="number" maxlength="6" />
</view>
<button class="code-btn" @tap="sendCode" :disabled="sending" :class="{ disabled: sending }">
{{ sending ? countdown + 's' : '获取验证码' }}
</button>
</view>
</view>
<view class="agree-row" @tap="agreed = !agreed">
<view class="agree-checkbox" :class="{ checked: agreed }">
<text v-if="agreed" class="agree-check-mark">✓</text>
</view>
<view class="agree-text">
<text>我已阅读并同意</text>
<text class="agree-link" @tap.stop="goPrivacy">《用户隐私协议》</text>
</view>
</view>
<button class="submit-btn" @tap="register" :disabled="submitting" :class="{ disabled: submitting }">
{{ submitting ? '提交中...' : '立即提交' }}
</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
userName: '',
phone: '',
smsCode: '',
agreed: false,
sending: false,
countdown: 60,
timer: null,
submitting: false
}
},
methods: {
sendCode() {
if (!/^1\d{10}$/.test(this.phone)) {
uni.showToast({ title: '请输入正确手机号', icon: 'none' })
return
}
this.sending = true
this.$u.api.sendSms(this.phone).then(res => {
if (res.code === 0) {
uni.showToast({ title: '验证码已发送', icon: 'none' })
this.startCountdown()
} else {
uni.showToast({ title: res.msg || '发送失败', icon: 'none' })
this.sending = false
}
}).catch(() => {
uni.showToast({ title: '网络错误', icon: 'none' })
this.sending = false
})
},
startCountdown() {
this.countdown = 60
this.timer = setInterval(() => {
this.countdown--
if (this.countdown <= 0) {
clearInterval(this.timer)
this.timer = null
this.sending = false
}
}, 1000)
},
goPrivacy() {
uni.navigateTo({ url: '/pages/privacy/privacy' })
},
register() {
if (!this.userName || !this.phone || !this.smsCode) {
uni.showToast({ title: '请填写完整信息', icon: 'none' })
return
}
if (!this.agreed) {
uni.showToast({ title: '请先阅读并同意《用户隐私协议》', icon: 'none' })
return
}
this.submitting = true
const openId = uni.getStorageSync('openIdGift')
this.$u.api.userRegist({
wenjuan_id: 1003,
open_id: openId,
user_name: this.userName,
user_phone: this.phone,
sms_code: this.smsCode,
channel: 1
}).then(res => {
if (res.code === 0) {
uni.setStorageSync('isRegisteredGift', true)
const data = res.Data || {}
const userId = data.user_id || data.uesr_id
if (userId) uni.setStorageSync('userIdGift', userId)
if (data.user_name) uni.setStorageSync('userNameGift', data.user_name)
if (data.user_phone) uni.setStorageSync('userPhoneGift', data.user_phone)
this.syncUserInfo(openId)
uni.showToast({ title: '注册成功' })
setTimeout(() => uni.navigateBack(), 1500)
} else {
uni.showToast({ title: res.msg || '注册失败', icon: 'none' })
}
}).catch(() => {
uni.showToast({ title: '网络错误', icon: 'none' })
}).finally(() => {
this.submitting = false
})
},
syncUserInfo(openId) {
this.$u.api.wxOpenidLogin({ wenjuan_id: 1003, open_id: openId }).then(loginRes => {
if (loginRes && loginRes.code === 0 && loginRes.Data) {
const data = loginRes.Data
if (data.user_id || data.uesr_id) uni.setStorageSync('userIdGift', data.user_id || data.uesr_id)
if (data.user_name) uni.setStorageSync('userNameGift', data.user_name)
if (data.user_phone) uni.setStorageSync('userPhoneGift', data.user_phone)
}
}).catch(err => {
console.error('注册后同步用户信息失败', err)
})
}
},
beforeDestroy() {
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
}
}
</script>
<style>
page {
background: linear-gradient(135deg, #FFF8ED 0%, #FFE9C4 100%);
}
.register-page {
padding: 60rpx 40rpx;
min-height: 100vh;
box-sizing: border-box;
}
.header {
margin-bottom: 50rpx;
text-align: center;
}
.header-title {
font-size: 40rpx;
font-weight: bold;
color: #5D533D;
display: block;
margin-bottom: 12rpx;
}
.header-desc {
font-size: 26rpx;
color: #8B7355;
}
.form {
background: rgba(255, 255, 255, 0.95);
border-radius: 24rpx;
padding: 50rpx 40rpx;
box-shadow: 0 8rpx 40rpx rgba(0, 0, 0, 0.06);
}
.field {
margin-bottom: 36rpx;
}
.label {
font-size: 28rpx;
font-weight: 500;
color: #5D533D;
margin-bottom: 16rpx;
display: block;
padding-left: 8rpx;
}
.input-wrap {
display: flex;
align-items: center;
height: 88rpx;
border: 2rpx solid #F0E4D0;
border-radius: 12rpx;
background: #FEFCF8;
padding: 0 24rpx;
box-sizing: border-box;
}
.input-wrap:focus-within {
border-color: #FD5401;
box-shadow: 0 0 0 4rpx rgba(253, 84, 1, 0.08);
}
.input-icon {
font-size: 32rpx;
margin-right: 16rpx;
flex-shrink: 0;
}
.input {
height: 100%;
font-size: 28rpx;
color: #333;
flex: 1;
border: none;
background: transparent;
outline: none;
width: 100%;
box-sizing: border-box;
}
.field-row {
display: flex;
align-items: center;
}
.code-input-wrap {
flex: 1;
margin-right: 20rpx;
}
.code-btn {
flex-shrink: 0;
height: 88rpx;
line-height: 88rpx;
padding: 0 28rpx;
font-size: 26rpx;
color: #FD5401;
background: #FFF0E6;
border: none;
border-radius: 12rpx;
font-weight: 500;
}
.code-btn.disabled {
color: #B8A898;
background: #F0E8E0;
}
.submit-btn {
width: 100%;
height: 96rpx;
line-height: 96rpx;
background: linear-gradient(135deg, #FD5401, #FF7A3D);
color: #fff;
font-size: 34rpx;
font-weight: 600;
border: none;
border-radius: 16rpx;
margin-top: 20rpx;
box-shadow: 0 6rpx 20rpx rgba(253, 84, 1, 0.3);
}
.submit-btn.disabled {
opacity: 0.5;
}
.notice {
display: flex;
align-items: flex-start;
margin-top: 16rpx;
padding: 26rpx 28rpx;
background: linear-gradient(135deg, #FFF3F3 0%, #FFE9E9 100%);
border-radius: 16rpx;
box-shadow: 0 4rpx 16rpx rgba(230, 67, 64, 0.12);
}
.notice-icon {
font-size: 32rpx;
line-height: 1.4;
margin-right: 14rpx;
flex-shrink: 0;
}
.notice-text {
flex: 1;
font-size: 26rpx;
font-weight: 600;
color: #E64340;
line-height: 1.6;
}
.agree-row {
display: flex;
align-items: center;
margin: -16rpx 8rpx 28rpx;
}
.agree-checkbox {
width: 36rpx;
height: 36rpx;
border: 2rpx solid #CCC;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-right: 12rpx;
box-sizing: border-box;
}
.agree-checkbox.checked {
border-color: #FD5401;
background: #FD5401;
}
.agree-check-mark {
font-size: 22rpx;
color: #fff;
font-weight: 700;
line-height: 1;
}
.agree-text {
display: flex;
align-items: center;
font-size: 24rpx;
color: #8B7355;
}
.agree-link {
color: #FD5401;
}
</style>
+306
View File
@@ -0,0 +1,306 @@
<template>
<view class="content">
<image src="https://aliyunoss.hn12301.net/learnlaw/gift_zt_bj.jpg" mode="widthFix" style="width:750rpx;position:absolute;top:0;left:0;z-index:0;"></image>
<view class="page-body">
<view class="stat-grid">
<view class="stat-row">
<view class="stat-item">
<view class="stat-icon icon-correct">
<text class="iconfont">✓</text>
</view>
<text class="stat-num">{{ correctCount }}</text>
<text class="stat-label">答对题数</text>
</view>
<view class="stat-item">
<view class="stat-icon icon-accuracy">
<text class="iconfont">%</text>
</view>
<text class="stat-num">{{ accuracy }}%</text>
<text class="stat-label">正确率</text>
</view>
</view>
<view class="stat-row">
<view class="stat-item">
<view class="stat-icon icon-time">
<text class="iconfont">⏱</text>
</view>
<text class="stat-num">{{ useTime }}s</text>
<text class="stat-label">用时</text>
</view>
<view class="stat-item">
<view class="stat-icon icon-wrong">
<text class="iconfont">✗</text>
</view>
<text class="stat-num">{{ wrongCount }}</text>
<text class="stat-label">错题数</text>
</view>
</view>
</view>
<view class="detail-section">
<view class="section-header">
<text class="section-title">答题详情</text>
<text class="section-subtitle">共 {{ detailList.length }} 题,{{ correctCount }} 对 {{ wrongCount }} 错</text>
</view>
<view class="detail-item" v-for="(item, i) in detailList" :key="i">
<view class="detail-index" :class="item.is_correct || item.status === 1 ? 'correct' : 'wrong'">
<text class="detail-index-text">{{ i + 1 }}</text>
</view>
<view class="detail-body">
<text class="detail-q">{{ item.tiku_question_content || '题目' + (i+1) }}</text>
<view class="detail-answer-row">
<text class="detail-label">你的答案:</text>
<text class="detail-value" :class="item.is_correct || item.status === 1 ? 'text-correct' : 'text-wrong'">{{ item.user_answer || '未作答' }}</text>
</view>
<view class="detail-answer-row" v-if="!(item.is_correct || item.status === 1)">
<text class="detail-label">正确答案:</text>
<text class="detail-value text-correct-answer">{{ item.tiku_correct_answer || '-' }}</text>
</view>
</view>
</view>
</view>
<button class="back-btn" @tap="goBack">返回首页</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
correctCount: 0,
wrongCount: 0,
accuracy: 0,
useTime: 0,
detailList: []
}
},
onLoad() {
try {
const data = uni.getStorageSync('resultDataGift') || {}
this.useTime = data.use_time || 0
if (data.resultDetailList && data.resultDetailList.length) {
this.detailList = data.resultDetailList
this.correctCount = data.resultDetailList.filter(r => r.is_correct === true || r.is_correct === 1 || r.status === 1).length
this.wrongCount = data.resultDetailList.length - this.correctCount
this.accuracy = Math.round(this.correctCount / data.resultDetailList.length * 100)
}
} catch(e) {
console.error('解析结果数据失败', e)
}
},
methods: {
goBack() {
uni.switchTab({ url: '/pages/index/index' })
}
}
}
</script>
<style lang="scss">
page {
background-color: #7CC6EB;
}
.content {
position: relative;
height: 100vh;
height: 100dvh;
}
.page-body {
position: relative;
z-index: 1;
height: 100%;
padding: 40rpx 30rpx;
box-sizing: border-box;
}
.stat-grid {
margin-bottom: 20rpx;
}
.stat-row {
display: flex;
gap: 14rpx;
margin-bottom: 14rpx;
}
.stat-row:last-child {
margin-bottom: 0;
}
.stat-item {
flex: 1;
background: rgba(255,255,255,0.95);
border-radius: 16rpx;
padding: 20rpx 12rpx 18rpx;
text-align: center;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
}
.stat-icon {
width: 44rpx;
height: 44rpx;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
margin-bottom: 8rpx;
}
.stat-icon .iconfont {
font-size: 22rpx;
font-weight: 700;
}
.icon-correct { background: #f6ffed; }
.icon-correct .iconfont { color: #52c41a; }
.icon-accuracy { background: #fff7e6; }
.icon-accuracy .iconfont { color: #fa8c16; }
.icon-time { background: #e6f7ff; }
.icon-time .iconfont { color: #1890ff; }
.icon-wrong { background: #fff1f0; }
.icon-wrong .iconfont { color: #f5222d; }
.stat-num {
display: block;
font-size: 34rpx;
font-weight: 700;
color: #333;
margin-bottom: 4rpx;
}
.stat-label {
font-size: 22rpx;
color: #999;
}
.detail-section {
background: rgba(255,255,255,0.95);
border-radius: 20rpx;
padding: 28rpx;
margin-bottom: 30rpx;
box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.04);
}
.section-header {
display: flex;
justify-content: space-between;
align-items: baseline;
margin-bottom: 20rpx;
padding-bottom: 16rpx;
border-bottom: 2rpx solid #f5f5f5;
}
.section-title {
font-size: 30rpx;
font-weight: 700;
color: #333;
}
.section-subtitle {
font-size: 24rpx;
color: #bbb;
}
.detail-item {
padding: 20rpx 0;
border-bottom: 1rpx solid #f0f0f0;
display: flex;
}
.detail-item:last-child {
border-bottom: none;
}
.detail-index {
width: 44rpx;
height: 44rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-right: 16rpx;
margin-top: 2rpx;
}
.detail-index.correct {
background: linear-gradient(135deg, #52c41a, #73d13d);
box-shadow: 0 2rpx 8rpx rgba(82,196,26,0.3);
}
.detail-index.wrong {
background: linear-gradient(135deg, #f5222d, #ff4d4f);
box-shadow: 0 2rpx 8rpx rgba(245,34,45,0.3);
}
.detail-index-text {
font-size: 20rpx;
font-weight: 700;
color: #fff;
}
.detail-body {
flex: 1;
min-width: 0;
}
.detail-q {
font-size: 26rpx;
color: #333;
line-height: 1.5;
display: block;
margin-bottom: 10rpx;
}
.detail-answer-row {
display: flex;
align-items: baseline;
margin-top: 4rpx;
}
.detail-label {
font-size: 22rpx;
color: #999;
flex-shrink: 0;
}
.detail-value {
font-size: 24rpx;
font-weight: 500;
}
.text-correct {
color: #52c41a;
}
.text-wrong {
color: #f5222d;
}
.text-correct-answer {
color: #52c41a;
}
.back-btn {
width: 690rpx;
height: 88rpx;
line-height: 88rpx;
background: linear-gradient(135deg, #FD5401, #FF7A3D);
color: #fff;
font-size: 32rpx;
font-weight: 600;
border: none;
border-radius: 44rpx;
box-shadow: 0 4rpx 16rpx rgba(253,84,1,0.3);
}
</style>
+220
View File
@@ -0,0 +1,220 @@
<template>
<view class="content">
<view class="page-header">
<text class="header-back" @tap="goBack">‹</text>
<text class="header-title">活动规则</text>
</view>
<view class="preview-wrap">
<view class="loading-box" v-if="loading">
<u-loading mode="circle" size="40"></u-loading>
<text class="loading-text">内容加载中...</text>
</view>
<scroll-view class="doc-scroll" scroll-y v-if="!loading && !error">
<rich-text class="doc-content" :nodes="htmlContent"></rich-text>
<view style="height:30rpx;"></view>
</scroll-view>
<view class="error-box" v-if="error">
<text class="error-text">{{ error }}</text>
<button class="preview-btn" @tap="loadDoc">重新加载</button>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
docUrl: 'https://goldnar.hntgov.net/img/gift_rules.docx',
loading: false,
error: '',
htmlContent: ''
}
},
onReady() {
this.loadDoc()
},
methods: {
goBack() {
uni.navigateBack()
},
loadDoc() {
this.loading = true
this.error = ''
this.htmlContent = ''
this.ensureMammoth().then(() => {
return this.fetchDocx(this.docUrl)
}).then(arrayBuffer => {
return window.mammoth.convertToHtml({
arrayBuffer
})
}).then(result => {
this.htmlContent = result.value || ''
this.loading = false
}).catch(err => {
console.error('加载文档失败', err)
this.loading = false
this.error = '文档加载失败,请重试'
})
},
ensureMammoth() {
if (window.mammoth) return Promise.resolve()
return new Promise((resolve, reject) => {
const urls = [
'/static/mammoth.browser.min.js',
'https://unpkg.com/mammoth@1.6.0/mammoth.browser.min.js'
]
let i = 0
const loadNext = () => {
if (i >= urls.length) {
reject(new Error('解析组件加载失败'))
return
}
const s = document.createElement('script')
s.src = urls[i++]
s.onload = () => window.mammoth ? resolve() : loadNext()
s.onerror = loadNext
document.head.appendChild(s)
}
loadNext()
})
},
fetchDocx(url) {
return new Promise((resolve, reject) => {
if (typeof fetch !== 'undefined') {
fetch(url).then(res => {
if (!res.ok) throw new Error('HTTP ' + res.status)
return res.arrayBuffer()
}).then(resolve).catch(() => this.downloadDocx(url).then(resolve).catch(reject))
return
}
this.downloadDocx(url).then(resolve).catch(reject)
})
},
downloadDocx(url) {
return new Promise((resolve, reject) => {
uni.downloadFile({
url: url,
success: (res) => {
if (res.statusCode !== 200) {
reject(new Error('下载失败'))
return
}
uni.getFileSystemManager().readFile({
filePath: res.tempFilePath,
success: (r) => resolve(r.data),
fail: () => {
if (res.tempFilePath && typeof fetch !== 'undefined') {
fetch(res.tempFilePath).then(r => r.arrayBuffer())
.then(resolve).catch(reject)
} else {
reject(new Error('读取文件失败'))
}
}
})
},
fail: reject
})
})
}
}
}
</script>
<style lang="scss">
page {
background-color: #F8F8F8;
}
.content {
position: relative;
display: flex;
flex-direction: column;
height: 100vh;
height: 100dvh;
}
.page-header {
display: flex;
align-items: center;
padding: 20rpx 30rpx;
background: linear-gradient(135deg, #FD5401, #FF7A3D);
flex-shrink: 0;
}
.header-back {
font-size: 48rpx;
color: #fff;
line-height: 1;
margin-right: 16rpx;
padding: 0 10rpx;
}
.header-title {
font-size: 30rpx;
font-weight: 600;
color: #fff;
flex: 1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.preview-wrap {
flex: 1;
background: #fff;
overflow: hidden;
}
.loading-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
}
.loading-text {
font-size: 26rpx;
color: #999;
margin-top: 20rpx;
}
.error-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
}
.error-text {
font-size: 26rpx;
color: #999;
margin-bottom: 30rpx;
}
.preview-btn {
width: 300rpx;
height: 76rpx;
line-height: 76rpx;
background: linear-gradient(135deg, #FD5401, #FF7A3D);
color: #fff;
font-size: 28rpx;
font-weight: 600;
border: none;
border-radius: 38rpx;
}
.doc-scroll {
height: 100%;
padding: 20rpx 30rpx;
box-sizing: border-box;
}
.doc-content {
font-size: 28rpx;
color: #333;
line-height: 1.8;
}
</style>
+336
View File
@@ -0,0 +1,336 @@
<template>
<view class="content">
<view class="page-header">
<text class="header-title">{{ current.title }}</text>
<text class="header-index">{{ currentIndex + 1 }} / {{ articles.length }}</text>
</view>
<view class="preview-wrap">
<view class="loading-box" v-if="loading">
<u-loading mode="circle" size="40"></u-loading>
<text class="loading-text">内容加载中...</text>
</view>
<scroll-view class="doc-scroll" scroll-y v-if="!loading && !error">
<rich-text class="doc-content" :nodes="htmlContent"></rich-text>
<view style="height:30rpx;"></view>
</scroll-view>
<view class="error-box" v-if="error">
<text class="error-text">{{ error }}</text>
<button class="preview-btn" @tap="loadArticle">重新加载</button>
</view>
</view>
<view class="page-footer">
<button class="nav-btn prev" :disabled="currentIndex <= 0" @tap="prev">上一篇</button>
<button class="nav-btn next" :disabled="currentIndex >= articles.length - 1" @tap="next">下一篇</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
articles: [{
title: '中华人民共和国宪法',
url: 'https://goldnar.hntgov.net/img/word8.docx'
},
{
title: '中华人民共和国民法典',
url: 'https://goldnar.hntgov.net/img/word4.docx'
},
{
title: '中华人民共和国测绘法',
url: 'https://goldnar.hntgov.net/img/word13_1.docx'
},
{
title: '中华人民共和国城市房地产管理法',
url: 'https://goldnar.hntgov.net/img/word14_1.docx'
},
{
title: '中华人民共和国矿产资源法',
url: 'https://goldnar.hntgov.net/img/word15_1.docx'
},
{
title: '中华人民共和国土地管理法',
url: 'https://goldnar.hntgov.net/img/word16_1.docx'
},
{
title: '中华人民共和国生态环境法典',
url: 'https://goldnar.hntgov.net/img/word9.docx'
},
{
title: '中华人民共和国渔业法',
url: 'https://goldnar.hntgov.net/img/word10.docx'
},
{
title: '中华人民共和国农村集体经济组织法',
url: 'https://goldnar.hntgov.net/img/word11.docx'
},
{
title: '湖南省实施《中华人民共和国法治宣传教育法》办法',
url: 'https://goldnar.hntgov.net/img/word1.docx'
},
{
title: '中华人民共和国安全生产法',
url: 'https://goldnar.hntgov.net/img/word5.docx'
},
{
title: '中华人民共和国法治宣传教育法',
url: 'https://goldnar.hntgov.net/img/word3.docx'
},
{
title: '中华人民共和国乡村振兴促进法',
url: 'https://goldnar.hntgov.net/img/word7.docx'
}
],
currentIndex: 0,
loading: false,
error: '',
htmlContent: ''
}
},
computed: {
current() {
return this.articles[this.currentIndex] || {}
}
},
onLoad(options) {
const idx = parseInt(options.index)
if (!isNaN(idx) && idx >= 0 && idx < this.articles.length) {
this.currentIndex = idx
} else {
this.currentIndex = 0
}
},
onReady() {
this.loadArticle()
},
methods: {
prev() {
if (this.currentIndex > 0) {
this.currentIndex--
this.loadArticle()
}
},
next() {
if (this.currentIndex < this.articles.length - 1) {
this.currentIndex++
this.loadArticle()
}
},
loadArticle() {
this.loading = true
this.error = ''
this.htmlContent = ''
this.ensureMammoth().then(() => {
return this.fetchDocx(this.current.url)
}).then(arrayBuffer => {
return window.mammoth.convertToHtml({
arrayBuffer
})
}).then(result => {
this.htmlContent = result.value || ''
this.loading = false
}).catch(err => {
console.error('加载文档失败', err)
this.loading = false
this.error = '文档加载失败,请重试'
})
},
ensureMammoth() {
if (window.mammoth) return Promise.resolve()
return new Promise((resolve, reject) => {
const urls = [
'/static/mammoth.browser.min.js',
'https://unpkg.com/mammoth@1.6.0/mammoth.browser.min.js'
]
let i = 0
const loadNext = () => {
if (i >= urls.length) {
reject(new Error('解析组件加载失败'))
return
}
const s = document.createElement('script')
s.src = urls[i++]
s.onload = () => window.mammoth ? resolve() : loadNext()
s.onerror = loadNext
document.head.appendChild(s)
}
loadNext()
})
},
fetchDocx(url) {
return new Promise((resolve, reject) => {
if (typeof fetch !== 'undefined') {
fetch(url).then(res => {
if (!res.ok) throw new Error('HTTP ' + res.status)
return res.arrayBuffer()
}).then(resolve).catch(() => this.downloadDocx(url).then(resolve).catch(reject))
return
}
this.downloadDocx(url).then(resolve).catch(reject)
})
},
downloadDocx(url) {
return new Promise((resolve, reject) => {
uni.downloadFile({
url: url,
success: (res) => {
if (res.statusCode !== 200) {
reject(new Error('下载失败'))
return
}
uni.getFileSystemManager().readFile({
filePath: res.tempFilePath,
success: (r) => resolve(r.data),
fail: () => {
if (res.tempFilePath && typeof fetch !== 'undefined') {
fetch(res.tempFilePath).then(r => r.arrayBuffer())
.then(resolve).catch(reject)
} else {
reject(new Error('读取文件失败'))
}
}
})
},
fail: reject
})
})
}
}
}
</script>
<style lang="scss">
page {
background-color: #F8F8F8;
}
.content {
position: relative;
display: flex;
flex-direction: column;
}
.page-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 30rpx;
background: linear-gradient(135deg, #FD5401, #FF7A3D);
flex-shrink: 0;
}
.header-title {
font-size: 30rpx;
font-weight: 600;
color: #fff;
flex: 1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.header-index {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.8);
margin-left: 20rpx;
}
.preview-wrap {
flex: 1;
background: #fff;
overflow: hidden;
padding-bottom: 100rpx;
}
.loading-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
}
.loading-text {
font-size: 26rpx;
color: #999;
margin-top: 20rpx;
}
.error-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
}
.error-text {
font-size: 26rpx;
color: #999;
margin-bottom: 30rpx;
}
.preview-btn {
width: 300rpx;
height: 76rpx;
line-height: 76rpx;
background: linear-gradient(135deg, #FD5401, #FF7A3D);
color: #fff;
font-size: 28rpx;
font-weight: 600;
border: none;
border-radius: 38rpx;
}
.doc-scroll {
height: 100%;
padding: 20rpx 30rpx;
box-sizing: border-box;
}
.doc-content {
font-size: 28rpx;
color: #333;
line-height: 1.8;
}
.page-footer {
display: flex;
gap: 24rpx;
padding: 20rpx 30rpx;
position: fixed;
bottom: 0rpx;
width: 750rpx;
background: #fff;
flex-shrink: 0;
border-top: 1rpx solid #f0f0f0;
}
.nav-btn {
flex: 1;
height: 76rpx;
line-height: 76rpx;
font-size: 28rpx;
font-weight: 600;
border: none;
border-radius: 38rpx;
margin: 0;
}
.nav-btn.prev {
background: #f5f5f5;
color: #333;
}
.nav-btn.next {
background: linear-gradient(135deg, #FD5401, #FF7A3D);
color: #fff;
}
.nav-btn[disabled] {
opacity: 0.4;
color: #999;
}
</style>
+156
View File
@@ -0,0 +1,156 @@
<template>
<view class="content">
<view class="page-body">
<image src="https://aliyunoss.hn12301.net/learnlaw/gift_zt_bj.jpg" mode="widthFix" style="width:750rpx;position:absolute;top:0;left:0;z-index:-1;"></image>
<view class="header">
<view class="header-icon"><text>📖</text></view>
<text class="header-title">法制学习</text>
<view class="header-line"></view>
</view>
<view class="doc-list" v-if="articles.length > 0">
<view class="doc-item" v-for="(item, i) in articles" :key="i" @tap="openArticle(i)">
<view class="doc-icon">
<text class="iconfont">📄</text>
</view>
<view class="doc-info">
<text class="doc-title">{{ item.title }}</text>
<text class="doc-desc">{{ item.desc }}</text>
</view>
<view class="doc-arrow">
<text>›</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
articles: [
{ title: '湖南省实施《中华人民共和国法治宣传教育法》办法', desc: '省级地方性法规', url: 'https://goldnar.hntgov.net/img/word1.docx' },
{ title: '全民阅读促进条例', desc: '行政法规', url: 'https://goldnar.hntgov.net/img/word2.docx' },
{ title: '中华人民共和国法治宣传教育法', desc: '全国性法律', url: 'https://goldnar.hntgov.net/img/word3.docx' },
{ title: '中华人民共和国民法典', desc: '全国性法律', url: 'https://goldnar.hntgov.net/img/word4.docx' }
]
}
},
methods: {
openArticle(i) {
uni.navigateTo({ url: '/pages/study/preview?index=' + i })
}
}
}
</script>
<style lang="scss">
page {
background-color: #7CC6EB;
}
.content {
position: relative;
height: 100vh;
height: 100dvh;
overflow: hidden;
}
.page-body {
position: relative;
z-index: 1;
height: 100%;
overflow-y: auto;
padding: 80rpx 30rpx 20rpx;
box-sizing: border-box;
}
.header {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 30rpx;
}
.header-icon {
font-size: 52rpx;
margin-bottom: 10rpx;
}
.header-title {
font-size: 36rpx;
font-weight: 800;
color: #fff;
text-shadow: 0 2rpx 8rpx rgba(0,0,0,0.15);
letter-spacing: 4rpx;
}
.header-line {
width: 60rpx;
height: 4rpx;
background: rgba(255,255,255,0.5);
border-radius: 2rpx;
margin-top: 12rpx;
}
.doc-list {
background: rgba(255,255,255,0.95);
border-radius: 20rpx;
overflow: hidden;
box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
}
.doc-item {
display: flex;
align-items: center;
padding: 28rpx 24rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.doc-item:last-child {
border-bottom: none;
}
.doc-icon {
width: 56rpx;
height: 56rpx;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-right: 16rpx;
background: #fff7e6;
border-radius: 50%;
}
.doc-icon .iconfont {
font-size: 32rpx;
}
.doc-info {
flex: 1;
min-width: 0;
}
.doc-title {
font-size: 28rpx;
color: #333;
font-weight: 500;
display: block;
}
.doc-desc {
font-size: 22rpx;
color: #bbb;
margin-top: 4rpx;
display: block;
}
.doc-arrow {
font-size: 36rpx;
color: #ccc;
flex-shrink: 0;
margin-left: 16rpx;
}
</style>