很多页面

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
+25 -5
View File
@@ -61,8 +61,9 @@
<script setup>
import { ref, onUnmounted,computed } from 'vue';
import phoneImg from "@/static/phone.png"
import smsImg from "@/static/sms.png"
import phoneImg from "@/static/phone.png"
import smsImg from "@/static/sms.png"
import api from "@/api/api.js"
const mobile = ref('');
const code = ref('');
const sending = ref(false);
@@ -94,8 +95,17 @@ const sendCode = () => {
return;
}
if (sending.value) return;
api.smsSend({
mobile: mobile.value,
type: 6
}).then(res => {
if (res.code === 200) {
uni.showToast({ title: '验证码已发送', icon: 'success' });
} else {
uni.showToast({ title: res.msg || '验证码发送失败', icon: 'none' });
}
});
// TODO: 调用发送验证码接口
uni.showToast({ title: '验证码已发送', icon: 'success' });
startTimer();
};
@@ -108,8 +118,18 @@ const onConfirm = () => {
uni.showToast({ title: '请输入正确的验证码', icon: 'none' });
return;
}
// TODO: 提交更换手机号
uni.showToast({ title: '已提交', icon: 'success' });
//String oldMobile,newMobile, sms = params.get("sms");
api.changeMobile({
newMobile: mobile.value,
sms: code.value,
oldMobile: uni.getStorageSync('userInfo').mobile
}).then(res => {
if (res.code === 200) {
uni.showToast({ title: '更换手机号成功', icon: 'success' });
} else {
uni.showToast({ title: res.msg || '更换手机号失败', icon: 'none' });
}
});
};
onUnmounted(() => {
+142
View File
@@ -0,0 +1,142 @@
<template>
<view class="change-pwd-page">
<!-- 顶部导航栏 -->
<uni-nav-bar
left-icon="left"
title="修改登录密码"
@clickLeft="goBack"
fixed
color="#8B2316"
height="140rpx"
:border="false"
backgroundColor="#eeeeee"
></uni-nav-bar>
<!-- 表单区域 -->
<view class="form-wrapper">
<view class="input-row">
<input class="text-input" :password="true" v-model="oldPwd" placeholder="请输入原密码" placeholder-style="color:#c7c7c7" />
</view>
<view class="input-row">
<input class="text-input" :password="!showNewPwd" v-model="newPwd" placeholder="新密码(6~16位数字字母组合)" placeholder-style="color:#c7c7c7" />
</view>
<view class="input-row">
<input class="text-input" :password="!showConfirmPwd" v-model="confirmPwd" placeholder="确认新密码" placeholder-style="color:#c7c7c7" />
</view>
<view class="forgot" @click="goForgot">忘记密码?</view>
</view>
<!-- 底部确定按钮 -->
<view class="bottom-bar">
<button class="confirm-btn" @click="onConfirm"> </button>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
import api from "@/api/api.js"
const oldPwd = ref('');
const newPwd = ref('');
const confirmPwd = ref('');
const showNewPwd = ref(false);
const showConfirmPwd = ref(false);
const goBack = () => {
uni.navigateBack();
};
const goForgot = () => {
// 跳转到忘记密码或短信登录页(如无专页,这里跳到短信登录)
uni.navigateTo({ url: '/pages_app/smsLogin/smsLogin' });
};
const isStrong = (pwd) => {
return /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{6,16}$/.test(pwd);
};
const onConfirm = () => {
if (!oldPwd.value) {
return uni.showToast({ title: '请输入原密码', icon: 'none' });
}
if (!newPwd.value) {
return uni.showToast({ title: '请输入新密码', icon: 'none' });
}
if (!isStrong(newPwd.value)) {
return uni.showToast({ title: '密码需6-16位数字字母组合', icon: 'none' });
}
if (confirmPwd.value !== newPwd.value) {
return uni.showToast({ title: '两次输入的密码不一致', icon: 'none' });
}
api.changePassword({
old_password: oldPwd.value,
password: newPwd.value
}).then(res => {
if (res.code === 200) {
uni.showToast({ title: '修改密码成功', icon: 'success' });
// 清除登录状态
uni.clearStorageSync();
setTimeout(() => {
uni.reLaunch({
url: '/pages_app/login/login'
});
}, 2000);
} else {
uni.showToast({ title: res.msg || '修改密码失败', icon: 'none' });
}
});
};
</script>
<style scoped>
.change-pwd-page {
background: #ffffff;
min-height: 100vh;
}
.form-wrapper {
padding: 40rpx 30rpx 0;
}
.input-row {
margin-bottom: 30rpx;
background: #ffffff;
border: 2rpx solid #eeeeee;
border-radius: 12rpx;
padding: 0 24rpx;
}
.text-input {
height: 96rpx;
font-size: 30rpx;
}
.forgot {
text-align: center;
color: #8b8b8b;
font-size: 28rpx;
margin-top: 20rpx;
}
.bottom-bar {
position: fixed;
left: 0;
right: 0;
bottom: 40rpx;
padding: 0 30rpx;
}
.confirm-btn {
height: 100rpx;
line-height: 100rpx;
border-radius: 50rpx;
background-color: #ffffff;
border: 2rpx solid #8B2316;
color: #8B2316;
font-size: 36rpx;
}
</style>
+160
View File
@@ -0,0 +1,160 @@
<template>
<view class="bank-card-page">
<!-- 顶部导航栏 -->
<uni-nav-bar
left-icon="left"
title="常用银行卡"
@clickLeft="goBack()"
right-text="添加"
@clickRight="addBankCard"
fixed
color="#8B2316"
height="140rpx"
:border="false"
backgroundColor="#eeeeee"
>
</uni-nav-bar>
<!-- 银行卡列表 -->
<view class="card-list">
<view class="card-item" v-for="(card, index) in bankCards" :key="card.uuid">
<view class="card-logo">
<view class="logo-bg">
<text class="logo-text">{{ getBankLogo(card.open_bank) }}</text>
</view>
</view>
<view class="card-info">
<view class="card-number">尾号{{ card.card_number }}储蓄卡</view>
<view class="bank-name">{{ card.open_bank }}</view>
</view>
</view>
</view>
<!-- 底部导航指示器 -->
<view class="bottom-indicator"></view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import navTo from '@/utils/navTo';
import api from '@/api/api';
const bankCards = ref([]);
// 银行卡数据
const getBankCardList = () => {
api.bankCardList({}).then(res => {
if (res.code === 200 && res.data.bankList) {
bankCards.value = res.data.bankList;
}
});
};
const goBack = () => {
uni.navigateBack();
};
const addBankCard = () => {
navTo({
url: '/pages_app/idcardAuth/idcardAuth'
});
};
// 获取银行logo文字
const getBankLogo = (bankName) => {
const bankLogos = {
'工商银行': '工',
'中国银行': '中',
'建设银行': '建',
'农业银行': '农',
'交通银行': '交',
'招商银行': '招',
'民生银行': '民',
'兴业银行': '兴',
'浦发银行': '浦',
'光大银行': '光',
'华夏银行': '华',
'中信银行': '信',
'平安银行': '平',
'广发银行': '广',
'邮储银行': '邮'
};
return bankLogos[bankName] || bankName.charAt(0);
};
onMounted(() => {
getBankCardList();
});
</script>
<style lang="scss" scoped>
.bank-card-page {
min-height: 100vh;
background: #F5F5F5;
position: relative;
}
/* 银行卡列表样式 */
.card-list {
padding: 30rpx;
.card-item {
background: #ffffff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
display: flex;
align-items: center;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
.card-logo {
margin-right: 30rpx;
.logo-bg {
width: 80rpx;
height: 80rpx;
background: #E60012;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 2rpx solid #E60012;
.logo-text {
color: #ffffff;
font-size: 32rpx;
font-weight: bold;
}
}
}
.card-info {
flex: 1;
.card-number {
font-size: 28rpx;
color: #000000;
margin-bottom: 8rpx;
font-weight: 500;
}
.bank-name {
font-size: 24rpx;
color: #666666;
}
}
}
}
/* 底部指示器 */
.bottom-indicator {
position: fixed;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 120rpx;
height: 6rpx;
background: #333333;
border-radius: 3rpx;
}
</style>
+414 -52
View File
@@ -3,8 +3,8 @@
<!-- 顶部导航栏 -->
<uni-nav-bar
left-icon="left"
title="身份验证"
@cviewckLeft="goBack"
:title="currentStep === 1 ? '身份验证' : '添加银行卡'"
@clickLeft="goBack()"
fixed
color="#8B2316"
height="140rpx"
@@ -18,18 +18,18 @@
<view class="progress-bar">
<view class="barbox">
<view class="imgbox">
<up-image :src="stepImg" width="46rpx" height="46rpx" ></up-image>
<view class="desc ">身份信息</view>
<up-image :src="currentStep >= 1 ? stepActiveImg : stepImg" width="46rpx" height="46rpx"></up-image>
<view class="desc" :class="{ active: currentStep >= 1 }">身份信息</view>
</view>
<view class="line"></view>
<view class="line" :class="{ active: currentStep >= 2 }"></view>
<view class="imgbox">
<up-image :src="stepImg" width="46rpx" height="46rpx" ></up-image>
<view class="desc">添加银行卡</view>
<up-image :src="currentStep >= 2 ? stepActiveImg : stepImg" width="46rpx" height="46rpx"></up-image>
<view class="desc" :class="{ active: currentStep >= 2 }">添加银行卡</view>
</view>
<view class="line"></view>
<view class="line" :class="{ active: currentStep >= 3 }"></view>
<view class="imgbox">
<up-image :src="stepImg" width="46rpx" height="46rpx" ></up-image>
<view class="desc">完成</view>
<up-image :src="currentStep >= 3 ? stepActiveImg : stepImg" width="46rpx" height="46rpx"></up-image>
<view class="desc" :class="{ active: currentStep >= 3 }">完成</view>
</view>
</view>
@@ -57,23 +57,58 @@
<!-- 输入表单 -->
<view class="form-section">
<view class="form-item">
<text class="form-label">姓名</text>
<input
class="form-input"
placeholder="请输入您的姓名"
v-model="formData.name"
placeholder-style="color: #cccccc"
/>
<!-- 第一步身份信息 -->
<view v-if="currentStep === 1">
<view class="form-item">
<text class="form-label">姓名</text>
<input
class="form-input"
placeholder="请输入您的姓名"
v-model="formData.name"
placeholder-style="color: #cccccc"
/>
</view>
<view class="form-item">
<text class="form-label">身份证号</text>
<input
class="form-input"
placeholder="请输入您的身份证号"
v-model="formData.idNumber"
placeholder-style="color: #cccccc"
/>
</view>
</view>
<view class="form-item">
<text class="form-label">身份证号</text>
<input
class="form-input"
placeholder="请输入您的身份证号"
v-model="formData.idNumber"
placeholder-style="color: #cccccc"
/>
<!-- 第二步添加银行卡 -->
<view v-if="currentStep === 2">
<view class="form-item">
<text class="form-label">银行卡号</text>
<view class="input-container">
<input
class="form-input"
placeholder="仅限借记卡"
v-model="formData.cardNumber"
placeholder-style="color: #cccccc"
/>
<view class="info-icon">
<text class="icon-text">!</text>
</view>
</view>
</view>
<view class="form-item">
<text class="form-label">手机号</text>
<view class="input-container">
<input
class="form-input"
placeholder="银行预留手机号"
v-model="formData.mobile"
placeholder-style="color: #cccccc"
/>
<view class="info-icon">
<text class="icon-text">!</text>
</view>
</view>
</view>
</view>
</view>
@@ -84,38 +119,215 @@
<!-- 下一步按钮 -->
<view class="bottom-actions">
<button class="next-btn" @click="onNextStep">下一步</button>
<button class="next-btn" :class="{ loading: isLoading }" @click="onNextStep" :disabled="isLoading">
<text v-if="!isLoading">下一步</text>
<text v-else>验证中...</text>
</button>
</view>
<!-- 短信验证码弹框 -->
<view v-if="showSmsDialog" class="sms-mask">
<view class="sms-dialog">
<view class="sms-title">短信验证码</view>
<view class="sms-subtitle">请输入手机{{ maskedMobile }}收到的验证码</view>
<view class="sms-input-row">
<input class="sms-input" v-model="smsCode" placeholder="请输入验证码" placeholder-style="color: #cccccc" />
<button class="sms-code-btn" :disabled="countdown > 0 || sendingCode" @click="onGetSmsCode">
<text v-if="countdown === 0">获取验证码</text>
<text v-else>{{ countdown }}s</text>
</button>
</view>
<view class="sms-actions">
<button class="sms-cancel" @click="onCancelSms">取消</button>
<button class="sms-confirm" @click="onConfirmSms">确定</button>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
import { ref, computed } from 'vue';
import stepImg from "@/static/add_card_no.png"
import stepActiveImg from "@/static/add_card_yes.png"
import navTo from '@/utils/navTo';
import api from '@/api/api';
const formData = ref({
name: '',
idNumber: ''
idNumber: '',
cardNumber: '',
mobile: ''
});
const isLoading = ref(false);
const currentStep = ref(1); // 当前步骤,1表示身份信息,2表示添加银行卡
// 短信弹框相关
const showSmsDialog = ref(false);
const smsCode = ref('');
const sendingCode = ref(false);
const countdown = ref(0);
let countdownTimer = null;
const maskedMobile = computed(() => {
const m = formData.value.mobile || '';
if (m && m.length >= 7) {
return `${m.slice(0,3)}****${m.slice(-4)}`;
}
return m || '***********';
});
const goBack = () => {
uni.navigateBack();
};
const onNextStep = () => {
if (!formData.value.name.trim()) {
uni.showToast({ title: '请输入姓名', icon: 'none' });
return;
}
if (!formData.value.idNumber.trim()) {
uni.showToast({ title: '请输入身份证号', icon: 'none' });
return;
}
uni.showToast({ title: '验证通过,跳转下一步', icon: 'success' });
// 这里可以跳转到下一步页面
// 身份证号格式验证
const validateIdNumber = (idNumber) => {
const reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
return reg.test(idNumber);
};
// 姓名格式验证
const validateName = (name) => {
const reg = /^[\u4e00-\u9fa5]{2,10}$/;
return reg.test(name);
};
// 银行卡号验证
const validateCardNumber = (cardNumber) => {
const reg = /^\d{16,19}$/;
return reg.test(cardNumber);
};
// 手机号验证
const validateMobile = (mobile) => {
const reg = /^1[3-9]\d{9}$/;
return reg.test(mobile);
};
const onNextStep = async () => {
if (currentStep.value === 1) {
// 第一步:身份信息验证
if (!formData.value.name.trim()) {
uni.showToast({ title: '请输入姓名', icon: 'none' });
return;
}
if (!validateName(formData.value.name)) {
uni.showToast({ title: '请输入正确的姓名(2-10个汉字)', icon: 'none' });
return;
}
if (!formData.value.idNumber.trim()) {
uni.showToast({ title: '请输入身份证号', icon: 'none' });
return;
}
if (!validateIdNumber(formData.value.idNumber)) {
uni.showToast({ title: '请输入正确的身份证号', icon: 'none' });
return;
}
// 显示加载状态
isLoading.value = true;
try {
// 调用身份验证API
const res = {code:200}
if (res.code === 200) {
uni.showToast({ title: '身份验证成功', icon: 'success' });
currentStep.value = 2;
isLoading.value = false;
} else {
uni.showToast({ title: res.msg || '身份验证失败', icon: 'none' });
isLoading.value = false;
}
} catch (error) {
console.error('身份验证失败:', error);
uni.showToast({ title: '网络错误,请重试', icon: 'none' });
isLoading.value = false;
}
} else if (currentStep.value === 2) {
// 第二步:银行卡信息验证
if (!formData.value.cardNumber.trim()) {
uni.showToast({ title: '请输入银行卡号', icon: 'none' });
return;
}
if (!validateCardNumber(formData.value.cardNumber)) {
uni.showToast({ title: '请输入正确的银行卡号', icon: 'none' });
return;
}
if (!formData.value.mobile.trim()) {
uni.showToast({ title: '请输入手机号', icon: 'none' });
return;
}
if (!validateMobile(formData.value.mobile)) {
uni.showToast({ title: '请输入正确的手机号', icon: 'none' });
return;
}
// 弹出短信验证码弹框
showSmsDialog.value = true;
}
};
const onGetSmsCode = async () => {
if (countdown.value > 0) return;
const res = await api.smsSend({
mobile: formData.value.mobile,
type: 3
});
if (res.code === 200) {
uni.showToast({ title: '短信验证码发送成功', icon: 'success' });
// 开始60秒倒计时
countdown.value = 60;
if (countdownTimer) clearInterval(countdownTimer);
countdownTimer = setInterval(() => {
if (countdown.value > 0) {
countdown.value -= 1;
} else {
clearInterval(countdownTimer);
countdownTimer = null;
}
}, 1000);
} else {
uni.showToast({ title: res.msg || '短信验证码发送失败', icon: 'none' });
}
};
const onConfirmSms = async () => {
if (!smsCode.value) {
uni.showToast({ title: '请输入短信验证码', icon: 'none' });
return;
}
const res = await api.identificationBankCardNew({
phone_number: formData.value.mobile,
sms: smsCode.value,
id_number: formData.value.idNumber,
card_number: formData.value.cardNumber,
id_name: formData.value.name
});
if (res.code === 200) {
uni.showToast({ title: '银行卡添加成功', icon: 'success' });
navTo({
url: '/pages_app/idcardAuth/bankCardList'
});
} else {
uni.showToast({ title: res.msg || '银行卡添加失败', icon: 'none' });
}
};
const onCancelSms = () => {
showSmsDialog.value = false;
};
</script>
<style lang="scss" scoped>
@@ -235,10 +447,11 @@ const onNextStep = () => {
}
}
.form-section {
background: #ffffff;
border-radius: 16rpx;
padding: 30rpx;
padding: 0;
.form-item {
@@ -246,58 +459,207 @@ const onNextStep = () => {
display: flex;
align-items: center;
border-bottom: 2rpx solid #eee;
padding: 0 30rpx 20rpx;
&:last-child {
margin-bottom: 0;
padding-bottom: 0;
}
.form-label {
display: block;
font-size: 28rpx;
color: #000000;
width:120rpx;
width: 120rpx;
font-weight: 500;
}
.form-input {
flex:1;
flex: 1;
height: 80rpx;
padding: 0 20rpx;
font-size: 28rpx;
background: #ffffff;
border: none;
outline: none;
&:focus {
border-color: #ff0000;
border-color: #8B2316;
}
}
.input-container {
display: flex;
align-items: center;
position: relative;
width: 80%;
.form-input {
flex: 9;
height: 80rpx;
padding: 0 20rpx;
font-size: 28rpx;
background: #ffffff;
border: none;
border-radius: 0;
outline: none;
&:focus {
/* 去除边框后无需变更边框颜色 */
}
}
.info-icon {
flex:1;
position: absolute;
right: 20rpx;
width: 40rpx;
height: 40rpx;
border-radius: 50%;
background: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
.icon-text {
font-size: 20rpx;
color: #cccccc;
font-weight: bold;
}
}
}
}
}
.warning-text {
background: #ffffff;
background: #fff3cd;
border: 2rpx solid #ffeaa7;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 60rpx;
font-size: 26rpx;
color: #000000;
color: #856404;
line-height: 1.6;
position: relative;
&::before {
content: "⚠️";
position: absolute;
left: 30rpx;
top: 30rpx;
font-size: 32rpx;
}
padding-left: 80rpx;
}
.bottom-actions {
padding: 0 30rox 40rpx;
padding: 0 30rpx 40rpx;
.next-btn {
margin:0 30rpx;
width: 100%;
height: 88rpx;
background: #cccccc;
background: #8B2316;
color: #ffffff;
border: none;
border-radius: 8rpx;
font-size: 32rpx;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
&:active {
background: #8B2316;
background: #6B1A0F;
}
&.loading {
background: #cccccc;
opacity: 0.7;
}
&:disabled {
background: #cccccc;
opacity: 0.7;
}
}
}
/* 短信弹框样式 */
.sms-mask {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.sms-dialog {
width: 680rpx;
background: #ffffff;
border-radius: 16rpx;
padding: 30rpx;
}
.sms-title {
text-align: center;
font-size: 34rpx;
color: #000000;
font-weight: 600;
margin-bottom: 20rpx;
}
.sms-subtitle {
text-align: center;
font-size: 28rpx;
color: #333333;
margin-bottom: 24rpx;
}
.sms-input-row {
display: flex;
align-items: center;
gap: 16rpx;
margin: 10rpx 0 30rpx;
}
.sms-input {
flex: 1;
height: 88rpx;
border: 2rpx solid #eeeeee;
border-radius: 8rpx;
padding: 0 20rpx;
font-size: 28rpx;
}
.sms-code-btn {
width: 200rpx;
height: 88rpx;
border-radius: 8rpx;
background: #eeeeee;
color: #8B2316;
font-size: 26rpx;
}
.sms-actions {
display: flex;
justify-content: space-between;
margin-top: 10rpx;
}
.sms-cancel,
.sms-confirm {
flex: 1;
height: 88rpx;
border-radius: 8rpx;
font-size: 32rpx;
}
.sms-cancel { background: #f5f5f5; color: #000000; margin-right: 20rpx; }
.sms-confirm { background: #8B2316; color: #ffffff; margin-left: 20rpx; }
</style>
+233 -72
View File
@@ -4,6 +4,8 @@
<uni-nav-bar
left-icon="left"
title="消息"
right-text="清除"
@clickRight="clearMsg"
@clickLeft="goBack"
fixed
color="#8B2316"
@@ -19,25 +21,28 @@
<view class="grid-item" @click="goBenefit">
<view class="icon-wrap gift">
<uni-icons type="gift" size="34" color="#fff"></uni-icons>
<view class="badge" v-if="badgeData.Module_Welfare > 0">{{ badgeData.Module_Welfare }}</view>
</view>
<text class="label">福利</text>
</view>
<view class="grid-item" @click="goOrder">
<view class="icon-wrap order">
<uni-icons type="list" size="34" color="#fff"></uni-icons>
<view class="badge" v-if="badgeData.Module_Order > 0">{{ badgeData.Module_Order }}</view>
</view>
<text class="label">订单</text>
</view>
<view class="grid-item" @click="goFollow">
<view class="icon-wrap visit">
<uni-icons type="heart" size="34" color="#fff"></uni-icons>
<view class="badge" v-if="followBadge > 0">{{ followBadge }}</view>
<view class="badge" v-if="badgeData.Module_Relation > 0">{{ badgeData.Module_Relation }}</view>
</view>
<text class="label">随访</text>
</view>
<view class="grid-item" @click="goReply">
<view class="icon-wrap reply">
<uni-icons type="chatbubble" size="34" color="#fff"></uni-icons>
<view class="badge" v-if="badgeData.Module_Comment > 0">{{ badgeData.Module_Comment }}</view>
</view>
<text class="label">回复我的</text>
</view>
@@ -54,13 +59,35 @@
@scrolltolower="onLoadMore"
lower-threshold="80"
>
<view class="group" v-for="(group, gIdx) in msgGroups" :key="gIdx">
<view class="group-time">{{ group.time }}</view>
<view class="card" v-for="(msg, idx) in group.items" :key="idx">
<view class="card-title">系统消息</view>
<view class="card-content">{{ msg }}</view>
<!-- 回复我的模块 - 聊天列表样式 -->
<view v-if="currentModule === 4" class="chat-list">
<view class="chat-item" v-for="(msg, idx) in msgList" :key="msg.id">
<view class="avatar">
<image :src="msg.avatar || '/static/default-avatar.png'" mode="aspectFill" @error="handleImageError"></image>
</view>
<view class="chat-content">
<view class="chat-header">
<text class="sender-name">{{ msg.sender_name || msg.title }}</text>
<text class="chat-time">{{ msg.create_date }}</text>
</view>
<view class="message-preview">{{ msg.content }}</view>
</view>
</view>
</view>
<!-- 其他模块 - 卡片样式 -->
<view v-else>
<view class="card" v-for="(msg, idx) in msgList" :key="msg.id">
<view class="card-title">{{ msg.title }}</view>
<view class="card-content">{{ msg.content }}</view>
<view class="card-time">{{ msg.create_date }}</view>
</view>
</view>
<!-- 空状态 -->
<view v-if="msgList.length === 0" class="empty-state">
<text class="empty-text">暂无消息</text>
</view>
<!-- 加载更多提示 -->
<view v-if="loading" class="loading-more">
<text class="loading-text">加载中...</text>
@@ -74,24 +101,21 @@
</template>
<script setup>
import { ref } from 'vue';
import { onMounted, ref } from 'vue';
import api from '@/api/api.js';
import docUrl from '@/utils/docUrl';
const followBadge = ref(5);
// 角标数据
const badgeData = ref({
Module_Order: 0, // 订单
Module_Comment: 0, // 回复我的
Module_Welfare: 0, // 福利
Module_Relation: 0 // 随访
});
const msgGroups = ref([
{
time: '2022-04-22 16:32:54',
items: ['恭喜您,您的肝胆积分又增加了,快去查看吧']
},
{
time: '2022-04-20 13:33:23',
items: ['恭喜您,您的肝胆积分又增加了,快去查看吧']
},
{
time: '2022-04-15 13:36:23',
items: ['恭喜您,您的肝胆积分又增加了,快去查看吧']
}
]);
// 消息列表数据
const msgList = ref([]);
const currentModule = ref(1); // 当前选中的模块,默认显示模块1的消息
// 刷新/加载状态
const refreshing = ref(false);
@@ -100,13 +124,7 @@
const page = ref(1);
const pageSize = ref(10);
// 模拟更多数据(请在接入接口后替换)
const mockMore = [
{ time: '2022-04-10 09:20:11', items: ['系统保养完成,服务更稳定~'] },
{ time: '2022-04-05 18:06:42', items: ['积分到账提醒,请注意查收。'] },
{ time: '2022-03-30 08:15:00', items: ['本周学术会议日程已发布。'] },
{ time: '2022-03-25 10:05:16', items: ['您有新的随访任务待处理。'] }
];
const goBack = () => {
uni.navigateBack({
@@ -118,23 +136,46 @@
});
};
const goBenefit = () => uni.showToast({ title: '福利', icon: 'none' });
const goOrder = () => uni.showToast({ title: '订单', icon: 'none' });
const goFollow = () => uni.showToast({ title: '随访', icon: 'none' });
const goReply = () => uni.showToast({ title: '回复我的', icon: 'none' });
const goBenefit = () => {
currentModule.value = 1;
getAppMesageList(1);
};
const goOrder = () => {
currentModule.value = 2;
getAppMesageList(2);
};
const goFollow = () => {
currentModule.value = 3;
getAppMesageList(3);
};
const goReply = () => {
currentModule.value = 4;
getAppMesageList(4);
};
const clearMsg = () => {
uni.showModal({
title: '提醒',
content: '是否要清除所有未读消息?',
success: (res) => {
if (res.confirm) {
api.appMesageRead({}).then(res => {
console.log(res);
});
getUnReadList();
}
}
});
}
// 下拉刷新
const onRefresh = async () => {
if (refreshing.value) return;
refreshing.value = true;
try {
await new Promise(r => setTimeout(r, 800));
page.value = 1;
noMore.value = false;
msgGroups.value.unshift({
time: new Date().toISOString().slice(0, 19).replace('T', ' '),
items: ['您有一条新的系统通知,欢迎查看。']
});
// 重新获取当前模块的消息列表
getAppMesageList(currentModule.value);
// 重新获取未读消息数量
getUnReadList();
} finally {
refreshing.value = false;
}
@@ -145,19 +186,73 @@
if (loading.value || noMore.value) return;
loading.value = true;
try {
await new Promise(r => setTimeout(r, 800));
const start = (page.value - 1) * 2;
const chunk = mockMore.slice(start, start + 2);
if (chunk.length === 0) {
noMore.value = true;
} else {
msgGroups.value.push(...chunk);
page.value += 1;
}
// 这里可以根据需要实现分页加载
// 目前接口返回的数据已经包含了分页信息
// 如果需要加载更多,可以调用 getAppMesageList 并传入页码参数
noMore.value = true; // 暂时设置为没有更多数据
} finally {
loading.value = false;
}
};
const getUnReadList = () => {
api.unReadList({}).then(res => {
console.log(res);
if (res.code === 200 && res.data) {
// 更新角标数据
badgeData.value = {
Module_Order: parseInt(res.data.Module_Order) || 0,
Module_Comment: parseInt(res.data.Module_Comment) || 0,
Module_Welfare: parseInt(res.data.Module_Welfare) || 0,
Module_Relation: parseInt(res.data.Module_Relation) || 0
};
}
}).catch(err => {
console.error('获取未读消息列表失败:', err);
});
};
const getAppMesageList = (module) => {
api.appMesageList({module: module}).then(res => {
console.log(res);
if (res.code === 200 && res.data && res.data.list) {
// 更新消息列表数据
msgList.value = res.data.list.map(item => ({
id: item.id,
title: item.title,
content: item.content,
create_date: item.create_date,
is_read: item.is_read,
extra: item.extra,
// 从extra字段提取用户信息
sender_name: item.extra?.user_name || item.title,
avatar: item.extra?.user_photo ?
(item.extra.user_photo.startsWith('http') ?
item.extra.user_photo :
`${getBaseUrl()}${item.extra.user_photo}`) :
'/static/default-avatar.png'
}));
}
}).catch(err => {
console.error('获取消息列表失败:', err);
});
};
// 获取基础URL
const getBaseUrl = () => {
return docUrl;
};
// 图片加载错误处理
const handleImageError = (e) => {
// 设置默认头像
e.target.src = '/static/default-avatar.png';
};
onMounted(() => {
getUnReadList();
getAppMesageList(1);
});
</script>
<style lang="scss" scoped>
@@ -243,41 +338,107 @@
box-sizing: border-box;
bottom: 0rpx;
// 加载状态样式
.loading-more, .no-more {
.loading-more, .no-more, .empty-state {
@include flex-center;
padding: 28rpx 0;
.loading-text, .no-more-text {
.loading-text, .no-more-text, .empty-text {
font-size: 26rpx;
color: $muted;
}
}
.group {
.group-time {
text-align: center;
color: $muted;
font-size: 24rpx;
margin: 26rpx 0 16rpx;
.card {
background: #fff;
border-radius: 16rpx;
padding: 26rpx;
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.04);
margin-top: 26rpx;
.card-title {
font-size: 30rpx;
color: $text-primary;
font-weight: 600;
margin-bottom: 14rpx;
}
.card {
background: #fff;
border-radius: 16rpx;
padding: 26rpx;
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.04);
margin-bottom: 26rpx;
.card-content {
font-size: 28rpx;
color: $text-secondary;
line-height: 1.6;
margin-bottom: 14rpx;
}
.card-title {
font-size: 30rpx;
color: $text-primary;
font-weight: 600;
margin-bottom: 14rpx;
.card-time {
font-size: 24rpx;
color: $muted;
text-align: right;
}
}
// 聊天列表样式
.chat-list {
.chat-item {
display: flex;
align-items: center;
padding: 24rpx;
border-bottom: 1rpx solid #f0f0f0;
background: #fff;
margin-top: 24rpx;
&:last-child {
border-bottom: none;
}
.card-content {
font-size: 28rpx;
color: $text-secondary;
line-height: 1.6;
.avatar {
width: 88rpx;
height: 88rpx;
border-radius: 50%;
overflow: hidden;
margin-right: 24rpx;
flex-shrink: 0;
image {
width: 100%;
height: 100%;
}
}
.chat-content {
flex: 1;
min-width: 0;
.chat-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8rpx;
.sender-name {
font-size: 30rpx;
color: $text-primary;
font-weight: 500;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.chat-time {
font-size: 24rpx;
color: $muted;
flex-shrink: 0;
margin-left: 16rpx;
}
}
.message-preview {
font-size: 28rpx;
color: $text-secondary;
line-height: 1.4;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
+177 -42
View File
@@ -37,11 +37,11 @@
<view class="summary-bar">
<view class="summary-item">
<up-image :src="downLoadImg" width="36rpx" height="36rpx" ></up-image>
<text class="summary-text">: {{ downloadCount }}</text>
<text class="summary-text">{{ activeTab === 'download' ? '下载账户' : '分享账户' }}: {{ downloadCount }}</text>
</view>
<view class="summary-item">
<up-image :src="moneyImg" width="36rpx" height="36rpx" ></up-image>
<text class="summary-text">: {{ totalAmount }}</text>
<text class="summary-text">{{ activeTab === 'download' ? '文件数量' : '分享文件' }}: {{ totalAmount }}</text>
</view>
</view>
@@ -55,12 +55,21 @@
@scrolltolower="onLoadMore"
:lower-threshold="100"
>
<!-- 空状态 -->
<view v-if="coursewareList.length === 0 && !loading" class="empty-state">
<text>{{ activeTab === 'download' ? '暂无下载数据' : '暂无分享数据' }}</text>
</view>
<view class="courseware-item" v-for="(item, index) in coursewareList" :key="index" @click="onItemClick(item)">
<view class="item-content">
<view class="courseware-name">
<text class="label">课件名称:</text>
<text class="value">{{ item.name }}</text>
</view>
<view class="courseware-provider" v-if="item.providername">
<text class="label">{{ activeTab === 'download' ? '提供者' : '下载者' }}:</text>
<text class="value">{{ item.providername }}</text>
</view>
<view class="courseware-time">
<text class="label">时间:</text>
<text class="value">{{ item.time }}</text>
@@ -78,7 +87,7 @@
</view>
<!-- 没有更多数据提示 -->
<view v-if="noMore" class="no-more">
<view v-if="noMore && coursewareList.length > 0" class="no-more">
<text>没有更多数据了</text>
</view>
</scroll-view>
@@ -86,7 +95,8 @@
</template>
<script setup>
import { ref } from 'vue';
import { ref, onMounted } from 'vue';
import api from '@/api/api';
import downLoadImg from "@/static/course_download.png"
import moneyImg from "@/static/course_yuan.png"
const activeTab = ref('download');
@@ -98,28 +108,11 @@ const pageSize = ref(10);
const downloadCount = ref(4);
const totalAmount = ref('20.00');
const coursewareList = ref([
{
name: '慢性病毒性肝炎患者干扰素治疗不良反应临床处理专家共识',
time: '2025-02-21',
status: '已支付'
},
{
name: '慢乙肝抗病毒治疗-把握时机正确选择',
time: '2024-11-27',
status: '已支付'
},
{
name: '肝病相关血小板减少症临床管理中国专家共识(2023)解读',
time: '2024-10-06',
status: '已支付'
},
{
name: '俞云松:耐药阳性菌感染诊疗思路(CHINET数据云)',
time: '2022-10-24',
status: '已支付'
}
]);
const coursewareList = ref([]);
onMounted(() => {
onRefresh();
});
const goBack = () => {
uni.navigateBack();
@@ -131,37 +124,173 @@ const switchTab = (tab) => {
page.value = 1;
noMore.value = false;
// 这里可以根据标签加载不同数据
onRefresh();
};
const onItemClick = (item) => {
uni.showToast({ title: `点击了: ${item.name}`, icon: 'none' });
// 这里可以根据需要跳转到详情页或执行下载操作
console.log('点击课件:', item);
if (activeTab.value === 'download') {
// 下载标签页的处理逻辑
if (item.download_path) {
uni.downloadFile({
url: item.download_path,
success: (res) => {
if (res.statusCode === 200) {
uni.openDocument({
filePath: res.tempFilePath,
success: () => {
console.log('打开文档成功');
},
fail: (err) => {
console.error('打开文档失败:', err);
uni.showToast({ title: '无法打开此文件', icon: 'none' });
}
});
}
},
fail: (err) => {
console.error('下载失败:', err);
uni.showToast({ title: '下载失败', icon: 'none' });
}
});
} else {
uni.showToast({ title: `点击了下载课件: ${item.name}`, icon: 'none' });
}
} else {
// 分享标签页的处理逻辑
uni.showToast({ title: `点击了分享课件: ${item.name}`, icon: 'none' });
// 这里可以添加分享相关的操作,比如查看分享详情、重新分享等
}
};
const onRefresh = () => {
refreshing.value = true;
page.value = 1;
noMore.value = false;
setTimeout(() => {
refreshing.value = false;
uni.showToast({ title: '刷新完成', icon: 'success' });
}, 1000);
if (activeTab.value === 'download') {
api.getGandanfileMyDownload({ page: page.value }).then(res => {
console.log(res);
if (res.code === 200 && res.data) {
const data = res.data.list;
// 更新摘要信息
downloadCount.value = res.data.downloadTotalAccount || 0;
totalAmount.value = res.data.downloadFileCount || 0;
// 处理课件列表数据
coursewareList.value = data.list.map(item => ({
name: item.title || '未知课件',
time: item.create_date ? item.create_date.split(' ')[0] : '',
status: item.order_status === 'paid' ? '已支付' : '未支付',
uuid: item.uuid,
order_id: item.order_id,
type: item.type,
providername: item.providername
}));
// 判断是否还有更多数据
noMore.value = data.pageNumber >= data.totalPage;
}
refreshing.value = false;
}).catch(err => {
console.error('获取下载列表失败:', err);
refreshing.value = false;
});
} else {
api.getGandanfileMyShare({ page: page.value }).then(res => {
console.log(res);
if (res.code === 200 && res.data) {
const data = res.data.list;
// 更新摘要信息 - 分享数据使用不同的字段
downloadCount.value = res.data.shareTotalAccount || 0;
totalAmount.value = res.data.shareloadFileCount || 0;
// 处理分享列表数据
coursewareList.value = data.list.map(item => ({
name: item.title || '未知课件',
time: item.create_date ? item.create_date.split(' ')[0] : '',
status: '已分享', // 分享数据没有支付状态,统一显示为已分享
uuid: item.uuid,
order_id: item.order_id,
type: item.type,
providername: item.downloadername, // 分享数据中下载者名称对应提供者
downloadername: item.downloadername
}));
// 判断是否还有更多数据
noMore.value = data.pageNumber >= data.totalPage;
}
refreshing.value = false;
}).catch(err => {
console.error('获取分享列表失败:', err);
refreshing.value = false;
});
}
};
const onLoadMore = () => {
if (loading.value || noMore.value) return;
loading.value = true;
page.value++;
setTimeout(() => {
if (page.value < 3) {
page.value++;
uni.showToast({ title: '加载完成', icon: 'success' });
} else {
noMore.value = true;
}
loading.value = false;
}, 1000);
if (activeTab.value === 'download') {
api.getGandanfileMyDownload({ page: page.value }).then(res => {
console.log(res);
if (res.code === 200 && res.data) {
const data = res.data.list;
// 追加新数据到列表
const newList = data.list.map(item => ({
name: item.title || '未知课件',
time: item.create_date ? item.create_date.split(' ')[0] : '',
status: item.order_status === 'paid' ? '已支付' : '未支付',
uuid: item.uuid,
order_id: item.order_id,
type: item.type,
providername: item.providername
}));
coursewareList.value = [...coursewareList.value, ...newList];
// 判断是否还有更多数据
noMore.value = data.pageNumber >= data.totalPage;
}
loading.value = false;
}).catch(err => {
console.error('加载更多下载列表失败:', err);
page.value--; // 恢复页码
loading.value = false;
});
} else {
api.getGandanfileMyShare({ page: page.value }).then(res => {
console.log(res);
if (res.code === 200 && res.data) {
const data = res.data.list;
// 追加新数据到列表
const newList = data.list.map(item => ({
name: item.title || '未知课件',
time: item.create_date ? item.create_date.split(' ')[0] : '',
status: '已分享', // 分享数据没有支付状态,统一显示为已分享
uuid: item.uuid,
order_id: item.order_id,
type: item.type,
providername: item.downloadername, // 分享数据中下载者名称对应提供者
downloadername: item.downloadername
}));
coursewareList.value = [...coursewareList.value, ...newList];
// 判断是否还有更多数据
noMore.value = data.pageNumber >= data.totalPage;
}
loading.value = false;
}).catch(err => {
console.error('加载更多分享列表失败:', err);
page.value--; // 恢复页码
loading.value = false;
});
}
};
</script>
@@ -240,6 +369,7 @@ const onLoadMore = () => {
border-bottom:2rpx solid #eee;
.item-content {
.courseware-name,
.courseware-provider,
.courseware-time,
.courseware-status {
display: flex;
@@ -269,10 +399,15 @@ const onLoadMore = () => {
}
}
.loading-more, .no-more {
.loading-more, .no-more, .empty-state {
text-align: center;
padding: 30rpx;
color: #999999;
font-size: 26rpx;
}
.empty-state {
padding: 100rpx 30rpx;
font-size: 28rpx;
}
</style>
+7 -6
View File
@@ -60,11 +60,9 @@
<view v-if="noMore && records.length > 0" class="no-more">
<text>没有更多数据了</text>
</view>
<!-- 调试按钮 -->
<view v-if="records.length > 0" class="debug-actions">
<button @click="testLoadMore" size="mini" type="primary">测试加载更多</button>
<text class="debug-info">当前页: {{ page }}, 加载中: {{ loading }}, 无更多: {{ noMore }}</text>
<view class="debug-actions">
<!-- <button @click="testLoadMore" size="mini" type="primary">测试加载更多</button>
<text class="debug-info">当前页: {{ page }}, 加载中: {{ loading }}, 无更多: {{ noMore }}</text> -->
</view>
</scroll-view>
</view>
@@ -221,7 +219,9 @@ $text: #333;
$muted: #999;
$card: #ffffff;
.flower-page { height: calc(100vh - 140rpx); background: $bg; }
.flower-page {
background: $bg;
}
.stats-bar {
display: flex;
@@ -297,6 +297,7 @@ $card: #ffffff;
}
.debug-actions {
height: 50rpx;
text-align: center;
padding: 20rpx;
border-top: 1rpx solid #eee;
+1 -1
View File
@@ -472,7 +472,7 @@
const formattedList = listData.map(item => ({
type: item.score_type_name,
time: item.create_date,
amount: -item.score // 支出为负数
amount: item.score // 支出为负数
}));
// 更新支出记录列表
+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>
+1 -1
View File
@@ -130,7 +130,7 @@
// 跳转到修改密码页面
const goToChangePassword = () => {
uni.navigateTo({
url: '/pages_app/pwdLogin/pwdLogin'
url: '/pages_app/changePassword/index'
});
};