SAAS模式调整
This commit is contained in:
parent
e7a726af56
commit
79ea4d33ce
@ -28,28 +28,39 @@
|
||||
:class="{
|
||||
'jc-bz-item-disabled': jcBz.paidStatus === 'paid'
|
||||
}"
|
||||
@click="handleJcBzClick(jcBz)"
|
||||
>
|
||||
<view class="jc-bz-info">
|
||||
<view class="jc-bz-content">
|
||||
<view class="jc-bz-name">{{ jcBz.bzMc || '暂无标准名称' }}</view>
|
||||
<view class="jc-bz-price">
|
||||
价格:<text class="price-value">¥{{ jcBz.bzJe || 0 }}</text>
|
||||
</view>
|
||||
<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 class="jc-bz-row">
|
||||
<view class="jc-bz-info" @click="handleJcBzClick(jcBz)">
|
||||
<view class="jc-bz-content">
|
||||
<view class="jc-bz-name">{{ jcBz.bzMc || '暂无标准名称' }}</view>
|
||||
<view class="jc-bz-price">
|
||||
价格:<text class="price-value">¥{{ jcBz.bzJe || 0 }}</text>
|
||||
</view>
|
||||
<view class="detail-item" v-if="jcBz.jfJsSj">
|
||||
<text class="detail-label">缴费结束:</text>
|
||||
<text class="detail-value">{{ jcBz.jfJsSj }}</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 class="detail-item" v-if="jcBz.jfJsSj">
|
||||
<text class="detail-label">缴费结束:</text>
|
||||
<text class="detail-value">{{ jcBz.jfJsSj }}</text>
|
||||
</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>
|
||||
@ -59,7 +70,6 @@
|
||||
<text>暂无就餐标准</text>
|
||||
</view>
|
||||
|
||||
<BasicSign ref="signCompRef" title="签名"></BasicSign>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@ -73,14 +83,11 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import XsPicker from "@/pages/base/components/XsPicker/index.vue"
|
||||
import BasicSign from "@/components/BasicSign/Sign.vue"
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import { useDataStore } from "@/store/modules/data";
|
||||
import { checkXsJcBmApi, jcXsBmJcApi } from "@/api/base/jcApi";
|
||||
import { JC_PAY_REDIRECT_URL } from "@/config";
|
||||
|
||||
const signCompRef = ref<any>(null);
|
||||
|
||||
const { getCurXs, getUser } = useUserStore();
|
||||
const { getJc, setJc, setJcBz, setData } = useDataStore();
|
||||
|
||||
@ -152,23 +159,25 @@ const getJcBzList = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 处理就餐标准点击
|
||||
// 处理就餐标准点击(左侧区域)
|
||||
const handleJcBzClick = (jcBz: any) => {
|
||||
// 已缴费的标准不能选择
|
||||
if (jcBz.paidStatus === 'paid') {
|
||||
uni.showToast({
|
||||
title: '该标准已缴费,无法重复选择',
|
||||
title: '已缴费成功!',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
handlePayClick(jcBz);
|
||||
};
|
||||
|
||||
// 根据状态处理不同逻辑
|
||||
/** 右侧「点击缴费」:未报名先无签名报名再跳转支付;已报名未缴费直接跳转支付 */
|
||||
const handlePayClick = (jcBz: any) => {
|
||||
if (jcBz.paidStatus === 'paid') return;
|
||||
if (jcBz.paidStatus === 'unpaid') {
|
||||
// 未缴费,直接跳转到收款码页面
|
||||
navigateToPayment(jcBz);
|
||||
} else if (jcBz.paidStatus === 'notApplied') {
|
||||
goToQrCode(jcBz);
|
||||
submitBmWithoutSign(jcBz);
|
||||
}
|
||||
};
|
||||
|
||||
@ -187,14 +196,8 @@ const navigateToPayment = (jcBz: any) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 跳转到收款码页面
|
||||
const goToQrCode = async (jcBz: any) => {
|
||||
// 显示加载中
|
||||
const data = await signCompRef.value.getSyncSignature();
|
||||
if (!data) {
|
||||
console.log("没有签名,或者取消了");
|
||||
return;
|
||||
}
|
||||
// 未报名:无签名提交报名,成功后跳转支付(与已报名未缴费最终一致)
|
||||
const submitBmWithoutSign = async (jcBz: any) => {
|
||||
uni.showLoading({
|
||||
title: '报名中...'
|
||||
});
|
||||
@ -209,12 +212,12 @@ const goToQrCode = async (jcBz: any) => {
|
||||
xm: curXs.value.xm,
|
||||
bzId: jcBz.id,
|
||||
jzId: getUser.jzId,
|
||||
qmFile: data.base64 || '', // 签名文件
|
||||
qmFile: '',
|
||||
bjId: curXs.value.bjId,
|
||||
bjmc: curXs.value.bjmc
|
||||
};
|
||||
const res = await jcXsBmJcApi(params);
|
||||
|
||||
|
||||
if (res.resultCode === 1) {
|
||||
uni.hideLoading();
|
||||
setTimeout(() => {
|
||||
@ -223,21 +226,9 @@ const goToQrCode = async (jcBz: any) => {
|
||||
icon: 'success'
|
||||
});
|
||||
}, 500);
|
||||
// 更新状态为未支付
|
||||
jcBz.paidStatus = 'unpaid';
|
||||
// 保存当前选中的标准到store
|
||||
setJcBz(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;
|
||||
}
|
||||
navigateToPayment(jcBz);
|
||||
} else {
|
||||
uni.hideLoading();
|
||||
setTimeout(() => {
|
||||
@ -332,9 +323,17 @@ onMounted(() => {
|
||||
&.jc-bz-item-disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.jc-bz-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.jc-bz-info {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.jc-bz-content {
|
||||
flex: 1;
|
||||
@ -398,14 +397,32 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.selection-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 30px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.jc-bz-side {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
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>
|
||||
<template #bottom>
|
||||
<view class="bottom-actions">
|
||||
<button
|
||||
v-if="!relaySuccess"
|
||||
class="action-btn publish-btn"
|
||||
@click="onRelayClick"
|
||||
>
|
||||
接龙
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
class="action-btn return-btn"
|
||||
@click="handleReturn"
|
||||
>
|
||||
返回
|
||||
</button>
|
||||
<view class="bottom-slot-wrap">
|
||||
<transition name="finger-fade">
|
||||
<view
|
||||
v-if="!isLoading && !relaySuccess && showFingerHint"
|
||||
class="finger-float"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<text class="finger-text">👆</text>
|
||||
</view>
|
||||
</transition>
|
||||
<view class="bottom-bar">
|
||||
<view class="bottom-actions">
|
||||
<button
|
||||
v-if="!relaySuccess"
|
||||
class="action-btn publish-btn"
|
||||
@click="onRelayClick"
|
||||
>
|
||||
点击接龙
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
class="action-btn return-btn"
|
||||
@click="handleReturn"
|
||||
>
|
||||
返回
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</BasicLayout>
|
||||
</template>
|
||||
|
||||
<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 { getByJlIdApi, jlzxFindByJlParamsApi, relayFinishApi } from "@/api/base/server";
|
||||
import { imagUrl } from "@/utils";
|
||||
@ -133,7 +146,7 @@ const noticeId = ref<string>("");
|
||||
const noticeDetail = ref<any>(null);
|
||||
const isLoading = ref(false);
|
||||
const studentList = ref<any[]>([]);
|
||||
const descExpanded = ref(false);
|
||||
const descExpanded = ref(true);
|
||||
|
||||
// 新增参数
|
||||
const njId = ref<string>("");
|
||||
@ -148,6 +161,42 @@ const selectedOption = ref<string>("");
|
||||
// 接龙成功状态
|
||||
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 signTitle = ref<string>("签名");
|
||||
@ -158,39 +207,52 @@ const showSignature = ref<boolean>(false);
|
||||
const userStore = useUserStore();
|
||||
const currentStudent = computed(() => userStore.curXs);
|
||||
|
||||
const descPreview = computed(() => {
|
||||
if (!noticeDetail.value?.jlms) return '';
|
||||
// 使用正则表达式去除 HTML 标签,兼容所有平台
|
||||
const text = noticeDetail.value.jlms
|
||||
.replace(/<[^>]+>/g, '') // 去除 HTML 标签
|
||||
.replace(/ /g, '\u3000') // 转换为中文全角空格(保留段落缩进)
|
||||
/** 将常见 HTML 实体转为字符;小程序 rich-text 对 — 等命名实体支持不完整,需先解码 */
|
||||
function decodeHtmlEntitiesForRichText(html: string): string {
|
||||
if (!html) return '';
|
||||
return html
|
||||
.replace(/ /g, '\u00A0')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/“/g, '"') // 左双引号
|
||||
.replace(/”/g, '"') // 右双引号
|
||||
.replace(/‘/g, "'") // 左单引号
|
||||
.replace(/’/g, "'") // 右单引号
|
||||
.replace(/—/g, '—') // 长破折号
|
||||
.replace(/–/g, '–') // 短破折号
|
||||
.replace(/…/g, '…') // 省略号
|
||||
.replace(/·/g, '·') // 间隔号
|
||||
.replace(/&#\d+;/g, '') // 移除数字型 HTML 实体
|
||||
.replace(/[\r\n]+/g, ' ') // 将换行符转换为空格
|
||||
.replace(/[ \t]+/g, ' ') // 只合并普通空格和制表符,保留全角空格
|
||||
.replace(/“/g, '\u201c')
|
||||
.replace(/”/g, '\u201d')
|
||||
.replace(/‘/g, '\u2018')
|
||||
.replace(/’/g, '\u2019')
|
||||
.replace(/—/g, '\u2014')
|
||||
.replace(/–/g, '\u2013')
|
||||
.replace(/…/g, '\u2026')
|
||||
.replace(/·/g, '\u00b7')
|
||||
.replace(/&#(\d+);/g, (_, n) => {
|
||||
const code = parseInt(n, 10);
|
||||
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();
|
||||
|
||||
return text.slice(0, 100) + (text.length > 100 ? '...' : '');
|
||||
});
|
||||
|
||||
// 处理完整内容,移除段落开头的多余空格(因为 CSS 已设置 text-indent)
|
||||
const processedContent = computed(() => {
|
||||
if (!noticeDetail.value?.jlms) return '';
|
||||
// 将段落开头的 4个 替换为空(CSS 会自动添加 2em 缩进)
|
||||
return noticeDetail.value.jlms
|
||||
.replace(/( \s*){4}/g, '') // 移除 4个连续的 (可能有空格分隔)
|
||||
.replace(/( ){4}/g, ''); // 移除紧挨着的 4个
|
||||
const stripped = String(noticeDetail.value.jlms)
|
||||
.replace(/( \s*){4}/g, '')
|
||||
.replace(/( ){4}/g, '');
|
||||
return decodeHtmlEntitiesForRichText(stripped);
|
||||
});
|
||||
|
||||
// 获取文件名
|
||||
@ -518,18 +580,72 @@ watch(studentList, (list) => {
|
||||
<style scoped lang="scss">
|
||||
.notice-detail-page {
|
||||
background-color: #f4f5f7;
|
||||
padding-bottom: 70px;
|
||||
padding-bottom: 80px;
|
||||
padding: 15px;
|
||||
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 {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
padding: 12px 15px;
|
||||
padding-bottom: calc(12px + constant(safe-area-inset-bottom));
|
||||
padding-bottom: calc(12px + env(safe-area-inset-bottom));
|
||||
background-color: #ffffff;
|
||||
border-top: 1px solid #e5e5e5;
|
||||
|
||||
.action-btn {
|
||||
width: 90%;
|
||||
|
||||
@ -34,6 +34,10 @@
|
||||
<text class="label">银行账号:</text>
|
||||
<text class="value">{{ xkTf.khZh }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">开户网点:</text>
|
||||
<text class="value">{{ xkTf.khWd || '—' }}</text>
|
||||
</view>
|
||||
<view class="info-column">
|
||||
<text class="label">缴费凭证:</text>
|
||||
<text class="value">
|
||||
|
||||
@ -94,6 +94,15 @@ const [register, { getValue, setValue }] = useForm({
|
||||
required: true,
|
||||
componentProps: { },
|
||||
},
|
||||
{
|
||||
field: "khWd",
|
||||
label: "开户网点",
|
||||
component: "BasicInput",
|
||||
required: true,
|
||||
componentProps: {
|
||||
placeholder: "如:泸州江阳支行",
|
||||
},
|
||||
},
|
||||
{
|
||||
field: "jfPz",
|
||||
label: "缴费凭证",
|
||||
|
||||
@ -113,7 +113,7 @@ interface TaskItem {
|
||||
kcmc?: string;
|
||||
kcId?: string;
|
||||
xsId?: string;
|
||||
ispj?: string; // A:已评价
|
||||
ispj?: string; // A:已评价 B:未评价(待评价)
|
||||
}
|
||||
|
||||
const taskList = ref<TaskItem[]>([]);
|
||||
@ -167,7 +167,11 @@ onLoad(async (options) => {
|
||||
xsId.value = options.xsId;
|
||||
}
|
||||
|
||||
loadTaskList();
|
||||
// openId 异步登录时 onShow 往往在 isLoginReady 仍为 false 时已执行过,需在 onLoad 末尾补一次加载;
|
||||
// 普通进入只由 onShow 拉取,避免与 onLoad 重复请求
|
||||
if (options && options.openId) {
|
||||
loadTaskList();
|
||||
}
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
@ -231,11 +235,27 @@ const loadTaskList = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
/** 该任务是否已被教师评价(锁定独立项目选择) */
|
||||
const isTaskEvaluated = (t: TaskItem) =>
|
||||
t.zpzt === 'C' || t.ispj === 'A';
|
||||
|
||||
// 开始挑战 - 跳转到提交页面
|
||||
const startChallenge = (task: TaskItem) => {
|
||||
// 验证:如果已经有一个项目提交了,不允许挑战其他项目
|
||||
const submittedTask = taskList.value.find(t => t.zpzt === 'A' && t.id !== task.id);
|
||||
if (submittedTask) {
|
||||
// 当前任务已评价:不可再挑战
|
||||
if (isTaskEvaluated(task)) {
|
||||
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({
|
||||
title: '已完成挑战',
|
||||
icon: 'none',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user