Compare commits

...

2 Commits

Author SHA1 Message Date
Yu 14dc062b99 Merge branch 'master' of http://101.37.69.204:3000/mirage/edu 2026-03-18 14:39:06 +08:00
Yu d8109d2f8a 整体页面布局修改 2026-03-18 14:37:30 +08:00
12 changed files with 506 additions and 615 deletions

View File

@ -47,9 +47,7 @@ class DevelopClientScript {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const isWindows = process.platform === 'win32' const isWindows = process.platform === 'win32'
const command = isWindows ? 'cmd.exe' : 'npm' const command = isWindows ? 'cmd.exe' : 'npm'
const args = isWindows const args = isWindows ? ['/c', 'npm', 'run', scriptName] : ['run', scriptName]
? ['/c', 'npm', 'run', scriptName]
: ['run', scriptName]
const runProcess = spawn(command, args) const runProcess = spawn(command, args)
@ -63,11 +61,7 @@ class DevelopClientScript {
runProcess.on('close', (code) => { runProcess.on('close', (code) => {
if (code !== 0) { if (code !== 0) {
reject( reject(new Error(`运行错误,请查看以下报错信息寻找解决方法: ${error.message}`))
new Error(
`运行错误,请查看以下报错信息寻找解决方法: ${error.message}`
)
)
} else { } else {
resolve() resolve()
} }

View File

@ -38,7 +38,10 @@ export function getTeacherInfo(data: any) {
} }
export function addStudent(data: any) { export function addStudent(data: any) {
return request.post({ url: 'frontapi/student/add', data: data }, { urlPrefix: '' }) console.log('【addStudent】开始调用传入数据:', data)
const result = request.post({ url: 'frontapi/enrollment/add', data: data }, { urlPrefix: '', isTransformResponse: false })
console.log('【addStudent】request.post 返回的 Promise:', result)
return result
} }
export function getTeacherQrcode(data: any) { export function getTeacherQrcode(data: any) {
@ -69,7 +72,29 @@ export function getRecruitmentList(data: any) {
// 获取招生统计数据(总招生人数、本日、本周、本月) // 获取招生统计数据(总招生人数、本日、本周、本月)
export function getEnrollmentStatistical() { export function getEnrollmentStatistical() {
return request.post( return request.post(
{ url: 'frontapi/student/enrollmentStatistical' }, { url: 'frontapi/enrollment/enrollmentStatistical' },
{ urlPrefix: '' }
)
}
// 获取预报名学生列表
export function getPreRegistrationList(data: {
page: number
limit: number
studentStatus?: number
name?: string
mobile?: string
}) {
return request.get(
{ url: 'frontapi/enrollment/preRegistrationList', data },
{ urlPrefix: '' }
)
}
// 获取预报名学生详情
export function getEnrollmentDetail(id: number) {
return request.get(
{ url: 'frontapi/enrollment/detail', data: { id } },
{ urlPrefix: '' } { urlPrefix: '' }
) )
} }

View File

@ -55,7 +55,8 @@
{ {
"path": "pages/my_recruitment/my_recruitment", "path": "pages/my_recruitment/my_recruitment",
"style": { "style": {
"navigationBarTitleText": "我的招生" "navigationBarTitleText": "我的招生",
"navigationStyle": "custom"
} }
}, },
{ {
@ -85,7 +86,7 @@
"list": [ "list": [
{ {
"iconPath": "static/images/tabbar/home.png", "iconPath": "static/images/tabbar/home.png",
"selectedIconPath": "static/images/tabbar/home_s.png", "selectedIconPath": "static/yubaoming/home_icon_active.png",
"pagePath": "pages/index/index", "pagePath": "pages/index/index",
"text": "首页" "text": "首页"
}, },

View File

@ -5,55 +5,34 @@
<!-- #endif --> <!-- #endif -->
</page-meta> </page-meta>
<view class="index"> <view class="index">
<!-- 顶部渐变背景 --> <!-- 顶部区域Logo + 欢迎文字 + 首页图片 -->
<view class="header-bg"> <view class="header-section">
<view class="header-content"> <image
src="/static/yubaoming/school_logo.png"
mode="aspectFit"
class="school-logo"
/>
<view class="welcome-text"> <view class="welcome-text">
<view class="title">👋 您好招生老师</view> <view class="title">您好招生老师</view>
<view class="subtitle">祝您招生顺利业绩长虹</view> <view class="subtitle">祝您招生顺利业绩长虹</view>
</view> </view>
<image
src="/static/yubaoming/home.png"
mode="widthFix"
class="home-image"
/>
<!-- 快捷操作标题 -->
<view class="section-title" v-if="isLogin">
<view class="title-icon"></view>
<text>快捷操作</text>
</view> </view>
</view> </view>
<!-- 主要内容区域 --> <!-- 主要内容区域 -->
<view class="main-content"> <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="goToMyRecruitment">
<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">{{ stats.total || 0 }}</view>
<view class="stat-label">总招生人数</view>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<view class="stat-value green">{{ stats.today || 0 }}</view>
<view class="stat-label">今日新增</view>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<view class="stat-value orange">{{ stats.week || 0 }}</view>
<view class="stat-label">本周新增</view>
</view>
</view>
</view>
<!-- 快捷操作入口 --> <!-- 快捷操作入口 -->
<view class="quick-actions" v-if="isLogin"> <view class="quick-actions" v-if="isLogin">
<view class="section-title">
<view class="title-icon">🚀</view>
<text>快捷操作</text>
</view>
<view class="action-grid three-column">
<view class="action-item" @click="goToPreRegistration"> <view class="action-item" @click="goToPreRegistration">
<view class="action-icon blue"> <view class="action-icon blue">
<u-icon name="edit-pen" size="40" color="#FFFFFF"></u-icon> <u-icon name="edit-pen" size="40" color="#FFFFFF"></u-icon>
@ -73,7 +52,6 @@
<text class="action-text">个人中心</text> <text class="action-text">个人中心</text>
</view> </view>
</view> </view>
</view>
<!-- 未登录提示 --> <!-- 未登录提示 -->
<view class="login-tip" v-if="!isLogin"> <view class="login-tip" v-if="!isLogin">
@ -244,148 +222,90 @@ onShow(async () => {
padding-bottom: calc(20rpx + env(safe-area-inset-bottom)); padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
} }
/* 顶部渐变背景 */ /* 顶部区域 */
.header-bg { .header-section {
background: linear-gradient(135deg, #3B82F6 0%, #2563EB 100%); display: flex;
padding: 80rpx 30rpx 80rpx; flex-direction: column;
border-radius: 0 0 40rpx 40rpx; align-items: center;
padding: 40rpx 30rpx;
position: relative;
.school-logo {
width: 350rpx;
height: 350rpx;
margin-top: -40rpx;
margin-bottom: -80rpx;
} }
.header-content {
.welcome-text { .welcome-text {
text-align: center;
margin-bottom: -100rpx;
.title { .title {
font-size: 40rpx; font-size: 40rpx;
font-weight: 600; font-weight: 600;
color: #FFFFFF; color: #000000;
margin-bottom: 12rpx; margin-bottom: 12rpx;
} }
.subtitle { .subtitle {
font-size: 28rpx; font-size: 28rpx;
color: rgba(255, 255, 255, 0.8); color: #333333;
} }
} }
.home-image {
width: calc(100% + 60rpx);
margin-left: -30rpx;
margin-right: -30rpx;
}
} }
/* 主要内容区域 */ /* 主要内容区域 */
.main-content { .main-content {
margin-top: 20rpx; padding: 0 30rpx 30rpx;
padding: 0 30rpx;
position: relative; position: relative;
z-index: 1; 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: 48rpx;
font-weight: 700;
margin-bottom: 12rpx;
&.blue {
color: #3B82F6;
}
&.green {
color: #10B981;
}
&.orange {
color: #F97316;
}
}
.stat-label {
font-size: 26rpx;
color: #6B7280;
}
}
.stat-divider {
width: 2rpx;
height: 80rpx;
background-color: #E5E7EB;
}
}
}
/* 快捷操作 */
.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 { .section-title {
display: flex; display: flex;
align-items: center; align-items: center;
font-size: 32rpx; font-size: 32rpx;
font-weight: 600; font-weight: 600;
color: #1F2937; color: #000000;
margin-bottom: 30rpx; padding: 20rpx 0;
position: absolute;
bottom: 50rpx;
left: 30rpx;
z-index: 10;
.title-icon { .title-icon {
margin-right: 12rpx; margin-right: 12rpx;
font-size: 36rpx; width: 10rpx;
height: 32rpx;
background: linear-gradient(135deg, #3B82F6 0%, #2563EB 100%);
border-radius: 3rpx;
} }
} }
.action-grid { /* 快捷操作 */
.quick-actions {
display: flex; display: flex;
flex-direction: row;
justify-content: space-between; justify-content: space-between;
gap: 20rpx;
&.three-column { margin-top: -30rpx;
justify-content: space-around;
}
.action-item { .action-item {
flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
background: #FFFFFF;
border-radius: 24rpx;
padding: 30rpx 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
&:active { &:active {
opacity: 0.7; opacity: 0.7;
@ -414,8 +334,8 @@ onShow(async () => {
.action-text { .action-text {
font-size: 26rpx; font-size: 26rpx;
color: #4B5563; color: #1F2937;
} font-weight: 500;
} }
} }
} }

View File

@ -17,7 +17,7 @@
<view class="w-full mt-[140rpx] pb-[60rpx]"> <view class="w-full mt-[140rpx] pb-[60rpx]">
<block v-if="!phoneLogin"> <block v-if="!phoneLogin">
<!-- #ifdef MP-WEIXIN || H5 --> <!-- #ifdef MP-WEIXIN || H5 -->
<view v-if="isOpenOtherAuth && isWeixin && inWxAuth"> <!-- <view v-if="isOpenOtherAuth && isWeixin && inWxAuth">
<u-button <u-button
type="primary" type="primary"
@click="wxLogin" @click="wxLogin"
@ -26,7 +26,7 @@
> >
用户一键登录 用户一键登录
</u-button> </u-button>
</view> </view> -->
<!-- #endif --> <!-- #endif -->
<view class="mt-[40rpx]"> <view class="mt-[40rpx]">
@ -35,7 +35,7 @@
:customStyle="{ height: '100rpx' }" :customStyle="{ height: '100rpx' }"
hover-class="none" hover-class="none"
> >
手机号登录 使用账号登录
</u-button> </u-button>
</view> </view>
</block> </block>
@ -173,7 +173,7 @@
>密码登录</span >密码登录</span
> >
</view> </view>
<view <!-- <view
v-if=" v-if="
formData.scene == LoginWayEnum.ACCOUNT && formData.scene == LoginWayEnum.ACCOUNT &&
includeLoginWay(LoginWayEnum.MOBILE) includeLoginWay(LoginWayEnum.MOBILE)
@ -188,10 +188,10 @@
" "
>验证码登录</span >验证码登录</span
> >
</view> </view> -->
<navigator url="/pages/register/register" hover-class="none" <!-- <navigator url="/pages/register/register" hover-class="none"
>注册账号</navigator >注册账号</navigator
> > -->
</view> </view>
</block> </block>
</view> </view>

View File

@ -1,398 +1,263 @@
<template> <template>
<page-meta :page-style="$theme.pageStyle"> <page-meta :page-style="$theme.pageStyle">
<navigation-bar :front-color="$theme.navColor" :background-color="$theme.navBgColor" />
</page-meta> </page-meta>
<view class="my-recruitment min-h-full pb-[env(safe-area-inset-bottom)]"> <view class="my-recruitment min-h-full pb-[env(safe-area-inset-bottom)]">
<view class="px-[30rpx] py-[20rpx]"> <view class="nav-bar">
<view class="filter-bar flex items-center justify-between"> <view class="back-btn" @click="goBack">
<view class="filter-item" @click="showTimeFilter = true"> <text class="back-icon"></text>
<text :class="timeFilter ? 'text-blue-500' : 'text-gray-600'">
{{ timeFilterText }}
</text>
<u-icon
name="arrow-down"
size="24"
:color="timeFilter ? '#3B82F6' : '#999'"
></u-icon>
</view> </view>
<view class="filter-item" @click="showRegionFilter = true"> <view class="nav-title">我的招生</view>
<text :class="regionFilter ? 'text-blue-500' : 'text-gray-600'">
{{ regionFilterText }}
</text>
<u-icon
name="arrow-down"
size="24"
:color="regionFilter ? '#3B82F6' : '#999'"
></u-icon>
</view> </view>
<view class="filter-item" @click="toggleSort"> <image
<text :class="sortBy ? 'text-blue-500' : 'text-gray-600'"> src="/static/yubaoming/recruitment_3.png"
{{ sortText }} mode="aspectFill"
</text> class="page-bg"
<u-icon />
:name="sortOrder === 'desc' ? 'arrow-down' : 'arrow-up'" <view class="px-[30rpx] content-wrapper">
size="24" <view class="intro-box">
:color="sortBy ? '#3B82F6' : '#999'" 启东市科信技工学校秉持着一系列极具特色与前瞻性的办学理念致力于为学生打造优质教育推动区域协同发展
></u-icon>
</view> </view>
<view class="student-image-wrapper">
<image
src="/static/yubaoming/student.png"
mode="aspectFit"
class="student-image"
/>
</view> </view>
</view> <z-paging
ref="paging"
<view class="px-[30rpx]"> v-model="list"
<view class="text-sm text-gray-500 mb-[20rpx]"> {{ total }} 条记录 </view> @query="queryList"
:fixed="false"
<view v-if="loading" class="flex justify-center py-[100rpx]"> height="100%"
<u-loading mode="circle"></u-loading> use-page-scroll
</view>
<view
v-else-if="list.length === 0"
class="empty-state flex flex-col items-center py-[100rpx]"
> >
<u-icon name="inbox" size="120" color="#E5E7EB"></u-icon> <view class="student-table">
<view class="text-gray-400 mt-[30rpx]">暂无招生数据</view> <!-- 表头 -->
<view class="table-header">
<view class="th th-name">姓名</view>
<view class="th th-gender">性别</view>
<view class="th th-score">成绩</view>
<view class="th th-major">专业</view>
<view class="th th-status">状态</view>
</view> </view>
<!-- 数据行 -->
<view v-else class="list">
<view <view
v-for="(item, index) in list" v-for="(item, index) in list"
:key="index" :key="index"
class="list-item bg-white rounded-lg p-[30rpx] mb-[20rpx]" class="table-row"
> >
<view class="flex items-center justify-between mb-[20rpx]"> <view class="td td-name">{{ item.name }}</view>
<view class="flex items-center"> <view class="td td-gender">{{ item.gender === 1 ? '男' : item.gender === 2 ? '女' : '-' }}</view>
<view <view class="td td-score">{{ item.highSchoolScore || '-' }}</view>
class="avatar bg-blue-100 text-blue-500 w-[80rpx] h-[80rpx] rounded-full flex items-center justify-center text-xl font-medium" <view class="td td-major">{{ item.majorName || '-' }}</view>
> <view class="td td-status">
{{ item.name ? item.name.charAt(0) : '?' }} <text :class="item.studentStatus === 1 ? 'status-enrolled' : 'status-pre'">
</view> {{ item.studentStatus === 1 ? '已报名' : '预报名' }}
<view class="ml-[20rpx]"> </text>
<view class="text-lg font-medium">{{ item.name }}</view>
<view class="text-sm text-gray-500 mt-[5rpx]">{{
item.gender === 1 ? '男' : '女'
}}</view>
</view> </view>
</view> </view>
<view class="text-right">
<view class="text-sm text-gray-500">{{ item.majorName }}</view>
<view class="text-xs text-gray-400 mt-[5rpx]">{{
item.createTime
}}</view>
</view> </view>
</z-paging>
</view> </view>
<view class="info-grid grid grid-cols-2 gap-[20rpx]">
<view class="info-item">
<view class="text-xs text-gray-400">身份证号</view>
<view class="text-sm mt-[5rpx]">{{ formatIdCard(item.idCard) }}</view>
</view>
<view class="info-item">
<view class="text-xs text-gray-400">毕业学校</view>
<view class="text-sm mt-[5rpx]">{{ item.previousSchool }}</view>
</view>
<view class="info-item">
<view class="text-xs text-gray-400">中考成绩</view>
<view class="text-sm mt-[5rpx]">{{ item.highSchoolScore }}</view>
</view>
<view class="info-item">
<view class="text-xs text-gray-400">身高/体重</view>
<view class="text-sm mt-[5rpx]"
>{{ item.height }}cm / {{ item.weight }}kg</view
>
</view>
</view>
<view class="status-bar mt-[20rpx] flex items-center justify-between">
<view class="flex items-center">
<view class="status-dot" :class="getStatusClass(item.status)"></view>
<view
class="text-sm ml-[10rpx]"
:class="getStatusTextClass(item.status)"
>
{{ getStatusText(item.status) }}
</view>
</view>
<view class="text-xs text-gray-400"> 鞋码: {{ item.shoeSize }} </view>
</view>
</view>
</view>
</view>
<u-action-sheet
v-model="showTimeFilter"
:list="timeFilterList"
@click="handleTimeFilter"
></u-action-sheet>
<u-action-sheet
v-model="showRegionFilter"
:list="regionFilterList"
@click="handleRegionFilter"
></u-action-sheet>
</view> </view>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, computed } from 'vue' import { ref, shallowRef } from 'vue'
import { onLoad } from '@dcloudio/uni-app' import { onLoad } from '@dcloudio/uni-app'
import { getRecruitmentList } from '@/api/app' import { getPreRegistrationList } from '@/api/app'
import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia'
const userStore = useUserStore()
const { userInfo } = storeToRefs(userStore)
const list = ref<any[]>([]) const list = ref<any[]>([])
const loading = ref(false) const paging = shallowRef()
const total = ref(0)
const showTimeFilter = ref(false) const queryList = async (pageNo, pageSize) => {
const showRegionFilter = ref(false) console.log('=== queryList 开始执行 ===', pageNo, pageSize)
const timeFilter = ref('')
const regionFilter = ref('')
const sortBy = ref('createTime')
const sortOrder = ref<'asc' | 'desc'>('desc')
const timeFilterText = computed(() => {
const map: Record<string, string> = {
'': '全部时间',
today: '今天',
week: '本周',
month: '本月',
year: '今年'
}
return map[timeFilter.value] || '全部时间'
})
const regionFilterText = computed(() => {
const map: Record<string, string> = {
'': '全部地区',
north: '北部地区',
south: '南部地区',
east: '东部地区',
west: '西部地区'
}
return map[regionFilter.value] || '全部地区'
})
const sortText = computed(() => {
const map: Record<string, string> = {
createTime: '时间',
highSchoolScore: '成绩',
height: '身高'
}
return map[sortBy.value] || '排序'
})
const timeFilterList = [
{ text: '全部时间' },
{ text: '今天' },
{ text: '本周' },
{ text: '本月' },
{ text: '今年' }
]
const regionFilterList = [
{ text: '全部地区' },
{ text: '北部地区' },
{ text: '南部地区' },
{ text: '东部地区' },
{ text: '西部地区' }
]
const formatIdCard = (idCard: string) => {
if (!idCard) return ''
return idCard.replace(/(\d{6})\d{8}(\d{4})/, '$1********$2')
}
const getStatusClass = (status: number) => {
const map: Record<number, string> = {
0: 'status-pending',
1: 'status-approved',
2: 'status-rejected'
}
return map[status] || 'status-pending'
}
const getStatusTextClass = (status: number) => {
const map: Record<number, string> = {
0: 'text-yellow-500',
1: 'text-green-500',
2: 'text-red-500'
}
return map[status] || 'text-yellow-500'
}
const getStatusText = (status: number) => {
const map: Record<number, string> = {
0: '待审核',
1: '已通过',
2: '已拒绝'
}
return map[status] || '待审核'
}
const handleTimeFilter = (index: number) => {
const valueMap = ['', 'today', 'week', 'month', 'year']
timeFilter.value = valueMap[index] || ''
loadList()
}
const handleRegionFilter = (index: number) => {
const valueMap = ['', 'north', 'south', 'east', 'west']
regionFilter.value = valueMap[index] || ''
loadList()
}
const toggleSort = () => {
if (sortBy.value === 'createTime') {
sortBy.value = 'highSchoolScore'
} else if (sortBy.value === 'highSchoolScore') {
sortBy.value = 'height'
} else {
sortBy.value = 'createTime'
}
loadList()
}
const loadList = async () => {
if (!userInfo.value || !userInfo.value.teacherId) {
return
}
loading.value = true
try { try {
const res = await getRecruitmentList({ const params = {
teacherId: userInfo.value.teacherId, page: pageNo,
timeFilter: timeFilter.value, limit: pageSize,
regionFilter: regionFilter.value, studentStatus: -1
sortBy: sortBy.value, }
sortOrder: sortOrder.value console.log('请求参数:', params)
}) const res = await getPreRegistrationList(params)
console.log('接口返回结果:', res)
if (res.code === 1 && res.data) { if (res && res.lists) {
list.value = res.data.list || [] console.log('获取数据成功, list长度:', res.lists?.length)
total.value = res.data.total || 0 paging.value.complete(res.lists)
} else {
console.log('接口返回异常:', res)
} }
} catch (error) { } catch (error) {
console.error('获取招生列表失败:', error) console.error('获取招生列表失败:', error)
uni.$u.toast('获取数据失败') uni.$u.toast('获取数据失败')
} finally {
loading.value = false
} }
} }
onMounted(() => { const goBack = () => {
loadList() uni.navigateBack()
}) }
onLoad(() => { onLoad(() => {
loadList() queryList(1, 10)
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.my-recruitment { .my-recruitment {
background-color: #f5f5f5; position: relative;
min-height: 100vh; min-height: 100vh;
background-color: transparent;
} }
.filter-bar { .nav-bar {
background: white; position: fixed;
padding: 20rpx; top: 50rpx;
border-radius: 12rpx; left: 0;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05); right: 0;
} z-index: 100;
.filter-item {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8rpx; justify-content: center;
font-size: 28rpx; padding-top: calc(20rpx + env(safe-area-inset-top));
padding: 10rpx 20rpx;
border-radius: 8rpx;
transition: all 0.2s ease;
&:active {
background: #f5f5f5;
}
} }
.list-item { .back-btn {
position: absolute;
left: 0;
padding: 20rpx 30rpx;
cursor: pointer;
}
.back-icon {
font-size: 48rpx;
color: white;
font-weight: bold;
}
.nav-title {
font-size: 36rpx;
color: white;
font-weight: 600;
}
.page-bg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: block;
z-index: 0;
}
.content-wrapper {
position: relative;
z-index: 10;
padding-top: 320rpx;
}
.intro-box {
background-color: white;
border: 2rpx solid white;
border-radius: 20rpx;
padding: 20rpx;
margin-bottom: 150rpx;
text-align: center;
font-size: 26rpx;
color: #0056e9;
line-height: 1.8;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
}
.student-image-wrapper {
display: flex;
justify-content: center;
margin-top: -200rpx;
margin-bottom: -330rpx;
position: relative;
z-index: -1;
}
.student-image {
width: 800rpx;
height: 600rpx;
}
.student-table {
background: white;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05); box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
} }
.avatar { .table-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex;
color: white; background: #ccddfb;
padding: 24rpx 20rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.15);
} }
.info-grid { .th {
padding: 20rpx 0; color: #333;
border-top: 1rpx solid #f0f0f0; font-size: 26rpx;
font-weight: 600;
text-align: center;
}
.table-row {
display: flex;
padding: 24rpx 20rpx;
border-bottom: 1rpx solid #f0f0f0; border-bottom: 1rpx solid #f0f0f0;
} align-items: center;
.info-item {
margin-bottom: 10rpx;
&:last-child { &:last-child {
margin-bottom: 0; border-bottom: none;
}
&:nth-child(even) {
background-color: #fafafa;
} }
} }
.status-bar { .td {
padding-top: 10rpx; font-size: 26rpx;
color: #333;
text-align: center;
} }
.status-dot { /* 列宽设置 */
width: 16rpx; .th-name, .td-name {
height: 16rpx; width: 20%;
border-radius: 50%; font-weight: 500;
&.status-pending {
background-color: #f59e0b;
} }
&.status-approved { .th-gender, .td-gender {
background-color: #10b981; width: 12%;
} }
&.status-rejected { .th-score, .td-score {
background-color: #ef4444; width: 15%;
}
} }
.empty-state { .th-major, .td-major {
background: white; width: 33%;
border-radius: 12rpx;
margin-top: 20rpx;
} }
.text-blue-500 { .th-status, .td-status {
color: #3b82f6; width: 20%;
} }
.text-gray-600 { /* 状态标签样式 */
color: #666; .status-enrolled {
color: #10B981;
font-weight: 500;
} }
.text-gray-400 { .status-pre {
color: #999; color: #F59E0B;
} font-weight: 500;
.text-gray-500 {
color: #888;
}
.text-yellow-500 {
color: #f59e0b;
}
.text-green-500 {
color: #10b981;
}
.text-red-500 {
color: #ef4444;
} }
</style> </style>

View File

@ -342,7 +342,14 @@ const validateForm = () => {
} }
const submit = async () => { const submit = async () => {
if (!validateForm()) return console.log('========== 开始提交 ==========')
console.log('当前表单数据:', JSON.parse(JSON.stringify(formData)))
if (!validateForm()) {
console.log('表单验证失败')
return
}
console.log('表单验证通过')
submitting.value = true submitting.value = true
@ -360,22 +367,31 @@ const submit = async () => {
recruitmentTeacherId: formData.recruitmentTeacherId recruitmentTeacherId: formData.recruitmentTeacherId
} }
console.log('========== 准备提交数据 ==========')
console.log('提交数据:', submitData) console.log('提交数据:', submitData)
console.log('majorId:', submitData.majorId, '类型:', typeof submitData.majorId)
console.log('recruitmentTeacherId:', submitData.recruitmentTeacherId, '类型:', typeof submitData.recruitmentTeacherId)
uni.showLoading({ uni.showLoading({
title: '提交中...', title: '提交中...',
mask: true mask: true
}) })
console.log('========== 开始调用 addStudent ==========')
const res = await addStudent(submitData) const res = await addStudent(submitData)
console.log('========== addStudent 调用完成 ==========')
uni.hideLoading() uni.hideLoading()
console.log('接口返回结果:', res) console.log('接口返回结果:', res)
console.log('返回code:', res?.code)
console.log('返回msg:', res?.msg)
console.log('返回data:', res?.data)
if (res.code === 1) { if (res?.code === 1) {
console.log('提交成功')
uni.showToast({ uni.showToast({
title: '提交成功', title: res?.msg || '提交成功',
icon: 'success', icon: 'success',
duration: 2000 duration: 2000
}) })
@ -384,21 +400,27 @@ const submit = async () => {
router.navigateTo('/pages/submit_success/submit_success') router.navigateTo('/pages/submit_success/submit_success')
}, 1500) }, 1500)
} else { } else {
console.log('提交失败:', res?.msg || '未知错误')
uni.showToast({ uni.showToast({
title: res.msg || '提交失败', title: res?.msg || '提交失败',
icon: 'none', icon: 'none',
duration: 2000 duration: 2000
}) })
} }
} catch (error) { } catch (error: any) {
uni.hideLoading() uni.hideLoading()
console.error('提交失败:', error) console.error('========== 提交异常 ==========')
console.error('错误对象:', error)
console.error('错误类型:', typeof error)
console.error('错误消息:', error?.message || error)
console.error('错误堆栈:', error?.stack)
uni.showToast({ uni.showToast({
title: '网络错误,请重试', title: '网络错误,请重试',
icon: 'none', icon: 'none',
duration: 2000 duration: 2000
}) })
} finally { } finally {
console.log('========== 提交结束 ==========')
submitting.value = false submitting.value = false
} }
} }

View File

@ -1,7 +1,12 @@
<template> <template>
<view class="user"> <view class="user">
<!-- 顶部渐变背景 --> <!-- 顶部横幅背景 -->
<view class="header-bg"> <view class="header-banner">
<image
src="/static/yubaoming/top_banner.png"
mode="widthFix"
class="banner-image"
/>
<view class="header-content"> <view class="header-content">
<view class="user-info" v-if="isLogin && userInfo"> <view class="user-info" v-if="isLogin && userInfo">
<view class="avatar"> <view class="avatar">
@ -30,7 +35,7 @@
<view class="stats-card" v-if="isLogin"> <view class="stats-card" v-if="isLogin">
<view class="card-header"> <view class="card-header">
<view class="card-title"> <view class="card-title">
<view class="title-icon">📊</view> <view class="title-icon"></view>
<text>招生数据</text> <text>招生数据</text>
</view> </view>
<view class="view-more" @click="goToRecruitment"> <view class="view-more" @click="goToRecruitment">
@ -65,7 +70,7 @@
<view class="chart-card" v-if="isLogin"> <view class="chart-card" v-if="isLogin">
<view class="card-header"> <view class="card-header">
<view class="card-title"> <view class="card-title">
<view class="title-icon">📈</view> <view class="title-icon"></view>
<text>招生目标</text> <text>招生目标</text>
</view> </view>
</view> </view>
@ -87,32 +92,40 @@
<!-- 快捷功能 --> <!-- 快捷功能 -->
<view class="quick-actions" v-if="isLogin"> <view class="quick-actions" v-if="isLogin">
<view class="section-title"> <view class="section-title">
<view class="title-icon">🚀</view> <view class="title-icon"></view>
<text>快捷功能</text> <text>快捷功能</text>
</view> </view>
<view class="action-grid"> <view class="action-grid">
<view class="action-item" @click="goToRecruitment"> <view class="action-item" @click="goToRecruitment">
<view class="action-icon blue"> <image
<u-icon name="list" size="36" color="#FFFFFF"></u-icon> src="/static/yubaoming/my_recruitment_menu.png"
</view> mode="aspectFit"
class="action-img"
/>
<text class="action-text">我的招生</text> <text class="action-text">我的招生</text>
</view> </view>
<view class="action-item" @click="openQrcodeModal"> <view class="action-item" @click="openQrcodeModal">
<view class="action-icon green"> <image
<u-icon name="scan" size="36" color="#FFFFFF"></u-icon> src="/static/yubaoming/recruitment_qrcode.png"
</view> mode="aspectFit"
class="action-img"
/>
<text class="action-text">招生二维码</text> <text class="action-text">招生二维码</text>
</view> </view>
<view class="action-item" @click="goTo('/pages/user_data/user_data')"> <view class="action-item" @click="goTo('/pages/user_data/user_data')">
<view class="action-icon purple"> <image
<u-icon name="account" size="36" color="#FFFFFF"></u-icon> src="/static/yubaoming/user_profile.png"
</view> mode="aspectFit"
class="action-img"
/>
<text class="action-text">个人资料</text> <text class="action-text">个人资料</text>
</view> </view>
<view class="action-item" @click="goTo('/pages/change_password/change_password')"> <view class="action-item" @click="goTo('/pages/change_password/change_password')">
<view class="action-icon orange"> <image
<u-icon name="lock" size="36" color="#FFFFFF"></u-icon> src="/static/yubaoming/change_password.png"
</view> mode="aspectFit"
class="action-img"
/>
<text class="action-text">修改密码</text> <text class="action-text">修改密码</text>
</view> </view>
</view> </view>
@ -123,18 +136,22 @@
<view class="action-list"> <view class="action-list">
<view class="action-list-item" @click="switchAccount"> <view class="action-list-item" @click="switchAccount">
<view class="item-left"> <view class="item-left">
<view class="item-icon blue"> <image
<u-icon name="reload" size="32" color="#FFFFFF"></u-icon> src="/static/yubaoming/switch_account.png"
</view> mode="aspectFit"
class="item-img"
/>
<text class="item-text">切换账号</text> <text class="item-text">切换账号</text>
</view> </view>
<u-icon name="arrow-right" size="28" color="#9CA3AF"></u-icon> <u-icon name="arrow-right" size="28" color="#9CA3AF"></u-icon>
</view> </view>
<view class="action-list-item" @click="logout"> <view class="action-list-item" @click="logout">
<view class="item-left"> <view class="item-left">
<view class="item-icon red"> <image
<u-icon name="close-circle" size="32" color="#FFFFFF"></u-icon> src="/static/yubaoming/logout.png"
</view> mode="aspectFit"
class="item-img"
/>
<text class="item-text">退出登录</text> <text class="item-text">退出登录</text>
</view> </view>
<u-icon name="arrow-right" size="28" color="#9CA3AF"></u-icon> <u-icon name="arrow-right" size="28" color="#9CA3AF"></u-icon>
@ -189,7 +206,7 @@ import { useUserStore } from '@/stores/user'
import { onShow } from '@dcloudio/uni-app' import { onShow } from '@dcloudio/uni-app'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { computed, reactive, ref } from 'vue' import { computed, reactive, ref } from 'vue'
import { getEnrollmentStatistical, getTeacherQrcodeImage } from '@/api/app' import { getEnrollmentStatistical, getTeacherQrcodeImage, getTeacherInfo } from '@/api/app'
import { TOKEN_KEY } from '@/enums/constantEnums' import { TOKEN_KEY } from '@/enums/constantEnums'
import cache from '@/utils/cache' import cache from '@/utils/cache'
@ -220,18 +237,36 @@ const progressPercent = computed(() => {
}) })
const getQrcode = async () => { const getQrcode = async () => {
console.log('=== getQrcode 开始执行 ===')
console.log('userInfo:', userInfo.value)
console.log('teacherId:', userInfo.value?.teacherId)
if (userInfo.value?.teacherId) { if (userInfo.value?.teacherId) {
try { try {
console.log('正在调用 getTeacherQrcodeImage, teacherId:', userInfo.value.teacherId)
const response = await getTeacherQrcodeImage(userInfo.value.teacherId) const response = await getTeacherQrcodeImage(userInfo.value.teacherId)
console.log('二维码接口返回:', response)
console.log('statusCode:', response.statusCode)
console.log('data类型:', typeof response.data)
console.log('data长度:', response.data?.byteLength || response.data?.length)
if (response.statusCode === 200) { if (response.statusCode === 200) {
const arrayBuffer = response.data const arrayBuffer = response.data
console.log('arrayBuffer:', arrayBuffer)
const base64 = uni.arrayBufferToBase64(arrayBuffer) const base64 = uni.arrayBufferToBase64(arrayBuffer)
console.log('base64长度:', base64.length)
qrcodeUrl.value = `data:image/png;base64,${base64}` qrcodeUrl.value = `data:image/png;base64,${base64}`
console.log('qrcodeUrl已设置:', qrcodeUrl.value.substring(0, 50) + '...')
} else {
console.log('接口返回非200状态码:', response.statusCode)
} }
} catch (error) { } catch (error) {
console.error('获取二维码失败:', error) console.error('获取二维码失败:', error)
} }
} else {
console.log('没有 teacherId跳过获取二维码')
} }
console.log('=== getQrcode 执行结束 ===')
} }
const getStats = async () => { const getStats = async () => {
@ -388,7 +423,21 @@ onShow(async () => {
console.log('getUser 完成后用户信息:', userInfo.value) console.log('getUser 完成后用户信息:', userInfo.value)
if (isLogin.value && userInfo.value) { if (isLogin.value && userInfo.value) {
console.log('条件满足,开始调用 getQrcode 和 getStats') console.log('条件满足,开始获取教师信息')
//
try {
const teacherRes = await getTeacherInfo({ id: userInfo.value.id })
console.log('getTeacherInfo 返回:', teacherRes)
if (teacherRes && teacherRes.id) {
// userInfo
userInfo.value.teacherId = teacherRes.id
console.log('设置 teacherId:', teacherRes.id)
}
} catch (error) {
console.error('获取教师信息失败:', error)
}
console.log('开始调用 getQrcode 和 getStats')
getQrcode() getQrcode()
getStats() getStats()
} else { } else {
@ -407,14 +456,23 @@ onShow(async () => {
padding-bottom: calc(20rpx + env(safe-area-inset-bottom)); padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
} }
/* 顶部渐变背景 */ /* 顶部横幅背景 */
.header-bg { .header-banner {
background: linear-gradient(135deg, #3B82F6 0%, #2563EB 100%); position: relative;
padding: 60rpx 30rpx 100rpx; width: 100%;
border-radius: 0 0 40rpx 40rpx;
.banner-image {
width: 100%;
display: block;
}
} }
.header-content { .header-content {
position: absolute;
top: 0;
left: 0;
right: 0;
padding: 170rpx 30rpx 100rpx;
.user-info { .user-info {
display: flex; display: flex;
align-items: center; align-items: center;
@ -501,7 +559,10 @@ onShow(async () => {
.title-icon { .title-icon {
margin-right: 12rpx; margin-right: 12rpx;
font-size: 36rpx; width: 10rpx;
height: 32rpx;
background: linear-gradient(135deg, #3B82F6 0%, #2563EB 100%);
border-radius: 3rpx;
} }
} }
@ -582,7 +643,10 @@ onShow(async () => {
.title-icon { .title-icon {
margin-right: 12rpx; margin-right: 12rpx;
font-size: 36rpx; width: 10rpx;
height: 32rpx;
background: linear-gradient(135deg, #3B82F6 0%, #2563EB 100%);
border-radius: 3rpx;
} }
} }
} }
@ -650,7 +714,10 @@ onShow(async () => {
.title-icon { .title-icon {
margin-right: 12rpx; margin-right: 12rpx;
font-size: 36rpx; width: 10rpx;
height: 32rpx;
background: linear-gradient(135deg, #3B82F6 0%, #2563EB 100%);
border-radius: 3rpx;
} }
} }
@ -667,28 +734,10 @@ onShow(async () => {
opacity: 0.7; opacity: 0.7;
} }
.action-icon { .action-img {
width: 100rpx; width: 100rpx;
height: 100rpx; height: 100rpx;
border-radius: 24rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 16rpx; 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 { .action-text {
@ -727,21 +776,10 @@ onShow(async () => {
display: flex; display: flex;
align-items: center; align-items: center;
.item-icon { .item-img {
width: 64rpx; width: 64rpx;
height: 64rpx; height: 64rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx; margin-right: 20rpx;
&.blue {
background: linear-gradient(135deg, #3B82F6 0%, #2563EB 100%);
}
&.red {
background: linear-gradient(135deg, #EF4444 0%, #DC2626 100%);
}
} }
.item-text { .item-text {

View File

@ -17,12 +17,22 @@ const requestHooks: RequestHooks = {
requestInterceptorsHook(options, config) { requestInterceptorsHook(options, config) {
const { urlPrefix, baseUrl, withToken, isAuth } = config const { urlPrefix, baseUrl, withToken, isAuth } = config
options.header = options.header ?? {} options.header = options.header ?? {}
console.log('【Request拦截器】原始请求URL:', options.url)
console.log('【Request拦截器】urlPrefix:', urlPrefix)
console.log('【Request拦截器】baseUrl:', baseUrl)
if (urlPrefix) { if (urlPrefix) {
options.url = `${urlPrefix}${options.url}` options.url = `${urlPrefix}${options.url}`
} }
if (baseUrl) { if (baseUrl) {
options.url = `${baseUrl}${options.url}` options.url = `${baseUrl}${options.url}`
} }
console.log('【Request拦截器】最终请求URL:', options.url)
console.log('【Request拦截器】请求方法:', options.method)
console.log('【Request拦截器】请求数据:', options.data)
const token = getToken() const token = getToken()
if (withToken && token) { if (withToken && token) {
@ -53,14 +63,23 @@ const requestHooks: RequestHooks = {
async responseInterceptorsHook(response, config) { async responseInterceptorsHook(response, config) {
const { isTransformResponse, isReturnDefaultResponse, isAuth } = config const { isTransformResponse, isReturnDefaultResponse, isAuth } = config
console.log('【Response拦截器】原始响应:', response)
console.log('【Response拦截器】响应状态码:', response.statusCode)
console.log('【Response拦截器】响应数据:', response.data)
console.log('【Response拦截器】isTransformResponse:', isTransformResponse)
console.log('【Response拦截器】isReturnDefaultResponse:', isReturnDefaultResponse)
if (isReturnDefaultResponse) { if (isReturnDefaultResponse) {
console.log('【Response拦截器】返回原始响应')
return response return response
} }
if (!isTransformResponse) { if (!isTransformResponse) {
console.log('【Response拦截器】返回 response.data')
return response.data return response.data
} }
const { logout } = useUserStore() const { logout } = useUserStore()
const { code, data, msg, show } = response.data as any const { code, data, msg, show } = response.data as any
console.log('【Response拦截器】解析后的 code:', code, 'msg:', msg)
switch (code) { switch (code) {
case RequestCodeEnum.SUCCESS: case RequestCodeEnum.SUCCESS:
msg && show && uni.$u.toast(msg) msg && show && uni.$u.toast(msg)

View File

@ -66,12 +66,7 @@ module.exports = {
'5xl': '44rpx' '5xl': '44rpx'
}, },
fontFamily: { fontFamily: {
sans: [ sans: ['Source Han Sans CN', 'Helvetica Neue', 'Arial', 'sans-serif']
'Source Han Sans CN',
'Helvetica Neue',
'Arial',
'sans-serif'
]
} }
}, },
plugins: [], plugins: [],

View File

@ -32,6 +32,18 @@ export default defineConfig({
} }
}, },
server: { server: {
port: 8991 port: 8991,
proxy: {
'/frontapi': {
target: 'http://192.168.123.91:8084',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/frontapi/, '/frontapi')
},
'/adminapi': {
target: 'http://192.168.123.91:8084',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/adminapi/, '/adminapi')
}
}
} }
}) })