843f28df by lttnew

个人会员中心+注册+bug

1 parent 8b3e35cc
......@@ -1714,6 +1714,14 @@ export function unbindUser() {
})
}
// 电子会员证下载
export function downStuCertSingle(pId) {
return request({
url: `/person/info/downStuCertSingle/${pId}`,
method: 'post'
})
}
/**
* 订单列表
* @param params
......@@ -2028,3 +2036,45 @@ export function memBerAuditList(params) {
params: params
})
}
/**
* 结算审核列表
* @param params
* @returns {*}
*/
export function settlementList(params) {
return request({
url: '/exam/paymentSubmit/list',
method: 'get',
params: params
})
}
/**
* 结算审核(通过/拒绝)
* @param data { ids, type, reason }
* @returns {*}
*/
export function settlementAudit(data) {
return request({
url: `/exam/paymentSubmit/audit/${data.ids}`,
method: 'post',
params: {
type: data.type,
reason: data.reason
}
})
}
/**
* 结算详情列表(根据结算单ID获取关联的缴费单)
* @param params { payIds, pageNum, pageSize }
* @returns {*}
*/
export function settlementDetailList(params) {
return request({
url: '/exam/payment/list',
method: 'get',
params: params
})
}
......
......@@ -120,7 +120,8 @@ function requestPaymentCredential(encodedData) {
header: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: encodedData
data: encodedData,
timeout: 60000 // 60秒超时,与业务请求保持一致
})
}
......
......@@ -60,10 +60,13 @@ function handleTokenExpire() {
// 显示错误提示
function showError(msg) {
console.log('showError called:', msg)
// 先隐藏可能存在的 loading,确保 Toast 能显示
uni.hideLoading()
uni.showToast({
title: msg || '请求失败',
icon: 'none',
duration: 2000
duration: 3000
})
}
......@@ -96,7 +99,7 @@ const request = function (req) {
timeout: req.timeout || 60000 * 5 // 添加超时设置
}).then(response => {
const {statusCode, data} = response
// HTTP状态码处理
if (statusCode != 200) {
throw new Error(`HTTP ${statusCode}`)
......@@ -118,14 +121,21 @@ const request = function (req) {
reject(new Error('登录已过期,请重新登录'))
return
}
// 其他业务错误
showError(data.msg || '请求失败')
reject(new Error(data.msg || '请求失败'))
// 业务错误(code != 200 且非 Token 过期)- 统一显示错误提示
const errorMsg = data.msg || '操作失败'
showError(errorMsg)
// code == 500 时仅显示提示,不抛出异常,避免页面 catch 覆盖错误信息
if (data.code == 500) {
return
}
reject(new Error(errorMsg))
}).catch(error => {
console.error('请求失败:', error)
// 网络错误处理
if (error.errMsg) {
if (error.errMsg.includes('timeout')) {
......@@ -136,12 +146,15 @@ const request = function (req) {
showError('请求失败,请稍后重试')
}
}
reject(error)
}).finally(() => {
if (req.showLoading != false) {
hideLoading()
}
// 延迟隐藏 loading,确保错误提示能显示
setTimeout(() => {
if (req.showLoading != false) {
hideLoading()
}
}, 100)
})
})
}
......
......@@ -13,16 +13,16 @@
<view class="vipData mt30" style="flex-wrap: wrap;padding: 20rpx;">
<view class="w50">
年限合计:
<text>{{ form?.renewYear }}</text>
<text>{{ form?.renewYear }}</text>
</view>
<view class="w50">
费用合计:
<text>{{ form?.allPrice }}</text>
<text>{{ form?.allPrice }}</text>
</view>
<view class="w50">
政策优惠:
<text>{{ form?.discount }}</text>
</view>
<text>{{ form?.discount }}</text>
</view>
<view class="w50">
付款费用:
<text>{{ (form?.finalPrice * 1).toFixed(2) }}</text>
......
......@@ -117,7 +117,7 @@
</view>
<view class="level-item">
<text class="level-label">考试级别</text>
<view class="select-wrapper" @click="changeLevelfather(n)">
<view class="select-wrapper exam-level-select" @click="changeLevelfather(n)">
<uni-data-select v-model="n.levelNew" :clear="false" :localdata="levelArr" @change="changeLevel"/>
</view>
</view>
......@@ -426,11 +426,14 @@ function getChosedStudentList() {
} else {
d.levelRecommend = '9'
}
if (!d.levelNew) {
// 原级别是一级时,levelNew 默认为空,不使用推荐值
if (d.levelOld === '1') {
d.levelNew = ''
} else if (!d.levelNew) {
d.levelNew = d.levelRecommend
}
if (!d.isPass) {
d.isPass = '1'
}
......@@ -475,10 +478,21 @@ function szToHz(num) {
let nowRow
function changeLevelfather(row) {
nowRow = row
api.jiDropDownBox({
perId: row.perId
function changeLevelfather(row, flag = 0) {
const data = _.map(infoList.value, (d) => {
return {
id: d.id,
levelNew: d.levelNew,
// score: d.score,
isPass: d.isPass
}
})
api.editLevel({
examId: form.value.examId,
personInfo: JSON.stringify(data),
transcript: form.value.transcript,
// perId: row.perId,
status: flag
}).then(res => {
levelArr.value = res.data
for (let l of levelArr.value) {
......@@ -489,62 +503,64 @@ function changeLevelfather(row) {
}
function changeLevel(e) {
if (e == nowRow.levelOld) {
uni.showToast({title: '考试级别重复,请重新选择!', icon: 'none'})
nowRow.levelNew = nowRow.levelRecommend
return
}
if (e !== nowRow.levelRecommend) {
uni.showModal({
title: '提示',
content: `建议考试级别为 "${szToHz(nowRow.levelRecommend)}级" ,确定要修改为${szToHz(e)}级吗?`,
success: function (res) {
if (res.confirm) {
// 切换考试级别时弹出确认框
uni.showModal({
title: '提示',
content: '请仔细核实本次等级申报是否正确!',
success: function (res) {
if (res.confirm) {
// 保存当前行数据
const data = _.map(infoList.value, (d) => {
return {
id: d.id,
levelNew: d.levelNew,
isPass: d.isPass
}
})
api.editLevel({
examId: form.value.examId,
personInfo: JSON.stringify(data),
transcript: form.value.transcript,
status: 0
}).then(() => {
// 保存成功后更新统计
getTablePersonInfo()
} else {
nowRow.levelNew = nowRow.levelRecommend
}
},
fail: function (res) {
nowRow.levelNew = nowRow.levelRecommend
})
} else {
// 取消时恢复为推荐级别
nowRow.levelNew = nowRow.levelRecommend != 0 ? nowRow.levelRecommend : ''
}
})
}
},
fail: function (res) {
nowRow.levelNew = nowRow.levelRecommend != 0 ? nowRow.levelRecommend : ''
}
})
}
function submitForm2(flag) {
// 循环校验考试级别
// 循环校验考试级别是否选择(考试级别重复可以提交)
for (let item of infoList.value) {
if (item.levelNew == item.levelOld) {
uni.showToast({title: `${item.realName}考试级别重复,请重新选择!`, icon: 'none'})
return
}
if (!item.levelNew) {
uni.showToast({title: `${item.realName}请选择考试级别!`, icon: 'none'})
return
}
}
if (flag === 1) {
if (infoList.value.length == 0) {
uni.showToast({title: '请选择考生', icon: 'none'})
return
}
uni.showModal({
title: '提示',
content: `请确认人员照片是否已更新?`,
success: function (res) {
if (res.confirm) {
// saveStep2(flag).then(() => {
// uni.showToast({title: '提交成功', icon: 'none'})
// uni.navigateTo({
// url: `/level/paymentDetail?examId=${form.value.examId}`
// })
// })
uni.navigateTo({
url: `/level/paymentDetail?examId=${form.value.examId}`
saveStep2(flag).then(() => {
uni.navigateTo({
url: `/level/paymentDetail?examId=${form.value.examId}`
})
})
}
}
......@@ -866,7 +882,15 @@ function handleDelete(row) {
}
.select-wrapper {
width: 120rpx;
width: 160rpx;
position: relative;
z-index: 99;
}
.exam-level-select {
position: relative;
z-index: 999;
margin-bottom: 300rpx;
}
}
}
......
......@@ -23,9 +23,10 @@
<view class="date">注册地:{{n.memName||'-'}}</view>
</view>
<view class="status">
<text v-if="isChosen(n)" class="text-gray">已选</text>
<text v-if="isChosen(n)" class="text-chosen">已选</text>
<!-- <text v-else-if="n.canChoose != 1" class="text-gray">不可选</text> -->
<text v-if="n.canChoose == 1" class="text-primary" @click="handleChoose(n)">选择</text>
<text v-else-if="n.canChoose == 1" class="text-primary" @click="handleChoose(n)">选择</text>
<text v-else class="text-gray">不可选</text>
</view>
</view>
</view>
......@@ -146,6 +147,15 @@
}
function handleChoose(row) {
// 校验是否已被其他位置选中
if (isChosen(row)) {
uni.showToast({
title: '该考官已被选中,不能重复选择',
icon: 'none'
})
return
}
if (row.canChoose != 1) {
uni.showToast({
title: '该考官资质已过期!',
......@@ -215,7 +225,7 @@
success: (res) => {
if (res.confirm) {
uni.showLoading({ title: '添加中' })
api.selfAdd(app.globalData.memberInfo.memId, row.perId).then(() => {
api.selfAdd(row.perId).then(() => {
uni.hideLoading()
uni.showToast({ title: '添加成功', icon: 'success' })
addPopup.value.close()
......@@ -317,6 +327,11 @@
color: #999;
}
.text-chosen {
color: #AD181F;
font-weight: bold;
}
.text-danger {
color: #dd524d;
}
......
<template>
<view class="settlement-audit-page">
<!-- Tab 切换 -->
<view class="tab-bar">
<view
v-for="tab in tabs"
:key="tab.value"
:class="['tab-item', { active: status === tab.value }]"
@click="onTabChange(tab.value)"
>
{{ tab.text }}
</view>
</view>
<!-- 列表区域 -->
<scroll-view scroll-y class="list-container" @scrolltolower="loadMore">
<view v-for="item in infoList" :key="item.id" class="list-item">
<!-- 选择框 -->
<view class="checkbox-wrap">
<checkbox-group @change="onCheckboxChange(item.id)">
<label>
<checkbox :value="item.id" :checked="isChecked(item.id)" :disabled="item.status != '1'" color="#C4121B" />
</label>
</checkbox-group>
</view>
<!-- 头部信息 -->
<view class="item-header">
<text class="code" @click="goDetail(item)">{{ item.code }}</text>
<text :class="['status-tag', 'status-' + item.status]">{{ statusArr[item.status] }}</text>
</view>
<!-- 主体信息 -->
<view class="item-body">
<view class="info-row">
<text class="label">结算名称</text>
<text class="value">{{ item.name }}</text>
</view>
<view class="info-row">
<text class="label">结算单位</text>
<text class="value">{{ item.memName }}</text>
</view>
<view class="info-row">
<text class="label">考试人数</text>
<text class="value">{{ item.personCount }}</text>
</view>
<view class="info-row">
<text class="label">费用合计</text>
<text class="value primary">¥{{ item.originPrice }}</text>
</view>
<view class="info-row">
<text class="label">结算金额</text>
<text class="value red">¥{{ item.price }}</text>
</view>
<view class="info-row">
<text class="label">结算状态</text>
<text :class="['value', item.status == '2' ? 'green' : item.status == '3' ? 'red' : '']">{{ item.verityStatusStr }}</text>
</view>
</view>
<!-- 底部信息 -->
<view class="item-footer">
<view class="footer-left">
<text class="date">提交: {{ formatDate(item.commitTime) }}</text>
<text v-if="item.auditTime" class="date">审核: {{ formatDate(item.auditTime) }}</text>
</view>
<view class="footer-right">
<text v-if="item.fileUrl" class="invoice-btn" @click="downloadInvoice(item)">发票</text>
<view v-else class="invoice-btn disabled">发票</view>
<button
v-if="item.status == '1'"
class="btn-settle"
@click="openSettlement(item)"
>结算</button>
</view>
</view>
</view>
<!-- 空状态 -->
<view v-if="infoList.length === 0 && !loading" class="empty">
<text>暂无数据</text>
</view>
<!-- 加载更多 -->
<view v-if="loading" class="loading-more">
<text>加载中...</text>
</view>
<view v-if="noMore && infoList.length > 0" class="loading-more">
<text>没有更多了</text>
</view>
</scroll-view>
<!-- 底部统计和批量按钮 -->
<view v-if="selectedIds.length > 0" class="bottom-bar">
<view class="stat-left">
<text class="stat-item">人数: <text class="value">{{ selectedStatistical.personCount }}</text></text>
<text class="stat-item">费用: <text class="value">¥{{ selectedStatistical.originPrice?.toFixed(2) }}</text></text>
<text class="stat-item">结算: <text class="value red">¥{{ selectedStatistical.price?.toFixed(2) }}</text></text>
</view>
<view class="btn-right">
<button class="batch-btn" @click="openBatchSettlement">
批量结算审核 ({{ selectedIds.length }})
</button>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import {onShow} from '@dcloudio/uni-app'
import * as api from '@/common/api.js'
import config from '@/config.js'
import _ from 'underscore'
const tabs = ref([
{ value: '', text: '全部' },
{ value: '1', text: '待结算' },
{ value: '2', text: '结算通过' },
{ value: '3', text: '结算拒绝' }
])
const statusArr = ref(['未提交', '待结算', '结算通过', '结算拒绝'])
const status = ref('')
const infoList = ref([])
const loading = ref(false)
const noMore = ref(false)
const pageNum = ref(1)
const pageSize = ref(10)
const total = ref(0)
const selectedIds = ref([])
const statistical = ref({
personCount: 0,
originPrice: 0,
price: 0
})
// 选中项的统计
const selectedStatistical = computed(() => {
let personCount = 0
let originPrice = 0
let price = 0
_.each(infoList.value, (info) => {
if (selectedIds.value.includes(info.id)) {
personCount += (info.personCount * 1)
originPrice += (info.originPrice * 1)
price += (info.price * 1)
}
})
return { personCount, originPrice, price }
})
onMounted(() => {
getList()
})
function onTabChange(value) {
status.value = value
pageNum.value = 1
infoList.value = []
selectedIds.value = []
noMore.value = false
getList()
}
async function getList() {
if (loading.value) return
loading.value = true
try {
const params = {
status: status.value,
pageNum: pageNum.value,
pageSize: pageSize.value
}
const res = await api.settlementList(params)
if (pageNum.value === 1) {
infoList.value = res.rows || []
} else {
infoList.value = [...infoList.value, ...(res.rows || [])]
}
total.value = res.total || 0
noMore.value = infoList.value.length >= total.value
// 计算统计
calculateStatistical()
} catch (err) {
console.error('获取结算列表失败', err)
} finally {
loading.value = false
}
}
function calculateStatistical() {
statistical.value = {
personCount: 0,
originPrice: 0,
price: 0
}
_.each(infoList.value, (info) => {
statistical.value.personCount += (info.personCount * 1)
statistical.value.originPrice += (info.originPrice * 1)
statistical.value.price += (info.price * 1)
})
}
function loadMore() {
if (!noMore.value && !loading.value) {
pageNum.value++
getList()
}
}
function formatDate(dateStr) {
if (!dateStr) return '-'
return dateStr.substring(0, 10)
}
// 复选框选择
function onCheckboxChange(id) {
const index = selectedIds.value.indexOf(id)
if (index > -1) {
selectedIds.value.splice(index, 1)
} else {
selectedIds.value.push(id)
}
}
function isChecked(id) {
return selectedIds.value.includes(id)
}
// 进入详情页
function goDetail(item) {
const data = encodeURIComponent(JSON.stringify(item))
uni.navigateTo({
url: `/level/settlementView?data=${data}`
})
}
// 下载发票
function downloadInvoice(item) {
if (!item.fileUrl) {
uni.showToast({ title: '暂无发票', icon: 'none' })
return
}
try {
const invoice = JSON.parse(item.fileUrl)
if (invoice && invoice[0]?.url) {
let fileUrl = invoice[0].url
// 确保 URL 格式正确
if (!fileUrl.startsWith('http')) {
fileUrl = config.baseUrl_api + fileUrl
}
// 小程序中使用 previewImage 预览,让用户长按保存
uni.previewImage({
urls: [fileUrl],
success: () => {
console.log('previewImage success')
},
fail: (err) => {
console.error('previewImage fail:', err)
uni.showToast({ title: '打开图片失败', icon: 'none' })
}
})
} else {
uni.showToast({ title: '发票文件不存在', icon: 'none' })
}
} catch (e) {
console.error('解析发票数据失败', e)
uni.showToast({ title: '解析发票数据失败', icon: 'none' })
}
}
// 打开结算审核 - 跳转到审核页面
function openSettlement(item) {
const ids = item.id
uni.navigateTo({
url: `/pages/rank/scoreAudit?ids=${ids}&pageType=3`
})
}
// 批量结算审核 - 跳转到审核页面
function openBatchSettlement() {
if (selectedIds.value.length === 0) {
uni.showToast({ title: '请先选择要结算的项目', icon: 'none' })
return
}
const ids = selectedIds.value.join(',')
uni.navigateTo({
url: `/pages/rank/scoreAudit?ids=${ids}&pageType=3`
})
}
function handleQuery() {
pageNum.value = 1
infoList.value = []
noMore.value = false
getList()
}
// 页面显示时刷新列表
onShow(() => {
handleQuery()
})
</script>
<style lang="scss" scoped>
.settlement-audit-page {
min-height: 100vh;
background: #f5f5f5;
}
/* Tab 切换 */
.tab-bar {
display: flex;
background: #fff;
padding: 20rpx 0;
.tab-item {
flex: 1;
text-align: center;
font-size: 28rpx;
color: #666;
position: relative;
&.active {
color: #C4121B;
font-weight: 600;
&::after {
content: '';
position: absolute;
bottom: -10rpx;
left: 50%;
transform: translateX(-50%);
width: 60rpx;
height: 4rpx;
background: #C4121B;
border-radius: 2rpx;
}
}
}
}
/* 列表区域 */
.list-container {
height: calc(100vh - 180rpx);
}
.list-item {
position: relative;
background: #fff;
margin: 20rpx 30rpx;
border-radius: 12rpx;
padding: 24rpx;
}
/* 复选框 */
.checkbox-wrap {
position: absolute;
top: 44rpx;
left: 20rpx;
transform: translateY(-50%);
z-index: 10;
}
.item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
padding-left: 60rpx;
.code {
font-size: 30rpx;
color: #1561cb;
font-weight: 600;
}
.status-tag {
font-size: 24rpx;
padding: 4rpx 16rpx;
border-radius: 20rpx;
&.status-1 { background: #fff3e0; color: #ff9800; }
&.status-2 { background: #e8f5e9; color: #4caf50; }
&.status-3 { background: #ffebee; color: #f44336; }
}
}
.item-body {
.info-row {
display: flex;
justify-content: space-between;
padding: 6rpx 0;
.label {
font-size: 26rpx;
color: #666;
}
.value {
font-size: 26rpx;
color: #333;
&.primary { color: #333; }
&.red { color: #C4121B; font-weight: 600; }
&.green { color: #4caf50; font-weight: 600; }
}
}
}
.item-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 16rpx;
padding-top: 16rpx;
border-top: 1rpx solid #f0f0f0;
.footer-left {
display: flex;
flex-direction: column;
.date {
font-size: 24rpx;
color: #999;
margin-bottom: 4rpx;
}
}
.footer-right {
display: flex;
align-items: center;
gap: 16rpx;
.invoice-btn {
font-size: 26rpx;
color: #1561cb;
&.disabled {
color: #ccc;
}
}
.btn-settle {
padding: 0rpx 20rpx;
background: #fff;
color: #C4121B;
font-size: 24rpx;
border: 2rpx solid #C4121B;
border-radius: 35rpx;
width: 150rpx;
&::after {
border: none;
}
}
}
}
/* 空状态 */
.empty {
text-align: center;
padding: 100rpx 0;
color: #999;
font-size: 28rpx;
}
.loading-more {
text-align: center;
padding: 20rpx;
color: #999;
font-size: 24rpx;
}
/* 底部栏 */
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
padding: 30rpx;
background: #fff;
border-top: 1rpx solid #eee;
box-sizing: border-box;
.stat-left {
flex: 1;
display: flex;
gap: 20rpx;
.stat-item {
font-size: 24rpx;
color: #666;
.value {
font-weight: 600;
color: #333;
&.red {
color: #C4121B;
}
}
}
}
.btn-right {
.batch-btn {
padding: 0rpx 30rpx;
background: #C4121B;
color: #fff;
font-size: 28rpx;
border-radius: 40rpx;
&::after {
border: none;
}
}
}
}
</style>
<template>
<view>
<scroll-view scroll-y class="detail-content">
<!-- 结算基本信息 -->
<view class="card">
<view class="card-header">
<view class="header-left">结算信息</view>
</view>
<view class="card-body">
<view class="info-row">
<text class="label">结算编号</text>
<text class="value">{{ form.code || '--' }}</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.memName || '--' }}</text>
</view>
<view class="info-row">
<text class="label">考试人数</text>
<text class="value">{{ form.personCount || 0 }}</text>
</view>
<view class="info-row">
<text class="label">费用合计</text>
<text class="value">¥{{ form.originPrice || 0 }}</text>
</view>
<view class="info-row">
<text class="label">结算金额</text>
<text class="value text-red">¥{{ form.price || 0 }}</text>
</view>
<view class="info-row">
<text class="label">支付方式</text>
<text class="value">民生付</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" v-if="form.fileUrl">
<text class="label">发票</text>
<text class="value text-primary" @click="downloadInvoice">下载发票</text>
</view>
</view>
</view>
<!-- 关联缴费单 -->
<view class="card">
<view class="card-header">
<view class="header-left">关联缴费单</view>
</view>
<!-- 统计信息 -->
<view class="payment-stat" v-if="paymentList.length > 0">
<text>费用合计: <text class="stat-value">¥{{ paymentStat.totalAmount?.toFixed(2) }}</text></text>
<text>人数合计: <text class="stat-value">{{ paymentStat.totalNum }}</text></text>
</view>
<view v-if="loadingPayment" class="state-tip">加载中...</view>
<view v-else-if="paymentList.length === 0" class="state-tip">暂无关联缴费单</view>
<view class="payment-list" v-else>
<view class="payment-item" v-for="(pay, index) in paymentList" :key="pay.payId">
<!-- 头部:序号 + 名称 + 状态 -->
<view class="item-header">
<!-- <view class="item-index">{{ index + 1 }}</view> -->
<view class="item-name">{{ pay.name }}</view>
<view :class="['status-tag', pay.submitId ? 'passed' : 'pending']">
{{ pay.submitId ? '已结算' : '未结算' }}
</view>
</view>
<!-- 主体:单列列表 -->
<view class="item-body">
<view class="info-line">
<text class="info-label">缴费编号</text>
<text class="info-value">{{ pay.payCode || '--' }}</text>
</view>
<view class="info-line">
<text class="info-label">缴费单位</text>
<text class="info-value">{{ pay.memberName || '--' }}</text>
</view>
<view class="info-line">
<text class="info-label">考试人数</text>
<text class="info-value">{{ pay.totalNum || 0 }}</text>
</view>
<view class="info-line">
<text class="info-label">总金额</text>
<text class="info-value text-red">¥{{ pay.totalAmount || 0 }}</text>
</view>
<view class="info-line">
<text class="info-label">支付方式</text>
<text class="info-value">民生付</text>
</view>
<view class="info-line">
<text class="info-label">审核状态</text>
<text :class="['info-value', getVerifyStatusClass(pay.verityStatus)]">{{ pay.verityStatusStr || '--' }}</text>
</view>
<view class="info-line">
<text class="info-label">提交日期</text>
<text class="info-value">{{ formatDate(pay.submitTime) }}</text>
</view>
<view class="info-line">
<text class="info-label">审核日期</text>
<text class="info-value">{{ formatDate(pay.verityDate) }}</text>
</view>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import * as api from '@/common/api.js'
import config from '@/config.js'
const loading = ref(false)
const loadingPayment = ref(false)
const form = ref({})
const paymentList = ref([])
const paymentStat = ref({
totalAmount: 0,
totalNum: 0
})
onLoad((options) => {
if (options.data) {
try {
form.value = JSON.parse(decodeURIComponent(options.data))
} catch (e) {
console.error('解析结算数据失败', e)
}
}
})
onMounted(() => {
if (form.value.payids) {
getPaymentList()
}
})
async function getPaymentList() {
loadingPayment.value = true
paymentStat.value = { totalAmount: 0, totalNum: 0 }
try {
const res = await api.settlementDetailList({ payIds: form.value.payids ,type:1 })
paymentList.value = res.rows || []
// 计算统计
let totalAmount = 0
let totalNum = 0
for (const pay of paymentList.value) {
totalAmount += (pay.totalAmount * 1) || 0
totalNum += (pay.totalNum * 1) || 0
}
paymentStat.value = { totalAmount, totalNum }
} catch (err) {
console.error('获取缴费单列表失败', err)
paymentList.value = []
} finally {
loadingPayment.value = false
}
}
function formatDate(dateStr) {
if (!dateStr) return '--'
if (typeof dateStr === 'string' && dateStr.indexOf('T') > -1) {
return dateStr.slice(0, 10)
}
return dateStr
}
function getVerifyStatusClass(status) {
if (status == '1') return 'text-success'
if (status == '2') return 'text-danger'
if (status == '3') return 'text-warning'
return ''
}
function downloadInvoice() {
if (!form.value.fileUrl) {
uni.showToast({ title: '暂无发票', icon: 'none' })
return
}
try {
const invoice = JSON.parse(form.value.fileUrl)
if (invoice && invoice[0]?.url) {
let fileUrl = invoice[0].url
if (!fileUrl.startsWith('http')) {
fileUrl = config.baseUrl_api + fileUrl
}
uni.previewImage({
urls: [fileUrl],
success: () => {
console.log('previewImage success')
},
fail: (err) => {
console.error('previewImage fail:', err)
uni.showToast({ title: '打开图片失败', icon: 'none' })
}
})
}
} catch (e) {
console.error('解析发票数据失败', e)
uni.showToast({ title: '解析发票数据失败', icon: 'none' })
}
}
</script>
<style scoped lang="scss">
// 颜色变量
$primary-color: #C4121B;
$success-color: #52c41a;
$bg-color: #f5f5f5;
$card-bg: #ffffff;
$text-primary: #333;
$text-secondary: #666;
$text-placeholder: #999;
$border-color: #eee;
.detail-content {
padding: 20rpx;
background: $bg-color;
min-height: 100vh;
box-sizing: border-box;
}
.card {
background: $card-bg;
border-radius: 16rpx;
margin-bottom: 20rpx;
overflow: hidden;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 24rpx 20rpx;
border-bottom: 1rpx solid $border-color;
.header-left {
font-size: 30rpx;
font-weight: 600;
color: $text-primary;
}
}
.card-body {
padding: 8rpx 24rpx 24rpx;
}
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 18rpx 0;
border-bottom: 1rpx solid #f5f5f5;
&:last-child {
border-bottom: none;
}
.label {
font-size: 28rpx;
color: $text-placeholder;
flex-shrink: 0;
width: 170rpx;
}
.value {
font-size: 28rpx;
color: $text-primary;
text-align: right;
flex: 1;
word-break: break-all;
}
}
.text-red {
color: $primary-color !important;
font-weight: 600;
}
.text-primary {
color: #1561cb !important;
}
.state-tip {
text-align: center;
padding: 60rpx 0;
font-size: 26rpx;
color: $text-placeholder;
}
.payment-stat {
display: flex;
justify-content: space-between;
padding: 20rpx 24rpx;
background: #FFF0F0;
border-bottom: 1rpx solid $border-color;
font-size: 26rpx;
color: $text-secondary;
.stat-value {
color: $primary-color;
font-weight: 600;
}
}
.payment-list {
padding: 0 24rpx 24rpx;
}
.payment-item {
border-radius: 12rpx;
padding: 24rpx;
margin-top: 16rpx;
border: 1rpx solid $border-color;
background: #fff;
.item-header {
display: flex;
align-items: center;
margin-bottom: 20rpx;
padding-bottom: 16rpx;
border-bottom: 1rpx solid #f5f5f5;
.item-index {
width: 40rpx;
height: 40rpx;
line-height: 40rpx;
text-align: center;
font-size: 22rpx;
color: #fff;
background: $primary-color;
border-radius: 50%;
margin-right: 16rpx;
}
.item-name {
flex: 1;
font-size: 28rpx;
font-weight: 600;
color: $text-primary;
}
.status-tag {
font-size: 22rpx;
padding: 4rpx 16rpx;
border-radius: 20rpx;
&.passed {
background: rgba($success-color, 0.1);
color: $success-color;
}
&.pending {
background: rgba($primary-color, 0.1);
color: $primary-color;
}
}
}
.item-body {
.info-line {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12rpx 0;
border-bottom: 1rpx solid #f5f5f5;
&:last-child {
border-bottom: none;
}
.info-label {
font-size: 26rpx;
color: $text-placeholder;
flex-shrink: 0;
}
.info-value {
font-size: 26rpx;
color: $text-primary;
text-align: right;
flex: 1;
margin-left: 24rpx;
word-break: break-all;
}
}
}
}
</style>
......@@ -63,7 +63,7 @@
<button class="btn-red" @click="login">登录</button>
</view>
<view class="center-item">
<text class="text-red" @click="goRegister">没有账号,去注册</text>
<button class="btn-red btn-register" @click="goRegister">没有账号,去注册</button>
</view>
</view>
<view class="wNumber">
......@@ -84,6 +84,25 @@
<image v-else src="@/static/login/xz2@2x.png"></image>
<view>登录即代表您同意<text>《用户协议》</text><text>《隐私策略》</text></view> -->
</view>
<!-- 注册提示弹框 -->
<uni-popup ref="registerPopup" type="center" :mask-click="false">
<view class="register-popup">
<view class="popup-title">注册提示</view>
<view class="popup-content">
<view class="popup-text">请准备以下材料,以便顺利完成注册</view>
<view class="popup-list">
<text>1. 单位营业执照(清晰照片)</text>
<text>2. 法人身份证正反面照片(清晰照片)</text>
<text>3. 单位营业执照(清晰照片)</text>
</view>
</view>
<view class="popup-btns">
<view class="popup-btn cancel" @click="closeRegisterPopup">取消</view>
<view class="popup-btn confirm" @click="confirmRegister">确定</view>
</view>
</view>
</uni-popup>
</view>
</template>
......@@ -107,6 +126,7 @@ const agree = ref(false)
const isRember = ref(true)
const loading = ref(false)
const codeUrl = ref(null)
const registerPopup = ref(null)
const inputstyle = ref({
borderColor: 'transparent',
fontSize: '30rpx'
......@@ -226,9 +246,17 @@ function login() {
}
function goRegister() {
const path = '/login/register'
registerPopup.value.open()
}
function closeRegisterPopup() {
registerPopup.value.close()
}
function confirmRegister() {
registerPopup.value.close()
uni.navigateTo({
url: path
url: '/login/register'
})
}
......@@ -510,11 +538,85 @@ function call(num) {
font-size: 24rpx;
width: 100vw;
justify-content: center;
image {
width: 40rpx;
height: 40rpx;
margin-right: 20rpx;
}
}
/* 注册按钮 */
.btn-register {
border-radius: 40rpx;
width: 600rpx;
line-height: 80rpx;
font-size: 36rpx;
background: #fff;
color: #AD181F;
border: 1rpx solid #AD181F;
margin: 0;
}
/* 注册提示弹框 */
.register-popup {
width: 600rpx;
background: #ffffff;
border-radius: 24rpx;
overflow: hidden;
}
.popup-title {
font-size: 32rpx;
font-weight: 500;
color: #333;
text-align: center;
padding: 40rpx 30rpx 20rpx;
}
.popup-content {
padding: 20rpx 30rpx 40rpx;
}
.popup-text {
font-size: 28rpx;
color: #333;
margin-bottom: 20rpx;
}
.popup-list {
display: flex;
flex-direction: column;
gap: 16rpx;
padding-left: 10rpx;
}
.popup-list text {
font-size: 26rpx;
color: #666;
line-height: 1.5;
}
.popup-btns {
display: flex;
border-top: 1rpx solid #eee;
}
.popup-btn {
flex: 1;
height: 100rpx;
line-height: 100rpx;
text-align: center;
font-size: 30rpx;
}
.popup-btn.cancel {
color: #666;
border-right: 1rpx solid #eee;
}
.popup-btn.confirm {
color: #AD181F;
font-weight: 500;
}
</style>
......
......@@ -12,19 +12,19 @@
<form>
<view class="round-input-item">
<image class="icon" :src="config.baseUrl_api+'/fs/static/login/tag01@2x.png'"></image>
<uni-easyinput :styles="inputstyle" v-model="registerForm.telNo" placeholder="请输入手机号" />
<uni-easyinput :styles="inputstyle" v-model="registerForm.telNo" placeholder="请输入手机号" @blur="validateTelNo" />
</view>
<view class="round-input-item">
<image class="icon" :src="config.baseUrl_api+'/fs/static/login/tag02@2x.png'"></image>
<uni-easyinput :styles="inputstyle" v-model="registerForm.password" placeholder="密码" type="password"/>
<uni-easyinput :styles="inputstyle" v-model="registerForm.password" placeholder="密码" type="password" @blur="validatePassword" />
</view>
<view class="round-input-item">
<image class="icon" :src="config.baseUrl_api+'/fs/static/login/tag03@2x.png'"></image>
<uni-easyinput :styles="inputstyle" v-model="registerForm.password2" placeholder="确认密码" type="password"/>
<uni-easyinput :styles="inputstyle" v-model="registerForm.password2" placeholder="确认密码" type="password" @blur="validatePassword2" />
</view>
<view class="round-input-item">
<image class="icon" :src="config.baseUrl_api+'/fs/static/login/tag03@2x.png'"></image>
<uni-easyinput :styles="inputstyle" placeholder="图形验证码" v-model="registerForm.captcha" />
<uni-easyinput :styles="inputstyle" placeholder="图形验证码" v-model="registerForm.captcha" @blur="validateCaptcha" />
<image :src="codeUrl" @click="getCode" />
</view>
<view class="round-input-item">
......@@ -138,12 +138,20 @@ function register() {
groupMemberRegister(registerForm.value)
.then((res) => {
uni.showToast({
title: `恭喜你,您的账号 ${registerForm.value.telNo} 注册成功!`,
icon: 'none'
uni.showModal({
title: '提示',
content: `恭喜你,您的账号 ${registerForm.value.telNo} 注册成功!`,
confirmText: '去登录',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
registerForm.value = {}
goLogin()
} else {
// 取消,保持当前页面
}
}
})
registerForm.value = {}
setTimeout(goLogin, 2000)
})
}
......@@ -160,6 +168,58 @@ function validPassword(pwd) {
return (lowerRegex.test(pwd) && upperRegex.test(pwd) && digitRegex.test(pwd) && symbolRegex.test(pwd) && specific.test(pwd))
}
// 手机号校验
function validateTelNo() {
const telNo = registerForm.value.telNo
if (!telNo) {
uni.showToast({ title: '手机号不能为空', icon: 'none' })
return false
}
if (!/^1[3-9]\d{9}$/.test(telNo)) {
uni.showToast({ title: '手机号格式不正确', icon: 'none' })
return false
}
return true
}
// 密码校验
function validatePassword() {
const password = registerForm.value.password
if (!password) {
uni.showToast({ title: '密码不能为空', icon: 'none' })
return false
}
if (!validPassword(password)) {
uni.showToast({ title: '密码必须为8~18位大小写字母、数字和特殊符号组合', icon: 'none', duration: 3000 })
return false
}
return true
}
// 确认密码校验
function validatePassword2() {
const password2 = registerForm.value.password2
if (!password2) {
uni.showToast({ title: '确认密码不能为空', icon: 'none' })
return false
}
if (registerForm.value.password && password2 !== registerForm.value.password) {
uni.showToast({ title: '两次密码不一致', icon: 'none' })
return false
}
return true
}
// 图形验证码校验
function validateCaptcha() {
const captcha = registerForm.value.captcha
if (!captcha) {
uni.showToast({ title: '图形验证码不能为空', icon: 'none' })
return false
}
return true
}
function goLogin() {
let path = '/login/loginC';
uni.navigateTo({
......
......@@ -51,7 +51,7 @@
</uni-list-item>
<uni-list-item v-if="form.menCode" :rightText="form.menCode" title="会员编号"/>
<uni-list-item :rightText="form.name" title="机构名称"/>
<uni-list-item title="所属省份">
<!-- <uni-list-item title="所属省份">
<template v-slot:footer>
<view class="frrr">
<uni-data-picker v-model="form.belongProvinceId" :clear-icon="false" :localdata="options"
......@@ -59,7 +59,7 @@
</uni-data-picker>
</view>
</template>
</uni-list-item>
</uni-list-item> -->
<uni-list-item
v-if="authenticationStatusa != 1&&authenticationStatusa != 0&&authenticationStatusa != 3&&newResult||form.associateId&&form.associateId>0||activeStatus==1"
:rightText="form.creditCode"
......@@ -96,7 +96,8 @@
</view>
</template>
</uni-list-item>
<uni-list-item :rightText="form.companyName||'--'" title="营业执照名称"/>
<uni-list-item :rightText="form.creditCode||'--'" title="社会信用代码"/>
<uni-list-item clickable title="营业执照">
<template v-slot:footer>
<view v-if="form.businessLicenseArr&&form.businessLicenseArr?.length>0" class="frrr"
......
......@@ -94,15 +94,23 @@ async function getList() {
return uni.showToast({title: '请输入考官姓名', icon: 'none'})
if (queryParams.value.type == 1 && !queryParams.value.certCode)
return uni.showToast({title: '请输入考官编号', icon: 'none'})
loading.value = true
const res = await api.getCoachList(queryParams.value)
infoList.value = res.rows
total.value = res.total
loading.value = false
try {
const res = await api.getCoachList(queryParams.value)
infoList.value = res.rows || []
total.value = res.total || 0
} catch (err) {
console.error('获取考官列表失败:', err)
infoList.value = []
total.value = 0
} finally {
loading.value = false
}
// 空数组提示
if (infoList.value.length === 0) {
uni.showToast({title: '请核实考官编号、有效期及归属地!', icon: 'none'})
uni.showToast({title: '请核实考官编号、有效期及归属地', icon: 'none', duration: 2000})
}
}
......@@ -163,7 +171,8 @@ async function confirmAddExpireExaminer() {
uni.navigateBack({delta: 1})
} catch (err) {
console.log(err)
uni.showToast({title: '添加失败', icon: 'none'})
const errMsg = err?.data?.msg || err?.msg || '添加失败'
uni.showToast({title: errMsg, icon: 'none', duration: 3000})
} finally {
expirePopup.value.close()
currentExpireItem.value = null
......
......@@ -18,10 +18,10 @@
<uni-forms-item label="机构名称" required>
<uni-easyinput v-model="form.name" :disabled="type" placeholder="机构名称" /></uni-forms-item>
<uni-forms-item label="所属省份" required>
<!-- <uni-forms-item label="所属省份" required>
<uni-data-select :clear="false" :disabled="type&&(belongProvinceId||belongProvinceId==0)"
v-model="form.belongProvinceId" :localdata="regionsList"></uni-data-select>
</uni-forms-item>
</uni-forms-item> -->
<uni-forms-item label="社会信用代码" required>
<uni-easyinput v-model="form.creditCode" :disabled="type&&!!creditCode&&newResult" />
</uni-forms-item>
......@@ -43,13 +43,11 @@
<uni-forms-item label="法人证件号" required>
<uni-easyinput v-model="form.legalIdcCode" />
</uni-forms-item>
<uni-forms-item label="营业执照名称" required>
<uni-easyinput v-model="form.companyName" />
</uni-forms-item>
<uni-forms-item v-if="form.deptType==6&&activeStatus!= 0" label="是否申请考点" required>
<!-- <uni-forms-item v-if="form.deptType==6&&activeStatus!= 0" label="是否申请考点" required>
<uni-data-checkbox v-model="form.applyPoints" :localdata="yesno" />
</uni-forms-item>
</uni-forms-item> -->
<uni-forms-item label="法人身份证" required>
<view class="imgArea">
......@@ -63,11 +61,17 @@
</uni-file-picker>
</view>
</uni-forms-item>
<uni-forms-item label="营业执照名称" required>
<uni-easyinput v-model="form.companyName" />
</uni-forms-item>
<uni-forms-item label="营业执照" required>
<uni-file-picker limit="1" v-model="businessLicenseArr" file-extname="png,jpg,jpeg,pdf"
file-mediatype="all" @select="selectFile" @delete="delSupplementFile"></uni-file-picker>
</uni-forms-item>
<!-- <uni-forms-item label="社会信用代码" required>
<uni-file-picker limit="1" v-model="businessLicenseArr" file-extname="png,jpg,jpeg,pdf"
file-mediatype="all" @select="selectFile" @delete="delSupplementFile"></uni-file-picker>
</uni-forms-item> -->
<uni-forms-item label="机构照片" required>
<uni-file-picker v-model="picArrR" limit="3" mode="grid" file-mediatype="image"
@select="upPicArr" @delete="delpicArr">
......
......@@ -240,6 +240,13 @@
"navigationBarTitleText": "我的订单",
"enablePullDownRefresh": false
}
},
{
"path": "certPreview",
"style": {
"navigationBarTitleText": "电子会员证",
"enablePullDownRefresh": false
}
}
]
},
......@@ -876,6 +883,20 @@
"navigationBarTitleText": "考生信息",
"enablePullDownRefresh": false
}
},
{
"path": "settlementAudit",
"style": {
"navigationBarTitleText": "结算审核",
"enablePullDownRefresh": false
}
},
{
"path": "settlementView",
"style": {
"navigationBarTitleText": "结算详情",
"enablePullDownRefresh": false
}
}
]
}, {
......
......@@ -239,18 +239,23 @@
<image :src="config.baseUrl_api+'/fs/static/icon/2.png'"/>
考试审核
</view>
<view @click="goPath('/level/ztx/cert?type=1')">
<image :src="config.baseUrl_api+'/fs/static/icon/18.png'"/>
证书发布
</view>
<view @click="goPath('/level/ztx/mail')">
<image :src="config.baseUrl_api+'/fs/static/icon/3.png'"/>
证书邮寄
</view>
<view @click="goPath('/level/ztx/cert?type=1')">
<image :src="config.baseUrl_api+'/fs/static/icon/18.png'"/>
证书发布
</view>
<view @click="goPath('/personalVip/changeLevelAudit')">
<image :src="config.baseUrl_api+'/fs/static/icon/26.png'"/>
变更审核
</view>
<view @click="goPath('/level/settlementAudit')">
<image :src="config.baseUrl_api+'/fs/static/icon/10.png'"/>
结算审核
</view>
<view @click="goPath('/personalVip/order?type=2')">
<image :src="config.baseUrl_api+'/fs/static/icon/6.png'"/>
订单列表
......
......@@ -284,7 +284,7 @@
getDetail()
getRegionsList()
getMyMemberCertUnitFeeApi()
// getMyMemberCertUnitFeeApi()
canUseDiscountApi()
getZtxDiscountPolicyApi()
getMyStatusAPI()
......
......@@ -176,7 +176,7 @@
// 审核
function goApproval(item) {
uni.navigateTo({
url: `/pages/rank/scoreAudit?ids=${item.examId}&pageType=2`
url: `/pages/rank/scoreAudit?ids=${item.examId}&pageType=1`
})
}
</script>
......
......@@ -86,19 +86,23 @@
if(!params.reason){
delete params.reason
}
try {
// 根据 pageType 自动切换接口
// 根据 pageType 1 审核考试 2 审核成绩 3 结算审核
if (pageType.value == '1') {
await api.auditDuanExam(params);
} else if (pageType.value == '2') {
await api.auditDuanScore(params);
} else if (pageType.value == '3') {
params.type = '1' // 结算审核固定 type = 1
await api.settlementAudit(params);
} else {
uni.showToast({ title: '页面类型错误', icon: 'none' });
submitting.value = false;
return;
}
uni.showToast({ title: '操作成功', icon: 'success' })
setTimeout(() => { uni.navigateBack() }, 1500)
} catch (err) {
......
......@@ -51,6 +51,14 @@
<uni-easyinput :styles="inputstyle" :placeholderStyle="placeholderStyle"
v-model="baseFormData.phone" placeholder="请输入联系方式" />
</uni-forms-item>
<uni-forms-item label="会员编号" name="perCode" v-if="baseFormData.perCode">
<uni-easyinput :styles="inputstyle" :placeholderStyle="placeholderStyle"
v-model="baseFormData.perCode" placeholder="请输入会员编号" />
</uni-forms-item>
<uni-forms-item label="会员有效期" name="validityDate" v-if="baseFormData.validityDate">
<uni-easyinput :styles="inputstyle" :placeholderStyle="placeholderStyle"
v-model="baseFormData.validityDate" placeholder="请输入会员有效期" />
</uni-forms-item>
<!-- <uni-forms-item label="所在地区">
......@@ -148,6 +156,8 @@
sex: '',
idcType: '0',
perType: '1', // (1:个人会员;2:教练;3:考官;4:裁判;5:临时会员;)
perCode:'',
validityDate:''
})
const items = ref(['身份证添加', '证件照录入'])
const idcTypeList = ref([{
......@@ -199,7 +209,7 @@
height: '316rpx'
});
onLoad((option) => {
onLoad(async (option) => {
if (option.tab == '1') {
current.value = 1
baseFormData.value.sourceFlag = 1
......@@ -210,8 +220,11 @@
disabledName.value = true
}
}
// console.log(current.value,option.tab)
// getRegionsList()
// 如果传入了 perId,预填会员信息
if (option.perId) {
perId.value = option.perId
await fetchMemberInfo(option.perId)
}
})
onMounted(() => {
......@@ -227,6 +240,40 @@
})
}
// 根据 perId 获取会员信息预填表单
async function fetchMemberInfo(id) {
if (!id) return
uni.showLoading({ title: '加载中...' })
try {
const res = await api.getInfo(id)
const data = res.data || {}
baseFormData.value.name = data.name || ''
baseFormData.value.idcCode = data.idcCode || ''
baseFormData.value.idcType = data.idcType || '0'
baseFormData.value.sex = data.sex || ''
baseFormData.value.birth = data.birth || ''
baseFormData.value.phone = data.phone || ''
baseFormData.value.perCode = data.perCode || ''
baseFormData.value.validityDate = data.validityDate ? data.validityDate.slice(0, 10) : ''
// 照片处理
if (data.photo2) {
const photoUrl = data.photo2.indexOf('http') === -1 ? config.baseUrl_api + data.photo2 : data.photo2
photoArr.value = {
url: photoUrl,
name: '头像',
extname: 'jpg'
}
baseFormData.value.photo = data.photo
baseFormData.value.photo2 = data.photo2
}
disabledName.value = true
} catch (e) {
console.error('获取会员信息失败', e)
} finally {
uni.hideLoading()
}
}
function onClickItem(e) {
if (current.value != e.currentIndex) {
current.value = e.currentIndex
......@@ -277,6 +324,8 @@
baseFormData.value.idcCode = res.data.code
baseFormData.value.name = res.data.name
baseFormData.value.uuid = res.data.uuid
baseFormData.value.perCode = res.data.perCode ||''
baseFormData.value.validityDate = res.data.validityDate?.slice(0,10) //去掉时分秒
// baseFormData.value.cityId = res.data.cityId
// baseFormData.value.address = res.data.address
} else {
......@@ -363,6 +412,8 @@
baseFormData.value.birth = res.data.birth
baseFormData.value.name = res.data.name
baseFormData.value.phone = res.data.phone
baseFormData.value.perCode = res.data.perCode ||''
baseFormData.value.validityDate = res.data.validityDate?.slice(0,10) //去掉时分秒
// baseFormData.value.cityId = res.data.cityId
// baseFormData.value.address = res.data.address
if (res.data.photo) {
......
<template>
<view class="preview-container">
<view class="loading-tip" v-if="loading">加载中...</view>
<view class="error-tip" v-else-if="showError">{{ errorMsg }}</view>
<view class="download-btn" @click="openDocument">
<text>查看会员证</text>
</view>
</view>
</template>
<script setup>
import { ref } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import config from "@/config.js";
const pdfUrl = ref("");
const loading = ref(true);
const showError = ref(false);
const errorMsg = ref("");
const tempFilePath = ref("");
onLoad(async (option) => {
if (option.url) {
pdfUrl.value = config.baseUrl_api + decodeURIComponent(option.url);
await downloadPdf();
}
});
const downloadPdf = () => {
return new Promise((resolve) => {
uni.showLoading({ title: "加载中..." });
uni.downloadFile({
url: pdfUrl.value,
success: (res) => {
uni.hideLoading();
if (res.statusCode === 200) {
tempFilePath.value = res.tempFilePath;
loading.value = false;
// 自动打开文档
openDocument();
} else {
showError.value = true;
errorMsg.value = "下载失败";
}
resolve();
},
fail: () => {
uni.hideLoading();
showError.value = true;
errorMsg.value = "下载失败";
resolve();
}
});
});
};
const openDocument = () => {
if (!tempFilePath.value) {
uni.showToast({ title: "文件未准备好", icon: "none" });
return;
}
uni.openDocument({
filePath: tempFilePath.value,
fileType: "pdf",
showMenu: true,
success: () => {
console.log("打开文档成功");
},
fail: () => {
uni.showToast({ title: "打开失败,请在右上角菜单中下载", icon: "none" });
}
});
};
</script>
<style lang="scss" scoped>
.preview-container {
min-height: 100vh;
background: #f5f5f5;
position: relative;
}
.loading-tip {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 28rpx;
color: #666;
}
.error-tip {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 28rpx;
color: #C40F18;
}
.download-btn {
position: fixed;
bottom: 50rpx;
left: 50%;
transform: translateX(-50%);
background: #C40F18;
color: #fff;
padding: 24rpx 60rpx;
border-radius: 40rpx;
font-size: 28rpx;
z-index: 100;
}
</style>
......@@ -7,7 +7,7 @@
<!-- 绑定/解绑学员 -->
<view class="bind-student" @click="handleBindAction">
<text>{{ isBound ? '解绑学员' : '绑定学员' }}</text>
<text>{{ isBound ? '切换学员' : '绑定学员' }}</text>
<image :src="config.baseUrl_api + '/fs/static/bd@2x.png'" class="bind-icon" mode="aspectFit"></image>
</view>
......@@ -22,7 +22,7 @@
mode="aspectFill">
</image>
</view>
<view class="member-id">注册会员{{ userInfo.userName }}</view>
<view class="member-id">{{ userInfo.userName }}</view>
</view>
<view class="user-bottom">
<view class="user-name">{{ perInfo?.perName }}</view>
......@@ -37,6 +37,16 @@
<image v-if="perInfo?.perValidDateFlag == 0" class="expired-stamp"
:src="config.baseUrl_api + '/fs/static/end@2x.png'" mode="aspectFit">
</image>
<!-- 卡片右下角按钮 -->
<view class="card-btns">
<view class="card-btn" @click="goToPay">
<text>缴费</text>
</view>
<view class="card-btn" @click="downCert">
<text>电子会员证</text>
</view>
</view>
</view>
<!-- 功能按钮卡片 -->
......@@ -56,7 +66,7 @@
</view>
<text class="func-text">个人会员</text>
</view>
<view class="func-item" @click="goToRecord">
<view class="func-item" @click="goToRecord(0)">
<view class="func-icon">
<image :src="config.baseUrl_api + '/fs/static/btn03@2x.png'"></image>
<!-- <uni-icons type="list" size="40" color="#1E88E5"></uni-icons> -->
......@@ -75,7 +85,7 @@
</view>
<uni-icons type="arrowright" size="20" color="#999"></uni-icons>
</view>
<view class="query-item" @click="goToWebView(1)">
<!-- <view class="query-item" @click="goToWebView(1)">
<view class="query-item-left">
<image :src="config.baseUrl_api + '/fs/static/user_icon03@2x.png'" class="query-item-icon"></image>
<text class="query-item-text">单位会员查询</text>
......@@ -95,7 +105,7 @@
<text class="query-item-text">旧版级位证书查询</text>
</view>
<uni-icons type="arrowright" size="20" color="#999"></uni-icons>
</view>
</view> -->
<view class="query-item" @click="goToWebView(4)">
<view class="query-item-left">
<image :src="config.baseUrl_api + '/fs/static/user_icon02@2x.png'" class="query-item-icon"></image>
......@@ -103,59 +113,59 @@
</view>
<uni-icons type="arrowright" size="20" color="#999"></uni-icons>
</view>
<view class="query-item" @click="goToWebView(5)">
<!-- <view class="query-item" @click="goToWebView(5)">
<view class="query-item-left">
<image :src="config.baseUrl_api + '/fs/static/user_icon02@2x.png'" class="query-item-icon"></image>
<text class="query-item-text">级位记录查询</text>
</view>
<uni-icons type="arrowright" size="20" color="#999"></uni-icons>
</view>
<view class="query-item" @click="goToWebView(6)">
</view> -->
<!-- <view class="query-item" @click="goToWebView(6)">
<view class="query-item-left">
<image :src="config.baseUrl_api + '/fs/static/user_icon02@2x.png'" class="query-item-icon"></image>
<text class="query-item-text">国际段位证书查询</text>
</view>
<uni-icons type="arrowright" size="20" color="#999"></uni-icons>
</view>
<view class="query-item" @click="goToWebView(7)">
</view> -->
<view class="query-item" @click="goToRecord(1)">
<view class="query-item-left">
<image :src="config.baseUrl_api + '/fs/static/user_icon02@2x.png'" class="query-item-icon"></image>
<text class="query-item-text">中国段位证书查询</text>
<text class="query-item-text">我的段位</text>
</view>
<uni-icons type="arrowright" size="20" color="#999"></uni-icons>
</view>
<view class="query-item" @click="goToWebView(8)">
<view class="query-item-left">
<image :src="config.baseUrl_api + '/fs/static/user_icon03@2x.png'" class="query-item-icon"></image>
<text class="query-item-text">级位考官查询</text>
<text class="query-item-text">级位考官</text>
</view>
<uni-icons type="arrowright" size="20" color="#999"></uni-icons>
</view>
<view class="query-item" @click="goToWebView(9)">
<view class="query-item-left">
<image :src="config.baseUrl_api + '/fs/static/user_icon03@2x.png'" class="query-item-icon"></image>
<text class="query-item-text">段位考官查询</text>
<text class="query-item-text">段位考官</text>
</view>
<uni-icons type="arrowright" size="20" color="#999"></uni-icons>
</view>
<view class="query-item" @click="goToWebView(10)">
<view class="query-item-left">
<image :src="config.baseUrl_api + '/fs/static/user_icon03@2x.png'" class="query-item-icon"></image>
<text class="query-item-text">大众教练员查询</text>
<text class="query-item-text">大众教练员</text>
</view>
<uni-icons type="arrowright" size="20" color="#999"></uni-icons>
</view>
<view class="query-item" @click="goToWebView(11)">
<view class="query-item-left">
<image :src="config.baseUrl_api + '/fs/static/user_icon03@2x.png'" class="query-item-icon"></image>
<text class="query-item-text">大众裁判员查询</text>
<text class="query-item-text">大众裁判员</text>
</view>
<uni-icons type="arrowright" size="20" color="#999"></uni-icons>
</view>
<view class="query-item" @click="goToWebView(12)">
<view class="query-item-left">
<image :src="config.baseUrl_api + '/fs/static/user_icon03@2x.png'" class="query-item-icon"></image>
<text class="query-item-text">培训讲师查询</text>
<text class="query-item-text">培训讲师</text>
</view>
<uni-icons type="arrowright" size="20" color="#999"></uni-icons>
</view>
......@@ -223,7 +233,8 @@
import to from 'await-to-js'
import {
bindUser,
unbindUser
unbindUser,
downStuCertSingle
} from '@/common/api.js'
const userStore = useUserStore()
......@@ -232,10 +243,10 @@
console.log(222,userInfo.value)
console.log(333,perInfo.value)
// 是否已绑定学员
// 是否已绑定学员(根据会员卡号判断)
const isBound = computed(() => {
const perId = userInfo.value?.perId
return perId !== undefined && perId !== null && perId !== 0
const perCode = perInfo.value?.perCode
return perCode !== undefined && perCode !== null && perCode !== ''
})
const bindPopup = ref(null)
......@@ -252,8 +263,8 @@ const showConfirm = ref(false)
}
})
watch(() => userInfo.value.perId, (val) => {
if (val !== undefined && val == 0) {
watch(() => perInfo.value?.perCode, (val) => {
if (val === undefined || val === null || val === '') {
nextTick(() => {
openBindPopup()
})
......@@ -282,30 +293,19 @@ const showConfirm = ref(false)
bindPopup.value?.close()
}
// 处理绑定/解绑操作
const handleBindAction = () => {
// 处理绑定/切换操作
const handleBindAction = async () => {
if (isBound.value) {
// 已绑定,执行解绑
uni.showModal({
content: '确认解绑吗?',
success: async (res) => {
if (res.confirm) {
uni.showLoading({
title: '解绑中...',
mask: true
})
const [err] = await to(unbindUser())
uni.hideLoading()
if (err) return
uni.showToast({
title: '解绑成功',
icon: 'success'
})
// 刷新用户信息
getWebInfo()
}
}
// 已绑定,先解绑再打开绑定弹框
uni.showLoading({
title: '解绑中...',
mask: true
})
const [err] = await to(unbindUser())
uni.hideLoading()
if (err) return
getWebInfo()
openBindPopup()
} else {
// 未绑定,打开绑定弹框
openBindPopup()
......@@ -341,15 +341,26 @@ const showConfirm = ref(false)
uni.hideLoading()
if (err) {
uni.showToast({
title: err?.data?.msg || err?.message || '绑定失败,请稍后重试',
icon: 'none',
duration: 3000
})
return
}else{
uni.showToast({
title: '绑定成功',
icon: 'success'
})
closeBindPopup()
getWebInfo()
}
uni.showToast({
title: '绑定成功',
icon: 'success'
})
closeBindPopup()
getWebInfo()
// uni.showToast({
// title: '绑定成功',
// icon: 'success'
// })
}
// 返回上一页
......@@ -358,12 +369,20 @@ const showConfirm = ref(false)
}
const goToAuth = () => {
if (!isBound.value) {
uni.showToast({ title: '请先绑定学员', icon: 'none' })
return
}
uni.navigateTo({
url: '/personal/personInfo'
});
};
const goToScore = () => {
if (!isBound.value) {
uni.showToast({ title: '请先绑定学员', icon: 'none' })
return
}
uni.navigateTo({
url: '/personal/memberInfo'
});
......@@ -371,26 +390,67 @@ const showConfirm = ref(false)
const goToWebView = (type) => {
// const url = "https://member.taekwondo.org.cn/#/authAccurate?type=" + type
const url = "http://tk001.wxjylt.com/pc.html#/authAccurate?type=" + type
const url = "https://tk001.wxjylt.com/pc.html#/authAccurate?type=" + type
uni.navigateTo({
url: "/pages/webview/webview?url=" + encodeURIComponent(url)
});
};
// 导航到级位记录
const goToRecord = () => {
const goToRecord = (type) => {
if (!isBound.value) {
uni.showToast({ title: '请先绑定学员', icon: 'none' })
return
}
uni.navigateTo({
url: '/personal/levelRecord'
url: '/personal/levelRecord?type=' + type
});
};
// 导航到我的订单
const goToOrder = () => {
if (!isBound.value) {
uni.showToast({ title: '请先绑定学员', icon: 'none' })
return
}
uni.navigateTo({
url: '/personal/order'
});
};
// 导航到缴费
const goToPay = () => {
const perId = userInfo.value?.perId
uni.navigateTo({
url: `/personal/addVip_per?perId=${perId}`
});
};
// 下载电子会员证
const downCert = async () => {
if (!isBound.value) {
uni.showToast({ title: '请先绑定学员', icon: 'none' })
return
}
const perId = userInfo.value?.perId
if (!perId) return
uni.showLoading({ title: '加载中...', mask: true })
const [err, res] = await to(downStuCertSingle(perId))
uni.hideLoading()
if (err) {
uni.showToast({ title: err?.data?.msg || '获取会员证失败', icon: 'none' })
return
}
const pdfUrl = res?.data
if (pdfUrl) {
uni.navigateTo({
url: `/personal/certPreview?url=${encodeURIComponent(pdfUrl)}`
})
}
};
// 显示退出登录确认框
......@@ -549,12 +609,33 @@ const showConfirm = ref(false)
.expired-stamp {
position: absolute;
right: 30rpx;
bottom: 100rpx;
bottom: 150rpx;
width: 150rpx;
height: 150rpx;
z-index: 1;
}
/* 卡片右下角按钮 */
.card-btns {
position: absolute;
right: 30rpx;
bottom: 73rpx;
z-index: 10;
display: flex;
gap: 16rpx;
}
.card-btn {
// background: rgba(255, 255, 255, 0.9);
border-radius: 30rpx;
padding: 12rpx 20rpx;
}
.card-btn text {
font-size: 24rpx;
color: #C40F18;
}
/* 功能按钮卡片 */
.func-card {
margin: -70rpx 30rpx 30rpx;
......
......@@ -76,6 +76,8 @@
<script setup>
import { ref, onMounted } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { useUserStore } from '../store/modules/user';
import { getAssoPers } from '@/common/api.js';
import { getPersonTecDetails } from '@/common/api.js';
......@@ -90,12 +92,22 @@
// 变更记录相关
const changeRecordPopup = ref(null);
const currentChangeRecord = ref(null);
const pageType = ref('');
// 返回上一页
const goBack = () => {
uni.navigateBack();
};
onLoad((option) => {
if (option.type) {
pageType.value = option.type;
if (pageType.value == '0') {
uni.setNavigationBarTitle({ title: '级位记录' })
} else if (pageType.value == '1') {
uni.setNavigationBarTitle({ title: '段位记录' })
}
}
});
// 显示变更记录
const showChangeRecord = (item) => {
// remark已经在getLevelRecords中解析过了,直接使用
......@@ -116,7 +128,7 @@
const getLevelRecords = async () => {
loading.value = true;
try {
const res = await getPersonTecDetails(0, perId.value); // 0表示级位
const res = await getPersonTecDetails(pageType.value, perId.value); // 0表示级位
levelRecords.value = res.data || [];
// 处理数据
levelRecords.value.forEach(item => {
......
......@@ -51,6 +51,14 @@
<uni-easyinput :styles="inputstyle" :placeholderStyle="placeholderStyle"
v-model="baseFormData.phone" placeholder="请输入联系方式" />
</uni-forms-item>
<uni-forms-item label="会员编号" name="perCode" v-if="baseFormData.perCode">
<uni-easyinput :styles="inputstyle" :placeholderStyle="placeholderStyle"
v-model="baseFormData.perCode" placeholder="请输入会员编号" />
</uni-forms-item>
<uni-forms-item label="会员有效期" name="validityDate" v-if="baseFormData.validityDate">
<uni-easyinput :styles="inputstyle" :placeholderStyle="placeholderStyle"
v-model="baseFormData.validityDate" placeholder="请输入会员有效期" />
</uni-forms-item>
<uni-forms-item label="所在地区">
......@@ -142,6 +150,8 @@
sex: '',
idcType: '0',
perType: '1', // (1:个人会员;2:教练;3:考官;4:裁判;5:临时会员;)
perCode:'',
validityDate:''
})
const items = ref(['身份证添加', '证件照录入'])
const idcTypeList = ref([{
......@@ -356,6 +366,8 @@
baseFormData.value.phone = res.data.phone
baseFormData.value.cityId = res.data.cityId
baseFormData.value.address = res.data.address
baseFormData.value.perCode = res.data.perCode ||''
baseFormData.value.validityDate = res.data.validityDate?.slice(0,10) //去掉时分秒
if (res.data.photo) {
console.log(res.data.photo)
if (res.data.photo.indexOf('http') == -1) {
......
......@@ -51,15 +51,15 @@
<view class="stats-row">
<view class="stat-item">
<text class="stat-label">人数合计</text>
<text class="stat-value">{{ item.allCount || 0 }}</text>
<text class="stat-value">{{ item.allCount || '/' }}</text>
</view>
<view class="stat-item">
<text class="stat-label">新会员合计</text>
<text class="stat-value">{{ item.newCount || 0 }}</text>
<text class="stat-value">{{ item.newCount || '/' }}</text>
</view>
<view class="stat-item">
<text class="stat-label">年限合计</text>
<text class="stat-value">{{ item.yearCount || 0 }}</text>
<text class="stat-value">{{ item.yearCount || '/' }}</text>
</view>
</view>
......@@ -493,7 +493,7 @@ onUnmounted(() => {
}
.stat-value {
font-size:32rpx;
font-weight:700;
// font-weight:700;
color:#333;
}
}
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!