8cf99911 by lttnew

证书

1 parent cdac517b
<template>
<view class="cert-detail-page">
<!-- 考级基本信息 -->
<view class="section">
<view class="section-header">
<uni-icons type="paperclip" size="18" color="#AD181F"></uni-icons>
<text class="section-title">考级基本信息</text>
</view>
<view class="info-card">
<view class="info-row">
<text class="label">考级名称</text>
<text class="value">{{ form.name || '-' }}</text>
</view>
<view class="info-row">
<text class="label">申请日期</text>
<text class="value">{{ formatDate(form.applyTime) }}</text>
</view>
<view class="info-row">
<text class="label">申请单位</text>
<text class="value">{{ form.memberName || '-' }}</text>
</view>
<view class="info-row">
<text class="label">考官</text>
<text class="value">{{ form.examinerNames || '-' }}</text>
</view>
<view class="info-row">
<text class="label">考试时间</text>
<text class="value">{{ form.startTime ? formatDateTime(form.startTime) : '-' }}</text>
</view>
<view class="info-row">
<text class="label">考级地点</text>
<text class="value">{{ form.address || '-' }}</text>
</view>
<view class="info-row" v-if="form.totalAmount">
<text class="label">总金额</text>
<text class="value text-red">¥{{ (form.totalAmount * 1).toFixed(2) }}</text>
</view>
</view>
</view>
<!-- 收货地址 -->
<view class="section">
<view class="section-header">
<uni-icons type="location-filled" size="18" color="#AD181F"></uni-icons>
<text class="section-title">收货地址</text>
</view>
<view class="info-card">
<view class="info-row">
<text class="label">姓名</text>
<text class="value">{{ addressForm.name || '-' }}</text>
</view>
<view class="info-row">
<text class="label">联系方式</text>
<text class="value">{{ addressForm.phone || '-' }}</text>
</view>
<view class="info-row">
<text class="label">收件地址</text>
<text class="value">{{ addressForm.address || '-' }}</text>
</view>
</view>
</view>
<!-- 考生信息 -->
<view class="section">
<view class="section-header">
<uni-icons type="person" size="18" color="#AD181F"></uni-icons>
<text class="section-title">考生信息</text>
<view class="person-summary">
{{ personSummary.total }}
</view>
</view>
<view class="person-stats" v-if="personSummary.levelArr && personSummary.levelArr.length > 0">
<view class="stat-item" v-for="item in personSummary.levelArr" :key="item.level">
{{ levelToChinese(item.level) }}级:{{ item.num }}
</view>
</view>
<view class="person-table">
<view class="table-header">
<view class="th th-name">姓名</view>
<!-- <view class="th th-idtype">证件类型</view> -->
<view class="th th-idcard">证件号码</view>
<view class="th th-level">级别</view>
</view>
<scroll-view scroll-y class="table-body-wrapper">
<view class="table-body">
<view class="table-row" v-for="(person, idx) in infoList" :key="idx">
<view class="td td-name">{{ person.realName }}</view>
<view class="td td-idtype">{{ person.idcTypeStr || '-' }}</view>
<view class="td td-idcard">{{ person.idcCode || '-' }}</view>
<view class="td td-level">
{{ levelToChinese(person.levelNew) }}
<text class="level-old" v-if="person.levelOld">({{ levelToChinese(person.levelOld) }}级)</text>
</view>
</view>
</view>
</scroll-view>
<view class="no-data" v-if="infoList.length == 0">
<text>暂无考生信息</text>
</view>
</view>
<view class="pagination-wrap" v-if="total > pageSize">
<view class="page-btn" @click="prevPage" :class="{ disabled: pageNum <= 1 }">
<uni-icons type="left" size="14" color="#666"></uni-icons>
</view>
<view class="page-num">{{ pageNum }}/{{ totalPage }}</view>
<view class="page-btn" @click="nextPage" :class="{ disabled: pageNum >= totalPage }">
<uni-icons type="right" size="14" color="#666"></uni-icons>
</view>
</view>
</view>
<!-- 审核信息 -->
<view class="section" v-if="recordList.length > 0">
<view class="section-header">
<uni-icons type="checkmark-circle" size="18" color="#AD181F"></uni-icons>
<text class="section-title">审核信息</text>
</view>
<view class="audit-table">
<view class="table-header">
<view class="th th-dept">审核协会</view>
<view class="th th-time">审核时间</view>
<view class="th th-status">审核状态</view>
<view class="th th-remark">备注</view>
</view>
<view class="table-body">
<view class="table-row" v-for="(record, idx) in recordList" :key="idx">
<view class="td td-dept">{{ record.auditDeptName || '-' }}</view>
<view class="td td-time">{{ formatDateTime(record.auditTime) }}</view>
<view class="td td-status">
<text :class="record.auditResult == '1' ? 'text-success' : 'text-danger'">
{{ record.auditResult == '1' ? '通过' : '未通过' }}
</text>
</view>
<view class="td td-remark">{{ record.auditMsg || '-' }}</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import * as api from '@/common/api.js'
import { ref, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
const loading = ref(false)
const examId = ref('')
const pageNum = ref(1)
const pageSize = ref(50)
const form = ref({})
const addressForm = ref({})
const infoList = ref([])
const personSummary = ref({ total: 0, levelArr: [] })
const total = ref(0)
const totalPage = computed(() => Math.ceil(total.value / pageSize.value))
const recordList = ref([])
onLoad((options) => {
if (options.examId) {
examId.value = options.examId
getExamInfo()
getStudentList()
getAuditRecords()
}
})
function getExamInfo() {
uni.showLoading({ title: '加载中' })
api.getExamInfo(examId.value).then(res => {
uni.hideLoading()
if (res.data) {
form.value = res.data
addressForm.value = res.data.postAddress || {}
}
}).catch(err => {
uni.hideLoading()
console.error('获取考试详情失败', err)
uni.showToast({ title: '获取详情失败', icon: 'none' })
})
}
function getStudentList() {
uni.showLoading({ title: '加载中' })
api.getStudentList({
examId: examId.value,
pageNum: pageNum.value,
pageSize: pageSize.value
}).then(res => {
uni.hideLoading()
if (res.rows) {
infoList.value = res.rows
total.value = res.total || 0
// 统计各级别人数
const levelMap = {}
res.rows.forEach(person => {
const level = person.levelNew
if (level) {
levelMap[level] = (levelMap[level] || 0) + 1
}
})
const levelArr = Object.keys(levelMap).map(key => ({
level: key,
num: levelMap[key]
})).sort((a, b) => b.level - a.level)
personSummary.value = {
total: total.value,
levelArr: levelArr
}
} else {
uni.hideLoading()
}
}).catch(err => {
uni.hideLoading()
console.error('获取考生列表失败', err)
})
}
function prevPage() {
if (pageNum.value > 1) {
pageNum.value--
getStudentList()
}
}
function nextPage() {
if (pageNum.value < totalPage.value) {
pageNum.value++
getStudentList()
}
}
function getAuditRecords() {
api.getLogs(examId.value, 1).then(res => {
recordList.value = res.data || []
}).catch(err => {
console.error('获取审核记录失败', err)
})
}
function formatDate(dateStr) {
if (!dateStr) return '-'
return dateStr.substring(0, 10)
}
function formatDateTime(dateStr) {
if (!dateStr) return '-'
return dateStr.replace('T', ' ').substring(0, 19)
}
function levelToChinese(level) {
if (!level) return '-'
const levelMap = {
'1': '一', '2': '二', '3': '三', '4': '四', '5': '五',
'6': '六', '7': '七', '8': '八', '9': '九', '10': '十'
}
return levelMap[String(level)] || level
}
</script>
<style lang="scss" scoped>
.cert-detail-page {
min-height: 100vh;
background-color: #f5f5f5;
padding: 20rpx;
padding-bottom: 40rpx;
}
.section {
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
}
.section-header {
display: flex;
align-items: center;
margin-bottom: 24rpx;
.section-title {
font-size: 30rpx;
font-weight: 600;
color: #333;
margin-left: 10rpx;
}
.person-summary {
margin-left: auto;
font-size: 26rpx;
color: #AD181F;
}
}
.info-card {
.info-row {
display: flex;
justify-content: space-between;
padding: 16rpx 0;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.label {
font-size: 26rpx;
color: #999;
flex-shrink: 0;
}
.value {
font-size: 26rpx;
color: #333;
text-align: right;
margin-left: 20rpx;
&.text-red {
color: #AD181F;
font-weight: 600;
}
}
}
}
.person-stats {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
margin-bottom: 24rpx;
padding: 20rpx;
background-color: #fafafa;
border-radius: 12rpx;
.stat-item {
font-size: 24rpx;
color: #666;
padding: 8rpx 16rpx;
background-color: #fff;
border-radius: 20rpx;
}
}
.person-table {
border: 1px solid #eee;
border-radius: 12rpx;
overflow: hidden;
}
.table-header {
display: flex;
background-color: #f5f5f5;
.th {
padding: 20rpx 0;
text-align: center;
font-size: 24rpx;
color: #666;
font-weight: 500;
}
}
.table-body-wrapper {
max-height: 600rpx;
}
.table-body {
.table-row {
display: flex;
border-bottom: 1px solid #eee;
&:last-child {
border-bottom: none;
}
.td {
padding: 20rpx 0;
text-align: center;
font-size: 24rpx;
color: #333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
.th-name, .td-name { width: 20%; }
// .th-idtype, .td-idtype { width: 18%; }
.th-idcard, .td-idcard { width: 60%; }
.th-level, .td-level { width: 20%; }
.level-old {
color: #999;
font-size: 20rpx;
margin-left: 4rpx;
}
.no-data {
padding: 60rpx 0;
text-align: center;
color: #999;
font-size: 26rpx;
}
.pagination-wrap {
display: flex;
justify-content: center;
align-items: center;
gap: 30rpx;
margin-top: 30rpx;
padding-top: 30rpx;
border-top: 1px solid #f0f0f0;
.page-btn {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
background-color: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
&.disabled {
opacity: 0.4;
}
}
.page-num {
font-size: 28rpx;
color: #333;
min-width: 120rpx;
text-align: center;
}
}
/* 审核信息表格 */
.audit-table {
border: 1px solid #eee;
border-radius: 12rpx;
overflow: hidden;
.table-header {
display: flex;
background-color: #f5f5f5;
.th {
padding: 20rpx 0;
text-align: center;
font-size: 24rpx;
color: #666;
font-weight: 500;
}
}
.table-body {
.table-row {
display: flex;
border-bottom: 1px solid #eee;
&:last-child {
border-bottom: none;
}
.td {
padding: 16rpx 0;
text-align: center;
font-size: 22rpx;
color: #333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
.th-dept, .td-dept { width: 25%; }
.th-time, .td-time { width: 25%; }
.th-status, .td-status { width: 15%; }
.th-remark, .td-remark { width: 35%; }
.text-success {
color: #4caf50;
}
.text-danger {
color: #f56c6c;
}
}
</style>
<template>
<view class="logistics-page">
<!-- 物流信息卡片 -->
<view class="logistics-info-card">
<view class="info-header">
<uni-icons type=" truck" size="20" color="#AD181F"></uni-icons>
<uni-icons type="paperplane-filled" size="18" color="#AD181F"></uni-icons>
<text class="header-title">物流信息</text>
</view>
<view class="info-content">
<view class="info-row">
<text class="label">运单号</text>
<text class="value tracking-number" @click="copyTracking">{{ currentItem.postCode || '-' }}
<text class="copy-btn">复制</text>
<text class="value tracking-number" @click="copyTracking">
{{ currentItem.postCode || '-' }}
</text>
</view>
<view class="info-row">
<text class="label">当前状态</text>
<text class="value status-sended" v-if="currentItem.postStatus == 1">已邮寄</text>
<text class="value status-pending" v-else>未邮寄</text>
</view>
<view class="info-row">
<text class="label">邮寄时间</text>
<text class="value">{{ currentItem.submitTime ? formatDateTime(currentItem.submitTime) : '-' }}</text>
</view>
</view>
</view>
<!-- 物流时间线 -->
<view class="timeline-section">
<view class="section-header">
<uni-icons type="location-filled" size="18" color="#AD181F"></uni-icons>
<uni-icons type="location-filled" size="20" color="#AD181F"></uni-icons>
<text class="section-title">运输轨迹</text>
</view>
......@@ -39,28 +28,28 @@
<view class="timeline-wrap" v-else>
<view class="timeline-list" v-if="trackingNodes.length > 0">
<view
class="timeline-item"
:class="{ first: idx === 0 }"
v-for="(node, idx) in trackingNodes"
:key="idx"
class="timeline-item"
:class="{ first: idx === 0 }"
>
<view class="timeline-left">
<view class="timeline-dot">
<uni-icons v-if="idx === 0" type="check" size="12" color="#fff"></uni-icons>
<text v-if="idx === 0" class="timeline-check"></text>
</view>
<view class="timeline-line" v-if="idx < trackingNodes.length - 1"></view>
<view v-if="idx < trackingNodes.length - 1" class="timeline-line"></view>
</view>
<view class="timeline-right">
<view class="timeline-content">
<view class="timeline-title">{{ node.categoryName }}</view>
<view class="timeline-time">{{ node.operationTime }}</view>
<view class="timeline-desc">{{ node.operationRemark }}</view>
<view class="timeline-title">{{ node.categoryName || '物流更新' }}</view>
<view class="timeline-time">{{ node.operationTime || '-' }}</view>
<view class="timeline-desc">{{ node.operationRemark || '-' }}</view>
</view>
</view>
</view>
</view>
<view class="no-logistics" v-else>
<view class="nodata" v-else>
<image mode="aspectFit" :src="config.baseUrl_api + '/fs/static/nodata.png'"></image>
<text>暂无物流信息</text>
</view>
......@@ -81,26 +70,25 @@ const currentItem = ref({})
const trackingNodes = ref([])
onLoad((options) => {
if (options.payId) {
if (!options.payId) return
payId.value = options.payId
if (options.postCode) currentItem.value.postCode = options.postCode
if (options.postStatus) currentItem.value.postStatus = parseInt(options.postStatus)
if (options.submitTime) currentItem.value.submitTime = options.submitTime
getLogisticsInfo()
}
})
function getLogisticsInfo() {
if (!payId.value) return
loading.value = true
api.queryTrace(payId.value).then(res => {
loading.value = false
api.queryTrace(payId.value).then((res) => {
trackingNodes.value = res.data || []
}).catch(err => {
loading.value = false
}).catch((err) => {
console.error('获取物流信息失败', err)
uni.showToast({ title: '获取物流信息失败', icon: 'none' })
}).finally(() => {
loading.value = false
})
}
......@@ -113,11 +101,6 @@ function copyTracking() {
}
})
}
function formatDateTime(dateStr) {
if (!dateStr) return '-'
return dateStr.replace('T', ' ').substring(0, 19)
}
</script>
<style lang="scss" scoped>
......@@ -128,7 +111,6 @@ function formatDateTime(dateStr) {
padding-bottom: 40rpx;
}
/* 物流信息卡片 */
.logistics-info-card {
background-color: #fff;
border-radius: 16rpx;
......@@ -155,11 +137,6 @@ function formatDateTime(dateStr) {
justify-content: space-between;
align-items: center;
padding: 16rpx 0;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.label {
font-size: 26rpx;
......@@ -170,32 +147,15 @@ function formatDateTime(dateStr) {
font-size: 26rpx;
color: #333;
&.status-sended {
color: #4caf50;
}
&.status-pending {
color: #ff9800;
}
&.tracking-number {
display: flex;
align-items: center;
gap: 10rpx;
}
max-width: 460rpx;
text-align: right;
word-break: break-all;
}
.copy-btn {
font-size: 22rpx;
color: #AD181F;
background-color: #fef0f0;
padding: 4rpx 12rpx;
border-radius: 16rpx;
}
}
}
/* 时间线 */
.timeline-section {
background-color: #fff;
border-radius: 16rpx;
......@@ -207,6 +167,36 @@ function formatDateTime(dateStr) {
align-items: center;
margin-bottom: 30rpx;
.section-icon {
position: relative;
width: 22rpx;
height: 22rpx;
margin-right: 10rpx;
flex-shrink: 0;
.section-icon-ring {
width: 100%;
height: 100%;
border: 2rpx solid #c4121b;
border-radius: 50%;
box-sizing: border-box;
background: #fff;
}
.section-icon-dot {
position: absolute;
left: 50%;
top: 50%;
width: 8rpx;
height: 8rpx;
margin-left: -4rpx;
margin-top: -4rpx;
border-radius: 50%;
background: #c4121b;
box-shadow: 0 0 0 4rpx rgba(196, 18, 27, 0.08);
}
}
.section-title {
font-size: 30rpx;
font-weight: 600;
......@@ -249,8 +239,8 @@ function formatDateTime(dateStr) {
}
.timeline-dot {
width: 28rpx;
height: 28rpx;
width: 32rpx;
height: 32rpx;
border-radius: 50%;
border: 2px solid #ccc;
background-color: #fff;
......@@ -261,12 +251,19 @@ function formatDateTime(dateStr) {
z-index: 1;
}
.timeline-check {
color: #fff;
font-size: 20rpx;
font-weight: 700;
line-height: 1;
}
.timeline-line {
width: 2rpx;
flex: 1;
background-color: #e0e0e0;
margin: 8rpx 0;
min-height: 60rpx;
margin: 10rpx 0;
min-height: 64rpx;
}
.timeline-right {
......@@ -297,8 +294,9 @@ function formatDateTime(dateStr) {
border-radius: 8rpx;
}
}
}
.no-logistics {
.nodata {
display: flex;
flex-direction: column;
align-items: center;
......@@ -315,6 +313,5 @@ function formatDateTime(dateStr) {
color: #999;
margin-top: 20rpx;
}
}
}
</style>
......
<template>
<view class="cert-mail-page">
<!-- 列表区域 -->
<view class="appList">
<view class="appItem" v-for="(item, index) in infoList" :key="index">
<view @click="handleView(item)">
<view class="status">
<text :class="item.postStatus == 1 ? 'text-success' : 'text-warning'">
<view class="mail-page" :class="{ 'no-scroll': showMailPopup }">
<scroll-view
scroll-y
class="mail-list-scroll"
:enhanced="true"
:show-scrollbar="false"
:scroll-enabled="!showMailPopup"
lower-threshold="200"
@scrolltolower="loadMore"
>
<view class="mail-list">
<view v-if="infoList.length > 0">
<view
v-for="(item, index) in infoList"
:key="item.payId || index"
class="order-card-new"
@click="handleView(item)"
>
<view class="card-header">
<view class="date">
<view class="data-header">
<text class="value">{{ item.payCode || '—' }} ·{{ item.memberName || '—' }}</text>
</view>
<text
class="status-tag"
:class="item.postStatus == 1 ? 'status-success' : 'status-pending'"
>
{{ item.postStatus == 1 ? '已邮寄' : '未邮寄' }}
</text>
</view>
<view class="date" v-if="item.submitTime">
<view class="text-primary" v-if="item.payCode">{{ item.payCode }}</view>
</view>
<view class="name mt0">{{ item.name }}</view>
<view class="flexbox">
<view>
缴费单位
<view>{{ item.memberName }}</view>
<view class="member-time">
<view class="label">
{{ item.name || '—' }}
</view>
<view>
证书人数
<view>{{ item.totalNum }}</view>
<view >{{ item.totalNum || 0 }}</view>
</view>
<view class="info-summary">
<view class="summary-right">
</view>
<view class="flex f-j-s">
<view class="mail-time" v-if="item.submitTime">
邮寄时间:{{ formatDate(item.submitTime) }}
</view>
<view class="mail-time" v-if="item.postCode ">
<view>物流编号{{ item.postCode || '-' }}</view>
<view class="detail-rows">
<view class="detail-col">
<text class="detail-label">邮寄时间</text>
<text class="detail-value">{{ item.submitTime ? formatDate(item.submitTime) : '—' }}</text>
</view>
<view class="detail-divider"></view>
<view class="detail-col">
<text class="detail-label">物流编号</text>
<text class="detail-value">{{ item.postCode || '—' }}</text>
</view>
</view>
<view class="func">
<view class="btn-group">
<button
v-if="deptType == '1' && item.postStatus != 1"
class="btn-mail"
@click.stop="handleMail(item)"
>邮寄证书</button>
<button class="btn-logistics" @click.stop="handleLogistics(item)">物流跟踪</button>
v-if="showMailButton(item)"
class="btn btn-mail"
@click.stop="openMailPopup(item)"
>
邮寄证书
</button>
<button class="btn btn-logistics" @click.stop="handleLogistics(item)">
物流跟踪
</button>
</view>
</view>
</view>
<!-- 空数据 -->
<view class="nodata" v-if="infoList.length == 0 && !loading">
<image mode="aspectFit" :src="config.baseUrl_api + '/fs/static/nodata.png'"></image>
<text>暂无数据</text>
<view v-else-if="!loading" class="empty">
<image
class="empty-img"
mode="aspectFit"
:src="config.baseUrl_api + '/fs/static/nodata.png'"
></image>
<text class="empty-text">暂无数据</text>
</view>
<view class="loading-tip" v-if="loading">加载中...</view>
<view class="no-more" v-if="!loading && loadStatus === 'nomore' && infoList.length">
没有更多了
</view>
</view>
</scroll-view>
<view v-if="showMailPopup" class="popup-mask" @touchmove.stop.prevent>
<view class="custom-modal" @click.stop>
<view class="modal-title">邮寄证书</view>
<view class="mail-form">
<view class="form-item">
<text class="form-label">邮寄方式</text>
<radio-group class="radio-group" @change="handleMailTypeChange">
<label class="radio-item" @click.stop>
<radio value="1" color="#C4121B" :checked="mailForm.type === '1'" />
<text>自动邮寄</text>
</label>
<label class="radio-item" @click.stop>
<radio value="2" color="#C4121B" :checked="mailForm.type === '2'" />
<text>人工邮寄</text>
</label>
</radio-group>
</view>
<!-- 上拉加载 -->
<view class="loading">
<uni-load-more :status="loadStatus" :contentText="loadMoreText"></uni-load-more>
<view v-if="mailForm.type === '2'" class="form-item">
<text class="form-label">快递公司</text>
<input
v-model.trim="mailForm.tt"
class="form-input"
placeholder="请输入快递公司"
maxlength="30"
/>
</view>
<view v-if="mailForm.type === '2'" class="form-item">
<text class="form-label">快递号</text>
<input
v-model.trim="mailForm.code"
class="form-input"
placeholder="请输入快递号"
maxlength="40"
/>
</view>
</view>
<view class="modal-btns">
<button class="modal-btn-cancel" @click="closeMailPopup">取消</button>
<button class="modal-btn-confirm" @click="submitMail">确定</button>
</view>
</view>
</view>
</view>
</template>
......@@ -60,31 +137,31 @@
<script setup>
import * as api from '@/common/api.js'
import config from '@/config.js'
import { ref, computed } from 'vue'
import { ref } from 'vue'
import { onLoad, onShow, onReachBottom } from '@dcloudio/uni-app'
const app = getApp()
const loading = ref(false)
// 加载状态
const loadStatus = ref('more') // more / loading / nomore
const loadMoreText = {
contentdown: '上拉加载更多',
contentrefresh: '正在加载...',
contentnomore: '没有更多了'
}
const loading = ref(false)
const infoList = ref([])
const total = ref(0)
const deptType = ref('')
const loadStatus = ref('more')
const showMailPopup = ref(false)
const currentRow = ref(null)
// 请求参数
const queryParams = ref({
pageNum: 1,
pageSize: 10,
type: '1'
})
// 列表数据
const infoList = ref([])
const total = ref(0)
const deptType = ref('')
const mailForm = ref({
payId: '',
type: '1',
tt: '',
code: ''
})
onLoad((options) => {
if (options.type) {
......@@ -102,88 +179,122 @@ onShow(() => {
}
})
// 初始化(刷新)
onReachBottom(() => {
if (loading.value || loadStatus.value !== 'more' || showMailPopup.value) return
queryParams.value.pageNum++
getList()
})
function init() {
deptType.value = app.globalData.deptType
// 重置列表和页码
deptType.value = String(app.globalData.deptType || '')
infoList.value = []
total.value = 0
queryParams.value.pageNum = 1
loadStatus.value = 'more'
getList()
}
// 获取列表
function getList() {
// 没有更多数据直接返回
if (loadStatus.value === 'nomore') return
if (loadStatus.value === 'nomore' || loading.value) return
loading.value = true
loadStatus.value = 'loading'
if (queryParams.value.pageNum === 1) {
uni.showLoading({ title: '加载中' })
}
api.paymentList({ ...queryParams.value }).then(res => {
const data = res.rows || []
const totalData = res.total || 0
// 拼接数据
infoList.value = [...infoList.value, ...data]
total.value = totalData
api.paymentList({ ...queryParams.value }).then((res) => {
const rows = res.rows || []
total.value = res.total || 0
// 判断是否还有更多
if (infoList.value.length >= totalData) {
loadStatus.value = 'nomore'
if (queryParams.value.pageNum === 1) {
infoList.value = rows
} else {
loadStatus.value = 'more'
infoList.value = infoList.value.concat(rows)
}
loadStatus.value = infoList.value.length >= total.value ? 'nomore' : 'more'
}).catch((err) => {
if (queryParams.value.pageNum > 1) {
queryParams.value.pageNum--
}
loadStatus.value = 'more'
console.error('获取邮寄列表失败', err)
uni.showToast({ title: '加载失败', icon: 'none' })
}).finally(() => {
loading.value = false
uni.hideLoading()
}).catch(() => {
loading.value = false
uni.hideLoading()
loadStatus.value = 'more'
})
}
// 上拉加载下一页
onReachBottom(() => {
if (loadStatus.value !== 'more' || loading.value) return
function loadMore() {
if (loading.value || loadStatus.value !== 'more' || showMailPopup.value) return
queryParams.value.pageNum++
getList()
})
}
// 查看详情
function handleView(item) {
uni.navigateTo({
url: `/level/ztx/certDetail?examId=${item.examId}`
url: `/pages/rank/applyDetail?examId=${item.examId}&type=${queryParams.value.type}`
})
}
// 邮寄证书
async function handleMail(item) {
const res = await uni.showModal({
title: '提示',
content: '是否确认寄送快递?',
confirmText: '确认',
cancelText: '取消'
})
function showMailButton(item) {
return deptType.value === '1' && String(item.postStatus) !== '1'
}
if (res.confirm) {
uni.showLoading({ title: '操作中' })
api.postCert(item.payId).then(() => {
uni.hideLoading()
function openMailPopup(item) {
currentRow.value = item
mailForm.value = {
payId: item.payId,
type: '1',
tt: '',
code: ''
}
showMailPopup.value = true
}
function closeMailPopup() {
showMailPopup.value = false
currentRow.value = null
}
function handleMailTypeChange(e) {
mailForm.value.type = e.detail.value
if (mailForm.value.type === '1') {
mailForm.value.tt = ''
mailForm.value.code = ''
}
}
async function submitMail() {
if (!mailForm.value.payId) return
if (mailForm.value.type === '2') {
if (!mailForm.value.tt) {
uni.showToast({ title: '请输入快递公司', icon: 'none' })
return
}
if (!mailForm.value.code) {
uni.showToast({ title: '请输入快递号', icon: 'none' })
return
}
}
try {
uni.showLoading({ title: '提交中' })
await api.postCert(mailForm.value.payId)
uni.showToast({ title: '操作成功', icon: 'success' })
// 操作成功后刷新列表
closeMailPopup()
init()
}).catch(() => {
} catch (err) {
console.error('邮寄证书失败', err)
} finally {
uni.hideLoading()
})
}
}
// 物流跟踪
function handleLogistics(item) {
if (item.postStatus != 1 || !item.postCode) {
if (String(item.postStatus) !== '1' || !item.postCode) {
uni.showToast({ title: '暂无物流信息', icon: 'none' })
return
}
......@@ -192,159 +303,395 @@ function handleLogistics(item) {
})
}
// 时间格式化
function formatDate(dateStr) {
if (!dateStr) return '-'
return dateStr.substring(0, 10)
}
// 等级转中文
function levelToChinese(level) {
const levelMap = {
'1': '一', '2': '二', '3': '三', '4': '四', '5': '五',
'6': '六', '7': '七', '8': '八', '9': '九', '10': '十'
}
return levelMap[String(level)] || level
if (!dateStr) return '—'
return String(dateStr).replace('T', ' ').substring(0, 10)
}
</script>
<style lang="scss" scoped>
.cert-mail-page {
.mail-page {
background: #ededf0;
height: 100vh;
min-height: 100vh;
background-color: #f5f5f5;
padding-bottom: 20rpx;
display: flex;
flex-direction: column;
overflow: hidden;
&.no-scroll {
overflow: hidden;
height: 100vh;
}
}
/* 列表区域 */
.appList {
padding: 0 20rpx;
.mail-list-scroll {
flex: 1;
height: 0;
min-height: 0;
overflow: hidden;
background: #ededf0;
}
.appItem {
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
.mail-list {
min-height: 100%;
box-sizing: border-box;
padding: 18rpx 22rpx 30rpx;
}
.status {
display: inline-block;
padding: 6rpx 20rpx;
border-radius: 20rpx;
font-size: 24rpx;
margin-bottom: 16rpx;
.empty {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 120rpx 0;
.text-success {
color: #4caf50;
.empty-img {
width: 300rpx;
height: 300rpx;
}
.text-warning {
color: #ff9800;
}
.empty-text {
color: #666;
font-size: 28rpx;
margin-top: 20rpx;
}
}
.loading-tip,
.no-more {
text-align: center;
padding: 20rpx 0;
color: #999;
font-size: 26rpx;
}
.order-card-new {
background: #fff;
margin-bottom: 20rpx;
padding: 20rpx 18rpx 16rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
border-radius: 18rpx;
display: flex;
flex-direction: column;
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 8rpx;
.date {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12rpx;
font-size: 24rpx;
color: #999;
.data-header {
display: flex;
align-items: center;
min-width: 0;
flex: 1;
margin-right: 16rpx;
}
.text-primary {
font-size: 28rpx;
color: #AD181F;
.value {
color: #c30d23;
font-size: 30rpx;
font-weight: bold;
max-width: 520rpx;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
line-height: 1.25;
}
.name {
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 20rpx;
margin-top: 10rpx;
.status-tag {
font-size: 22rpx;
flex-shrink: 0;
line-height: 1.2;
&.status-pending {
color: #faad14;
}
&.mt0 {
margin-top: 0;
&.status-success {
color: #52c41a;
}
}
}
}
.flexbox {
.member-time {
width: 100%;
padding-bottom: 2rpx;
display: flex;
justify-content: space-between;
background-color: #fafafa;
border-radius: 12rpx;
padding: 20rpx;
margin: 20rpx 0;
align-items: flex-start;
gap: 20rpx;
view {
.label {
color: #333;
flex: 1;
text-align: center;
font-size: 24rpx;
color: #999;
view {
min-width: 0;
font-size: 28rpx;
font-weight: 700;
line-height: 1.45;
word-break: break-word;
.star {
color: #777;
font-size: 22rpx;
margin-right: 10rpx;
vertical-align: 2rpx;
}
}
view:last-child {
flex-shrink: 0;
font-size: 26rpx;
color: #222;
font-weight: 500;
line-height: 1.3;
padding-top: 2rpx;
}
}
}
.info-summary {
display: flex;
justify-content: flex-end;
margin-top: 4rpx;
.summary-right {
text-align: right;
.summary-top {
font-size: 26rpx;
color: #333;
font-weight: 500;
margin-top: 8rpx;
}
.summary-bottom {
margin-top: 8rpx;
font-size: 24rpx;
color: #999;
}
}
}
.mail-time {
.detail-rows {
margin-top: 12rpx;
background: #f3f6fc;
border-radius: 14rpx;
padding: 14rpx 18rpx;
display: flex;
align-items: stretch;
justify-content: space-between;
}
.detail-col {
display: flex;
flex: 1;
min-width: 0;
flex-direction: column;
justify-content: center;
align-items: center;
box-sizing: border-box;
}
.detail-divider {
width: 1rpx;
background: #d8deea;
margin: 4rpx 18rpx;
flex-shrink: 0;
}
// .detail-col:last-child {
// align-items: flex-end;
// }
.detail-row {
display: flex;
justify-content: space-between;
align-items: center;
flex: 1;
min-width: 0;
padding: 10rpx 0;
font-size: 24rpx;
color: #666;
margin-top: 16rpx;
.detail-label {
color: #999;
flex-shrink: 0;
margin-right: 12rpx;
}
.func {
.detail-value {
color: #333;
flex: 1;
text-align: right;
word-break: break-all;
font-size: 25rpx;
}
}
.detail-col .detail-label {
color: #999;
font-size: 22rpx;
line-height: 1.2;
}
.detail-col .detail-value {
margin-top: 10rpx;
color: #333;
font-size: 26rpx;
line-height: 1.3;
max-width: 100%;
word-break: break-all;
}
.btn-group {
display: flex;
gap: 20rpx;
margin-top: 24rpx;
padding-top: 24rpx;
border-top: 1px solid #f0f0f0;
justify-content: flex-end;
align-items: center;
gap: 14rpx;
width: 100%;
margin-top: 14rpx;
flex-wrap: wrap;
.btn {
min-width: 138rpx;
height: 50rpx;
line-height: 48rpx;
padding: 0 18rpx;
border-radius: 12rpx;
font-size: 24rpx;
white-space: nowrap;
font-weight: 500;
background: transparent;
text-align: center;
margin: 0;
border: none;
button {
height: 64rpx;
line-height: 64rpx;
font-size: 26rpx;
border-radius: 32rpx;
&::after {
border: none;
display: none;
}
}
.btn-mail {
width: 100px;
background: #AD181F;
color: #fff;
color: #c30d23;
border: 1rpx solid #c30d23;
background: #fff;
}
.btn-logistics {
width: 100px;
background: linear-gradient(135deg, #2196f3, #42a5f5);
color: #fff;
}
color: #666;
border: 1rpx solid #ccc;
background: #fff;
}
}
.nodata {
.popup-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
z-index: 999;
}
image {
width: 300rpx;
height: 300rpx;
}
.custom-modal {
width: 620rpx;
background: #fff;
border-radius: 20rpx;
padding: 36rpx 30rpx 30rpx;
box-sizing: border-box;
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.2);
}
.modal-title {
font-size: 36rpx;
font-weight: 600;
color: #333;
text-align: center;
}
text {
.mail-form {
margin-top: 32rpx;
}
.form-item {
margin-bottom: 24rpx;
}
.form-label {
display: block;
margin-bottom: 12rpx;
font-size: 28rpx;
color: #999;
margin-top: 20rpx;
color: #333;
font-weight: 500;
}
.radio-group {
display: flex;
gap: 36rpx;
flex-wrap: wrap;
}
.radio-item {
display: inline-flex;
align-items: center;
font-size: 28rpx;
color: #333;
}
.form-input {
width: 100%;
height: 84rpx;
padding: 0 24rpx;
border-radius: 14rpx;
background: #f5f6f8;
box-sizing: border-box;
font-size: 28rpx;
color: #333;
}
.modal-btns {
display: flex;
justify-content: space-between;
gap: 20rpx;
margin-top: 30rpx;
}
.modal-btn-cancel,
.modal-btn-confirm {
flex: 1;
height: 80rpx;
line-height: 80rpx;
border-radius: 40rpx;
font-size: 32rpx;
border: none;
margin: 0;
padding: 0;
&::after {
border: none;
}
}
.loading {
padding: 30rpx 0;
text-align: center;
.modal-btn-cancel {
background: #f5f5f5;
color: #666;
}
.modal-btn-confirm {
background: #c4121b;
color: #fff;
}
</style>
......
......@@ -918,13 +918,6 @@
}
},
{
"path": "ztx/certDetail",
"style": {
"navigationBarTitleText": "考试详情",
"enablePullDownRefresh": false
}
},
{
"path": "ztx/certLogistics",
"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!