ab9493d5 by lttnew

添加考生

1 parent 91e9e0c4
......@@ -705,6 +705,15 @@ export function editLevel(data) {
})
}
// 添加考生(考级)
export function addPerson(data) {
return request({
url: '/exam/person',
method: 'post',
params: data
})
}
export function getVerityList(params) {
return request({
url: '/exam/info/verityList',
......@@ -2173,3 +2182,11 @@ export function getSmsCode(data) {
params: data
})
}
export function inMyMember(params) {
return request({
url: `/person/info/inMyMember/`,
method: 'get',
params
})
}
......
......@@ -15,13 +15,13 @@
</uni-list-item>
<uni-list-item title="会员编号" v-if="form.menCode" :rightText="form.menCode" />
<uni-list-item title="机构名称" :rightText="form.name" />
<uni-list-item title="所属省份">
<!-- <uni-list-item title="所属省份">
<template v-slot:footer>
<uni-data-select :clear="false" disabled
v-model="form.belongProvinceId" :localdata="regionsList">
</uni-data-select>
</template>
</uni-list-item>
</uni-list-item> -->
<uni-list-item title="社会信用代码" :rightText="form.creditCode" />
<uni-list-item v-if="isR" title="联系人" :rightText="form.certSiteContact" />
<uni-list-item v-else title="联系人" :rightText="form.siteContact" />
......
......@@ -73,10 +73,22 @@
</view>
<!-- 操作栏(红框顶部统计+添加按钮) -->
<button class="btn-add-student" @click="goChooseStudent">
<uni-icons color="#fff" size="16" type="plus"></uni-icons>
添加考生
</button>
<view class="action-bar flex f-j-s">
<view class="btn-group">
<button class="btn-add-student btn-outline" @click="goChooseStudent">
在线选择
</button>
</view>
<view >
<button class="btn-add-student" @click="handleAdd">
<uni-icons color="#fff" size="18" type="plus"></uni-icons>
添加
</button>
<!-- <uni-icons color="#faad14" size="18" type="warning"></uni-icons> 支持添加外部人员 -->
</view>
</view>
<view class="action-bar">
<view class="stat-info">
<text class="stat-total">{{ tablePersonInfo.total || 0 }}</text>
......@@ -86,7 +98,6 @@
</view>
</view>
</view>
</view>
<!-- 考生列表(红框主体) -->
......@@ -154,7 +165,40 @@
<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 ref="addPopup" type="center" :mask-click="false" style="z-index: 99999">
<view class="add-popup">
<view class="popup-title">添加考生</view>
<view class="popup-content">
<!-- <view class="form-item">
<view class="form-label">姓名</view>
<view class="form-input">
<input v-model="addForm.name" placeholder="请输入姓名" placeholder-class="placeholder-class"/>
</view>
</view> -->
<view class="form-item">
<view class="form-label">证件类型</view>
<view class="form-input">
<picker :value="idcTypeIndex" :range="idcTypeList" range-key="text" @change="onIdcTypeChange">
<view class="picker-value">{{ idcTypeList[idcTypeIndex]?.text || '请选择证件类型' }}</view>
</picker>
</view>
</view>
<view class="form-item">
<view class="form-label">证件号</view>
<view class="form-input">
<input v-model="addForm.idcCode" placeholder="请输入证件号" placeholder-class="placeholder-class"/>
</view>
</view>
</view>
<view class="popup-btns">
<view class="popup-btn cancel" @click="closeAddPopup">取消</view>
<view class="popup-btn confirm" @click="confirmAdd">确定</view>
</view>
</view>
</uni-popup>
</view>
</template>
......@@ -220,6 +264,20 @@ const range = ref([{
text: '否'
}])
// 添加考生弹框相关
const addPopup = ref(null)
const addForm = ref({
name: '',
idcType: '0',
idcCode: ''
})
const idcTypeList = ref([
{ value: '0', text: '身份证' },
{ value: '1', text: '护照' },
{ value: '2', text: '军官证' }
])
const idcTypeIndex = ref(0)
let examId
onLoad(option => {
......@@ -254,6 +312,7 @@ function initData(option) {
}
onShow(() => {
console.log('addApply onShow called, examId:', examId, 'form.examId:', form.value.examId)
uni.$on('chosen', updateData)
const curExamId = examId || form.value.examId
if (curExamId) {
......@@ -405,6 +464,90 @@ function goChooseStudent() {
})
}
// 打开添加弹框
function handleAdd() {
addForm.value = {
name: '',
idcType: '0',
idcCode: ''
}
idcTypeIndex.value = 0
addPopup.value?.open()
}
// 关闭添加弹框
function closeAddPopup() {
addPopup.value?.close()
}
// 证件类型选择
function onIdcTypeChange(e) {
idcTypeIndex.value = e.detail.value
addForm.value.idcType = idcTypeList.value[idcTypeIndex.value].value
}
// 确认添加考生
async function confirmAdd() {
if (!addForm.value.idcCode) {
uni.showToast({ title: '请输入证件号', icon: 'none' })
return
}
uni.showLoading({ title: '校验中...', mask: true })
try {
const checkRes = await api.inMyMember({ idcCode: addForm.value.idcCode })
uni.hideLoading()
if (checkRes.data) {
// 人员已存在,直接添加
await handelAddPerson()
} else {
// 人员不在本机构,弹出确认框
uni.showModal({
title: '系统提示',
content: '该人员已在其他机构登记,是否申请调入?如仅办理业务,请前往业务页面,点击【添加】直接办理。',
confirmText: '添加',
cancelText: '调动',
success: async function (res) {
if (res.confirm) {
await handelAddPerson()
} else if (res.cancel) {
// 跳转调动页面
uni.navigateTo({
url: '/personal/memberTransfer'
})
}
}
})
}
} catch (err) {
uni.hideLoading()
console.log(err)
}
}
// 执行添加考生
async function handelAddPerson() {
uni.showLoading({ title: '添加中...', mask: true })
try {
// 调用 /exam/person 接口添加考生
await api.addPerson({
examId: form.value.examId,
idcCode: addForm.value.idcCode,
idcType: addForm.value.idcType,
name: addForm.value.name || ''
})
uni.hideLoading()
uni.showToast({ title: '添加成功', icon: 'success' })
closeAddPopup()
getChosedStudentList()
} catch (err) {
uni.hideLoading()
console.log(err)
}
}
// 格式化日期时间
function formatDateTime(dateStr) {
if (!dateStr) return '-'
......@@ -781,19 +924,46 @@ function handleDelete(row) {
display: flex;
align-items: center;
justify-content: center;
padding: 0 30rpx;
padding: 0 40rpx;
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);
&.btn-outline {
background: #fff;
color: #AD181F;
border: 2rpx solid #AD181F;
box-shadow: none;
margin-left: 20rpx;
}
}
.btn-group {
display: flex;
align-items: center;
}
.hint-text {
font-size: 24rpx;
color: #faad14;
margin-top: 16rpx;
display: flex;
align-items: center;
}
/* 考生列表(核心优化) */
.student-list {
position: relative;
z-index: 1;
.student-card {
position: relative;
z-index: 1;
// display: flex;
// align-items: center;
......@@ -863,6 +1033,8 @@ function handleDelete(row) {
margin-top: 20rpx;
margin-left: 100rpx;
height: 100rpx;
position: relative;
z-index: 1;
// flex-direction: column;
// align-items: flex-end;
// gap: 16rpx;
......@@ -887,13 +1059,7 @@ function handleDelete(row) {
.select-wrapper {
width: 160rpx;
position: relative;
z-index: 99;
}
.exam-level-select {
position: relative;
z-index: 999;
margin-bottom: 300rpx;
z-index: 1;
}
}
}
......@@ -990,4 +1156,88 @@ function handleDelete(row) {
font-weight: 600;
}
}
/* 添加考生弹框 */
.add-popup {
width: 600rpx;
background: #ffffff;
border-radius: 24rpx;
overflow: hidden;
position: relative;
z-index: 9999;
}
.popup-title {
font-size: 32rpx;
font-weight: 500;
color: #333;
text-align: center;
padding: 40rpx 30rpx 20rpx;
}
.popup-content {
padding: 20rpx 30rpx 40rpx;
}
.form-item {
display: flex;
align-items: center;
margin-bottom: 24rpx;
}
.form-item:last-child {
margin-bottom: 0;
}
.form-label {
width: 120rpx;
font-size: 28rpx;
color: #333;
flex-shrink: 0;
}
.form-input {
flex: 1;
background: #f5f5f5;
border-radius: 12rpx;
padding: 20rpx 24rpx;
}
.form-input input {
font-size: 28rpx;
color: #333;
width: 100%;
}
.placeholder-class {
color: #999;
}
.picker-value {
font-size: 28rpx;
color: #333;
}
.popup-btns {
display: flex;
border-top: 1rpx solid #eee;
}
.popup-btn {
flex: 1;
height: 100rpx;
line-height: 100rpx;
text-align: center;
font-size: 30rpx;
}
.popup-btn.cancel {
color: #666;
border-right: 1rpx solid #eee;
}
.popup-btn.confirm {
color: #C4121B;
font-weight: 500;
}
</style>
......
......@@ -45,7 +45,7 @@
</view>
<view class="info-row" v-if="form.fileUrl">
<text class="label">发票</text>
<text class="value text-primary" @click="downloadInvoice">下载发票</text>
<text class="value text-primary" @click="downloadInvoice">查看发票</text>
</view>
</view>
</view>
......@@ -190,18 +190,58 @@ function downloadInvoice() {
try {
const invoice = JSON.parse(form.value.fileUrl)
if (invoice && invoice[0]?.url) {
let fileUrl = invoice[0].url
if (!fileUrl.startsWith('http')) {
fileUrl = config.baseUrl_api + fileUrl
let url = invoice[0].url
if (!url.startsWith('http')) {
url = config.baseUrl_api + url
}
uni.previewImage({
urls: [fileUrl],
success: () => {
console.log('previewImage success')
// 从 URL 中获取文件名判断类型
let ext = ''
const urlParts = url.split('/')
const fileNameFromUrl = urlParts[urlParts.length - 1] || ''
if (fileNameFromUrl) {
const nameParts = fileNameFromUrl.split('.')
if (nameParts.length > 1) {
ext = nameParts[nameParts.length - 1].toLowerCase()
}
}
const isImage = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(ext)
uni.showLoading({ title: '加载中' })
uni.downloadFile({
url: url,
success: (res) => {
uni.hideLoading()
if (res.statusCode === 200) {
if (isImage) {
// 图片预览
uni.previewImage({
urls: [res.tempFilePath]
})
} else {
// 文件下载后打开
uni.saveFile({
tempFilePath: res.tempFilePath,
success: (saveRes) => {
uni.openDocument({
filePath: saveRes.savedFilePath,
showMenu: true,
fail: () => {
uni.showToast({ title: '打开失败', icon: 'none' })
}
})
},
fail: () => {
uni.showToast({ title: '保存失败', icon: 'none' })
}
})
}
} else {
uni.showToast({ title: '下载失败', icon: 'none' })
}
},
fail: (err) => {
console.error('previewImage fail:', err)
uni.showToast({ title: '打开图片失败', icon: 'none' })
fail: () => {
uni.hideLoading()
uni.showToast({ title: '下载失败', icon: 'none' })
}
})
}
......
......@@ -54,18 +54,22 @@
<view class="name mt0">{{ item.name || '-' }}</view>
<view class="flexbox">
<view>
结算单位
<view>{{ item.memName || '-' }}</view>
</view>
<view>
结算金额
<view class="text-red">¥{{ Number(item.price || 0).toFixed(2) }}</view>
</view>
<view>
费用合计
<view class="text-red">¥{{ Number(item.originPrice || 0).toFixed(2) }}</view>
</view>
</view>
<view class="flex f-j-s">
<view class="info-time" v-if="item.commitTime">
提交时间:{{ formatDate(item.commitTime) }}
</view>
<view class="info-time" v-if="item.auditTime">
审核时间:{{ formatDate(item.auditTime) }}
</view>
</view>
</view>
</view>
......@@ -202,7 +206,7 @@ function formatDate(dateStr) {
if (typeof dateStr === 'string' && dateStr.indexOf('T') > -1) {
return dateStr.slice(0, 10)
}
return dateStr
return dateStr.slice(0, 10)
}
function goDetail(item) {
......
......@@ -18,18 +18,24 @@
</view>
<view class="appList" v-else>
<view class="appItem" v-for="(item, index) in list" :key="item.payId || index" @click="toggleSelect(item)">
<view class="select-indicator" :class="{ selected: isSelected(item) }">
<uni-icons v-if="isSelected(item)" type="checkmark" size="14" color="#fff"></uni-icons>
</view>
<view class="appItem" v-for="(item, index) in list" :key="item.payId || index">
<view class="item-content">
<view class="status">
<text :class="getStatusClass(item.verityStatus)">
{{ item.verityStatusStr || '审核中' }}
</text>
</view>
<view class="date">
<view class="text-primary" v-if="item.payCode">{{ item.payCode }}</view>
<view class="item-top">
<checkbox-group @change="onCheckboxChange(item.payId)" class="inline-checkbox">
<label>
<checkbox :value="item.payId" :checked="isSelected(item.payId)" color="#C4121B" />
</label>
</checkbox-group>
<view class="paycode-wrap">
<view class="date1">
<view class="text-primary" v-if="item.payCode">{{ item.payCode }}</view>
</view>
<view class="status">
<text :class="getStatusClass(item.verityStatus)">
{{ item.verityStatusStr || '审核中' }}
</text>
</view>
</view>
</view>
<view class="name mt0">{{ item.name || '-' }}</view>
......@@ -174,18 +180,17 @@ function formatDate(dateStr) {
return dateStr
}
function toggleSelect(item) {
if (selectedIds.value.has(item.payId)) {
selectedIds.value.delete(item.payId)
function onCheckboxChange(payId) {
if (selectedIds.value.has(payId)) {
selectedIds.value.delete(payId)
} else {
selectedIds.value.add(item.payId)
selectedIds.value.add(payId)
}
selectedIds.value = new Set(selectedIds.value)
}
// 修复:这里传 item 而不是 payId
function isSelected(item) {
return selectedIds.value.has(item.payId)
function isSelected(payId) {
return selectedIds.value.has(payId)
}
function handleSettlement() {
......@@ -227,60 +232,58 @@ function handleSettlement() {
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
display: flex;
align-items: center;
position: relative;
// 选择框 - 修复样式
.select-indicator {
width: 36rpx;
height: 36rpx;
border-radius: 50%;
border: 2rpx solid #ddd;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
flex-shrink: 0;
transition: all 0.2s;
&.selected {
background-color: #AD181F;
border-color: #AD181F;
}
}
.item-content {
flex: 1;
min-width: 0;
}
.status {
display: inline-block;
padding: 6rpx 20rpx;
border-radius: 20rpx;
font-size: 24rpx;
.item-top {
display: flex;
align-items: center;
margin-bottom: 16rpx;
.text-success {
color: #4caf50;
border-bottom: 1px dashed #e5e5e5;
.inline-checkbox {
display: inline-flex;
margin-right: 10rpx;
width: 50rpx;
margin-bottom: 10rpx;
checkbox {
transform: scale(0.85);
}
}
.text-warning {
color: #ff9800;
.paycode-wrap {
display: flex;
align-items: center;
flex: 1;
}
.text-danger {
color: #f5222d;
.date1 {
display: inline-flex;
align-items: center;
}
}
.date {
font-size: 24rpx;
color: #999;
}
.status {
display: inline-block;
padding: 6rpx 20rpx;
border-radius: 20rpx;
font-size: 24rpx;
background: #f5f5f5;
margin-top: 10rpx;
.text-primary {
font-size: 28rpx;
color: #AD181F;
.text-success {
color: #4caf50;
}
.text-warning {
color: #ff9800;
}
.text-danger {
color: #f5222d;
}
}
}
.name {
......@@ -295,6 +298,11 @@ function handleSettlement() {
}
}
.text-primary {
font-size: 28rpx;
color: #AD181F;
}
.flexbox {
display: flex;
justify-content: space-between;
......
......@@ -85,8 +85,8 @@ onLoad((options) => {
// 数字转汉字
function szToHz(num) {
const arr = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十']
return arr[num] || num + 1
const hzArr = ['〇', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十']
return hzArr[parseInt(num)]
}
async function getConfirm(ids) {
......@@ -213,7 +213,7 @@ async function handleSubmit() {
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
border-left: 8rpx solid #27a9e7;
// border-left: 8rpx solid #27a9e7;
.section-title {
font-size: 28rpx;
......
......@@ -44,8 +44,8 @@
<text class="value"> {{ form.auditTime }}</text>
</view>
<view class="info-item">
<text class="label">下载发票</text>
<text class="value link" @click="handelInvoice(form.fileUrl)"> 下载发票</text>
<text class="label">发票</text>
<text class="value link" @click="handelInvoice(form.fileUrl)"> 查看发票</text>
</view>
</view>
</view>
......@@ -266,7 +266,6 @@ function getVerityStatusClass(status) {
}
function handelInvoice(fileUrl) {
console.log('fileUrl', fileUrl)
if (!fileUrl) {
uni.showToast({ title: '暂无发票', icon: 'none' })
return
......@@ -274,61 +273,61 @@ function handelInvoice(fileUrl) {
try {
const invoice = JSON.parse(fileUrl)
if (invoice && invoice.length > 0) {
let url = invoice[0].url
const fileName = invoice[0].name || ''
// 避免使用可选链操作符,确保兼容性
let ext = ''
if (fileName) {
const parts = fileName.split('.')
if (parts.length > 1) {
ext = parts[parts.length - 1].toLowerCase()
}
let url = invoice[0].url || ''
if (!url) {
uni.showToast({ title: '暂无发票', icon: 'none' })
return
}
console.log('发票信息:', { url, fileName, ext })
// 判断是否需要拼接 baseUrl
if (url.indexOf('http') === -1) {
url = config.baseUrl_api + url
}
console.log('完整下载地址:', url)
uni.showLoading({ title: '下载中' })
// 从 URL 中获取文件名判断类型
let ext = ''
const urlParts = url.split('/')
const fileNameFromUrl = urlParts[urlParts.length - 1] || ''
if (fileNameFromUrl) {
const nameParts = fileNameFromUrl.split('.')
if (nameParts.length > 1) {
ext = nameParts[nameParts.length - 1].toLowerCase()
}
}
const isImage = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(ext)
uni.showLoading({ title: '加载中' })
uni.downloadFile({
url: url,
success: (res) => {
console.log('下载成功:', res)
uni.hideLoading()
if (res.statusCode === 200) {
// 图片类型用 previewImage 预览
if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(ext)) {
if (isImage) {
// 图片预览
uni.previewImage({
urls: [res.tempFilePath],
success: () => {
console.log('预览成功')
},
fail: (err) => {
console.error('预览失败:', err)
uni.showToast({ title: '预览失败', icon: 'none' })
}
urls: [res.tempFilePath]
})
} else {
// PDF 等文档用 openDocument 打开
uni.openDocument({
filePath: res.tempFilePath,
showMenu: true,
success: () => {
console.log('打开文档成功')
// 文件下载后打开
uni.saveFile({
tempFilePath: res.tempFilePath,
success: (saveRes) => {
uni.openDocument({
filePath: saveRes.savedFilePath,
showMenu: true,
fail: () => {
uni.showToast({ title: '打开失败', icon: 'none' })
}
})
},
fail: (err) => {
console.error('打开失败:', err)
uni.showToast({ title: '打开失败', icon: 'none' })
fail: () => {
uni.showToast({ title: '保存失败', icon: 'none' })
}
})
}
} else {
uni.showToast({ title: '下载失败: ' + res.statusCode, icon: 'none' })
uni.showToast({ title: '下载失败', icon: 'none' })
}
},
fail: (err) => {
console.error('下载失败:', err)
fail: () => {
uni.hideLoading()
uni.showToast({ title: '下载失败', icon: 'none' })
}
......
......@@ -239,18 +239,31 @@ let hasOpenedBindPopup = false
onShow(() => {
let webUserName = uni.getStorageSync('webUserName')
console.log(webUserName)
if (!webUserName) {
wxLogin().then(getWebInfo)
// 登录后需要等待数据加载完成
wxLogin().then(res => {
getWebInfo().then(() => {
// 数据加载完成后检查是否需要弹出绑定框
checkAndOpenBindPopup()
})
})
} else {
// 已登录,直接检查
checkAndOpenBindPopup()
}
// 只有当 perInfo 数据存在且 perCode 为空时才弹出
if (perInfo.value && !perInfo.value.perCode && !hasOpenedBindPopup) {
})
// 检查是否需要弹出绑定框
const checkAndOpenBindPopup = () => {
// 确保 userStore 数据已更新
const currentPerInfo = userStore.perInfo
if (currentPerInfo && !currentPerInfo.perCode && !hasOpenedBindPopup) {
hasOpenedBindPopup = true
nextTick(() => {
openBindPopup()
})
}
})
}
// watch(() => perInfo.value, (newVal, oldVal) => {
......
......@@ -119,6 +119,10 @@
<text class="more" @click.stop="goToDetail(item)">更多</text>
</view>
<view class="btn-flex">
<!-- 待缴费:去缴费 -->
<!-- <template v-if="item.payStatus == 0">
<button class="btn btn-pay" @click.stop="goPay(item)">去缴费</button>
</template> -->
<button class="btn btn-info" @click.stop="goToDetail(item)">查看明细</button>
<!-- 已缴费:申请开票/已开票(需要审核通过才能开票) -->
<template v-if="item.payStatus == 1 && item.invoiceStatus != 1 && item.auditStatus == 2 && item.price > 0">
......@@ -368,6 +372,21 @@ const goToDetail = (item) => {
uni.navigateTo({url: `/personalVip/orderDetail?rangeId=${item.sourceId || item.id}&type=${queryParams.type}`});
};
// 去缴费
const goPay = (item) => {
const baseFormData = {
rangeId: item.sourceId || item.id,
payYear: item.content?.yearCount || 1,
sourceId: item.sourceId || item.id,
tradeNo: item.tradeNo,
price: item.price
};
const formStr = encodeURIComponent(JSON.stringify(baseFormData));
uni.navigateTo({
url: `/personal/goPay_per?baseFormData=${formStr}`
});
};
// 删除订单
const handleDelete = (item) => {
currentOrder.value = item;
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!