edu/uniapp/src/pages/user/user.vue

821 lines
24 KiB
Vue
Raw Normal View History

2022-08-26 09:52:43 +00:00
<template>
<view class="user">
<!-- 顶部渐变背景 -->
<view class="header-bg">
<view class="header-content">
<view class="user-info" v-if="isLogin && userInfo">
<view class="avatar">
<text class="avatar-text">{{ userInfo.name ? userInfo.name.charAt(0) : '?' }}</text>
</view>
<view class="user-detail">
<view class="user-name">{{ userInfo.name || '招生老师' }}</view>
<view class="user-role">{{ userInfo.nickname || '招生专员' }}</view>
</view>
</view>
<view class="user-info" v-else>
<view class="avatar">
<text class="avatar-text">?</text>
</view>
<view class="user-detail">
<view class="user-name">未登录</view>
<view class="login-btn" @click="goToLogin">点击登录</view>
</view>
</view>
</view>
</view>
<!-- 主要内容区域 -->
<view class="main-content">
<!-- 招生数据卡片 -->
<view class="stats-card" v-if="isLogin">
<view class="card-header">
<view class="card-title">
<view class="title-icon">📊</view>
<text>招生数据</text>
</view>
<view class="view-more" @click="goToRecruitment">
<text>查看详情</text>
<u-icon name="arrow-right" size="24" color="#3B82F6"></u-icon>
</view>
</view>
<view class="stats-grid">
<view class="stat-item">
<view class="stat-value blue">{{ recruitmentStats.total || 0 }}</view>
<view class="stat-label">总招生</view>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<view class="stat-value green">{{ recruitmentStats.today || 0 }}</view>
<view class="stat-label">今日</view>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<view class="stat-value orange">{{ recruitmentStats.week || 0 }}</view>
<view class="stat-label">本周</view>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<view class="stat-value purple">{{ recruitmentStats.month || 0 }}</view>
<view class="stat-label">本月</view>
</view>
</view>
</view>
<!-- 数据可视化 - 进度条 -->
<view class="chart-card" v-if="isLogin">
<view class="card-header">
<view class="card-title">
<view class="title-icon">📈</view>
<text>招生目标</text>
</view>
</view>
<view class="target-progress">
<view class="progress-header">
<text class="progress-label">本月目标完成度</text>
<text class="progress-value">{{ progressPercent }}%</text>
</view>
<view class="progress-bar">
<view class="progress-fill" :style="{ width: progressPercent + '%' }"></view>
</view>
<view class="progress-stats">
<text>已完成 {{ recruitmentStats.month || 0 }} </text>
<text>目标 50 </text>
</view>
</view>
</view>
<!-- 快捷功能 -->
<view class="quick-actions" v-if="isLogin">
<view class="section-title">
<view class="title-icon">🚀</view>
<text>快捷功能</text>
</view>
<view class="action-grid">
<view class="action-item" @click="goToRecruitment">
<view class="action-icon blue">
<u-icon name="list" size="36" color="#FFFFFF"></u-icon>
</view>
<text class="action-text">我的招生</text>
</view>
<view class="action-item" @click="openQrcodeModal">
<view class="action-icon green">
<u-icon name="scan" size="36" color="#FFFFFF"></u-icon>
</view>
<text class="action-text">招生二维码</text>
</view>
<view class="action-item" @click="goTo('/pages/user_data/user_data')">
<view class="action-icon purple">
<u-icon name="account" size="36" color="#FFFFFF"></u-icon>
</view>
<text class="action-text">个人资料</text>
</view>
<view class="action-item" @click="goTo('/pages/change_password/change_password')">
<view class="action-icon orange">
<u-icon name="lock" size="36" color="#FFFFFF"></u-icon>
</view>
<text class="action-text">修改密码</text>
</view>
</view>
</view>
<!-- 账号管理 -->
<view class="account-actions" v-if="isLogin">
<view class="action-list">
<view class="action-list-item" @click="switchAccount">
<view class="item-left">
<view class="item-icon blue">
<u-icon name="reload" size="32" color="#FFFFFF"></u-icon>
</view>
<text class="item-text">切换账号</text>
</view>
<u-icon name="arrow-right" size="28" color="#9CA3AF"></u-icon>
</view>
<view class="action-list-item" @click="logout">
<view class="item-left">
<view class="item-icon red">
<u-icon name="close-circle" size="32" color="#FFFFFF"></u-icon>
</view>
<text class="item-text">退出登录</text>
</view>
<u-icon name="arrow-right" size="28" color="#9CA3AF"></u-icon>
</view>
</view>
</view>
<!-- 未登录提示 -->
<view class="login-tip" v-if="!isLogin">
<view class="tip-icon">🔒</view>
<view class="tip-text">登录后查看招生数据</view>
<view class="tip-button" @click="goToLogin">立即登录</view>
</view>
</view>
<!-- 二维码弹窗 -->
<u-popup
v-model="showQrcodeModal"
mode="center"
:round="10"
:closeable="true"
:closeOnClickOverlay="true"
>
<view class="qrcode-modal">
<view class="text-center font-medium text-lg mb-[30rpx]">我的招生二维码</view>
<view class="qrcode-container">
<image
v-if="qrcodeUrl"
:src="qrcodeUrl"
mode="widthFix"
class="qrcode-image"
@longpress="handleSaveQrcode"
/>
<view v-else class="qrcode-loading">
<u-loading mode="circle" size="40"></u-loading>
<view class="mt-[20rpx] text-gray-500">加载中...</view>
</view>
</view>
<view class="text-center text-sm text-gray-500 mt-[20rpx]">长按二维码保存到相册</view>
<view class="mt-[30rpx]">
<u-button type="primary" @click="handleSaveQrcode" :custom-style="btnStyle">
保存到相册
</u-button>
</view>
</view>
</u-popup>
</view>
2022-08-26 09:52:43 +00:00
</template>
2022-09-08 08:28:56 +00:00
<script setup lang="ts">
import { useUserStore } from '@/stores/user'
import { onShow } from '@dcloudio/uni-app'
import { storeToRefs } from 'pinia'
import { computed, reactive, ref } from 'vue'
import { getEnrollmentStatistical, getTeacherQrcodeImage } from '@/api/app'
import { TOKEN_KEY } from '@/enums/constantEnums'
import cache from '@/utils/cache'
const qrcodeUrl = ref('')
const showQrcodeModal = ref(false)
const recruitmentStats = ref({
total: 0,
today: 0,
week: 0,
month: 0
})
const btnStyle = {
height: '80rpx',
fontSize: '28rpx',
borderRadius: '40rpx',
background: 'linear-gradient(135deg, #3B82F6 0%, #2563EB 100%)'
}
const userStore = useUserStore()
const { userInfo, isLogin } = storeToRefs(userStore)
// 计算进度百分比
const progressPercent = computed(() => {
const target = 50
const current = recruitmentStats.value.month || 0
return Math.min(Math.round((current / target) * 100), 100)
})
const getQrcode = async () => {
if (userInfo.value?.teacherId) {
try {
const response = await getTeacherQrcodeImage(userInfo.value.teacherId)
if (response.statusCode === 200) {
const arrayBuffer = response.data
const base64 = uni.arrayBufferToBase64(arrayBuffer)
qrcodeUrl.value = `data:image/png;base64,${base64}`
}
} catch (error) {
console.error('获取二维码失败:', error)
}
}
}
const getStats = async () => {
console.log('=== getStats 开始执行 ===')
try {
console.log('正在调用 getEnrollmentStatistical...')
const res = await getEnrollmentStatistical()
console.log('接口返回结果:', res)
// 接口直接返回数据对象,没有 code 包装
if (res && res.total_enroll_count !== undefined) {
console.log('total_enroll_count:', res.total_enroll_count)
console.log('today_enroll_count:', res.today_enroll_count)
console.log('week_enroll_count:', res.week_enroll_count)
console.log('month_enroll_count:', res.month_enroll_count)
recruitmentStats.value = {
total: res.total_enroll_count,
today: res.today_enroll_count,
week: res.week_enroll_count,
month: res.month_enroll_count
}
console.log('更新后的 recruitmentStats:', recruitmentStats.value)
} else {
console.log('接口返回数据异常:', res)
}
} catch (error) {
console.error('获取招生统计失败:', error)
}
console.log('=== getStats 执行结束 ===')
}
const openQrcodeModal = () => {
showQrcodeModal.value = true
if (!qrcodeUrl.value) {
getQrcode()
}
}
const handleSaveQrcode = () => {
if (!qrcodeUrl.value) {
uni.$u.toast('二维码未加载')
return
}
uni.showActionSheet({
itemList: ['保存到相册'],
success: (res) => {
if (res.tapIndex === 0) {
// #ifdef MP-WEIXIN
saveQrcodeForMp()
// #endif
// #ifdef H5
saveQrcodeForH5()
// #endif
}
}
})
}
const saveQrcodeForMp = () => {
const base64Data = qrcodeUrl.value.replace(/^data:image\/\w+;base64,/, '')
const filePath = `${wx.env.USER_DATA_PATH}/qrcode_${Date.now()}.png`
const fsm = wx.getFileSystemManager()
fsm.writeFile({
filePath,
data: base64Data,
encoding: 'base64',
success: () => {
uni.saveImageToPhotosAlbum({
filePath,
success: () => {
uni.$u.toast('保存成功')
},
fail: () => {
uni.$u.toast('保存失败,请检查相册权限')
}
})
},
fail: () => {
uni.$u.toast('保存失败')
}
})
}
const saveQrcodeForH5 = () => {
const link = document.createElement('a')
link.href = qrcodeUrl.value
link.download = `qrcode_${Date.now()}.png`
link.click()
uni.$u.toast('保存成功')
}
const goToRecruitment = () => {
uni.navigateTo({
url: '/pages/my_recruitment/my_recruitment'
})
}
const goTo = (url: string) => {
uni.navigateTo({
url
})
}
const goToLogin = () => {
uni.navigateTo({
url: '/pages/login/login'
})
}
const switchAccount = () => {
uni.showModal({
title: '切换账号',
content: '确定要切换到其他账号吗?',
success: (res) => {
if (res.confirm) {
userStore.logout()
uni.navigateTo({
url: '/pages/login/login'
})
}
}
})
}
const logout = () => {
uni.showModal({
title: '退出登录',
content: '确定要退出登录吗?',
success: (res) => {
if (res.confirm) {
userStore.logout()
uni.switchTab({
url: '/pages/index/index'
})
uni.$u.toast('已退出登录')
}
}
})
}
onShow(async () => {
console.log('=== onShow 开始执行 ===')
console.log('初始登录状态:', isLogin.value)
console.log('缓存中的token:', cache.get(TOKEN_KEY))
if (!isLogin.value && cache.get(TOKEN_KEY)) {
userStore.token = cache.get(TOKEN_KEY)
console.log('已从缓存设置token')
}
await userStore.getUser()
console.log('getUser 完成后登录状态:', isLogin.value)
console.log('getUser 完成后用户信息:', userInfo.value)
if (isLogin.value && userInfo.value) {
console.log('条件满足,开始调用 getQrcode 和 getStats')
getQrcode()
getStats()
} else {
console.log('条件不满足,跳过数据获取')
console.log('isLogin:', isLogin.value)
console.log('userInfo:', userInfo.value)
}
console.log('=== onShow 执行结束 ===')
})
2022-09-08 08:28:56 +00:00
</script>
2022-08-26 09:52:43 +00:00
<style lang="scss" scoped>
.user {
min-height: 100vh;
background-color: #F5F7FA;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
}
/* 顶部渐变背景 */
.header-bg {
background: linear-gradient(135deg, #3B82F6 0%, #2563EB 100%);
padding: 60rpx 30rpx 100rpx;
border-radius: 0 0 40rpx 40rpx;
}
.header-content {
.user-info {
display: flex;
align-items: center;
.avatar {
width: 120rpx;
height: 120rpx;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 30rpx;
border: 4rpx solid rgba(255, 255, 255, 0.3);
.avatar-text {
font-size: 48rpx;
font-weight: 600;
color: #FFFFFF;
}
}
.user-detail {
.user-name {
font-size: 40rpx;
font-weight: 600;
color: #FFFFFF;
margin-bottom: 10rpx;
}
.user-role {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.8);
background: rgba(255, 255, 255, 0.2);
padding: 6rpx 20rpx;
border-radius: 20rpx;
display: inline-block;
}
.login-btn {
font-size: 28rpx;
color: #FFFFFF;
background: rgba(255, 255, 255, 0.2);
padding: 10rpx 30rpx;
border-radius: 30rpx;
display: inline-block;
&:active {
background: rgba(255, 255, 255, 0.3);
}
}
}
}
}
/* 主要内容区域 */
.main-content {
margin-top: 20rpx;
padding: 0 30rpx;
position: relative;
z-index: 1;
}
/* 数据卡片 */
.stats-card {
background: #FFFFFF;
border-radius: 24rpx;
padding: 30rpx;
margin-bottom: 30rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
.card-title {
display: flex;
align-items: center;
font-size: 32rpx;
font-weight: 600;
color: #1F2937;
.title-icon {
margin-right: 12rpx;
font-size: 36rpx;
}
}
.view-more {
display: flex;
align-items: center;
font-size: 26rpx;
color: #3B82F6;
text {
margin-right: 8rpx;
}
}
}
.stats-grid {
display: flex;
align-items: center;
justify-content: space-around;
.stat-item {
flex: 1;
text-align: center;
.stat-value {
font-size: 44rpx;
font-weight: 700;
margin-bottom: 10rpx;
&.blue {
color: #3B82F6;
}
&.green {
color: #10B981;
}
&.orange {
color: #F97316;
}
&.purple {
color: #8B5CF6;
}
}
.stat-label {
font-size: 24rpx;
color: #6B7280;
}
}
.stat-divider {
width: 2rpx;
height: 60rpx;
background-color: #E5E7EB;
}
}
}
/* 图表卡片 */
.chart-card {
background: #FFFFFF;
border-radius: 24rpx;
padding: 30rpx;
margin-bottom: 30rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
.card-title {
display: flex;
align-items: center;
font-size: 32rpx;
font-weight: 600;
color: #1F2937;
.title-icon {
margin-right: 12rpx;
font-size: 36rpx;
}
}
}
.target-progress {
margin-bottom: 30rpx;
.progress-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
.progress-label {
font-size: 28rpx;
color: #4B5563;
}
.progress-value {
font-size: 32rpx;
font-weight: 600;
color: #3B82F6;
}
}
.progress-bar {
height: 16rpx;
background-color: #E5E7EB;
border-radius: 8rpx;
overflow: hidden;
margin-bottom: 16rpx;
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #3B82F6 0%, #10B981 100%);
border-radius: 8rpx;
transition: width 0.5s ease;
}
}
.progress-stats {
display: flex;
justify-content: space-between;
font-size: 24rpx;
color: #9CA3AF;
}
}
}
/* 快捷操作 */
.quick-actions {
background: #FFFFFF;
border-radius: 24rpx;
padding: 30rpx;
margin-bottom: 30rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
.section-title {
display: flex;
align-items: center;
font-size: 32rpx;
font-weight: 600;
color: #1F2937;
margin-bottom: 30rpx;
.title-icon {
margin-right: 12rpx;
font-size: 36rpx;
}
}
.action-grid {
display: flex;
justify-content: space-between;
.action-item {
display: flex;
flex-direction: column;
align-items: center;
&:active {
opacity: 0.7;
}
.action-icon {
width: 100rpx;
height: 100rpx;
border-radius: 24rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 16rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
&.blue {
background: linear-gradient(135deg, #3B82F6 0%, #2563EB 100%);
}
&.green {
background: linear-gradient(135deg, #10B981 0%, #059669 100%);
}
&.purple {
background: linear-gradient(135deg, #8B5CF6 0%, #7C3AED 100%);
}
&.orange {
background: linear-gradient(135deg, #F97316 0%, #EA580C 100%);
}
}
.action-text {
font-size: 26rpx;
color: #4B5563;
}
}
}
}
/* 账号管理 */
.account-actions {
background: #FFFFFF;
border-radius: 24rpx;
padding: 0 30rpx;
margin-bottom: 30rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
.action-list {
.action-list-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx 0;
border-bottom: 2rpx solid #F3F4F6;
&:last-child {
border-bottom: none;
}
&:active {
background: #F9FAFB;
}
.item-left {
display: flex;
align-items: center;
.item-icon {
width: 64rpx;
height: 64rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
&.blue {
background: linear-gradient(135deg, #3B82F6 0%, #2563EB 100%);
}
&.red {
background: linear-gradient(135deg, #EF4444 0%, #DC2626 100%);
}
}
.item-text {
font-size: 30rpx;
color: #1F2937;
font-weight: 500;
}
}
}
}
}
/* 未登录提示 */
.login-tip {
background: #FFFFFF;
border-radius: 24rpx;
padding: 60rpx 40rpx;
text-align: center;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
.tip-icon {
font-size: 80rpx;
margin-bottom: 20rpx;
}
.tip-text {
font-size: 30rpx;
color: #6B7280;
margin-bottom: 30rpx;
}
.tip-button {
display: inline-block;
background: linear-gradient(135deg, #3B82F6 0%, #2563EB 100%);
color: #FFFFFF;
font-size: 30rpx;
font-weight: 500;
padding: 20rpx 60rpx;
border-radius: 40rpx;
&:active {
opacity: 0.9;
}
}
}
/* 二维码弹窗 */
.qrcode-modal {
padding: 40rpx;
min-width: 500rpx;
.qrcode-container {
display: flex;
justify-content: center;
align-items: center;
padding: 40rpx;
background: #f8f8f8;
border-radius: 20rpx;
margin-bottom: 20rpx;
.qrcode-image {
width: 400rpx;
height: 400rpx;
}
.qrcode-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 400rpx;
height: 400rpx;
}
}
}
</style>