SAAS模式调整
This commit is contained in:
parent
e7a726af56
commit
79ea4d33ce
@ -28,28 +28,39 @@
|
|||||||
:class="{
|
:class="{
|
||||||
'jc-bz-item-disabled': jcBz.paidStatus === 'paid'
|
'jc-bz-item-disabled': jcBz.paidStatus === 'paid'
|
||||||
}"
|
}"
|
||||||
@click="handleJcBzClick(jcBz)"
|
|
||||||
>
|
>
|
||||||
<view class="jc-bz-info">
|
<view class="jc-bz-row">
|
||||||
<view class="jc-bz-content">
|
<view class="jc-bz-info" @click="handleJcBzClick(jcBz)">
|
||||||
<view class="jc-bz-name">{{ jcBz.bzMc || '暂无标准名称' }}</view>
|
<view class="jc-bz-content">
|
||||||
<view class="jc-bz-price">
|
<view class="jc-bz-name">{{ jcBz.bzMc || '暂无标准名称' }}</view>
|
||||||
价格:<text class="price-value">¥{{ jcBz.bzJe || 0 }}</text>
|
<view class="jc-bz-price">
|
||||||
</view>
|
价格:<text class="price-value">¥{{ jcBz.bzJe || 0 }}</text>
|
||||||
<view class="jc-bz-desc">{{ jcBz.bzSm || '暂无描述' }}</view>
|
|
||||||
|
|
||||||
<!-- 显示更多详细信息 -->
|
|
||||||
<view class="jc-bz-details">
|
|
||||||
<view class="detail-item" v-if="jcBz.jfKsSj">
|
|
||||||
<text class="detail-label">缴费开始:</text>
|
|
||||||
<text class="detail-value">{{ jcBz.jfKsSj }}</text>
|
|
||||||
</view>
|
</view>
|
||||||
<view class="detail-item" v-if="jcBz.jfJsSj">
|
<view class="jc-bz-desc">{{ jcBz.bzSm || '暂无描述' }}</view>
|
||||||
<text class="detail-label">缴费结束:</text>
|
|
||||||
<text class="detail-value">{{ jcBz.jfJsSj }}</text>
|
<!-- 显示更多详细信息 -->
|
||||||
|
<view class="jc-bz-details">
|
||||||
|
<view class="detail-item" v-if="jcBz.jfKsSj">
|
||||||
|
<text class="detail-label">缴费开始:</text>
|
||||||
|
<text class="detail-value">{{ jcBz.jfKsSj }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="detail-item" v-if="jcBz.jfJsSj">
|
||||||
|
<text class="detail-label">缴费结束:</text>
|
||||||
|
<text class="detail-value">{{ jcBz.jfJsSj }}</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="jc-bz-side" @click.stop>
|
||||||
|
<view
|
||||||
|
v-if="jcBz.paidStatus === 'unpaid' || jcBz.paidStatus === 'notApplied'"
|
||||||
|
class="pay-btn"
|
||||||
|
@click="handlePayClick(jcBz)"
|
||||||
|
>
|
||||||
|
点击缴费
|
||||||
|
</view>
|
||||||
|
<text v-else-if="jcBz.paidStatus === 'paid'" class="paid-tag">已缴费</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -59,7 +70,6 @@
|
|||||||
<text>暂无就餐标准</text>
|
<text>暂无就餐标准</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<BasicSign ref="signCompRef" title="签名"></BasicSign>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@ -73,14 +83,11 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import XsPicker from "@/pages/base/components/XsPicker/index.vue"
|
import XsPicker from "@/pages/base/components/XsPicker/index.vue"
|
||||||
import BasicSign from "@/components/BasicSign/Sign.vue"
|
|
||||||
import { useUserStore } from "@/store/modules/user";
|
import { useUserStore } from "@/store/modules/user";
|
||||||
import { useDataStore } from "@/store/modules/data";
|
import { useDataStore } from "@/store/modules/data";
|
||||||
import { checkXsJcBmApi, jcXsBmJcApi } from "@/api/base/jcApi";
|
import { checkXsJcBmApi, jcXsBmJcApi } from "@/api/base/jcApi";
|
||||||
import { JC_PAY_REDIRECT_URL } from "@/config";
|
import { JC_PAY_REDIRECT_URL } from "@/config";
|
||||||
|
|
||||||
const signCompRef = ref<any>(null);
|
|
||||||
|
|
||||||
const { getCurXs, getUser } = useUserStore();
|
const { getCurXs, getUser } = useUserStore();
|
||||||
const { getJc, setJc, setJcBz, setData } = useDataStore();
|
const { getJc, setJc, setJcBz, setData } = useDataStore();
|
||||||
|
|
||||||
@ -152,23 +159,25 @@ const getJcBzList = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理就餐标准点击
|
// 处理就餐标准点击(左侧区域)
|
||||||
const handleJcBzClick = (jcBz: any) => {
|
const handleJcBzClick = (jcBz: any) => {
|
||||||
// 已缴费的标准不能选择
|
|
||||||
if (jcBz.paidStatus === 'paid') {
|
if (jcBz.paidStatus === 'paid') {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '该标准已缴费,无法重复选择',
|
title: '已缴费成功!',
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
handlePayClick(jcBz);
|
||||||
|
};
|
||||||
|
|
||||||
// 根据状态处理不同逻辑
|
/** 右侧「点击缴费」:未报名先无签名报名再跳转支付;已报名未缴费直接跳转支付 */
|
||||||
|
const handlePayClick = (jcBz: any) => {
|
||||||
|
if (jcBz.paidStatus === 'paid') return;
|
||||||
if (jcBz.paidStatus === 'unpaid') {
|
if (jcBz.paidStatus === 'unpaid') {
|
||||||
// 未缴费,直接跳转到收款码页面
|
|
||||||
navigateToPayment(jcBz);
|
navigateToPayment(jcBz);
|
||||||
} else if (jcBz.paidStatus === 'notApplied') {
|
} else if (jcBz.paidStatus === 'notApplied') {
|
||||||
goToQrCode(jcBz);
|
submitBmWithoutSign(jcBz);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -187,14 +196,8 @@ const navigateToPayment = (jcBz: any) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 跳转到收款码页面
|
// 未报名:无签名提交报名,成功后跳转支付(与已报名未缴费最终一致)
|
||||||
const goToQrCode = async (jcBz: any) => {
|
const submitBmWithoutSign = async (jcBz: any) => {
|
||||||
// 显示加载中
|
|
||||||
const data = await signCompRef.value.getSyncSignature();
|
|
||||||
if (!data) {
|
|
||||||
console.log("没有签名,或者取消了");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uni.showLoading({
|
uni.showLoading({
|
||||||
title: '报名中...'
|
title: '报名中...'
|
||||||
});
|
});
|
||||||
@ -209,12 +212,12 @@ const goToQrCode = async (jcBz: any) => {
|
|||||||
xm: curXs.value.xm,
|
xm: curXs.value.xm,
|
||||||
bzId: jcBz.id,
|
bzId: jcBz.id,
|
||||||
jzId: getUser.jzId,
|
jzId: getUser.jzId,
|
||||||
qmFile: data.base64 || '', // 签名文件
|
qmFile: '',
|
||||||
bjId: curXs.value.bjId,
|
bjId: curXs.value.bjId,
|
||||||
bjmc: curXs.value.bjmc
|
bjmc: curXs.value.bjmc
|
||||||
};
|
};
|
||||||
const res = await jcXsBmJcApi(params);
|
const res = await jcXsBmJcApi(params);
|
||||||
|
|
||||||
if (res.resultCode === 1) {
|
if (res.resultCode === 1) {
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -223,21 +226,9 @@ const goToQrCode = async (jcBz: any) => {
|
|||||||
icon: 'success'
|
icon: 'success'
|
||||||
});
|
});
|
||||||
}, 500);
|
}, 500);
|
||||||
// 更新状态为未支付
|
|
||||||
jcBz.paidStatus = 'unpaid';
|
jcBz.paidStatus = 'unpaid';
|
||||||
// 保存当前选中的标准到store
|
|
||||||
setJcBz(jcBz);
|
setJcBz(jcBz);
|
||||||
// 报名成功后直接跳转支付链接(链接后续可改为动态配置)
|
navigateToPayment(jcBz);
|
||||||
const payUrl = JC_PAY_REDIRECT_URL;
|
|
||||||
try {
|
|
||||||
if (typeof (window as any).plus !== 'undefined' && (window as any).plus.runtime?.openURL) {
|
|
||||||
(window as any).plus.runtime.openURL(payUrl);
|
|
||||||
} else {
|
|
||||||
window.location.href = payUrl;
|
|
||||||
}
|
|
||||||
} catch (_e) {
|
|
||||||
window.location.href = payUrl;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -332,9 +323,17 @@ onMounted(() => {
|
|||||||
&.jc-bz-item-disabled {
|
&.jc-bz-item-disabled {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.jc-bz-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
.jc-bz-info {
|
.jc-bz-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
.jc-bz-content {
|
.jc-bz-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@ -398,14 +397,32 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.selection-indicator {
|
|
||||||
display: flex;
|
.jc-bz-side {
|
||||||
align-items: center;
|
flex-shrink: 0;
|
||||||
justify-content: center;
|
display: flex;
|
||||||
width: 30px;
|
align-items: center;
|
||||||
margin-left: 10px;
|
justify-content: center;
|
||||||
}
|
padding-left: 12px;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pay-btn {
|
||||||
|
padding: 8px 14px;
|
||||||
|
background: linear-gradient(135deg, #ff8c42, #ff6b00);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 20px;
|
||||||
|
white-space: nowrap;
|
||||||
|
box-shadow: 0 2px 8px rgba(255, 107, 0, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.paid-tag {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #67c23a;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -100,28 +100,41 @@
|
|||||||
<!-- 添加签名组件 - 只在需要时显示 -->
|
<!-- 添加签名组件 - 只在需要时显示 -->
|
||||||
<BasicSign v-if="showSignature" ref="signCompRef" :title="signTitle"></BasicSign>
|
<BasicSign v-if="showSignature" ref="signCompRef" :title="signTitle"></BasicSign>
|
||||||
<template #bottom>
|
<template #bottom>
|
||||||
<view class="bottom-actions">
|
<view class="bottom-slot-wrap">
|
||||||
<button
|
<transition name="finger-fade">
|
||||||
v-if="!relaySuccess"
|
<view
|
||||||
class="action-btn publish-btn"
|
v-if="!isLoading && !relaySuccess && showFingerHint"
|
||||||
@click="onRelayClick"
|
class="finger-float"
|
||||||
>
|
aria-hidden="true"
|
||||||
接龙
|
>
|
||||||
</button>
|
<text class="finger-text">👆</text>
|
||||||
<button
|
</view>
|
||||||
v-else
|
</transition>
|
||||||
class="action-btn return-btn"
|
<view class="bottom-bar">
|
||||||
@click="handleReturn"
|
<view class="bottom-actions">
|
||||||
>
|
<button
|
||||||
返回
|
v-if="!relaySuccess"
|
||||||
</button>
|
class="action-btn publish-btn"
|
||||||
|
@click="onRelayClick"
|
||||||
|
>
|
||||||
|
点击接龙
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-else
|
||||||
|
class="action-btn return-btn"
|
||||||
|
@click="handleReturn"
|
||||||
|
>
|
||||||
|
返回
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
</BasicLayout>
|
</BasicLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, watch, nextTick } from "vue";
|
import { ref, computed, watch, nextTick, onUnmounted } from "vue";
|
||||||
import { onLoad } from "@dcloudio/uni-app";
|
import { onLoad } from "@dcloudio/uni-app";
|
||||||
import { getByJlIdApi, jlzxFindByJlParamsApi, relayFinishApi } from "@/api/base/server";
|
import { getByJlIdApi, jlzxFindByJlParamsApi, relayFinishApi } from "@/api/base/server";
|
||||||
import { imagUrl } from "@/utils";
|
import { imagUrl } from "@/utils";
|
||||||
@ -133,7 +146,7 @@ const noticeId = ref<string>("");
|
|||||||
const noticeDetail = ref<any>(null);
|
const noticeDetail = ref<any>(null);
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
const studentList = ref<any[]>([]);
|
const studentList = ref<any[]>([]);
|
||||||
const descExpanded = ref(false);
|
const descExpanded = ref(true);
|
||||||
|
|
||||||
// 新增参数
|
// 新增参数
|
||||||
const njId = ref<string>("");
|
const njId = ref<string>("");
|
||||||
@ -148,6 +161,42 @@ const selectedOption = ref<string>("");
|
|||||||
// 接龙成功状态
|
// 接龙成功状态
|
||||||
const relaySuccess = ref<boolean>(false);
|
const relaySuccess = ref<boolean>(false);
|
||||||
|
|
||||||
|
/** 底部手指提示:加载完成后浮动展示,5 秒后淡出隐藏 */
|
||||||
|
const showFingerHint = ref(false);
|
||||||
|
const fingerDismissScheduled = ref(false);
|
||||||
|
let fingerHideTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
|
function clearFingerTimer() {
|
||||||
|
if (fingerHideTimer != null) {
|
||||||
|
clearTimeout(fingerHideTimer);
|
||||||
|
fingerHideTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(isLoading, (loading) => {
|
||||||
|
if (loading) return;
|
||||||
|
if (relaySuccess.value) return;
|
||||||
|
if (fingerDismissScheduled.value) return;
|
||||||
|
fingerDismissScheduled.value = true;
|
||||||
|
showFingerHint.value = true;
|
||||||
|
clearFingerTimer();
|
||||||
|
fingerHideTimer = setTimeout(() => {
|
||||||
|
showFingerHint.value = false;
|
||||||
|
fingerHideTimer = null;
|
||||||
|
}, 5000);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearFingerTimer();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(relaySuccess, (ok) => {
|
||||||
|
if (ok) {
|
||||||
|
clearFingerTimer();
|
||||||
|
showFingerHint.value = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 签名相关
|
// 签名相关
|
||||||
const signCompRef = ref<any>(null);
|
const signCompRef = ref<any>(null);
|
||||||
const signTitle = ref<string>("签名");
|
const signTitle = ref<string>("签名");
|
||||||
@ -158,39 +207,52 @@ const showSignature = ref<boolean>(false);
|
|||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const currentStudent = computed(() => userStore.curXs);
|
const currentStudent = computed(() => userStore.curXs);
|
||||||
|
|
||||||
const descPreview = computed(() => {
|
/** 将常见 HTML 实体转为字符;小程序 rich-text 对 — 等命名实体支持不完整,需先解码 */
|
||||||
if (!noticeDetail.value?.jlms) return '';
|
function decodeHtmlEntitiesForRichText(html: string): string {
|
||||||
// 使用正则表达式去除 HTML 标签,兼容所有平台
|
if (!html) return '';
|
||||||
const text = noticeDetail.value.jlms
|
return html
|
||||||
.replace(/<[^>]+>/g, '') // 去除 HTML 标签
|
.replace(/ /g, '\u00A0')
|
||||||
.replace(/ /g, '\u3000') // 转换为中文全角空格(保留段落缩进)
|
|
||||||
.replace(/</g, '<')
|
.replace(/</g, '<')
|
||||||
.replace(/>/g, '>')
|
.replace(/>/g, '>')
|
||||||
.replace(/&/g, '&')
|
|
||||||
.replace(/"/g, '"')
|
.replace(/"/g, '"')
|
||||||
.replace(/“/g, '"') // 左双引号
|
.replace(/“/g, '\u201c')
|
||||||
.replace(/”/g, '"') // 右双引号
|
.replace(/”/g, '\u201d')
|
||||||
.replace(/‘/g, "'") // 左单引号
|
.replace(/‘/g, '\u2018')
|
||||||
.replace(/’/g, "'") // 右单引号
|
.replace(/’/g, '\u2019')
|
||||||
.replace(/—/g, '—') // 长破折号
|
.replace(/—/g, '\u2014')
|
||||||
.replace(/–/g, '–') // 短破折号
|
.replace(/–/g, '\u2013')
|
||||||
.replace(/…/g, '…') // 省略号
|
.replace(/…/g, '\u2026')
|
||||||
.replace(/·/g, '·') // 间隔号
|
.replace(/·/g, '\u00b7')
|
||||||
.replace(/&#\d+;/g, '') // 移除数字型 HTML 实体
|
.replace(/&#(\d+);/g, (_, n) => {
|
||||||
.replace(/[\r\n]+/g, ' ') // 将换行符转换为空格
|
const code = parseInt(n, 10);
|
||||||
.replace(/[ \t]+/g, ' ') // 只合并普通空格和制表符,保留全角空格
|
return Number.isFinite(code) ? String.fromCharCode(code) : _;
|
||||||
|
})
|
||||||
|
.replace(/&#x([0-9a-fA-F]+);/g, (_, h) => {
|
||||||
|
const code = parseInt(h, 16);
|
||||||
|
return Number.isFinite(code) ? String.fromCharCode(code) : _;
|
||||||
|
})
|
||||||
|
.replace(/&/g, '&');
|
||||||
|
}
|
||||||
|
|
||||||
|
const descPreview = computed(() => {
|
||||||
|
if (!noticeDetail.value?.jlms) return '';
|
||||||
|
const stripped = String(noticeDetail.value.jlms).replace(/<[^>]+>/g, '');
|
||||||
|
const decoded = decodeHtmlEntitiesForRichText(stripped);
|
||||||
|
const text = decoded
|
||||||
|
.replace(/\u00A0/g, ' ')
|
||||||
|
.replace(/[\r\n]+/g, ' ')
|
||||||
|
.replace(/[ \t]+/g, ' ')
|
||||||
.trim();
|
.trim();
|
||||||
|
|
||||||
return text.slice(0, 100) + (text.length > 100 ? '...' : '');
|
return text.slice(0, 100) + (text.length > 100 ? '...' : '');
|
||||||
});
|
});
|
||||||
|
|
||||||
// 处理完整内容,移除段落开头的多余空格(因为 CSS 已设置 text-indent)
|
// 处理完整内容,移除段落开头的多余空格(因为 CSS 已设置 text-indent)
|
||||||
const processedContent = computed(() => {
|
const processedContent = computed(() => {
|
||||||
if (!noticeDetail.value?.jlms) return '';
|
if (!noticeDetail.value?.jlms) return '';
|
||||||
// 将段落开头的 4个 替换为空(CSS 会自动添加 2em 缩进)
|
const stripped = String(noticeDetail.value.jlms)
|
||||||
return noticeDetail.value.jlms
|
.replace(/( \s*){4}/g, '')
|
||||||
.replace(/( \s*){4}/g, '') // 移除 4个连续的 (可能有空格分隔)
|
.replace(/( ){4}/g, '');
|
||||||
.replace(/( ){4}/g, ''); // 移除紧挨着的 4个
|
return decodeHtmlEntitiesForRichText(stripped);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 获取文件名
|
// 获取文件名
|
||||||
@ -518,18 +580,72 @@ watch(studentList, (list) => {
|
|||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.notice-detail-page {
|
.notice-detail-page {
|
||||||
background-color: #f4f5f7;
|
background-color: #f4f5f7;
|
||||||
padding-bottom: 70px;
|
padding-bottom: 80px;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bottom-slot-wrap {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 浮动在底部按钮上方,不占布局高度 */
|
||||||
|
.finger-float {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: calc(56px + constant(safe-area-inset-bottom));
|
||||||
|
bottom: calc(56px + env(safe-area-inset-bottom));
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 998;
|
||||||
|
filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.12));
|
||||||
|
}
|
||||||
|
|
||||||
|
.finger-text {
|
||||||
|
font-size: 40px;
|
||||||
|
line-height: 1;
|
||||||
|
display: inline-block;
|
||||||
|
transform-origin: center center;
|
||||||
|
animation: fingerFloatHint 1.25s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 手指旋转 180°,并做轻微上下浮动 */
|
||||||
|
@keyframes fingerFloatHint {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: translateY(0) rotate(180deg) scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateY(-12px) rotate(180deg) scale(1.06);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.finger-fade-enter-active,
|
||||||
|
.finger-fade-leave-active {
|
||||||
|
transition: opacity 0.45s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.finger-fade-enter-from,
|
||||||
|
.finger-fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-bar {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-top: 1px solid #e5e5e5;
|
||||||
|
}
|
||||||
|
|
||||||
.bottom-actions {
|
.bottom-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 12px 15px;
|
padding: 12px 15px;
|
||||||
|
padding-bottom: calc(12px + constant(safe-area-inset-bottom));
|
||||||
|
padding-bottom: calc(12px + env(safe-area-inset-bottom));
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
border-top: 1px solid #e5e5e5;
|
|
||||||
|
|
||||||
.action-btn {
|
.action-btn {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
|
|||||||
@ -34,6 +34,10 @@
|
|||||||
<text class="label">银行账号:</text>
|
<text class="label">银行账号:</text>
|
||||||
<text class="value">{{ xkTf.khZh }}</text>
|
<text class="value">{{ xkTf.khZh }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="info-row">
|
||||||
|
<text class="label">开户网点:</text>
|
||||||
|
<text class="value">{{ xkTf.khWd || '—' }}</text>
|
||||||
|
</view>
|
||||||
<view class="info-column">
|
<view class="info-column">
|
||||||
<text class="label">缴费凭证:</text>
|
<text class="label">缴费凭证:</text>
|
||||||
<text class="value">
|
<text class="value">
|
||||||
|
|||||||
@ -94,6 +94,15 @@ const [register, { getValue, setValue }] = useForm({
|
|||||||
required: true,
|
required: true,
|
||||||
componentProps: { },
|
componentProps: { },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
field: "khWd",
|
||||||
|
label: "开户网点",
|
||||||
|
component: "BasicInput",
|
||||||
|
required: true,
|
||||||
|
componentProps: {
|
||||||
|
placeholder: "如:泸州江阳支行",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
field: "jfPz",
|
field: "jfPz",
|
||||||
label: "缴费凭证",
|
label: "缴费凭证",
|
||||||
|
|||||||
@ -113,7 +113,7 @@ interface TaskItem {
|
|||||||
kcmc?: string;
|
kcmc?: string;
|
||||||
kcId?: string;
|
kcId?: string;
|
||||||
xsId?: string;
|
xsId?: string;
|
||||||
ispj?: string; // A:已评价
|
ispj?: string; // A:已评价 B:未评价(待评价)
|
||||||
}
|
}
|
||||||
|
|
||||||
const taskList = ref<TaskItem[]>([]);
|
const taskList = ref<TaskItem[]>([]);
|
||||||
@ -167,7 +167,11 @@ onLoad(async (options) => {
|
|||||||
xsId.value = options.xsId;
|
xsId.value = options.xsId;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadTaskList();
|
// openId 异步登录时 onShow 往往在 isLoginReady 仍为 false 时已执行过,需在 onLoad 末尾补一次加载;
|
||||||
|
// 普通进入只由 onShow 拉取,避免与 onLoad 重复请求
|
||||||
|
if (options && options.openId) {
|
||||||
|
loadTaskList();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onShow(() => {
|
onShow(() => {
|
||||||
@ -231,11 +235,27 @@ const loadTaskList = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 该任务是否已被教师评价(锁定独立项目选择) */
|
||||||
|
const isTaskEvaluated = (t: TaskItem) =>
|
||||||
|
t.zpzt === 'C' || t.ispj === 'A';
|
||||||
|
|
||||||
// 开始挑战 - 跳转到提交页面
|
// 开始挑战 - 跳转到提交页面
|
||||||
const startChallenge = (task: TaskItem) => {
|
const startChallenge = (task: TaskItem) => {
|
||||||
// 验证:如果已经有一个项目提交了,不允许挑战其他项目
|
// 当前任务已评价:不可再挑战
|
||||||
const submittedTask = taskList.value.find(t => t.zpzt === 'A' && t.id !== task.id);
|
if (isTaskEvaluated(task)) {
|
||||||
if (submittedTask) {
|
uni.showToast({
|
||||||
|
title: '该任务已评价,无法再次挑战',
|
||||||
|
icon: 'none',
|
||||||
|
duration: 2000
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 仅当存在「另一任务已提交且已被评价」时才禁止换项;已提交但 ispj=B(待评价)时仍可点击开始挑战
|
||||||
|
const otherLocked = taskList.value.find(
|
||||||
|
(t) => t.id !== task.id && isTaskEvaluated(t)
|
||||||
|
);
|
||||||
|
if (otherLocked) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '已完成挑战',
|
title: '已完成挑战',
|
||||||
icon: 'none',
|
icon: 'none',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user