很多页面

This commit is contained in:
haomingming
2025-09-03 17:13:10 +08:00
parent 92bfc36d51
commit 84f59b20f6
26 changed files with 29536 additions and 616 deletions
+238
View File
@@ -0,0 +1,238 @@
<template>
<uni-nav-bar
left-icon="left"
title="兑换福利卡"
@clickLeft="goBack"
fixed
color="#8B2316"
height="140rpx"
:border="false"
backgroundColor="#eeeeee"
></uni-nav-bar>
<view class="exchange-page">
<!-- 顶部红色横幅 -->
<view class="top-banner">
<view class="banner-text">
<view class="line1">已兑换{{ exchangedCount }}张</view>
<view class="line2" @click="goMyWelfare">查看现有权益</view>
</view>
<view class="help-btn" @click="showHelp">帮助说明</view>
</view>
<!-- 使用统一的自定义居中模态框 -->
<view v-if="centerVisible" class="center-modal" @click.self="closeCenter">
<view class="center-modal-content">
<view class="center-title">{{ centerHelp ? '帮助说明' : '提示' }}</view>
<view v-if="centerHelp" class="help-content center-help">
<text>1、点击“兑换福利卡”,输入密码即可兑换相应权益</text>
<text>2、每张福利卡仅限兑换一次,兑换后权益可在“我的福利-使用福利”中查看</text>
<text>3、福利卡长期有效,福利卡不能退换或者折现</text>
<text>4、查找文献权益不限文献类型,如指南共识、论文、电子书、课件或者视频</text>
</view>
<view v-else class="center-body">{{ centerText }}</view>
<view class="center-actions">
<button class="center-btn" @click="closeCenter">知道了</button>
</view>
</view>
</view>
<!-- 输入提示 -->
<view class="tips">请输入16位福利卡密码(不区分大小写)<text class="paste-action" @click="pasteFromClipboard">粘贴</text></view>
<!-- 四段输入框 -->
<view class="code-inputs">
<input class="code-box" type="text" v-model="code1" maxlength="4" placeholder="" :focus="f1" @input="handleInput(1, $event)" @paste="handlePaste"/>
<input class="code-box" type="text" v-model="code2" maxlength="4" placeholder="" :focus="f2" @input="handleInput(2, $event)"/>
<input class="code-box" type="text" v-model="code3" maxlength="4" placeholder="" :focus="f3" @input="handleInput(3, $event)"/>
<input class="code-box" type="text" v-model="code4" maxlength="4" placeholder="" :focus="f4" @input="handleInput(4, $event)"/>
</view>
<!-- 按钮 -->
<view class="btn-wrapper">
<button class="submit-btn" :disabled="!isFull" @click="submit">立即兑换</button>
</view>
</view>
</template>
<script setup>
import { ref, computed, nextTick } from 'vue'
import api from '@/api/api';
const exchangedCount = ref(5)
const code1 = ref('')
const code2 = ref('')
const code3 = ref('')
const code4 = ref('')
const f1 = ref(true)
const f2 = ref(false)
const f3 = ref(false)
const f4 = ref(false)
const helpVisible = ref(false)
const centerVisible = ref(false)
const centerText = ref('')
const centerHelp = ref(false)
const isFull = computed(() => (code1.value+code2.value+code3.value+code4.value).length === 16)
const goBack = () => {
uni.navigateBack({
fail() {
uni.redirectTo({ url: '/pages/index/index' })
}
})
}
const showHelp = () => {
centerHelp.value = true
centerVisible.value = true
}
// 通用模态框
const openCenter = (text) => {
centerText.value = text
centerHelp.value = false
centerVisible.value = true
}
const closeCenter = () => {
centerVisible.value = false
centerHelp.value = false
}
const setFocus = (idx) => {
f1.value = idx === 1
f2.value = idx === 2
f3.value = idx === 3
f4.value = idx === 4
}
const handleInput = (idx, e) => {
const raw = (e.detail && e.detail.value) || ''
const sanitized = raw.replace(/[^a-zA-Z0-9]/g, '')
if (idx === 1) code1.value = sanitized
if (idx === 2) code2.value = sanitized
if (idx === 3) code3.value = sanitized
if (idx === 4) code4.value = sanitized
if (sanitized.length === 4 && idx < 4) {
nextTick(() => setFocus(idx + 1))
}
}
const submit = () => {
if (!isFull.value) return
const code = (code1.value+code2.value+code3.value+code4.value).toUpperCase()
uni.showToast({ title: '兑换中: '+ code, icon: 'none' })
api.exchangeWelfareCard({password: code}).then(res => {
console.log(res)
if (res.code == 200) {
uni.showToast({ title: '兑换成功', icon: 'success' })
uni.navigateBack()
}
})
}
const goMyWelfare = () => {
uni.navigateTo({ url: '/pages_app/myWelfare/myWelfare' })
}
// 处理粘贴(H5等支持paste事件的平台)
const handlePaste = (e) => {
const text = (e.clipboardData && e.clipboardData.getData('text')) || ''
fillByText(text)
// 阻止默认粘贴到单个输入框
e && e.preventDefault && e.preventDefault()
}
// 从剪贴板读取(App、小程序)
const pasteFromClipboard = () => {
uni.getClipboardData({
success: (res) => {
fillByText(res.data || '')
}
})
}
const fillByText = (raw) => {
const v = String(raw || '').replace(/[^a-zA-Z0-9]/g, '').toUpperCase().slice(0, 16)
code1.value = v.slice(0, 4)
code2.value = v.slice(4, 8)
code3.value = v.slice(8, 12)
code4.value = v.slice(12, 16)
nextTick(() => setFocus(v.length >= 16 ? 4 : Math.floor((v.length)/4) + 1))
}
</script>
<style lang="scss" scoped>
$nav-height: 140rpx;
.exchange-page{
min-height: 100vh;
background: #f5f6f7;
}
.top-banner{
position: relative;
height: 280rpx;
background: linear-gradient(180deg,#ff6a4a 0%, #e93b2d 100%);
border-bottom-left-radius: 40rpx;
border-bottom-right-radius: 40rpx;
.banner-text{
position: absolute;
left: 48rpx;
top: 120rpx;
color: #fff;
.line1{font-size: 44rpx;font-weight: 600;}
.line2{font-size: 36rpx;margin-top: 12rpx;}
}
.help-btn{
position: absolute;
right: 40rpx;
top: 90rpx;
background: rgba(255,255,255,.95);
color: #e04835;
border-radius: 999rpx;
padding: 10rpx 24rpx;
font-size: 24rpx;
}
}
.tips{
margin: 48rpx;
color: #333;
font-size: 32rpx;
.paste-action{
color: #007aff;
text-decoration: underline;
}
}
.code-inputs{
display: flex;
gap: 30rpx;
padding: 0 48rpx;
.code-box{
flex: 1;
height: 96rpx;
background: #fff;
border-radius: 16rpx;
text-align: center;
font-size: 36rpx;
}
}
.btn-wrapper{
padding: 80rpx 48rpx 0;
.submit-btn{
width: 100%;
height: 100rpx;
background: linear-gradient(90deg,#ff4d2e,#e93b2d);
border-radius: 60rpx;
color: #fff;
font-size: 36rpx;
}
.submit-btn[disabled]{
opacity: .6;
}
}
/* 帮助弹层样式 */
.center-help{padding: 0 32rpx;display:flex;flex-direction:column;align-items:center;gap: 16rpx;color:#333;font-size:28rpx;line-height:1.6;}
/* 自定义通用模态框 */
.center-modal{position: fixed;left:0;right:0;top:0;bottom:0;background: rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;z-index: 9999;}
.center-modal-content{width: 640rpx;background:#fff;border-radius:24rpx;overflow:hidden;}
.center-title{font-size: 32rpx;color:#333;text-align:center;padding:28rpx 24rpx 12rpx;font-weight:600;}
.center-body{padding:0 32rpx 12rpx;color:#333;font-size:28rpx;line-height:1.7;text-align:center;}
.center-actions{padding: 24rpx 24rpx 28rpx;}
.center-btn{width:100%;height:88rpx;border-radius:999rpx;background:#e93b2d;color:#fff;font-size:30rpx;}
</style>
+175 -130
View File
@@ -11,100 +11,121 @@
></uni-nav-bar>
<view class="benefits-page">
<!-- 顶部红色横幅 -->
<view class="top-banner">
<view class="banner-text">
<view class="line1">已兑换{{ cardInfo.length }}张</view>
<view class="line2" @click="goMyWelfare">查看现有权益</view>
</view>
<view class="help-btn" @click="showRules">帮助说明</view>
</view>
<!-- 头部导航栏 -->
<!-- 帮助说明模态框(与兑换页一致的居中弹层) -->
<view v-if="centerVisible" class="center-modal" @click.self="closeCenter">
<view class="center-modal-content">
<view class="center-title">帮助说明</view>
<view class="center-help">
<text>1、点击“兑换福利卡”,输入密码即可兑换相应权益</text>
<text>2、每张福利卡仅限兑换一次,兑换后权益可在“我的福利-使用福利”中查看</text>
<text>3、福利卡长期有效,福利卡不能退换或者折现</text>
<text>4、查找文献权益不限文献类型,如指南共识、论文、电子书、课件或者视频</text>
</view>
<view class="center-actions">
<button class="center-btn" @click="closeCenter">知道了</button>
</view>
</view>
</view>
<!-- 福利卡片列表 -->
<view class="scrollbox">
<scroll-view class="benefits-list" scroll-y="true" :show-scrollbar="false">
<view
class="benefit-card"
v-for="(benefit, index) in benefitsList"
:key="index"
:class="benefit.type"
@click="claimBenefit(benefit)"
>
<view class="card-title">{{ benefit.title }}</view>
<!-- <view class="card-bg">
</view> -->
<view class="card-content">
<view class="card-details">
<view class="left-section">
<text class="condition">{{ benefit.condition }}</text>
<text class="requirement">{{ benefit.requirement }}</text>
</view>
<view class="right-section">
<text class="reward-type">{{ benefit.rewardType }}</text>
<text class="reward-value">{{ benefit.rewardValue }}</text>
</view>
</view>
<!-- 有卡展示(多张) -->
<view v-if="hasCard" >
<view class="card-wrapper" v-for="(card, cIdx) in cardInfo" :key="card.id">
<view class="card-header">
<text>卡号:{{ card.idcard }}</text>
<text class="time">兑换时间:{{ card.exchange_date }}</text>
</view>
<view class="card-body">
<view class="benefit-line" v-for="(w, idx) in card.welfare_list" :key="idx">
<text class="index">{{ idx + 1 }}、</text>
<text class="text">{{ w.type_name }}{{ w.num }}{{ w.type_unit }}</text>
</view>
</view>
</scroll-view>
</view>
<!-- 加载提示 -->
<view class="loadmore-tip" v-if="isLoading || isLastPage">
<text>{{ isLoading ? '加载中...' : (isLastPage ? '没有更多了' : '') }}</text>
</view>
</view>
<!-- 兑换福利卡 -->
<view class="emptybox">
<up-image :src="emptyImg" width="176rpx" height="204rpx" ></up-image>
<!-- 无卡空状态 -->
<view v-else class="emptybox">
<up-image :src="emptyImg" width="176rpx" height="204rpx"></up-image>
<view class="empty_desc">暂无福利卡</view>
</view>
<!-- 底部导航栏 -->
<view class="bottom-nav">
<view class="nav-item" @click="goPointsDetail">
<up-image :src="jifenImg" width="34rpx" height="34rpx" ></up-image>
<up-image :src="jifenImg" width="34rpx" height="34rpx"></up-image>
<text class="nav-text">兑换福利卡</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
import { ref, onMounted } from 'vue';
import jifenImg from "@/static/duihuan.png"
import emptyImg from "@/static/icon_empty.png"
// 当前选中的标签页
const activeTab = ref(0);
import api from '@/api/api';
import { onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
// 是否有福利卡以及示例卡信息
const hasCard = ref(true)
const cardInfo = ref([]);
// 分页状态
const pageNum = ref(1)
const pageSize = ref(10)
const isLastPage = ref(false)
const isLoading = ref(false)
const getMyWelfareCard = (opts = { isRefresh: false }) => {
if (isLoading.value) return
isLoading.value = true
api.myWelfareCard({page: pageNum.value}).then(res => {
console.log(res)
if (res.code == 200) {
const list = Array.isArray(res.data.list) ? res.data.list : []
if (opts.isRefresh) {
cardInfo.value = list
} else {
cardInfo.value = pageNum.value === 1 ? list : cardInfo.value.concat(list)
}
hasCard.value = list.length > 0 || cardInfo.value.length > 0
isLastPage.value = !!res.data.isLastPage
}
})
.finally(() => {
isLoading.value = false
uni.stopPullDownRefresh()
})
}
onMounted(() => {
getMyWelfareCard()
})
// 福利列表数据
const benefitsList = ref([
{
type: 'points',
title: '肝胆积分 (5个新随访)',
condition: '赠送积分',
requirement: '200积分',
rewardType: '立即领取',
rewardValue: ''
},
{
type: 'video',
title: '肝胆视频',
condition: '再新增随访',
requirement: '1个',
rewardType: '赠送下载',
rewardValue: '2集'
},
{
type: 'courseware',
title: '肝胆课件',
condition: '再新增随访',
requirement: '6个',
rewardType: '赠送下载',
rewardValue: '1篇'
},
{
type: 'usb',
title: '知识U盘',
condition: '再新增随访 (年度计算)',
requirement: '96个',
rewardType: '赠送U盘',
rewardValue: '1个'
}
]);
// 下拉刷新
onPullDownRefresh(() => {
pageNum.value = 1
isLastPage.value = false
getMyWelfareCard({ isRefresh: true })
})
// 上拉加载更多
onReachBottom(() => {
if (isLastPage.value || isLoading.value) return
pageNum.value += 1
getMyWelfareCard()
})
// 方法
const goBack = () => {
@@ -117,49 +138,21 @@
});
};
const centerVisible = ref(false)
const showRules = () => {
uni.showToast({
title: '福利规则',
icon: 'none'
});
centerVisible.value = true
};
const switchTab = (index) => {
activeTab.value = index;
// 这里可以根据标签页切换加载不同的数据
uni.showToast({
title: `切换到${['领取福利', '使用福利', '兑福利卡'][index]}`,
icon: 'none'
});
};
const claimBenefit = (benefit) => {
uni.showToast({
title: `领取${benefit.title}`,
icon: 'none'
});
const closeCenter = () => {
centerVisible.value = false
};
const goPointsDetail = () => {
uni.showToast({
title: '积分详情',
icon: 'none'
});
uni.navigateTo({ url: '/pages_app/myWelfareCard/exchange' })
};
const goBenefitDetail = () => {
uni.showToast({
title: '福利详情',
icon: 'none'
});
};
const addPatient = () => {
uni.showToast({
title: '添加患者',
icon: 'none'
});
};
const goMyWelfare = () => {
uni.navigateTo({ url: '/pages_app/myWelfare/myWelfare' })
}
</script>
<style lang="scss" scoped>
@@ -196,7 +189,6 @@
.benefits-page {
min-height: 100vh;
background-color: $bg-color;
padding-top: $nav-height; // 为固定导航栏预留空间
.emptybox{
display: flex;
height: 100vh;
@@ -330,7 +322,7 @@
}
.benefits-list {
.benefit-card {
background: $white;
border-radius: 20rpx;
@@ -340,22 +332,6 @@
overflow: hidden;
border:2rpx solid #fff;
&.points {
}
&.video {
}
&.courseware {
}
&.usb {
}
.card-bg {
position: absolute;
top: 0;
@@ -364,7 +340,6 @@
z-index:0;
}
.card-content {
@@ -388,7 +363,7 @@
justify-content: space-between;
align-items: center;
padding:0 40rpx;
height: 220rpx;
height: 220rpx;
.left-section,
.right-section {
display: flex;
@@ -420,6 +395,60 @@
}
}
/* 顶部横幅与卡片样式 */
.top-banner{
position: relative;
height: 280rpx;
background: linear-gradient(180deg,#ff6a4a 0%, #e93b2d 100%);
border-bottom-left-radius: 40rpx;
border-bottom-right-radius: 40rpx;
@include shadow;
.banner-text{
position: absolute;
left: 48rpx;
top: 120rpx;
color: #fff;
.line1{font-size: 44rpx;font-weight: 600;}
.line2{font-size: 36rpx;margin-top: 12rpx;}
}
.help-btn{
position: absolute;
right: 40rpx;
top: 90rpx;
background: rgba(255,255,255,.95);
color: #e04835;
border-radius: 999rpx;
padding: 10rpx 24rpx;
font-size: 24rpx;
}
}
.card-wrapper{
margin: 24rpx 30rpx;
background: #fff;
border-radius: 16rpx;
@include shadow;
overflow: hidden;
.card-header{
background: linear-gradient(90deg,#ff7e4a,#ff4d2e);
color: #fff;
padding: 24rpx 28rpx;
font-size: 26rpx;
display: flex;
justify-content: space-between;
.time{opacity:.95}
}
.card-body{
padding: 34rpx 28rpx 40rpx;
.benefit-line{
font-size: 30rpx;
color: #333;
line-height: 56rpx;
.index{color:#333}
}
}
}
.bottom-nav {
position: fixed;
bottom: 0;
@@ -449,4 +478,20 @@
}
}
}
/* 底部加载提示 */
.loadmore-tip{
text-align: center;
color: #999;
font-size: 24rpx;
padding: 20rpx 0 120rpx; // 给底部按钮留出空间
}
/* 居中模态框样式(复用兑换页风格) */
.center-modal{position: fixed;left:0;right:0;top:0;bottom:0;background: rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;z-index: 9999;}
.center-modal-content{width: 640rpx;background:#fff;border-radius:24rpx;overflow:hidden;}
.center-title{font-size: 32rpx;color:#333;text-align:center;padding:28rpx 24rpx 12rpx;font-weight:600;}
.center-help{padding: 0 32rpx;display:flex;flex-direction:column;align-items:center;gap: 16rpx;color:#333;font-size:28rpx;line-height:1.6;}
.center-actions{padding: 24rpx 24rpx 28rpx;}
.center-btn{width:100%;height:88rpx;border-radius:999rpx;background:#e93b2d;color:#fff;font-size:30rpx;}
</style>