b339acb1 by lttnew

级位审核

1 parent d923ac96
<template>
<view>
<view class="pd30">
<uni-steps :options="list1" :active="active" />
<view class="hasfixedbottom">
<view class="add-apply-page">
<!-- 顶部步骤条 -->
<view class="steps-bar">
<view class="step-item" :class="{ active: active >= 0, current: active == 0 }">
<view class="step-circle">1</view>
<view class="step-text">考级基本信息</view>
</view>
<view class="step-line" :class="{ active: active >= 1 }"></view>
<view class="step-item" :class="{ active: active >= 1, current: active == 1 }">
<view class="step-circle">2</view>
<view class="step-text">添加考生</view>
</view>
</view>
<view class="page-content">
<!-- 步骤1:考级基本信息 -->
<view class="wBox" v-if="active == 0">
<uni-forms ref="baseForm" :modelValue="form" label-width="100">
<uni-forms-item label="考试名称">
......@@ -24,182 +36,148 @@
<uni-forms-item label="考级地点" required>
<uni-easyinput v-model="form.examLocation" placeholder="考级地点" />
</uni-forms-item>
<uni-forms-item @updateData="updateData" :label="`考官${ec}`" v-for="ec in examinerForChoose"
:key="ec">
<uni-forms-item :label="`考官${ec}`" v-for="ec in examinerForChoose" :key="ec">
<view class="maskbox">
<view class="mask" @click="selectFN(ec)"></view>
<uni-easyinput v-model="form[`examiner_${ec}`]" clearable placeholder="点击选择考官" />
</view>
</uni-forms-item>
</uni-forms>
</view>
<view class="wBox" v-if="active == 1">
<!-- 添加考生 -->
<view class="flexbox">
<button class="btn-red-kx mini w100" @click="chooseOnline">
<uni-icons type="personadd" size="16" color="#AD181F"></uni-icons>
在线选择</button>
<!-- <button class="btn-red-kx mini w45" @click="handleUpdate">
<uni-icons type="upload" size="16" color="#AD181F"></uni-icons>
上传成绩单</button> -->
</view>
<view class="vipData mt30" style="flex-wrap:wrap">
<view class="w25">合计:<text>{{tablePersonInfo.total}}</text></view>
<view class="w25" v-for="l in tablePersonInfo.levelArr" :key="l.level">
{{ szToHz(l.level) }}级:<text>{{l.num}}</text>
<!-- 步骤2:添加考生 -->
<view class="step2-content" v-if="active == 1">
<!-- 考级信息卡片 -->
<view class="exam-info-card">
<view class="card-header">
<text class="card-title">考级信息</text>
</view>
<view class="info-grid">
<view class="info-item">
<text class="info-label">考试名称</text>
<text class="info-value">{{form.name || '-'}}</text>
</view>
<view class="userlist">
<view class="item" v-for="(n,index) in infoList" :key="index"
style="background-color: #fffafa;">
<view class="w100">
<view class="del" @click="handleDelete(n)">删除</view>
<view style="display: flex;">
<!--
<view class="photobox">
<image class="photo" v-if="n.photo" :src="n.photo" mode='aspectFill'></image>
<view class="colorful" v-else>{{n.realName?.slice(0,1)}}</view>
</view> -->
<view>
<view class="name">{{n.realName}} <text>{{n.perCode}}</text></view>
<view class="date">{{n.idcTypeStr}}{{n.idcCode}}</view>
<view class="info-item">
<text class="info-label">申请单位</text>
<text class="info-value">{{form.memberName || '-'}}</text>
</view>
<view class="info-item">
<text class="info-label">考试地点</text>
<text class="info-value">{{form.examLocation || '-'}}</text>
</view>
<view class="flexbox mt30">
<view>
原有级别
<text style="padding: 15rpx 0;">{{ szToHz(n.levelOld) }}</text>
<view class="info-item">
<text class="info-label">考试时间</text>
<text class="info-value">{{formatDateTime(form.startTime)}} - {{formatDateTime(form.endTime)}}</text>
</view>
<view style="width: 40%;">
考试级别
<!-- <text style="padding: 15rpx 0;">{{ szToHz(n.levelNew) }}</text> -->
<view @click="changeLevelfather(n)">
<uni-data-select v-model="n.levelNew" :localdata="levelArr"
@change="changeLevel"></uni-data-select>
</view>
</view>
<view style="width: 30%;">
是否通过
<view>
<uni-data-select :clear="false" v-model="n.isPass"
:localdata="range"></uni-data-select>
</view>
<!-- 操作栏(红框顶部统计+添加按钮) -->
<button class="btn-add-student" @click="goChooseStudent">
<uni-icons type="plus" size="16" color="#fff"></uni-icons>
添加考生
</button>
<view class="action-bar">
<view class="stat-info">
<text class="stat-total">{{tablePersonInfo.total || 0}}</text>
<view class="level-tags">
<view class="level-tag" v-for="l in tablePersonInfo.levelArr" :key="l.level">
{{ szToHz(l.level) }}级:{{l.num}}
</view>
</view>
</view>
</view>
<view class="nodata" v-if="infoList.length==0">
<image mode="aspectFit" :src="config.baseUrl_api + '/fs/static/nodata.png'"></image>
<text>待添加考生</text>
<!-- 考生列表(红框主体) -->
<view class="student-list">
<view class="student-card" v-for="(n,index) in infoList" :key="index">
<!-- 左侧:头像+考生信息 -->
<view class="card-left">
<view class="avatar">
<image v-if="n.photo" :src="n.photo" mode="aspectFill" />
<image v-else :src="config.baseUrl_api + '/fs/static/tx@2x.png'"
mode="aspectFill">
</image>
<!-- <text v-else class="avatar-text">{{(n.realName || '').slice(0,1)}}</text> -->
</view>
<view class="student-info">
<view class="student-name">{{n.realName}} <text class="per-code">{{n.perCode}}</text></view>
<view class="student-idcard">{{n.idcTypeStr}}{{n.idcCode}}</view>
</view>
</view>
<!-- 右侧:原级别/考试级别/是否通过 -->
<view class="card-right">
<view class="level-item">
<text class="level-label">原级别</text>
<text class="level-value">{{ szToHz(n.levelOld) }}</text>
</view>
<view class="level-item">
<text class="level-label">考试级别</text>
<view class="select-wrapper" @click="changeLevelfather(n)">
<uni-data-select v-model="n.levelNew" :localdata="levelArr" @change="changeLevel" :clear="false" />
</view>
<view class="fixedBottom" v-if="active == 0">
<button class="btn-red-kx" style="width: 40%;" @click="submitForm(0)">保存</button>
<button class="btn-red" style="width: 40%;" @click="submitForm(1)">下一步</button>
</view>
<view class="fixedBottom" v-if="active == 1">
<button class="btn-red-kx" style="width: 25%;" @click="active=0">上一步</button>
<button class="btn-red-kx" style="width: 25%;" @click="submitForm2(0)">保存</button>
<button class="btn-red" style="width: 30%;" @click="submitForm2(1)">提交审核</button>
<view class="level-item">
<text class="level-label">是否通过</text>
<view class="select-wrapper">
<uni-data-select v-model="n.isPass" :localdata="range" :clear="false" />
</view>
<uni-popup ref="choseStudent" type="bottom" background-color="#fff" animation>
<view class="popBody">
<view class="searchbar">
<uni-easyinput placeholderStyle="font-size:30rpx" :input-border="false" prefixIcon="search"
v-model="studentQuery.name" placeholder="搜索姓名" @blur="chooseOnline" @clear="chooseOnline">
</uni-easyinput>
</view>
<view class="userlist" style="height:80vh;overflow: auto;">
<view class="item" v-for=" (n,index) in studentList" :key="index">
<view @click="checkThis(n)">
<image class="icon" v-if="n.checked"
:src="config.baseUrl_api+'/fs/static/member/dx_dwn.png'" />
<image class="icon" v-else :src="config.baseUrl_api+'/fs/static/member/dx.png'" />
</view>
<view class="photobox">
<image class="photo" v-if="n.photo" :src="n.photo" mode='aspectFill'>
</image>
<view class="colorful" v-else>{{n.name.slice(0,1)}}</view>
<!-- 删除按钮 -->
<view class="delete-btn" @click="handleDelete(n)">
<uni-icons type="trash" size="18" color="#dd524d"></uni-icons>
</view>
<view>
<view class="name">{{n.name}} <text>{{n.perCode}}</text></view>
<view class="date">到期时间:{{n.validityDate}}</view>
<view class="date" style="color: #1561CB;" v-if="n.levelJi&&n.levelJi!=0">
级位:{{szToHz(n.levelJi)}}</view>
<view class="date" v-else>级位:十级</view>
</view>
<!-- 空状态 -->
<view class="empty" v-if="infoList.length==0">
<image class="empty-img" mode="aspectFit" :src="config.baseUrl_api + '/fs/static/nodata.png'" />
<text class="empty-text">暂无考生信息</text>
</view>
<view class="nodata" v-if="studentList.length==0">
<image mode="aspectFit" :src="config.baseUrl_api + '/fs/static/nodata.png'"></image>
<text>无可参加考试会员</text>
</view>
</view>
<button class="btn-red-kx" v-if="studentList.length!=0" @click="submitStudents">确定</button>
</view>
</uni-popup>
<uni-popup ref="UpPop" type="bottom" background-color="#fff" animation>
<view class="popBody">
<view class="h3 text-center">上传成绩单</view>
<text class="must">*请上传大小不超过 10MB 格式为 png/jpg/jpeg/pdf/zip 的文件</text>
<uni-file-picker v-model="transcript" class="mtb30" file-mediatype="all"
file-extname="png,jpg,jpeg,pdf,zip" @select="selectFile" @progress="fileProgress"
@delete="delSupplementFile"></uni-file-picker>
<button class="btn-red" @click="uploadSure">确定</button>
<!-- 底部按钮 -->
<view class="fixedBottom" v-if="active == 0">
<button class="btn-red-kx" style="width: 40%;" @click="submitForm(0)">保存</button>
<button class="btn-red" style="width: 40%;" @click="submitForm(1)">下一步</button>
</view>
<view class="fixedBottom" v-if="active == 1">
<button class="btn-red-kx" style="width: 25%;" @click="prev">上一步</button>
<button class="btn-red-kx" style="width: 25%;" @click="submitForm2(0)">保存</button>
<button class="btn-red" style="width: 30%;" @click="submitForm2(1)">提交审核</button>
</view>
</uni-popup>
</view>
</template>
<script setup>
import {
ref
} from 'vue';
import { ref } from 'vue';
import * as api from '@/common/api.js';
import {
onLoad,
onShow
} from '@dcloudio/uni-app';
import { onLoad, onShow } from '@dcloudio/uni-app';
import config from '@/config.js'
import dayjs from 'dayjs'
import _ from 'underscore'
const app = getApp();
const memberInfo = app.globalData.memberInfo
const form = ref({
type: 1
type: '1'
});
const dataList = ref([]);
const examinerForChoose = ['A', 'B', 'C']
const examinerArr = []
let examinerArr = []
const active = ref(0)
const total = ref(0)
const list1 = ref([{
title: '考级基本信息'
}, {
title: '添加考生'
}])
const choseStudent = ref(null)
const UpPop = ref(null)
const studentList = ref([])
const infoList = ref([])
const ids = ref([])
const tablePersonInfo = ref({})
const transcript = ref([])
const studentQuery = ref({
name: ''
})
const transcript = ref([]) // 补全缺失变量
const levelArr = ref([{
value: '10',
text: '十级'
......@@ -231,6 +209,7 @@
value: '1',
text: '一级'
}])
const range = ref([{
value: '1',
text: '是'
......@@ -238,41 +217,41 @@
value: '0',
text: '否'
}])
let examId
onLoad(option => {
console.log(option)
if (app.globalData.isLogin) {
form.value.memberName = app.globalData.memberInfo.name
form.value.applyTime = dayjs().format('YYYY-MM-DD')
_.each(examinerForChoose, ec => {
form.value[`examiner_${ec}`] = null
})
if (option.examId) {
examId = option.examId
getDetail()
}
initData(option)
} else {
app.firstLoadCallback = () => {
initData(option)
};
}
});
function initData(option) {
form.value.memberName = app.globalData.memberInfo.name
form.value.applyTime = dayjs().format('YYYY-MM-DD')
_.each(examinerForChoose, ec => {
form.value[`examiner_${ec}`] = null
})
if (option.examId) {
examId = option.examId
// 如果是编辑模式,从URL参数获取active
if (option.step == '2') {
active.value = 1
getDetail()
getChosedStudentList()
} else {
getDetail()
}
};
}
});
onShow(option => {
// if(!!option){
// console.log(option)
// }
}
onShow(() => {
uni.$on('chosen', updateData)
})
......@@ -282,30 +261,28 @@
}
function getDetail() {
api.getLevelApplyInfo(examId).then(res => {
api.getLevelApplyInfo(examId || form.value.examId).then(res => {
const data = res.data
if (data.examiner) {
_.each(data.examiner.split(','), (id) => {
examinerArr.push({
perId: id
})
_.each(data.examiner.split(','), (id, i) => {
examinerArr[i] = { perId: id }
})
_.each(data.examinerNames.split(','), (name, i) => {
data[`examiner_${examinerForChoose[i]}`] = name
if (examinerArr[i]) {
examinerArr[i].name = name
}
})
}
form.value = data
})
}
function selectFN(ec) {
const chosen = []
const type = form.value.type
_.each(examinerForChoose, ec => {
const key = `examiner_${ec}`
_.each(examinerForChoose, ecKey => {
const key = `examiner_${ecKey}`
if (form.value[key]) {
const examiner = _.find(examinerArr, (e) => {
return e.name == form.value[key]
......@@ -316,8 +293,7 @@
}
})
const arr = encodeURIComponent(JSON.stringify(chosen))
console.log(ec, chosen, type)
let path = `/level/chooseExaminer?type=${type}&chosen=${arr}&ec=${ec}`
let path = `/level/chooseExaminer?type=${form.value.type}&chosen=${arr}&ec=${ec}`
uni.navigateTo({
url: path
});
......@@ -347,33 +323,45 @@
form.value.examiner = null
form.value.examinerNames = null
}
// draftFlag: 0-保存 1-保存并下一步
form.value.draftFlag = flag === 0 ? '1' : '0'
if (flag === 0) {
// 仅保存
save()
} else {
// 保存并下一步 - 需校验
if (!form.value.applyTime) {
uni.showToast({ title: '请选择申请日期', icon: 'none' })
return
}
if (!form.value.startTime) {
uni.showToast({ title: '请选择考试开始时间', icon: 'none' })
return
}
if (!form.value.endTime) {
uni.showToast({ title: '请选择考试结束时间', icon: 'none' })
return
}
if (!form.value.examLocation) {
uni.showToast({ title: '请输入考级地点', icon: 'none' })
return
}
if (dayjs(form.value.startTime).valueOf() < dayjs(form.value.applyTime).valueOf()) {
uni.showToast({
title: `考试开始时间应大于申请日期`,
icon: 'none'
})
uni.showToast({ title: '考试开始时间应大于申请日期', icon: 'none' })
return
}
if (dayjs(form.value.endTime).valueOf() <= dayjs(form.value.startTime).valueOf()) {
uni.showToast({
title: `考试结束时间应大于考试开始时间`,
icon: 'none'
})
uni.showToast({ title: '考试结束时间应大于考试开始时间', icon: 'none' })
return
}
if (examinerIds.length % 2 === 0) {
uni.showToast({
title: `录入的考官人数必须为单数`,
icon: 'none'
})
uni.showToast({ title: '录入的考官人数必须为单数', icon: 'none' })
return
}
save().then(() => {
// form.value.examId 下一步
active.value = 1
getChosedStudentList()
if (form.value.examId) {
......@@ -390,77 +378,31 @@
function save() {
if (form.value.examId) {
return api.updateLevelInfo(form.value).then(() => {
uni.showToast({
title: `保存成功`,
icon: 'none'
})
uni.showToast({ title: '保存成功', icon: 'none' })
})
} else {
return api.addLevelInfo(form.value).then((res) => {
form.value.examId = res.data.examId
form.value.name = res.data.name
uni.showToast({
title: `保存成功`,
icon: 'none'
})
uni.showToast({ title: '保存成功', icon: 'none' })
})
}
}
function chooseOnline() {
uni.showLoading({
title: '加载中',
icon: 'none'
})
var obj = {
memId: memberInfo.memId,
examId: form.value.examId,
examType: form.value.type,
name: studentQuery.value.name
}
api.chooseStudentsList(obj).then(response => {
studentList.value = response.rows
for (var s of studentList.value) {
s.checked = false
if (s.photo && s.photo.indexOf('http') == -1) {
s.photo = config.baseUrl_api + s.photo
function prev() {
active.value = 0
}
}
uni.hideLoading()
choseStudent.value.open()
function goChooseStudent() {
uni.navigateTo({
url: `/level/chooseStudent?examId=${form.value.examId}&memId=${memberInfo.memId}&examType=${form.value.type}`
})
}
function checkThis(item) {
if (item.checked) {
item.checked = false
} else {
item.checked = true
}
}
function submitStudents() {
ids.value = []
for (var s of studentList.value) {
if (s.checked) {
ids.value.push(s.perId)
}
}
if (ids.value.length == 0) {
uni.showToast({
title: '请选择考生',
icon: 'none'
})
return
}
api.batchChoose({
examId: form.value.examId,
perIds: ids.value
}).then(() => {
getChosedStudentList()
choseStudent.value.close()
})
// 格式化日期时间
function formatDateTime(dateStr) {
if (!dateStr) return '-'
return dateStr.substring(0, 10)
}
function getChosedStudentList() {
......@@ -491,21 +433,20 @@
})
infoList.value = res.rows
}).then(getTablePersonInfo)
}
function getTablePersonInfo() {
const total = infoList.value.length
const levelArr = []
const levelArrData = []
_.each(infoList.value, (d) => {
const temp = _.find(levelArr, (l) => {
const temp = _.find(levelArrData, (l) => {
return l.level == d.levelNew
})
if (temp) {
temp.num++
} else {
levelArr.push({
levelArrData.push({
level: d.levelNew,
num: 1
})
......@@ -514,7 +455,7 @@
tablePersonInfo.value = {
total: total,
levelArr: _.sortBy(levelArr, (l) => {
levelArr: _.sortBy(levelArrData, (l) => {
return l.level
})
}
......@@ -540,13 +481,9 @@
})
}
function changeLevel(e) {
if (e == nowRow.levelOld) {
uni.showToast({
title: `考试级别重复,请重新选择!`,
icon: 'none'
})
uni.showToast({ title: '考试级别重复,请重新选择!', icon: 'none' })
nowRow.levelNew = nowRow.levelRecommend
return
}
......@@ -557,7 +494,7 @@
success: function(res) {
if (res.confirm) {
getTablePersonInfo()
}else{
} else {
nowRow.levelNew = nowRow.levelRecommend
}
},
......@@ -569,71 +506,41 @@
}
function submitForm2(flag) {
//循环infoList.value 如果item.levelNew == item.levelOld 提示错误
// 循环校验考试级别
for (var item of infoList.value) {
if (item.levelNew == item.levelOld) {
uni.showToast({
title: `${item.realName}考试级别重复,请重新选择!`,
icon: 'none'
})
uni.showToast({ title: `${item.realName}考试级别重复,请重新选择!`, icon: 'none' })
return
}
if (!item.levelNew) {
uni.showToast({
title: `${item.realName}请选择考试级别!`,
icon: 'none'
})
uni.showToast({ title: `${item.realName}请选择考试级别!`, icon: 'none' })
return
}
}
if (flag === 1) {
if (infoList.value.length == 0) {
uni.showToast({
title: '请选择考生',
icon: 'none'
})
uni.showToast({ title: '请选择考生', icon: 'none' })
return
}
// if (!form.value.transcript) {
// uni.showToast({
// title: '请上传成绩单',
// icon: 'none'
// })
// return
// }
uni.showModal({
title: '提示',
content: `请确认人员照片是否已更新?`,
success: function(res) {
if (res.confirm) {
console.log('用户点击确定');
saveStep2(flag).then(Response => {
if (flag === 1) {
uni.showToast({
title: `提交成功`
})
saveStep2(flag).then(() => {
uni.showToast({ title: '提交成功', icon: 'none' })
uni.navigateTo({
url: `/level/paymentDetail?examId=${form.value.examId}`
})
} else {
uni.showToast({
title: `操作成功`
})
}
})
} else if (res.cancel) {
console.log('用户点击取消');
}
}
})
} else {
saveStep2(flag).then(res => {
uni.showToast({
title: `操作成功`
})
saveStep2(flag).then(() => {
uni.showToast({ title: '操作成功', icon: 'none' })
})
}
}
......@@ -643,7 +550,6 @@
return {
id: d.id,
levelNew: d.levelNew,
// score: d.score,
isPass: d.isPass
}
})
......@@ -655,88 +561,332 @@
})
}
function handleUpdate() {
UpPop.value.open()
function handleDelete(row) {
uni.showModal({
title: '提示',
content: `确定删除${row.realName}?`,
success: function(res) {
if (res.confirm) {
api.dellevelPerson(row.id).then(() => {
uni.showToast({ title: '操作成功', icon: 'none' })
getChosedStudentList()
})
}
}
})
}
</script>
<style lang="scss" scoped>
.add-apply-page {
min-height: 100vh;
background: #f5f5f5;
padding-bottom: 120rpx;
}
/* 顶部步骤条 */
.steps-bar {
display: flex;
align-items: center;
justify-content: center;
padding: 15rpx 60rpx;
background: #fff;
margin-top: 20rpx;
.step-item {
display: flex;
align-items: center;
.step-circle {
width: 48rpx;
height: 48rpx;
border-radius: 50%;
background: #fff;
border: 2rpx solid #666;
color: #666;
font-size: 24rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 10rpx;
transition: all 0.3s;
}
.step-text {
font-size: 24rpx;
color: #666;
transition: all 0.3s;
}
&.active .step-circle {
background: #fff;
color: #AD181F;
border-color: #AD181F;
}
let selectFileValue = {}
function selectFile(e) {
let file = e.tempFiles[0]
if (!file) {
return
&.active .step-text {
color: #AD181F;
font-weight: 600;
}
for (const n in e.tempFiles) {
api.uploadFileList(e.tempFilePaths[n]).then(data => {
selectFileValue = {
url: data,
name: e.tempFiles[n].name,
extname: e.tempFiles[n].extname
&.current .step-circle {
background: #AD181F;
color: #fff;
box-shadow: 0 4rpx 12rpx rgba(173, 24, 31, 0.3);
}
transcript.value.push(selectFileValue)
});
&.current .step-text {
color: #AD181F;
font-weight: 600;
}
}
.step-line {
width: 120rpx;
height: 4rpx;
background: #e0e0e0;
margin: 0 20rpx;
transition: all 0.3s;
&.active {
background: #AD181F;
}
}
}
function fileProgress(e) {
console.log('progress:' + e)
/* 步骤2样式 */
.step2-content {
padding: 0 20rpx;
margin-top: 20rpx;
}
function delSupplementFile(e) {
transcript.value = _.remove(transcript.value, function(n) {
return n.name != e.tempFile.name;
});
/* 考级信息卡片 */
.exam-info-card {
background: #fff;
border-radius: 16rpx;
margin-bottom: 20rpx;
overflow: hidden;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
.card-header {
padding: 24rpx 30rpx;
background: linear-gradient(135deg, #AD181F 0%, #c42a2a 100%);
.card-title {
font-size: 28rpx;
font-weight: 600;
color: #fff;
}
}
function uploadSure() {
// 上传确定
if (transcript.value.length == 0) {
uni.showToast({
title: `请上传成绩单`,
icon: 'error'
})
return
.info-grid {
display: flex;
flex-wrap: wrap;
padding: 20rpx 0;
.info-item {
width: 100%;
padding: 12rpx 30rpx;
box-sizing: border-box;
display: flex;
.info-label {
display: block;
font-size: 24rpx;
color: #666;
margin-bottom: 8rpx;
}
form.value.transcript = JSON.stringify(_.map(transcript.value, (t) => {
return {
name: t.name,
url: t.url
.info-value {
display: block;
font-size: 26rpx;
color: #333;
font-weight: 500;
margin-left: 20rpx;
}
}
}
}))
UpPop.value.close()
}
function handleDelete(row) {
uni.showModal({
title: '提示',
content: `确定删除${row.realName}?`,
success: function(res) {
if (res.confirm) {
api.dellevelPerson(row.id).then(res => {
uni.showToast({
title: `操作成功`
})
getChosedStudentList()
})
/* 操作栏(统计+添加按钮) */
.action-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 24rpx;
background: #fff;
border-radius: 16rpx;
margin: 20rpx 0;
.stat-info {
display: flex;
align-items: center;
// justify-self: unset;
.stat-total {
font-size: 28rpx;
font-weight: 600;
color: #333;
}
.level-tags {
display: flex;
flex-wrap: wrap;
margin-left: 10rpx;
gap: 10rpx;
.level-tag {
padding: 4rpx 12rpx;
background: #FFF5F5;
border: 1rpx solid #FFDDDD;
border-radius: 6rpx;
font-size: 24rpx;
color: #AD181F;
}
}
})
}
</script>
<style lang="scss" scoped>
:deep(.uni-progress-bar) {
display: none;
}
.btn-add-student {
display: flex;
align-items: center;
justify-content: center;
padding: 0 30rpx;
height: 64rpx;
background: linear-gradient(135deg, #AD181F 0%, #c42a2a 100%);
border-radius: 32rpx;
font-size: 26rpx;
color: #fff;
box-shadow: 0 4rpx 16rpx rgba(173, 24, 31, 0.3);
}
/* 考生列表(核心优化) */
.student-list {
.student-card {
position: relative;
// display: flex;
// align-items: center;
// justify-content: space-between;
padding: 24rpx;
background: #fff;
border-radius: 16rpx;
margin-bottom: 20rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
/* 左侧:头像+考生信息 */
.card-left {
display: flex;
align-items: center;
flex: 1;
.avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background: linear-gradient(135deg, #AD181F 0%, #c42a2a 100%);
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
overflow: hidden;
.avatar-text {
font-size: 32rpx;
color: #fff;
font-weight: 600;
}
image {
width: 100%;
height: 100%;
}
}
.item {
.del {
color: #AD181F;
position: absolute;
right: 30rpx;
.student-info {
.student-name {
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 8rpx;
.per-code {
font-size: 24rpx;
color: #666;
font-weight: normal;
margin-left: 12rpx;
}
}
.student-idcard {
font-size: 24rpx;
color: #666;
}
}
}
/* 右侧:原级别/考试级别/是否通过(垂直布局,完美对齐) */
.card-right {
display: flex;
justify-content: space-between;
// padding:0 30px;
margin-top: 20rpx;
margin-left:100rpx;
// flex-direction: column;
// align-items: flex-end;
// gap: 16rpx;
.level-item {
display: flex;
flex-direction: column;
align-items: flex-end;
.level-label {
font-size: 24rpx;
color: #666;
margin-bottom: 6rpx;
}
.level-value {
font-size: 28rpx;
color: #333;
font-weight: 500;
}
.select-wrapper {
width: 120rpx;
}
}
}
/* 删除按钮 */
.delete-btn {
position: absolute;
top: 16rpx;
right: 16rpx;
padding: 8rpx;
}
}
/* 空状态 */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80rpx 0;
background: #fff;
border-radius: 16rpx;
image {
width: 240rpx;
height: 240rpx;
margin-bottom: 20rpx;
}
text {
font-size: 26rpx;
color: #666;
}
}
}
......@@ -753,10 +903,6 @@
padding-bottom: 20rpx;
}
.popBody {
padding: 40rpx 30rpx;
}
.maskbox {
position: relative;
......@@ -770,7 +916,38 @@
}
}
:deep(.file-picker__progress) {
opacity: 0;
/* 底部按钮 */
.fixedBottom {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
justify-content: space-around;
align-items: center;
padding: 20rpx 0;
background: #fff;
border-top: 1rpx solid #f0f0f0;
z-index: 999;
.btn-red {
background: linear-gradient(135deg, #AD181F 0%, #c42a2a 100%);
color: #fff;
border: none;
border-radius: 32rpx;
height: 72rpx;
font-size: 28rpx;
font-weight: 600;
}
.btn-red-kx {
background: #fff;
color: #AD181F;
border: 2rpx solid #AD181F;
border-radius: 32rpx;
height: 72rpx;
font-size: 28rpx;
font-weight: 600;
}
}
</style>
\ No newline at end of file
......
......@@ -11,24 +11,27 @@
<view class="indexboxre">
<view class="userlist">
<view class="item" v-for="(n,index) in list" :key="index">
<view>
<view class="item-content">
<view class="name-row">
<view class="name">{{n.perName}}</view>
<view class="expired-tag" v-if="n.canChoose != 1">
<text class="expired-text">已过期</text>
</view>
</view>
<view class="date">会员号:{{n.perCode||'-'}}</view>
<view class="date">有效日期:{{n.roleInfo && n.roleInfo.validTime ? n.roleInfo.validTime : '-'}}</view>
<view class="date">有效日期:{{ formatDate(n.roleInfo && n.roleInfo.validTime ? n.roleInfo.validTime : '-') }}</view>
<view class="date">注册地:{{n.memName||'-'}}</view>
<view class="date" :class="{'text-danger': n.canChoose != 1}">
状态:{{ n.canChoose == 1 ? '正常' : '资质已过期' }}
</view>
</view>
<view class="status">
<text v-if="isChosen(n)" class="text-gray">已选</text>
<text v-else class="text-primary" @click="handleChoose(n)">选择</text>
<!-- <text v-else-if="n.canChoose != 1" class="text-gray">不可选</text> -->
<text v-if="n.canChoose == 1" class="text-primary" @click="handleChoose(n)">选择</text>
</view>
</view>
</view>
<view class="nodata" v-if="list.length==0 && !loading">
<image mode="aspectFit" :src="config.baseUrl_api + '/fs/static/nodata.png'"></image>
<text>暂无考官数据</text>
<view class="empty" v-if="list.length==0 && !loading">
<image class="empty-img" mode="aspectFit" :src="config.baseUrl_api + '/fs/static/nodata.png'"></image>
<text class="empty-text">暂无考官数据</text>
</view>
</view>
......@@ -132,6 +135,16 @@
return chosen.some(c => c.perId == n.perId)
}
// 格式化日期,去掉时间部分
function formatDate(dateStr) {
if (!dateStr || dateStr === '-') return '-'
// 如果是完整日期时间格式,截取日期部分
if (dateStr.length > 10) {
return dateStr.substring(0, 10)
}
return dateStr
}
function handleChoose(row) {
if (row.canChoose != 1) {
uni.showToast({
......@@ -252,10 +265,33 @@
background: #fff;
border-radius: 10rpx;
.item-content {
flex: 1;
}
.name-row {
display: flex;
align-items: center;
margin-bottom: 10rpx;
justify-content: space-between;
}
.name {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 10rpx;
}
.expired-tag {
// margin-left: 15rpx;
// padding: 0 12rpx;
// background: #fff0f0;
// border: 1px solid #ffcccc;
// border-radius: 6rpx;
}
.expired-text {
font-size: 22rpx;
color: #dd524d;
}
.date {
......
<template>
<view class="choose-student-page">
<!-- 顶部搜索栏 -->
<view class="search-header">
<view class="search-box">
<uni-icons type="search" size="18" color="#999"></uni-icons>
<input
class="search-input"
v-model="searchName"
placeholder="搜索考生姓名"
@confirm="searchStudents"
/>
<uni-icons v-if="searchName" type="clear" size="16" color="#999" @click="clearSearch"></uni-icons>
</view>
<text class="cancel-btn" @click="goBack">取消</text>
</view>
<!-- 已选考生 -->
<view class="selected-section" v-if="selectedList.length > 0">
<view class="section-header">
<text class="section-title">已选考生</text>
<text class="section-count">{{selectedList.length}}</text>
</view>
<scroll-view class="selected-scroll" scroll-x>
<view class="selected-item" v-for="(item, index) in selectedList" :key="index">
<view class="avatar-small">
<image v-if="item.photo" :src="item.photo" mode="aspectFill"></image>
<text v-else>{{(item.name || '').slice(0,1)}}</text>
</view>
<text class="name-small">{{item.name}}</text>
<view class="remove-btn" @click="removeSelected(index)">
<uni-icons type="closeempty" size="12" color="#fff"></uni-icons>
</view>
</view>
</scroll-view>
</view>
<!-- 考生列表 -->
<scroll-view class="student-scroll" scroll-y @scrolltolower="loadMore">
<view class="student-list">
<view class="student-card" v-for="(item, index) in studentList" :key="index" @click="toggleSelect(item)">
<view class="checkbox">
<image v-if="item.checked" src="/static/member/dx_dwn.png" mode="aspectFit"></image>
<view v-else class="checkbox-empty"></view>
</view>
<view class="avatar">
<image v-if="item.photo" :src="item.photo" mode="aspectFill"></image>
<image v-else :src="config.baseUrl_api + '/fs/static/tx@2x.png'"
mode="aspectFill">
</image>
<!-- <text v-else class="avatar-text">{{(item.name || '').slice(0,1)}}</text> -->
</view>
<view class="student-info">
<view class="student-name">
{{item.name}}
<text class="per-code">{{item.perCode}}</text>
</view>
<view class="student-detail">
<text class="level-info">级位:{{ formatLevel(item.levelJi) }}</text>
<text class="validity">到期:{{ formatDate(item.validityDate) }}</text>
</view>
</view>
</view>
<!-- 加载状态 -->
<view class="loading-more" v-if="loading">
<text>加载中...</text>
</view>
<view class="no-more" v-if="!loading && noMore && studentList.length > 0">
<text>没有更多了</text>
</view>
<!-- 空状态 -->
<view class="empty" v-if="!loading && studentList.length === 0">
<image class="empty-img" :src="config.baseUrl_api + '/fs/static/nodata.png'"></image>
<text class="empty-text">暂无考生信息</text>
</view>
</view>
</scroll-view>
<!-- 底部确定按钮 -->
<view class="fixedBottom">
<view class="bottom-info">
<text class="info-text">已选择 <text class="count">{{selectedList.length}}</text></text>
</view>
<view style="text-align:right">
<button class="btn-confirm" @click="confirmSelect">确定添加</button>
</view>
</view>
</view>
</template>
<script setup>
import * as api from '@/common/api.js'
import config from '@/config.js'
import {ref} from 'vue'
import { onLoad } from '@dcloudio/uni-app'
let examId = ''
let memId = ''
let examType = ''
const searchName = ref('')
const studentList = ref([])
const selectedList = ref([])
const loading = ref(false)
const noMore = ref(false)
const pageNum = ref(1)
const pageSize = 20
onLoad((option) => {
examId = option.examId || ''
memId = option.memId || ''
examType = option.examType || '1'
getStudentList()
})
function getStudentList(isMore = false) {
if (loading.value) return
loading.value = true
api.chooseStudentsList({
memId: memId,
examId: examId,
examType: examType,
name: searchName.value
}).then(res => {
const rows = res.rows || []
rows.forEach(item => {
item.checked = selectedList.value.some(s => s.perId === item.perId)
if (item.photo && item.photo.indexOf('http') == -1) {
item.photo = config.baseUrl_api + item.photo
}
})
if (isMore) {
studentList.value = [...studentList.value, ...rows]
} else {
studentList.value = rows
}
noMore.value = rows.length < pageSize
pageNum.value++
loading.value = false
}).catch(() => {
loading.value = false
})
}
function loadMore() {
if (!noMore.value) {
getStudentList(true)
}
}
function searchStudents() {
pageNum.value = 1
noMore.value = false
getStudentList()
}
function clearSearch() {
searchName.value = ''
searchStudents()
}
function toggleSelect(item) {
item.checked = !item.checked
if (item.checked) {
selectedList.value.push({
perId: item.perId,
name: item.name,
photo: item.photo
})
} else {
const index = selectedList.value.findIndex(s => s.perId === item.perId)
if (index > -1) {
selectedList.value.splice(index, 1)
}
}
}
function removeSelected(index) {
const item = selectedList.value[index]
const student = studentList.value.find(s => s.perId === item.perId)
if (student) {
student.checked = false
}
selectedList.value.splice(index, 1)
}
function confirmSelect() {
if (selectedList.value.length === 0) {
uni.showToast({ title: '请选择考生', icon: 'none' })
return
}
uni.showLoading({ title: '添加中...' })
api.batchChoose({
examId: examId,
perIds: selectedList.value.map(s => s.perId)
}).then(() => {
uni.hideLoading()
uni.showToast({ title: '添加成功', icon: 'success' })
setTimeout(() => {
uni.navigateBack()
}, 1500)
}).catch(() => {
uni.hideLoading()
})
}
function goBack() {
uni.navigateBack()
}
function formatDate(dateStr) {
if (!dateStr) return '-'
return dateStr.substring(0, 10)
}
function formatLevel(level) {
if (!level || level == 0) return '十级'
const hzArr = ['〇', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十']
return hzArr[parseInt(level)] + '级'
}
</script>
<style lang="scss" scoped>
.choose-student-page {
min-height: 100vh;
background: #f5f5f5;
display: flex;
flex-direction: column;
}
/* 搜索头部 */
.search-header {
display: flex;
align-items: center;
padding: 20rpx 30rpx;
background: #fff;
border-bottom: 1rpx solid #f0f0f0;
.search-box {
flex: 1;
display: flex;
align-items: center;
padding: 0 20rpx;
height: 64rpx;
background: #f5f5f5;
border-radius: 32rpx;
.search-input {
flex: 1;
height: 100%;
font-size: 26rpx;
margin-left: 10rpx;
}
}
.cancel-btn {
margin-left: 20rpx;
font-size: 28rpx;
color: #666;
}
}
/* 已选区域 */
.selected-section {
background: #fff;
padding: 20rpx 30rpx;
border-bottom: 1rpx solid #f0f0f0;
.section-header {
display: flex;
align-items: center;
margin-bottom: 16rpx;
.section-title {
font-size: 26rpx;
font-weight: 600;
color: #333;
}
.section-count {
margin-left: 12rpx;
font-size: 24rpx;
color: #AD181F;
}
}
.selected-scroll {
white-space: nowrap;
.selected-item {
display: inline-flex;
align-items: center;
position: relative;
margin-right: 24rpx;
.avatar-small {
width: 64rpx;
height: 64rpx;
border-radius: 50%;
background: linear-gradient(135deg, #AD181F 0%, #c42a2a 100%);
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
text {
font-size: 24rpx;
color: #fff;
}
image {
width: 100%;
height: 100%;
}
}
.name-small {
display: block;
text-align: center;
font-size: 22rpx;
color: #666;
margin-top: 8rpx;
width: 80rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.remove-btn {
position: absolute;
top: -6rpx;
right: -6rpx;
width: 28rpx;
height: 28rpx;
background: #999;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
}
}
}
/* 考生列表 */
.student-scroll {
flex: 1;
height: calc(100vh - 280rpx);
}
.student-list {
padding: 20rpx 30rpx;
.student-card {
display: flex;
align-items: center;
padding: 24rpx;
background: #fff;
border-radius: 16rpx;
margin-bottom: 16rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
.checkbox {
margin-right: 20rpx;
.checkbox-empty {
width: 40rpx;
height: 40rpx;
border: 2rpx solid #ddd;
border-radius: 50%;
}
image {
width: 40rpx;
height: 40rpx;
}
}
.avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background: linear-gradient(135deg, #AD181F 0%, #c42a2a 100%);
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
.avatar-text {
font-size: 32rpx;
color: #fff;
font-weight: 600;
}
image {
width: 100%;
height: 100%;
}
}
.student-info {
flex: 1;
.student-name {
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 8rpx;
.per-code {
font-size: 22rpx;
color: #999;
font-weight: normal;
margin-left: 12rpx;
}
}
.student-detail {
display: flex;
gap: 20rpx;
.level-info {
font-size: 24rpx;
color: #1561CB;
}
.validity {
font-size: 24rpx;
color: #666;
}
}
}
}
.loading-more,
.no-more {
text-align: center;
padding: 30rpx;
font-size: 24rpx;
color: #999;
}
}
/* 底部固定栏 */
.fixedBottom {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 30rpx;
background: #fff;
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
.bottom-info {
.info-text {
font-size: 26rpx;
color: #666;
.count {
color: #AD181F;
font-weight: 600;
}
}
}
.btn-confirm {
padding: 0 50rpx;
height: 72rpx;
line-height: 72rpx;
background: linear-gradient(135deg, #AD181F 0%, #c42a2a 100%);
border-radius: 36rpx;
font-size: 28rpx;
color: #fff;
box-shadow: 0 4rpx 16rpx rgba(173, 24, 31, 0.3);
}
}
.empty {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 120rpx 0;
.empty-img {
width: 300rpx;
height: 300rpx;
opacity: 0.08;
}
.empty-text {
color: #999;
font-size: 28rpx;
margin-top: 20rpx;
}
}
</style>
......@@ -680,7 +680,16 @@
"enablePullDownRefresh": false
}
}, {
},
{
"path": "chooseStudent",
"style": {
"navigationBarTitleText": "添加考生",
"enablePullDownRefresh": false
}
},
{
"path": "addApply",
"style": {
"navigationBarTitleText": "编辑级位考试",
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!