33f3f307 by 张猛

Merge remote-tracking branch 'origin/master'

# Conflicts:
#	pages/index/home.vue
2 parents 5d5e4447 7f5a246b
......@@ -1684,3 +1684,65 @@ export function editAddressOrder(examId, addressId) {
method: 'put'
})
}
/**
* 查询证书列表
* @param query
* @returns {*}
*/
export function certsLList(query) {
return request({
url: '/exam/payment/certsList',
method: 'get',
params: query
})
}
/**
* 段位考试成绩列表
* @param params
* @returns {*}
*/
export function getRankScoreList(params) {
return request({
url: '/exam/info/rank/scoreList',
method: 'get',
params: params
})
}
/**
* 证书邮寄
* @param ids
* @returns {*}
*/
export function postCert(ids) {
return request({
url: `/exam/payment/postCert/${ids}`,
method: 'post'
})
}
/**
* 查看物流信息
* @param id
* @returns {*}
*/
export function queryTrace(id) {
return request({
url: `/exam/payment/queryTrace/${id}`,
method: 'get'
})
}
/**
* 获取考试详情
* @param examId
* @returns {*}
*/
export function getExamInfo(examId) {
return request({
url: `/exam/info/${examId}`,
method: 'get'
})
}
......
......@@ -62,3 +62,242 @@ export function getRankScoreList(params) {
params: params
})
}
/**
* 证书邮寄
* @param ids
* @returns {*}
*/
export function postCert(ids) {
return request({
url: `/exam/payment/postCert/${ids}`,
method: 'post'
})
}
/**
* 查看物流信息
* @param id
* @returns {*}
*/
export function queryTrace(id) {
return request({
url: `/exam/payment/queryTrace/${id}`,
method: 'get'
})
}
/**
* 获取考试详情
* @param examId
* @returns {*}
*/
export function getExamInfo(examId) {
return request({
url: `/exam/info/${examId}`,
method: 'get'
})
}
/**
* 获取考生列表
* @param params
* @returns {*}
*/
export function getStudentList(params) {
return request({
url: '/exam/person/list',
method: 'get',
params: params
})
}
/**
* 缴费单列表
* @param query
* @returns {*}
*/
export function paymentList(query) {
return request({
url: '/exam/payment/list',
method: 'get',
params: query
})
}
/**
* 获取审核记录
* @param examId
* @param type
* @returns {*}
*/
export function getLogs(examId, type) {
return request({
url: `/exam/info/getLogs/${examId}/${type}`,
method: 'get'
})
}
/**
* 考点审核列表
* @param query
* @returns {*}
*/
export function listInfo(query) {
return request({
url: '/member/examPointApply/list',
method: 'get',
params: query
})
}
/**
* 考点审核
* @param params
* @returns {*}
*/
export function ztxAudit(params) {
return request({
url: `/member/examPointApply/audit/${params.ids}`,
method: 'post',
params
})
}
/**
* 获取机构资料
* @param memId
* @returns {*}
*/
export function getInstitutionInfo(memId) {
return request({
url: `/groupMember/authentication/info/${memId}`,
method: 'get'
})
}
/**
* 会员审核列表
* @param params
* @returns {*}
*/
export function certifiedNewList(params) {
return request({
url: `/system/certifiedNew/list`,
method: 'get',
params
})
}
/**
* 会员审核
* @param params
* @returns {*}
*/
export function shenAudit(params) {
return request({
url: `/system/certifiedNew/shenAudit/${params.ids}`,
method: 'post',
params
})
}
/**
* 获取审核日志
* @param id
* @returns {*}
*/
export function getAuditLogs(id) {
return request({
url: `/system/config/getLogs/${id}`,
method: 'get'
})
}
/**
* 获取会员审核日志
* @param id
* @returns {*}
*/
export function certifiedNewGetLogs(id) {
return request({
url: `/system/certifiedNew/getLogs/${id}`,
method: 'get'
})
}
/**
* 结算列表
* @param params
* @returns {*}
*/
export function settlementList(params) {
return request({
url: `/exam/paymentSubmit/list`,
method: 'get',
params
})
}
/**
* 结算详情
* @param id
* @returns {*}
*/
export function settlementInfo(id) {
return request({
url: `/exam/paymentSubmit/info/${id}`,
method: 'get'
})
}
/**
* 结算审核
* @param params
* @returns {*}
*/
export function settlementAudit(params) {
return request({
url: `/exam/paymentSubmit/audit/${params.ids}`,
method: 'post',
params
})
}
/**
* 结算申请列表(用于新建结算申请)
* @param params
* @returns {*}
*/
export function settlementApplyList(params) {
return request({
url: `/exam/payment/list`,
method: 'get',
params
})
}
/**
* 提交结算申请
* @param data
* @returns {*}
*/
export function settlementCommit(data) {
return request({
url: `/exam/paymentSubmit/commit`,
method: 'post',
params: data
})
}
/**
* 结算确认 - 获取费用明细
* @param ids
* @returns {*}
*/
export function settlementConfirm(ids) {
return request({
url: `/exam/paymentSubmit/confirm/${ids}`,
method: 'post'
})
}
......
......@@ -119,7 +119,7 @@ page {
.pd30{padding: 30rpx;box-sizing: border-box;}
.appList{width: 700rpx;margin:30rpx auto;
.appItem{background: #FFFFFF;padding: 20rpx 30rpx 30rpx;margin-bottom: 30rpx;
.appItem{background: #eb7c7c;padding: 20rpx 30rpx 30rpx;margin-bottom: 30rpx;
box-shadow: 0rpx 12rpx 116rpx 0rpx rgba(196,203,214,0.1);
border-radius: 15rpx;position: relative;
.iconbox{position: absolute;left: 20rpx;}
......
<template>
<view class="logistics-page">
<!-- 物流信息卡片 -->
<view class="logistics-info-card">
<view class="info-header">
<uni-icons type=" truck" size="20" 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>
</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>
<text class="section-title">运输轨迹</text>
</view>
<view class="loading-wrap" v-if="loading">
<uni-load-more status="loading"></uni-load-more>
</view>
<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"
>
<view class="timeline-left">
<view class="timeline-dot">
<uni-icons v-if="idx === 0" type="check" size="12" color="#fff"></uni-icons>
</view>
<view class="timeline-line" v-if="idx < trackingNodes.length - 1"></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>
</view>
</view>
</view>
<view class="no-logistics" v-else>
<image mode="aspectFit" src="/static/nodata.png"></image>
<text>暂无物流信息</text>
</view>
</view>
</view>
</view>
</template>
<script setup>
import * as api from '@/common/api.js'
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
const loading = ref(false)
const payId = ref('')
const currentItem = ref({})
const trackingNodes = ref([])
onLoad((options) => {
if (options.payId) {
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
trackingNodes.value = res.data || []
}).catch(err => {
loading.value = false
console.error('获取物流信息失败', err)
uni.showToast({ title: '获取物流信息失败', icon: 'none' })
})
}
function copyTracking() {
if (!currentItem.value.postCode) return
uni.setClipboardData({
data: currentItem.value.postCode,
success: () => {
uni.showToast({ title: '已复制', icon: 'success' })
}
})
}
function formatDateTime(dateStr) {
if (!dateStr) return '-'
return dateStr.replace('T', ' ').substring(0, 19)
}
</script>
<style lang="scss" scoped>
.logistics-page {
min-height: 100vh;
background-color: #f5f5f5;
padding: 20rpx;
padding-bottom: 40rpx;
}
/* 物流信息卡片 */
.logistics-info-card {
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
}
.info-header {
display: flex;
align-items: center;
margin-bottom: 24rpx;
.header-title {
font-size: 30rpx;
font-weight: 600;
color: #333;
margin-left: 10rpx;
}
}
.info-content {
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16rpx 0;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.label {
font-size: 26rpx;
color: #999;
}
.value {
font-size: 26rpx;
color: #333;
&.status-sended {
color: #4caf50;
}
&.status-pending {
color: #ff9800;
}
&.tracking-number {
display: flex;
align-items: center;
gap: 10rpx;
}
}
.copy-btn {
font-size: 22rpx;
color: #AD181F;
background-color: #fef0f0;
padding: 4rpx 12rpx;
border-radius: 16rpx;
}
}
}
/* 时间线 */
.timeline-section {
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
}
.section-header {
display: flex;
align-items: center;
margin-bottom: 30rpx;
.section-title {
font-size: 30rpx;
font-weight: 600;
color: #333;
margin-left: 10rpx;
}
}
.loading-wrap {
padding: 60rpx 0;
text-align: center;
}
.timeline-wrap {
.timeline-list {
padding-left: 10rpx;
}
.timeline-item {
display: flex;
position: relative;
&.first {
.timeline-dot {
background-color: #AD181F;
border-color: #AD181F;
}
.timeline-title {
color: #AD181F;
}
}
}
.timeline-left {
display: flex;
flex-direction: column;
align-items: center;
margin-right: 20rpx;
}
.timeline-dot {
width: 28rpx;
height: 28rpx;
border-radius: 50%;
border: 2px solid #ccc;
background-color: #fff;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
z-index: 1;
}
.timeline-line {
width: 2rpx;
flex: 1;
background-color: #e0e0e0;
margin: 8rpx 0;
min-height: 60rpx;
}
.timeline-right {
flex: 1;
padding-bottom: 40rpx;
}
.timeline-content {
.timeline-title {
font-size: 28rpx;
color: #333;
font-weight: 500;
}
.timeline-time {
font-size: 24rpx;
color: #999;
margin-top: 8rpx;
}
.timeline-desc {
font-size: 26rpx;
color: #666;
margin-top: 12rpx;
line-height: 1.6;
background-color: #fafafa;
padding: 16rpx;
border-radius: 8rpx;
}
}
.no-logistics {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80rpx 0;
image {
width: 200rpx;
height: 200rpx;
}
text {
font-size: 28rpx;
color: #999;
margin-top: 20rpx;
}
}
}
</style>
<template>
<view class="cost-settlement-page">
<!-- 顶部标签栏 -->
<view class="tab-bar">
<view
v-for="tab in tabs"
:key="tab.value"
class="tab-item"
:class="{ active: currentTab === tab.value }"
@click="onTabChange(tab.value)"
>
<text class="tab-text">{{ tab.label }}</text>
<view class="tab-indicator" v-if="currentTab === tab.value"></view>
</view>
</view>
<!-- 新建按钮 -->
<view class="action-bar">
<view class="btn-add" @click="goAdd">
<uni-icons type="plus" size="16" color="#fff"></uni-icons>
<text>新建结算申请</text>
</view>
</view>
<!-- 列表内容 -->
<scroll-view
scroll-y
class="list-content"
@scrolltolower="onLoadMore"
>
<view class="loading-wrap" v-if="loading && list.length === 0">
<uni-load-more status="loading"></uni-load-more>
</view>
<view class="empty-wrap" v-else-if="!loading && list.length === 0">
<view class="nodata">
<image mode="aspectFit" src="/static/nodata.png"></image>
<text>暂无数据</text>
</view>
</view>
<view class="appList" v-else>
<view class="appItem" v-for="(item, index) in list" :key="index">
<view @click="goDetail(item)">
<view class="status">
<text :class="getStatusClass(item.status)">
{{ getStatusText(item.status) }}
</text>
</view>
<view class="date">
<view class="text-primary" v-if="item.code">{{ item.code }}</view>
</view>
<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="flex f-j-s">
<view class="info-time" v-if="item.commitTime">
提交时间:{{ formatDate(item.commitTime) }}
</view>
</view>
</view>
</view>
</view>
<view class="loading" v-if="list.length > 0">
<uni-load-more :status="loadStatus" :contentText="loadMoreText"></uni-load-more>
</view>
</scroll-view>
</view>
</template>
<script setup>
import * as api from '@/common/api_exam.js'
import { ref } from 'vue'
import { onLoad, onShow } from '@dcloudio/uni-app'
const app = getApp()
const tabs = [
{ label: '全部', value: '' },
{ label: '待结算', value: '1' },
{ label: '结算通过', value: '2' },
{ label: '结算拒绝', value: '3' }
]
const currentTab = ref('1')
const list = ref([])
const loading = ref(false)
const loadStatus = ref('more')
const loadMoreText = {
contentdown: '上拉加载更多',
contentrefresh: '正在加载...',
contentnomore: '没有更多了'
}
const queryParams = ref({
pageNum: 1,
pageSize: 10
})
onLoad(() => {
if (app.globalData.isLogin) {
init()
} else {
app.firstLoadCallback = () => {
init()
}
}
})
onShow(() => {
if (app.globalData.isLogin && list.value.length > 0) {
refreshList()
}
})
function init() {
list.value = []
queryParams.value.pageNum = 1
loadStatus.value = 'more'
getList()
}
function refreshList() {
list.value = []
queryParams.value.pageNum = 1
loadStatus.value = 'more'
getList()
}
function onTabChange(value) {
currentTab.value = value
init()
}
function getList() {
if (loadStatus.value === 'nomore') return
if (loading.value && list.value.length > 0) return
loading.value = true
if (list.value.length === 0) {
loadStatus.value = 'loading'
}
const params = {
...queryParams.value,
status: currentTab.value
}
api.settlementList(params).then(res => {
const data = res.rows || []
const totalData = res.total || 0
if (queryParams.value.pageNum === 1) {
list.value = data
} else {
list.value = [...list.value, ...data]
}
if (list.value.length >= totalData) {
loadStatus.value = 'nomore'
} else {
loadStatus.value = 'more'
}
}).catch(() => {
loadStatus.value = 'more'
}).finally(() => {
loading.value = false
})
}
function onLoadMore() {
if (loadStatus.value !== 'more' || loading.value) return
queryParams.value.pageNum++
getList()
}
function getStatusText(status) {
const map = { 1: '待结算', 2: '结算通过', 3: '结算拒绝' }
return map[status] || '未知'
}
function getStatusClass(status) {
const map = {
1: 'text-warning',
2: 'text-success',
3: 'text-danger'
}
return map[status] || ''
}
function formatDate(dateStr) {
if (!dateStr) return '-'
if (typeof dateStr === 'string' && dateStr.indexOf('T') > -1) {
return dateStr.slice(0, 10)
}
return dateStr
}
function goDetail(item) {
const itemStr = encodeURIComponent(JSON.stringify(item))
uni.navigateTo({
url: `/level/ztx/costSettlementDetail?item=${itemStr}`
})
}
function goAdd() {
uni.navigateTo({
url: `/level/ztx/costSettlementAdd`
})
}
</script>
<style lang="scss" scoped>
.cost-settlement-page {
min-height: 100vh;
background-color: #f5f5f5;
}
.tab-bar {
display: flex;
background-color: #fff;
padding: 0 20rpx;
position: sticky;
top: 0;
z-index: 100;
.tab-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 24rpx 0;
position: relative;
.tab-text {
font-size: 28rpx;
color: #666;
}
.tab-indicator {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 80rpx;
height: 6rpx;
background-color: #AD181F;
border-radius: 3rpx;
}
&.active {
.tab-text {
color: #AD181F;
font-weight: 600;
}
}
}
}
.action-bar {
padding: 20rpx;
background-color: #f5f5f5;
.btn-add {
display: flex;
align-items: center;
justify-content: center;
gap: 8rpx;
background-color: #AD181F;
color: #fff;
padding: 20rpx 0;
border-radius: 12rpx;
font-size: 28rpx;
&:active {
opacity: 0.8;
}
}
}
.list-content {
height: calc(100vh - 200rpx);
// padding: 0 20rpx 20rpx;
}
.loading-wrap,
.empty-wrap {
padding: 100rpx 0;
text-align: center;
}
/* 列表样式 */
.appList {
padding: 0;
margin:0rpx auto;
}
.appItem {
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
.status {
display: inline-block;
padding: 6rpx 20rpx;
border-radius: 20rpx;
font-size: 24rpx;
margin-bottom: 16rpx;
.text-success {
color: #4caf50;
}
.text-warning {
color: #ff9800;
}
.text-danger {
color: #f5222d;
}
}
.date {
font-size: 24rpx;
color: #999;
}
.text-primary {
font-size: 28rpx;
color: #AD181F;
}
.name {
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 20rpx;
margin-top: 10rpx;
&.mt0 {
margin-top: 0;
}
}
.flexbox {
display: flex;
justify-content: space-between;
background-color: #fafafa;
border-radius: 12rpx;
padding: 20rpx;
margin: 20rpx 0;
view {
flex: 1;
text-align: center;
font-size: 24rpx;
color: #999;
view {
font-size: 28rpx;
color: #333;
font-weight: 500;
margin-top: 8rpx;
}
}
.text-red {
color: #AD181F;
}
}
.flex {
display: flex;
&.f-j-s {
justify-content: space-between;
}
}
.info-time {
font-size: 24rpx;
color: #666;
margin-top: 16rpx;
}
}
.nodata {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
image {
width: 300rpx;
height: 300rpx;
}
text {
font-size: 28rpx;
color: #999;
margin-top: 20rpx;
}
}
.loading {
padding: 30rpx 0;
text-align: center;
}
</style>
<template>
<view class="cost-settlement-add-page">
<!-- 列表内容 -->
<scroll-view
scroll-y
class="list-content"
@scrolltolower="onLoadMore"
>
<view class="loading-wrap" v-if="loading && list.length === 0">
<uni-load-more status="loading"></uni-load-more>
</view>
<view class="empty-wrap" v-else-if="!loading && list.length === 0">
<view class="nodata">
<image mode="aspectFit" src="/static/nodata.png"></image>
<text>暂无可结算的缴费单</text>
</view>
</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="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>
<view class="name mt0">{{ item.name || '-' }}</view>
<view class="flexbox">
<view>
缴费单位
<view>{{ item.applyMemberName || '-' }}</view>
</view>
<view>
考试人数
<view>{{ item.totalNum || 0 }}</view>
</view>
<view>
总金额
<view class="text-red">¥{{ Number(item.totalAmount || 0).toFixed(2) }}</view>
</view>
</view>
<view class="flex f-j-s">
<view class="info-time" v-if="item.submitTime">
提交时间:{{ formatDate(item.submitTime) }}
</view>
</view>
</view>
</view>
</view>
<view class="loading" v-if="list.length > 0">
<uni-load-more :status="loadStatus" :contentText="loadMoreText"></uni-load-more>
</view>
</scroll-view>
<!-- 底部提交按钮 -->
<view class="bottom-bar" v-if="selectedIds.size > 0">
<view class="summary-info">
<view class="summary-item">
<text class="summary-label">费用合计:</text>
<text class="summary-value text-red">¥{{ totalAmount.toFixed(2) }}</text>
</view>
<view class="summary-item">
<text class="summary-label">人数合计:</text>
<text class="summary-value">{{ totalNum }}</text>
</view>
</view>
<view class="submit-btn" @click="handleSettlement">
结算申请
</view>
</view>
</view>
</template>
<script setup>
import * as api from '@/common/api_exam.js'
import { ref, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
const loading = ref(false)
const list = ref([])
const selectedIds = ref(new Set())
const loadStatus = ref('more')
const loadMoreText = {
contentdown: '上拉加载更多',
contentrefresh: '正在加载...',
contentnomore: '没有更多了'
}
const queryParams = ref({
pageNum: 1,
pageSize: 10,
type: 1,
submitFlag: '0'
})
// 选中的费用合计
const totalAmount = computed(() => {
return list.value
.filter(item => selectedIds.value.has(item.payId))
.reduce((sum, item) => sum + (Number(item.totalAmount) || 0), 0)
})
// 选中的人数合计
const totalNum = computed(() => {
return list.value
.filter(item => selectedIds.value.has(item.payId))
.reduce((sum, item) => sum + (Number(item.totalNum) || 0), 0)
})
onLoad(() => {
getList()
})
function getList() {
if (loadStatus.value === 'nomore') return
if (loading.value && list.value.length > 0) return
loading.value = true
if (list.value.length === 0) {
loadStatus.value = 'loading'
}
api.settlementApplyList(queryParams.value).then(res => {
const data = res.rows || []
const totalData = res.total || 0
if (queryParams.value.pageNum === 1) {
list.value = data
} else {
list.value = [...list.value, ...data]
}
if (list.value.length >= totalData) {
loadStatus.value = 'nomore'
} else {
loadStatus.value = 'more'
}
}).catch(() => {
loadStatus.value = 'more'
}).finally(() => {
loading.value = false
})
}
function onLoadMore() {
if (loadStatus.value !== 'more' || loading.value) return
queryParams.value.pageNum++
getList()
}
function getStatusClass(status) {
const map = {
1: 'text-success',
2: 'text-danger',
3: 'text-warning'
}
return map[status] || 'text-warning'
}
function formatDate(dateStr) {
if (!dateStr) return '-'
if (typeof dateStr === 'string' && dateStr.indexOf('T') > -1) {
return dateStr.slice(0, 10)
}
return dateStr
}
function toggleSelect(item) {
if (selectedIds.value.has(item.payId)) {
selectedIds.value.delete(item.payId)
} else {
selectedIds.value.add(item.payId)
}
selectedIds.value = new Set(selectedIds.value)
}
// 修复:这里传 item 而不是 payId
function isSelected(item) {
return selectedIds.value.has(item.payId)
}
function handleSettlement() {
if (selectedIds.value.size === 0) {
uni.showToast({ title: '请选择要结算的缴费单', icon: 'none' })
return
}
const ids = Array.from(selectedIds.value).join(',')
uni.navigateTo({
url: `/level/ztx/costSettlementConfirm?ids=${ids}`
})
}
</script>
<style lang="scss" scoped>
.cost-settlement-add-page {
min-height: 100vh;
background-color: #f5f5f5;
padding-bottom: 120rpx;
}
.list-content {
height: calc(100vh - 120rpx);
padding: 20rpx;
box-sizing: border-box;
}
.loading-wrap,
.empty-wrap {
padding: 100rpx 0;
text-align: center;
}
.appList {
padding: 0;
}
.appItem {
background-color: #fff;
border-radius: 16rpx;
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;
margin-bottom: 16rpx;
.text-success {
color: #4caf50;
}
.text-warning {
color: #ff9800;
}
.text-danger {
color: #f5222d;
}
}
.date {
font-size: 24rpx;
color: #999;
}
.text-primary {
font-size: 28rpx;
color: #AD181F;
}
.name {
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 20rpx;
margin-top: 10rpx;
&.mt0 {
margin-top: 0;
}
}
.flexbox {
display: flex;
justify-content: space-between;
background-color: #fafafa;
border-radius: 12rpx;
padding: 20rpx;
margin: 20rpx 0;
view {
flex: 1;
text-align: center;
font-size: 24rpx;
color: #999;
view {
font-size: 28rpx;
color: #333;
font-weight: 500;
margin-top: 8rpx;
}
}
.text-red {
color: #AD181F;
}
}
.flex {
display: flex;
&.f-j-s {
justify-content: space-between;
}
}
.info-time {
font-size: 24rpx;
color: #666;
margin-top: 16rpx;
}
}
.nodata {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
image {
width: 300rpx;
height: 300rpx;
}
text {
font-size: 28rpx;
color: #999;
margin-top: 20rpx;
}
}
.loading {
padding: 30rpx 0;
text-align: center;
}
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #fff;
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 30rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
z-index: 99;
.summary-info {
display: flex;
flex-direction: column;
gap: 8rpx;
.summary-item {
display: flex;
align-items: center;
.summary-label {
font-size: 24rpx;
color: #666;
}
.summary-value {
font-size: 28rpx;
color: #333;
font-weight: 600;
}
.text-red {
color: #AD181F;
}
}
}
.submit-btn {
background-color: #AD181F;
color: #fff;
padding: 20rpx 50rpx;
border-radius: 40rpx;
font-size: 28rpx;
&:active {
opacity: 0.8;
}
}
}
</style>
\ No newline at end of file
<template>
<view class="settlement-confirm-page">
<!-- 统计卡片区 -->
<view class="stats-grid">
<view class="stat-card">
<view class="stat-label">考试总人数</view>
<view class="stat-value">{{ form.personCount || 0 }}<text class="stat-unit"></text></view>
</view>
<view class="stat-card">
<view class="stat-label">费用合计</view>
<view class="stat-value">{{ form.allFee || '0.00' }}<text class="stat-unit"></text></view>
</view>
<view class="stat-card">
<view class="stat-label">结算费用</view>
<view class="stat-value text-red">{{ form.fee || '0.00' }}<text class="stat-unit"></text></view>
</view>
</view>
<!-- 级别分布明细 -->
<view class="level-section">
<view class="section-title">级别分布明细</view>
<view class="level-tags">
<view v-for="(val, inx) in form.count" :key="inx" class="level-tag" :class="`level-${inx % 3 + 1}`">
{{ szToHz(inx) }}级:{{ val }}
</view>
</view>
</view>
<view class="divider"></view>
<!-- 发票上传区域 -->
<view class="upload-section">
<view class="section-title">发票上传</view>
<view class="upload-area">
<view class="upload-tip">支持格式:jpg、png、pdf,大小不超过10MB</view>
<uni-file-picker
v-model="fileList"
file-mediatype="image"
file-extname="jpg,jpeg,png,pdf"
:limit="1"
@select="selectFile"
@progress="fileProgress"
@success="fileSuccess"
@delete="fileDelete"
>
<view class="upload-btn" v-if="!fileList.length">
<uni-icons type="plus" size="24" color="#AD181F"></uni-icons>
<!-- <text>上传发票</text> -->
</view>
<view v-else class="file-item">
<uni-icons type="paperclip" size="16" color="#AD181F"></uni-icons>
<text class="file-name">{{ fileList[0].name }}</text>
</view>
</uni-file-picker>
</view>
</view>
<!-- 底部操作按钮 -->
<view class="bottom-bar">
<view class="cancel-btn" @click="handleCancel">取消</view>
<view class="submit-btn" @click="handleSubmit">提交结算申请</view>
</view>
</view>
</template>
<script setup>
import * as api from '@/common/api_exam.js'
import { uploadFileList } from '@/common/api.js'
import { ref, } from 'vue';
import { onLoad } from '@dcloudio/uni-app'
const form = ref({})
const fileList = ref([])
const loading = ref(false)
const ids = ref('')
const uploadedUrl = ref('') // 上传后的文件路径
onLoad((options) => {
const { ids: optIds } = options
if (optIds) {
ids.value = optIds
getConfirm(optIds)
}
})
// 数字转汉字
function szToHz(num) {
const arr = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十']
return arr[num] || num + 1
}
async function getConfirm(ids) {
loading.value = true
try {
const res = await api.settlementConfirm(ids)
form.value = res.data || {}
} catch (err) {
console.error('获取结算明细失败', err)
uni.showToast({ title: '获取数据失败', icon: 'none' })
} finally {
loading.value = false
}
}
function selectFile(e) {
const file = e.tempFiles[0]
if (!file) return
uni.showLoading({ title: '上传中' })
uploadFileList(e.tempFilePaths[0]).then(data => {
uploadedUrl.value = data
uni.hideLoading()
uni.showToast({ title: '上传成功', icon: 'success' })
}).catch(err => {
uni.hideLoading()
uni.showToast({ title: '上传失败', icon: 'none' })
})
}
function fileProgress(e) {
console.log('上传进度:', e)
}
function fileSuccess(e) {
console.log('上传成功:', e)
}
function fileDelete(e) {
fileList.value = []
uploadedUrl.value = ''
}
function handleCancel() {
uni.navigateBack()
}
async function handleSubmit() {
if (!uploadedUrl.value) {
uni.showToast({ title: '请上传发票', icon: 'none' })
return
}
// 使用上传后的真实路径
const url = JSON.stringify([{ url: uploadedUrl.value, name: fileList.value[0]?.name || 'invoice' }])
try {
uni.showLoading({ title: '提交中...' })
await api.settlementCommit({ ids: ids.value, url })
uni.hideLoading()
uni.showToast({ title: '提交成功', icon: 'success' })
setTimeout(() => {
uni.navigateBack({
delta: 2 // 返回两级页面
})
}, 1500)
} catch (err) {
uni.hideLoading()
console.error('提交失败', err)
uni.showToast({ title: '提交失败', icon: 'none' })
}
}
</script>
<style lang="scss" scoped>
.settlement-confirm-page {
min-height: 100vh;
background-color: #f5f5f5;
padding: 20rpx;
padding-bottom: 140rpx;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20rpx;
margin-bottom: 20rpx;
}
.stat-card {
background: #fff;
border-radius: 16rpx;
padding: 30rpx 20rpx;
text-align: center;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
.stat-label {
font-size: 24rpx;
color: #999;
margin-bottom: 12rpx;
}
.stat-value {
font-size: 36rpx;
font-weight: 700;
color: #333;
line-height: 1.2;
}
.stat-unit {
font-size: 24rpx;
font-weight: 400;
color: #999;
margin-left: 4rpx;
}
.text-red {
color: #AD181F;
}
}
.level-section {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
border-left: 8rpx solid #27a9e7;
.section-title {
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 24rpx;
}
.level-tags {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
.level-tag {
padding: 12rpx 24rpx;
border-radius: 30rpx;
font-size: 24rpx;
font-weight: 500;
background: #fff;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
}
.level-1 {
background: linear-gradient(135deg, #fef2e8, #ffe4d6);
color: #c2410c;
}
.level-2 {
background: linear-gradient(135deg, #eef2ff, #e0e7ff);
color: #1e40af;
}
.level-3 {
background: linear-gradient(135deg, #ecfdf5, #d1fae5);
color: #065f46;
}
}
}
.divider {
height: 1rpx;
background-color: #eee;
margin: 20rpx 0;
}
.upload-section {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
.section-title {
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 24rpx;
}
.upload-area {
background: #fafcff;
padding: 24rpx;
border-radius: 12rpx;
border: 2rpx dashed #cbd5e1;
display: flex;
flex-direction: column;
align-items: center;
gap: 20rpx;
.upload-tip {
font-size: 24rpx;
color: #999;
text-align: center;
}
.upload-btn {
display: flex;
flex-direction: column;
align-items: center;
gap: 12rpx;
padding: 30rpx 60rpx;
border-radius: 16rpx;
color: #AD181F;
font-size: 28rpx;
}
.file-item {
display: flex;
align-items: center;
gap: 12rpx;
padding: 20rpx 30rpx;
background: #fff;
border-radius: 12rpx;
border: 1rpx solid #eee;
.file-name {
font-size: 26rpx;
color: #333;
max-width: 400rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #fff;
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 30rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
z-index: 99;
.cancel-btn {
flex: 1;
text-align: center;
padding: 24rpx 0;
border-radius: 40rpx;
font-size: 28rpx;
background-color: #f5f5f5;
color: #666;
margin-right: 20rpx;
}
.submit-btn {
flex: 2;
text-align: center;
padding: 24rpx 0;
border-radius: 40rpx;
font-size: 28rpx;
background-color: #AD181F;
color: #fff;
}
}
</style>
<template>
<view class="audit-page">
<!-- 提示信息 -->
<view class="tips-box">
<uni-icons type="info" size="18" color="#13B5B1"></uni-icons>
<text class="tips-text" v-if="type === 'batch'">批量审核 {{ ids.split(',').length }} 条记录</text>
<text class="tips-text" v-else>单个审核</text>
</view>
<!-- 审核表单 -->
<view class="form-section">
<view class="section-header">
<uni-icons type="edit" size="18" color="#AD181F"></uni-icons>
<text class="section-title">审核信息</text>
</view>
<view class="form-card">
<view class="form-item">
<text class="form-label">审批结果</text>
<view class="radio-group">
<view
class="radio-item"
:class="{ selected: form.flag === '1' }"
@click="form.flag = '1'"
>
<view class="radio-circle">
<uni-icons v-if="form.flag === '1'" type="checkmark" size="10" color="#fff"></uni-icons>
</view>
<text>审批通过</text>
</view>
<view
class="radio-item"
:class="{ selected: form.flag === '0' }"
@click="form.flag = '0'"
>
<view class="radio-circle">
<uni-icons v-if="form.flag === '0'" type="checkmark" size="10" color="#fff"></uni-icons>
</view>
<text>审批拒绝</text>
</view>
</view>
</view>
<view class="form-item">
<text class="form-label">备注</text>
<textarea
v-model="form.reason"
class="textarea"
placeholder="请输入备注信息(拒绝时必填)"
:maxlength="500"
/>
</view>
</view>
</view>
<!-- 提交按钮 -->
<view class="submit-wrap">
<button class="btn-cancel" @click="goBack">取消</button>
<button class="btn-submit" @click="doSubmit" :disabled="submitting">
<text v-if="!submitting">确认提交</text>
<text v-else>提交中...</text>
</button>
</view>
</view>
</template>
<script setup>
import * as api from '@/common/api_exam.js'
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
const type = ref('single') // single 或 batch
const ids = ref('')
const submitting = ref(false)
const form = ref({
flag: '1',
reason: ''
})
onLoad((options) => {
if (options.type) {
type.value = options.type
}
if (options.ids) {
ids.value = options.ids
}
})
function goBack() {
uni.navigateBack()
}
function doSubmit() {
if (form.value.flag === '0' && !form.value.reason) {
uni.showToast({
title: '请输入拒绝理由',
icon: 'none'
})
return
}
if (submitting.value) return
submitting.value = true
const params = {
ids: ids.value,
flag: form.value.flag,
reason: form.value.reason || ''
}
api.ztxAudit(params).then(res => {
uni.showToast({
title: '操作成功',
icon: 'success'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
}).catch(err => {
console.error('审核失败', err)
uni.showToast({
title: '操作失败',
icon: 'none'
})
submitting.value = false
})
}
</script>
<style lang="scss" scoped>
.audit-page {
min-height: 100vh;
background-color: #f5f5f5;
padding: 20rpx;
padding-bottom: 120rpx;
}
.tips-box {
background-color: #e8f8f7;
border-radius: 12rpx;
padding: 24rpx;
margin-bottom: 20rpx;
display: flex;
align-items: center;
gap: 12rpx;
.tips-text {
font-size: 26rpx;
color: #13B5B1;
}
}
.form-section {
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
}
.section-header {
display: flex;
align-items: center;
margin-bottom: 30rpx;
.section-title {
font-size: 30rpx;
font-weight: 600;
color: #333;
margin-left: 10rpx;
}
}
.form-card {
.form-item {
margin-bottom: 30rpx;
&:last-child {
margin-bottom: 0;
}
.form-label {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 16rpx;
font-weight: 500;
}
.radio-group {
display: flex;
gap: 40rpx;
}
.radio-item {
display: flex;
align-items: center;
gap: 12rpx;
font-size: 28rpx;
color: #333;
.radio-circle {
width: 36rpx;
height: 36rpx;
border-radius: 50%;
border: 2rpx solid #ddd;
display: flex;
align-items: center;
justify-content: center;
}
&.selected {
.radio-circle {
background-color: #13B5B1;
border-color: #13B5B1;
}
}
}
.textarea {
width: 100%;
height: 200rpx;
border: 1px solid #eee;
border-radius: 12rpx;
padding: 20rpx;
font-size: 28rpx;
color: #333;
background-color: #fafafa;
box-sizing: border-box;
}
}
}
.submit-wrap {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 110rpx;
background-color: #fff;
display: flex;
align-items: center;
justify-content: center;
gap: 30rpx;
padding: 0 30rpx;
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
button {
flex: 1;
height: 80rpx;
line-height: 80rpx;
font-size: 28rpx;
border-radius: 40rpx;
border: none;
}
.btn-cancel {
background-color: #f5f5f5;
color: #666;
}
.btn-submit {
background: linear-gradient(135deg, #13B5B1, #15c5c1);
color: #fff;
&[disabled] {
background: #ccc;
}
}
}
</style>
<template>
<view class="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.memName || '-' }}</text>
</view>
<view class="info-row">
<text class="label">所属协会</text>
<text class="value">{{ form.shenMemName || '-' }}</text>
</view>
<view class="info-row">
<text class="label">审核状态</text>
<text class="value" :class="getStatusClass(form.auditStatus)">
{{ getStatusText(form.auditStatus) }}
</text>
</view>
<view class="info-row">
<text class="label">是否需要</text>
<text class="value">{{ form.selfSelect == 1 ? '否' : '是' }}</text>
</view>
<view class="info-row">
<text class="label">会员有效期</text>
<text class="value">{{ formatDate(form.memValidDate) }}</text>
</view>
<view class="info-row">
<text class="label">申请日期</text>
<text class="value">{{ formatDate(form.commitTime) }}</text>
</view>
<view class="info-row">
<text class="label">审核日期</text>
<text class="value">{{ formatDate(form.auditTime) }}</text>
</view>
<view class="info-row">
<text class="label">考官</text>
<text class="value">{{ form.examiners || '-' }}</text>
</view>
</view>
</view>
<!-- 审核记录 -->
<view class="section" v-if="auditList.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-list">
<view class="audit-item" v-for="(item, index) in auditList" :key="index">
<view class="audit-dot" :class="item.auditResult == 2 ? 'pass' : 'fail'"></view>
<view class="audit-content">
<view class="audit-row">
<text class="audit-label">审核协会</text>
<text class="audit-value">{{ item.auditDeptName || '-' }}</text>
</view>
<view class="audit-row">
<text class="audit-label">审核时间</text>
<text class="audit-value">{{ formatDateTime(item.auditTime) }}</text>
</view>
<view class="audit-row">
<text class="audit-label">审核状态</text>
<text class="audit-value" :class="item.auditResult == 2 ? 'text-success' : 'text-danger'">
{{ item.auditResult == 2 ? '通过' : '拒绝' }}
</text>
</view>
<view class="audit-row" v-if="item.auditMsg">
<text class="audit-label">备注</text>
<text class="audit-value">{{ item.auditMsg }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 考官信息 -->
<view class="section" v-if="examinerList.length > 0">
<view class="section-header">
<uni-icons type="person" size="18" color="#AD181F"></uni-icons>
<text class="section-title">考官信息</text>
</view>
<view class="examiner-table" v-if="examinerList.length > 0">
<view class="table-header">
<view class="th th-name">姓名</view>
<view class="th th-code">会员号</view>
<view class="th th-idcard">证件号码</view>
</view>
<view class="table-body">
<view class="table-row" v-for="(item, index) in examinerList" :key="index">
<view class="td td-name">{{ item.perName }}</view>
<view class="td td-code">{{ item.perCode || '-' }}</view>
<view class="td td-idcard">{{ item.perIdcCode || '-' }}</view>
</view>
</view>
</view>
<view class="no-data" v-else>
<text>暂无考官信息</text>
</view>
</view>
</view>
</template>
<script setup>
import * as api from '@/common/api_exam.js'
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
const form = ref({})
const auditList = ref([])
const examinerList = ref([])
const auditStatusMap = { 1: '审核中', 2: '审核通过', 3: '审核拒绝' }
onLoad((options) => {
if (options.item) {
try {
const itemData = JSON.parse(decodeURIComponent(options.item))
form.value = itemData
if (itemData.auditList) {
auditList.value = itemData.auditList
}
if (itemData.memId) {
getExaminerList(itemData.memId)
}
} catch (e) {
console.error('解析数据失败', e)
}
}
})
function getExaminerList(memId) {
api.listInfo({ memId: memId }).then(res => {
if (res.rows) {
examinerList.value = res.rows
}
}).catch(err => {
console.error('获取考官列表失败', err)
})
}
function getStatusText(status) {
return auditStatusMap[status] || '-'
}
function getStatusClass(status) {
const classMap = {
1: 'text-warning',
2: 'text-success',
3: 'text-danger'
}
return classMap[status] || ''
}
function formatDate(dateStr) {
if (!dateStr) return '-'
return dateStr.substring(0, 10)
}
function formatDateTime(dateStr) {
if (!dateStr) return '-'
if (typeof dateStr === 'string' && dateStr.indexOf('T') > -1) {
const [date, time] = dateStr.split('T')
return `${date} ${time?.slice(0, 5) || ''}`
}
return dateStr
}
</script>
<style lang="scss" scoped>
.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;
}
}
.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-success {
color: #4caf50;
}
&.text-warning {
color: #ff9800;
}
&.text-danger {
color: #f44336;
}
}
}
}
/* 审核记录 */
.audit-list {
.audit-item {
display: flex;
padding: 20rpx 0;
border-bottom: 1px dashed #eee;
&:last-child {
border-bottom: none;
}
.audit-dot {
width: 14rpx;
height: 14rpx;
border-radius: 50%;
margin-top: 8rpx;
margin-right: 20rpx;
flex-shrink: 0;
&.pass {
background-color: #4caf50;
}
&.fail {
background-color: #f44336;
}
}
.audit-content {
flex: 1;
.audit-row {
display: flex;
margin-bottom: 10rpx;
&:last-child {
margin-bottom: 0;
}
.audit-label {
font-size: 24rpx;
color: #999;
width: 140rpx;
flex-shrink: 0;
}
.audit-value {
font-size: 26rpx;
color: #333;
flex: 1;
&.text-success {
color: #4caf50;
}
&.text-danger {
color: #f44336;
}
}
}
}
}
}
/* 考官表格 */
.examiner-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;
}
}
.th-name { width: 25%; }
.th-code { width: 30%; }
.th-idcard { width: 45%; }
.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;
}
}
.td-name { width: 25%; }
.td-code { width: 30%; }
.td-idcard { width: 45%; }
}
}
.no-data {
padding: 60rpx 0;
text-align: center;
color: #999;
font-size: 26rpx;
}
</style>
<template>
<view class="institution-page">
<view class="loading-wrap" v-if="loading">
<uni-load-more status="loading"></uni-load-more>
</view>
<view v-else>
<!-- 基本信息 -->
<view class="section">
<view class="section-header">
<uni-icons type="office" 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.parentDeptName || '-' }}</text>
</view>
<!-- <view class="info-row">
<text class="label">会员编号</text>
<text class="value">{{ form.menCode || '-' }}</text>
</view> -->
<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">{{ form.provinceStr + form.cityStr + form.regionStr || '-' }}</text>
</view>
<view class="info-row">
<text class="label">详细地址</text>
<text class="value">{{ form.adress || '-' }}</text>
</view>
<view class="info-row">
<text class="label">联系人</text>
<text class="value">{{ form.siteContact || '-' }}</text>
</view>
<view class="info-row">
<text class="label">联系方式</text>
<text class="value">{{ form.siteTel || '-' }}</text>
</view>
<view class="info-row" v-if="form.applyPoints !== undefined">
<text class="label">是否为考点</text>
<text class="value">{{ form.applyPoints == 1 ? '是' : '否' }}</text>
</view>
</view>
</view>
<!-- 营业执照 -->
<view class="section" v-if="form.businessLicense">
<view class="section-header">
<uni-icons type="document" 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.companyName || '-' }}</text>
</view>
<view class="info-row">
<text class="label">社会信用代码</text>
<text class="value">{{ form.creditCode || '-' }}</text>
</view>
<view class="info-row" v-if="form.validityDate">
<text class="label">有效期</text>
<text class="value">{{ form.validityDate }}</text>
</view>
<view class="info-row">
<text class="label">营业执照</text>
<view>
<template v-slot:footer>
<view style="width: 50vw;word-break: break-all;text-align: right;" @click="download(form.businessLicense[0]?.url)" v-if="form.businessLicense&&form.businessLicense?.length>0">
<text class="text-primary">{{form.businessLicense[0]?.name}}</text>
</view>
</template>
</view>
</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>
<view class="info-card">
<view class="info-row">
<text class="label">法人姓名</text>
<text class="value">{{ form.legal || '-' }}</text>
</view>
<view class="info-row">
<text class="label">法人证件号</text>
<text class="value">{{ form.legalIdcCode || '-' }}</text>
</view>
</view>
</view>
<!-- 证件照片 -->
<view class="section" v-if="form.legalIdcPhoto && form.legalIdcPhoto.length > 0">
<view class="section-header">
<uni-icons type="photo" size="18" color="#AD181F"></uni-icons>
<text class="section-title">法人证件照</text>
</view>
<view class="image-list">
<image
v-for="(img, index) in form.legalIdcPhoto"
:key="index"
class="preview-image"
:src="getImageUrl(img)"
mode="aspectFit"
@click="previewImage(form.legalIdcPhoto, index)"
/>
</view>
</view>
<!-- 机构照片 -->
<view class="section" v-if="form.pictures && form.pictures.length > 0">
<view class="section-header">
<uni-icons type="image" size="18" color="#AD181F"></uni-icons>
<text class="section-title">机构照片</text>
</view>
<view class="image-list">
<image
v-for="(img, index) in form.pictures"
:key="index"
class="preview-image"
:src="getImageUrl(img)"
mode="aspectFit"
@click="previewImage(form.pictures, index)"
/>
</view>
</view>
</view>
</view>
</template>
<script setup>
import * as api from '@/common/api.js'
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
// const config = require('@/config.js')
import config from '@/config.js'
const loading = ref(false)
const form = ref({})
onLoad((options) => {
if (options.memId) {
getInstitutionInfo(options.memId)
}
})
function download(url) {
console.log(url)
if (url.indexOf('.png') > -1 || url.indexOf('.jpg') > -1) {
if(url.indexOf('http')>-1){
uni.previewImage({
urls: [url],
success: function(res) {
console.log(res,[url],'111')
}
})
} else {
uni.previewImage({
urls: [config.baseUrl_api + url],
success: function(res) {
console.log(url,'222')
}
})
}
} else {
if(url.indexOf('http')>-1){
goWebView(url)
} else {
goWebView(config.baseUrl_api + url)
}
}
}
function goWebView(url) {
url = url.replace("http://", "https://")
uni.showLoading({
title: '下载中'
});
uni.downloadFile({
url: url,
success: function(res) {
uni.hideLoading();
var filePath = res.tempFilePath;
uni.showLoading({
title: '正在打开'
});
uni.openDocument({
filePath: filePath,
showMenu: true,
success: function(res) {
uni.hideLoading();
},
fail: function(err) {
uni.hideLoading();
uni.showToast({
title: err,
icon: 'none',
duration: 2000
});
}
});
},
fail: function(error) {
uni.hideLoading();
uni.showToast({
title:`下载失败`,
icon: 'none',
duration: 2000
});
}
});
}
function getInstitutionInfo(memId) {
loading.value = true
api.getGroupMemberInfoById(memId).then(res => {
loading.value = false
if (res.data) {
form.value = res.data
form.value.businessLicense = JSON.parse((form.value.businessLicense))
// 处理图片字段
if (form.value.pictures && typeof form.value.pictures === 'string') {
form.value.pictures = form.value.pictures.split(',').filter(Boolean)
}
if (form.value.legalIdcPhoto && typeof form.value.legalIdcPhoto === 'string') {
form.value.legalIdcPhoto = form.value.legalIdcPhoto.split(',').filter(Boolean)
}
}
}).catch(err => {
loading.value = false
console.error('获取机构资料失败', err)
uni.showToast({
title: '获取机构资料失败',
icon: 'none'
})
})
}
function getImageUrl(path) {
if (!path) return ''
if (path.startsWith('http')) return path
return config.baseUrl_api + path
}
function previewImage(images, currentIndex) {
const urls = images.map(img => getImageUrl(img))
uni.previewImage({
urls: urls,
current: currentIndex
})
}
</script>
<style lang="scss" scoped>
.institution-page {
min-height: 100vh;
background-color: #f5f5f5;
padding: 20rpx;
padding-bottom: 40rpx;
}
.loading-wrap {
padding: 100rpx 0;
text-align: center;
}
.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;
}
}
.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;
flex: 1;
word-break: break-all;
}
}
}
.image-list {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
.preview-image {
width: 200rpx;
height: 200rpx;
border-radius: 12rpx;
background-color: #f5f5f5;
}
}
</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'">
{{ 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>
<view>
证书人数
<view>{{ item.totalNum }}</view>
</view>
</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>
</view>
</view>
<view class="func">
<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>
</view>
</view>
</view>
<!-- 空数据 -->
<view class="nodata" v-if="infoList.length == 0 && !loading">
<image mode="aspectFit" src="/static/nodata.png"></image>
<text>暂无数据</text>
</view>
<!-- 上拉加载 -->
<view class="loading">
<uni-load-more :status="loadStatus" :contentText="loadMoreText"></uni-load-more>
</view>
</view>
</template>
<script setup>
import * as api from '@/common/api.js'
import { ref, computed } 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 queryParams = ref({
pageNum: 1,
pageSize: 10,
type: '1'
})
// 列表数据
const infoList = ref([])
const total = ref(0)
const deptType = ref('')
onLoad((options) => {
if (options.type) {
queryParams.value.type = options.type
}
})
onShow(() => {
if (app.globalData.isLogin) {
init()
} else {
app.firstLoadCallback = () => {
init()
}
}
})
// 初始化(刷新)
function init() {
deptType.value = app.globalData.deptType
// 重置列表和页码
infoList.value = []
queryParams.value.pageNum = 1
loadStatus.value = 'more'
getList()
}
// 获取列表
function getList() {
// 没有更多数据直接返回
if (loadStatus.value === 'nomore') return
loading.value = true
loadStatus.value = 'loading'
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
// 判断是否还有更多
if (infoList.value.length >= totalData) {
loadStatus.value = 'nomore'
} else {
loadStatus.value = 'more'
}
loading.value = false
uni.hideLoading()
}).catch(() => {
loading.value = false
uni.hideLoading()
loadStatus.value = 'more'
})
}
// 上拉加载下一页
onReachBottom(() => {
if (loadStatus.value !== 'more' || loading.value) return
queryParams.value.pageNum++
getList()
})
// 查看详情
function handleView(item) {
uni.navigateTo({
url: `/level/ztx/certDetail?examId=${item.examId}`
})
}
// 邮寄证书
async function handleMail(item) {
const res = await uni.showModal({
title: '提示',
content: '是否确认寄送快递?',
confirmText: '确认',
cancelText: '取消'
})
if (res.confirm) {
uni.showLoading({ title: '操作中' })
api.postCert(item.payId).then(() => {
uni.hideLoading()
uni.showToast({ title: '操作成功', icon: 'success' })
// 操作成功后刷新列表
init()
}).catch(() => {
uni.hideLoading()
})
}
}
// 物流跟踪
function handleLogistics(item) {
if (item.postStatus != 1 || !item.postCode) {
uni.showToast({ title: '暂无物流信息', icon: 'none' })
return
}
uni.navigateTo({
url: `/level/ztx/certLogistics?payId=${item.payId}&postCode=${item.postCode}&postStatus=${item.postStatus}&submitTime=${item.submitTime || ''}`
})
}
// 时间格式化
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
}
</script>
<style lang="scss" scoped>
.cert-mail-page {
min-height: 100vh;
background-color: #f5f5f5;
padding-bottom: 20rpx;
}
/* 列表区域 */
.appList {
padding: 0 20rpx;
}
.appItem {
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
.status {
display: inline-block;
padding: 6rpx 20rpx;
border-radius: 20rpx;
font-size: 24rpx;
margin-bottom: 16rpx;
.text-success {
color: #4caf50;
}
.text-warning {
color: #ff9800;
}
}
.date {
font-size: 24rpx;
color: #999;
}
.text-primary {
font-size: 28rpx;
color: #AD181F;
}
.name {
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 20rpx;
margin-top: 10rpx;
&.mt0 {
margin-top: 0;
}
}
.flexbox {
display: flex;
justify-content: space-between;
background-color: #fafafa;
border-radius: 12rpx;
padding: 20rpx;
margin: 20rpx 0;
view {
flex: 1;
text-align: center;
font-size: 24rpx;
color: #999;
view {
font-size: 28rpx;
color: #333;
font-weight: 500;
margin-top: 8rpx;
}
}
}
.mail-time {
font-size: 24rpx;
color: #666;
margin-top: 16rpx;
}
.func {
display: flex;
gap: 20rpx;
margin-top: 24rpx;
padding-top: 24rpx;
border-top: 1px solid #f0f0f0;
justify-content: flex-end;
button {
height: 64rpx;
line-height: 64rpx;
font-size: 26rpx;
border-radius: 32rpx;
border: none;
}
.btn-mail {
width: 100px;
background: linear-gradient(135deg, #4caf50, #66bb6a);
color: #fff;
}
.btn-logistics {
width: 100px;
background: linear-gradient(135deg, #2196f3, #42a5f5);
color: #fff;
}
}
}
.nodata {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
image {
width: 300rpx;
height: 300rpx;
}
text {
font-size: 28rpx;
color: #999;
margin-top: 20rpx;
}
}
.loading {
padding: 30rpx 0;
text-align: center;
}
</style>
\ No newline at end of file
<template>
<view class="audit-page">
<!-- 顶部标签栏 -->
<view class="tab-bar">
<view
v-for="tab in tabs"
:key="tab.value"
class="tab-item"
:class="{ active: currentTab === tab.value }"
@click="onTabChange(tab.value)"
>
<text class="tab-text">{{ tab.label }}</text>
<view class="tab-indicator" v-if="currentTab === tab.value"></view>
</view>
</view>
<!-- 列表内容 -->
<scroll-view
scroll-y
class="list-content"
@scrolltolower="onLoadMore"
>
<view class="loading-wrap" v-if="loading && list.length === 0">
<uni-load-more status="loading"></uni-load-more>
</view>
<view class="empty-wrap" v-else-if="!loading && list.length === 0">
<uni-load-more status="noMore"></uni-load-more>
</view>
<view class="card-list" v-else>
<view
class="card-item"
v-for="item in list"
:key="item.recordId || item.id"
@click="goDetail(item)"
>
<view class="card-header">
<text class="company-name">{{ item.memName || '-' }}</text>
<text class="status-badge" :class="getStatusClass(item.shenAuditStatus)">
{{ getStatusText(item.shenAuditStatus) }}
</text>
</view>
<view class="card-body">
<view class="info-row">
<text class="label">所属协会</text>
<text class="value">{{ item.shenMemName || '-' }}</text>
</view>
<view class="info-row">
<text class="label">会员编号</text>
<text class="value">{{ item.wfCode || '-' }}</text>
</view>
<view class="info-row">
<text class="label">认证年限</text>
<text class="value">{{ item.renewYear ? item.renewYear + '年' : '-' }}</text>
</view>
<view class="info-row">
<text class="label">付款费用</text>
<text class="value text-red">¥{{ Number(item.finalPrice || 0).toFixed(2) }}</text>
</view>
<view class="info-row">
<text class="label">提交日期</text>
<text class="value">{{ formatDate(item.commitTime) }}</text>
</view>
</view>
<view class="card-footer">
<view class="tag-row">
<view class="tag" :class="item.isNew == 1 ? 'tag-new' : 'tag-renew'">
{{ item.isNew == 1 ? '新会员' : '续费' }}
</view>
</view>
<view class="btn-row">
<view class="btn btn-default" @click.stop="goInstitutionInfo(item)">机构资料</view>
<view class="btn btn-primary" @click.stop="goAudit(item)" v-if="item.shenAuditStatus == 1">审核</view>
</view>
</view>
</view>
</view>
<view class="load-more-wrap" v-if="list.length > 0">
<uni-load-more :status="loadStatus"></uni-load-more>
</view>
</scroll-view>
</view>
</template>
<script setup>
import * as api from '@/common/api_exam.js'
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
const tabs = [
{ label: '全部', value: '' },
{ label: '审核中', value: '1' },
{ label: '审核通过', value: '2' },
{ label: '审核拒绝', value: '3' }
]
const currentTab = ref('')
const list = ref([])
const loading = ref(false)
const loadStatus = ref('more')
const queryParams = ref({
pageNum: 1,
pageSize: 10,
auditFlag:'1',
})
onLoad(() => {
getList()
})
function onTabChange(value) {
currentTab.value = value
list.value = []
queryParams.value.pageNum = 1
getList()
}
function getList() {
if (loading.value) return
loading.value = true
loadStatus.value = 'loading'
const params = {
...queryParams.value,
shenAuditStatus: currentTab.value
}
api.certifiedNewList(params).then(res => {
if (res.rows) {
if (queryParams.value.pageNum === 1) {
list.value = res.rows
} else {
list.value = [...list.value, ...res.rows]
}
loadStatus.value = res.rows.length >= queryParams.value.pageSize ? 'more' : 'noMore'
} else {
list.value = []
loadStatus.value = 'noMore'
}
}).catch(err => {
console.error('获取会员审核列表失败', err)
loadStatus.value = 'noMore'
}).finally(() => {
loading.value = false
})
}
function onLoadMore() {
if (loadStatus.value === 'noMore' || loading.value) return
queryParams.value.pageNum++
getList()
}
function getStatusText(status) {
const map = { 1: '审核中', 2: '审核通过', 3: '审核拒绝', 4: '已撤回' }
return map[status] || '-'
}
function getStatusClass(status) {
const map = {
1: 'status-warning',
2: 'status-success',
3: 'status-danger',
4: 'status-gray'
}
return map[status] || ''
}
function formatDate(dateStr) {
if (!dateStr) return '-'
if (typeof dateStr === 'string' && dateStr.indexOf('T') > -1) {
return dateStr.slice(0, 10)
}
return dateStr
}
function goDetail(item) {
uni.navigateTo({
url: `/group/groupOrderDetail?form=${encodeURIComponent(JSON.stringify(item))}`
})
}
function goInstitutionInfo(item) {
uni.navigateTo({
url: `/level/ztx/institutionInfo?memId=${item.memId}`
})
}
function goAudit(item) {
uni.navigateTo({
url: `/level/ztx/memberAuditPage?type=single&ids=${item.recordId || item.id}`
})
}
</script>
<style lang="scss" scoped>
.audit-page {
min-height: 100vh;
background-color: #f5f5f5;
}
.tab-bar {
display: flex;
background-color: #fff;
padding: 0 20rpx;
position: sticky;
top: 0;
z-index: 100;
.tab-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 24rpx 0;
position: relative;
.tab-text {
font-size: 28rpx;
color: #666;
}
.tab-indicator {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 80rpx;
height: 6rpx;
background-color: #AD181F;
border-radius: 3rpx;
}
&.active {
.tab-text {
color: #AD181F;
font-weight: 600;
}
}
}
}
.list-content {
height: calc(100vh - 100rpx);
padding: 20rpx;
box-sizing: border-box;
}
.loading-wrap,
.empty-wrap {
padding: 100rpx 0;
text-align: center;
}
.card-list {
.card-item {
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
padding-bottom: 20rpx;
border-bottom: 1rpx solid #f0f0f0;
.company-name {
font-size: 32rpx;
font-weight: 600;
color: #333;
flex: 1;
margin-right: 20rpx;
}
.status-badge {
font-size: 24rpx;
padding: 6rpx 16rpx;
border-radius: 20rpx;
flex-shrink: 0;
&.status-warning {
background-color: #fff7e6;
color: #fa8c16;
}
&.status-success {
background-color: #f6ffed;
color: #52c41a;
}
&.status-danger {
background-color: #fff1f0;
color: #f5222d;
}
&.status-gray {
background-color: #f5f5f5;
color: #999;
}
}
}
.card-body {
.info-row {
display: flex;
justify-content: space-between;
padding: 12rpx 0;
.label {
font-size: 26rpx;
color: #999;
flex-shrink: 0;
}
.value {
font-size: 26rpx;
color: #333;
text-align: right;
flex: 1;
margin-left: 20rpx;
}
.text-red {
color: #AD181F;
}
}
}
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20rpx;
padding-top: 20rpx;
border-top: 1rpx solid #f0f0f0;
.tag-row {
display: flex;
gap: 12rpx;
.tag {
font-size: 24rpx;
padding: 6rpx 16rpx;
border-radius: 20rpx;
font-weight: 500;
&.tag-new {
background-color: rgba(#AD181F, 0.1);
color: #AD181F;
}
&.tag-renew {
background-color: rgba(#999, 0.1);
color: #666;
}
}
}
.btn-row {
display: flex;
gap: 16rpx;
.btn {
font-size: 26rpx;
padding: 12rpx 24rpx;
border-radius: 8rpx;
}
.btn-default {
background-color: #f5f5f5;
color: #666;
}
.btn-primary {
background-color: #AD181F;
color: #fff;
}
}
}
}
}
.load-more-wrap {
padding: 20rpx 0;
text-align: center;
}
</style>
<template>
<view class="audit-page">
<!-- 提示信息 -->
<!-- <view class="tips-box">
<uni-icons type="info" size="18" color="#C4121B"></uni-icons>
<text class="tips-text" v-if="type === 'batch'">批量审核 {{ ids.split(',').length }} 条记录</text>
<text class="tips-text" v-else>单个审核</text>
</view> -->
<!-- 审核表单 -->
<view class="form-section">
<view class="section-header">
<uni-icons type="edit" size="18" color="#AD181F"></uni-icons>
<text class="section-title">审核信息</text>
</view>
<view class="form-card">
<view class="form-item">
<text class="form-label">审批结果</text>
<view class="radio-group">
<view
class="radio-item"
:class="{ selected: form.flag === '1' }"
@click="form.flag = '1'"
>
<view class="radio-circle">
<uni-icons v-if="form.flag === '1'" type="checkmark" size="10" color="#fff"></uni-icons>
</view>
<text>审批通过</text>
</view>
<view
class="radio-item"
:class="{ selected: form.flag === '0' }"
@click="form.flag = '0'"
>
<view class="radio-circle">
<uni-icons v-if="form.flag === '0'" type="checkmark" size="10" color="#fff"></uni-icons>
</view>
<text>审批拒绝</text>
</view>
</view>
</view>
<view class="form-item">
<text class="form-label">备注</text>
<textarea
v-model="form.reason"
class="textarea"
placeholder="请输入备注信息(拒绝时必填)"
:maxlength="500"
/>
</view>
</view>
</view>
<!-- 提交按钮 -->
<view class="submit-wrap">
<button class="btn-cancel" @click="goBack">取消</button>
<button class="btn-submit" @click="doSubmit" :disabled="submitting">
<text v-if="!submitting">确认提交</text>
<text v-else>提交中...</text>
</button>
</view>
</view>
</template>
<script setup>
import * as api from '@/common/api_exam.js'
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
const type = ref('single') // single 或 batch
const ids = ref('')
const submitting = ref(false)
const form = ref({
flag: '1',
reason: ''
})
onLoad((options) => {
if (options.type) {
type.value = options.type
}
if (options.ids) {
ids.value = options.ids
}
})
function goBack() {
uni.navigateBack()
}
function doSubmit() {
if (form.value.flag === '0' && !form.value.reason) {
uni.showToast({
title: '请输入拒绝理由',
icon: 'none'
})
return
}
if (submitting.value) return
submitting.value = true
const params = {
ids: ids.value,
flag: form.value.flag,
reason: form.value.reason || ''
}
api.shenAudit(params).then(res => {
uni.showToast({
title: '操作成功',
icon: 'success'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
}).catch(err => {
console.error('审核失败', err)
uni.showToast({
title: '操作失败',
icon: 'none'
})
submitting.value = false
})
}
</script>
<style lang="scss" scoped>
.audit-page {
min-height: 100vh;
background-color: #f5f5f5;
padding: 20rpx;
// padding-bottom: 120rpx;
box-sizing: border-box;
}
.tips-box {
background-color: #e8f8f7;
border-radius: 12rpx;
padding: 24rpx;
margin-bottom: 20rpx;
display: flex;
align-items: center;
gap: 12rpx;
.tips-text {
font-size: 26rpx;
color: #C4121B;
}
}
.form-section {
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
}
.section-header {
display: flex;
align-items: center;
margin-bottom: 30rpx;
.section-title {
font-size: 30rpx;
font-weight: 600;
color: #333;
margin-left: 10rpx;
}
}
.form-card {
.form-item {
margin-bottom: 30rpx;
&:last-child {
margin-bottom: 0;
}
.form-label {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 16rpx;
font-weight: 500;
}
.radio-group {
display: flex;
gap: 40rpx;
}
.radio-item {
display: flex;
align-items: center;
gap: 12rpx;
font-size: 28rpx;
color: #333;
.radio-circle {
width: 36rpx;
height: 36rpx;
border-radius: 50%;
border: 2rpx solid #ddd;
display: flex;
align-items: center;
justify-content: center;
}
&.selected {
.radio-circle {
background-color: #C4121B;
border-color: #C4121B;
}
}
}
.textarea {
width: 100%;
height: 200rpx;
border: 1rpx solid #eee;
border-radius: 12rpx;
padding: 20rpx;
font-size: 28rpx;
color: #333;
background-color: #fafafa;
box-sizing: border-box;
}
}
}
.submit-wrap {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 110rpx;
background-color: #fff;
display: flex;
align-items: center;
justify-content: center;
gap: 30rpx;
padding: 0 30rpx;
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
button {
flex: 1;
height: 80rpx;
line-height: 80rpx;
font-size: 28rpx;
border-radius: 40rpx;
border: none;
}
.btn-cancel {
background-color: #f5f5f5;
color: #666;
}
.btn-submit {
background: #C4121B;
// background: linear-gradient(135deg, #C4121B, #15c5c1);
color: #fff;
&[disabled] {
background: #ccc;
}
}
}
</style>
......@@ -38,7 +38,7 @@
"quickapp" : {},
/* 小程序特有相关 */
"mp-weixin" : {
"appid" : "wx523ee37fff4fea9d",
"appid" : "wx5d51e8ed31bbdbb7",
"setting" : {
"urlCheck" : false,
"minified" : false,
......
......@@ -738,6 +738,110 @@
},
{
"path": "ztx/mail",
"style": {
"navigationBarTitleText": "证书邮寄",
"enablePullDownRefresh": false
}
},
{
"path": "ztx/certDetail",
"style": {
"navigationBarTitleText": "考试详情",
"enablePullDownRefresh": false
}
},
{
"path": "ztx/certLogistics",
"style": {
"navigationBarTitleText": "物流跟踪",
"enablePullDownRefresh": false
}
},
{
"path": "ztx/examinationVerification",
"style": {
"navigationBarTitleText": "考点审核",
"enablePullDownRefresh": false
}
},
{
"path": "ztx/examinationDetail",
"style": {
"navigationBarTitleText": "查看详情",
"enablePullDownRefresh": false
}
},
{
"path": "ztx/examinationAudit",
"style": {
"navigationBarTitleText": "单位会员认证审核",
"enablePullDownRefresh": false
}
},
{
"path": "ztx/institutionInfo",
"style": {
"navigationBarTitleText": "机构资料",
"enablePullDownRefresh": false
}
},
{
"path": "ztx/memberAudit",
"style": {
"navigationBarTitleText": "会员审核",
"enablePullDownRefresh": false
}
},
{
"path": "ztx/memberAuditPage",
"style": {
"navigationBarTitleText": "审核",
"enablePullDownRefresh": false
}
},
{
"path": "ztx/costSettlement",
"style": {
"navigationBarTitleText": "费用结算",
"enablePullDownRefresh": false
}
},
{
"path": "ztx/costSettlementDetail",
"style": {
"navigationBarTitleText": "结算详情",
"enablePullDownRefresh": false
}
},
{
"path": "ztx/costSettlementAdd",
"style": {
"navigationBarTitleText": "新建结算申请",
"enablePullDownRefresh": false
}
},
{
"path": "ztx/costSettlementConfirm",
"style": {
"navigationBarTitleText": "结算申请确认",
"enablePullDownRefresh": false
}
},
{
"path": "examStudentList",
"style": {
"navigationBarTitleText": "考生信息",
......
This diff could not be displayed because it is too large.
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!