添加就餐相关页面
This commit is contained in:
parent
9ffab44ca4
commit
a27e4e8a40
92
src/api/base/jcApi.ts
Normal file
92
src/api/base/jcApi.ts
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import { get, post } from "@/utils/request";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取就餐标准列表
|
||||||
|
*/
|
||||||
|
export const jcGetJcBzListApi = async (params: any) => {
|
||||||
|
return await get("/mobile/jc/getJcBzList", params);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取就餐标准详情
|
||||||
|
*/
|
||||||
|
export const jcGetJcBzDetailApi = async (params: any) => {
|
||||||
|
return await get("/mobile/jc/getJcBzDetail", params);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学生报名就餐标准
|
||||||
|
*/
|
||||||
|
export const jcBmJcBzApi = async (params: any) => {
|
||||||
|
return await post("/mobile/jc/bmJcBz", params);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取学生已报名的就餐标准列表
|
||||||
|
*/
|
||||||
|
export const jcGetXsBmJcBzListApi = async (params: any) => {
|
||||||
|
return await get("/mobile/jc/getXsBmJcBzList", params);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消报名就餐标准
|
||||||
|
*/
|
||||||
|
export const jcCancelBmJcBzApi = async (params: any) => {
|
||||||
|
return await post("/mobile/jc/cancelBmJcBz", params);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取就餐标准报名倒计时
|
||||||
|
*/
|
||||||
|
export const jcGetBmExpiredTimeApi = async (params: any) => {
|
||||||
|
return await get("/mobile/jc/getBmExpiredTime", params);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发起就餐标准缴费
|
||||||
|
*/
|
||||||
|
export const jcFqJcBzJfjApi = async (params: any) => {
|
||||||
|
return await post("/mobile/jc/fqJcBzJfj", params);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询就餐标准缴费状态
|
||||||
|
*/
|
||||||
|
export const jcJcBzJfCxjApi = async (params: any) => {
|
||||||
|
return await post("/mobile/jc/jcBzJfcx", params);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取就餐标准报名学生列表
|
||||||
|
*/
|
||||||
|
export const jcGetJcBzBmXsListApi = async (params: any) => {
|
||||||
|
return await get("/mobile/jc/getJcBzBmXsList", params);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取就餐标准统计信息
|
||||||
|
*/
|
||||||
|
export const jcGetJcBzTjInfoApi = async (params: any) => {
|
||||||
|
return await get("/mobile/jc/getJcBzTjInfo", params);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前学期就餐标准配置
|
||||||
|
*/
|
||||||
|
export const jcGetDqXqJcBzConfigApi = async () => {
|
||||||
|
return await get("/mobile/jc/getDqXqJcBzConfig");
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取学生就餐记录
|
||||||
|
*/
|
||||||
|
export const jcGetXsJcJlApi = async (params: any) => {
|
||||||
|
return await get("/mobile/jc/getXsJcJl", params);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取就餐标准支付倒计时
|
||||||
|
*/
|
||||||
|
export const jcGetJcBzPayExpiredTimeApi = async (params: any) => {
|
||||||
|
return await get("/mobile/jc/getJcBzPayExpiredTime", params);
|
||||||
|
};
|
||||||
265
src/pages/base/components/JcBzList/index.vue
Normal file
265
src/pages/base/components/JcBzList/index.vue
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<!-- 就餐标准网格列表 -->
|
||||||
|
<view class="jc-bz-grid" v-if="jcBzList.length > 0">
|
||||||
|
<view
|
||||||
|
v-for="(jcBz, index) in jcBzList"
|
||||||
|
:key="jcBz.id || index"
|
||||||
|
class="jc-bz-item"
|
||||||
|
:class="{ selected: jcBz.isSelected }"
|
||||||
|
@click="toggleSelection(jcBz)"
|
||||||
|
>
|
||||||
|
<view class="jc-bz-header">
|
||||||
|
<view class="jc-bz-name">{{ jcBz.bzmc }}</view>
|
||||||
|
<view class="detail-btn" @click.stop="goToDetail(jcBz)">
|
||||||
|
<image src="/static/base/home/details.svg" class="detail-icon" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="jc-bz-info">
|
||||||
|
<view class="jc-bz-price">
|
||||||
|
<text>价格:</text>
|
||||||
|
<text class="price-value">¥{{ jcBz.jfje }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="jc-bz-desc">{{ jcBz.bzms || '暂无描述' }}</view>
|
||||||
|
</view>
|
||||||
|
<view v-if="jcBz.isSelected" class="selected-mark">
|
||||||
|
<uni-icons
|
||||||
|
type="checkbox-filled"
|
||||||
|
color="#3FBF72"
|
||||||
|
size="22"
|
||||||
|
></uni-icons>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 暂无数据提示 -->
|
||||||
|
<view v-else class="empty-jc-bz-list">
|
||||||
|
<view class="empty-icon">
|
||||||
|
<u-icon name="list" size="50" color="#C8C9CC"></u-icon>
|
||||||
|
</view>
|
||||||
|
<view class="empty-text">暂无就餐标准数据</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, onMounted } from "vue";
|
||||||
|
import { useDataStore } from "@/store/modules/data";
|
||||||
|
import { jcGetJcBzListApi } from "@/api/base/jcApi";
|
||||||
|
const { setJcBzData } = useDataStore();
|
||||||
|
|
||||||
|
// 接收外部传入属性并设置默认值
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
xsId: string,
|
||||||
|
canSelected: boolean,
|
||||||
|
multiple: boolean,
|
||||||
|
}>(), {
|
||||||
|
xsId: '',
|
||||||
|
canSelected: false,
|
||||||
|
multiple: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 定义一个上级传入的emit响应事件用于接收数据变更
|
||||||
|
const emit = defineEmits(['change'])
|
||||||
|
|
||||||
|
// 就餐标准列表数据
|
||||||
|
const jcBzList = ref<any>([]);
|
||||||
|
|
||||||
|
// 获取就餐标准列表
|
||||||
|
const getJcBzList = async () => {
|
||||||
|
try {
|
||||||
|
const res = await jcGetJcBzListApi({
|
||||||
|
xsId: props.xsId
|
||||||
|
});
|
||||||
|
if (res.resultCode === 1) {
|
||||||
|
jcBzList.value = res.result || [];
|
||||||
|
// 初始化选中状态
|
||||||
|
initSelectedStatus();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取就餐标准列表失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化选中状态
|
||||||
|
const initSelectedStatus = () => {
|
||||||
|
// 获取本地存储的已选就餐标准ID数组
|
||||||
|
let selectedJcBzIds = uni.getStorageSync("selectedJcBzIds") || [];
|
||||||
|
|
||||||
|
jcBzList.value.forEach((jcBz: any) => {
|
||||||
|
jcBz.isSelected = selectedJcBzIds.includes(jcBz.id);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 切换选餐标准
|
||||||
|
const toggleSelection = (jcBz: any) => {
|
||||||
|
if (!props.canSelected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 获取本地存储的已选就餐标准ID数组
|
||||||
|
let selectedJcBzIds = uni.getStorageSync("selectedJcBzIds") || [];
|
||||||
|
if (jcBz.isSelected) {
|
||||||
|
jcBz.isSelected = false;
|
||||||
|
if (props.multiple) {
|
||||||
|
// 如果是多选,则从已选数组中移除
|
||||||
|
selectedJcBzIds = selectedJcBzIds.filter(
|
||||||
|
(id: string) => id !== jcBz.id
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// 如果是单选,则清空已选数组
|
||||||
|
selectedJcBzIds = [];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (props.multiple) {
|
||||||
|
// 如果是多选,则添加到已选数组
|
||||||
|
if (!selectedJcBzIds.includes(jcBz.id)) {
|
||||||
|
selectedJcBzIds.push(jcBz.id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果是单选,则清空已选数组并添加当前标准
|
||||||
|
selectedJcBzIds = [jcBz.id];
|
||||||
|
// 清理选中状态
|
||||||
|
jcBzList.value.forEach((jcBz: any) => {
|
||||||
|
jcBz.isSelected = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
jcBz.isSelected = true;
|
||||||
|
}
|
||||||
|
// 更新本地存储
|
||||||
|
uni.setStorageSync("selectedJcBzIds", selectedJcBzIds);
|
||||||
|
emit("change", selectedJcBzIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
const goToDetail = (jcBz: any) => {
|
||||||
|
setJcBzData(jcBz);
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/base/jc/detail`,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听学生ID变更
|
||||||
|
watch(() => props.xsId, (newVal) => {
|
||||||
|
if (newVal) {
|
||||||
|
getJcBzList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.xsId) {
|
||||||
|
getJcBzList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.jc-bz-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding: 15px 15px 0 15px;
|
||||||
|
|
||||||
|
.jc-bz-item {
|
||||||
|
position: relative;
|
||||||
|
width: calc(50% - 10px);
|
||||||
|
margin-bottom: 15px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 15px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:nth-child(odd) {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(even) {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
border: 1px solid #3fbf72;
|
||||||
|
background-color: rgba(63, 191, 114, 0.05);
|
||||||
|
box-shadow: 0 2px 8px rgba(63, 191, 114, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.jc-bz-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
.jc-bz-name {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
flex: 1;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-btn {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 4px;
|
||||||
|
|
||||||
|
.detail-icon {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.jc-bz-info {
|
||||||
|
.jc-bz-price {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
.price-value {
|
||||||
|
color: #ff6b00;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.jc-bz-desc {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-mark {
|
||||||
|
position: absolute;
|
||||||
|
top: -6px;
|
||||||
|
right: -6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暂无数据样式
|
||||||
|
.empty-jc-bz-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 60px 20px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.empty-icon {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
background-color: #f5f6f7;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #303133;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
221
src/pages/base/components/JcRecordList/index.vue
Normal file
221
src/pages/base/components/JcRecordList/index.vue
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<!-- 就餐记录列表 -->
|
||||||
|
<view class="jc-record-list" v-if="jcRecordList.length > 0">
|
||||||
|
<view
|
||||||
|
v-for="(record, index) in jcRecordList"
|
||||||
|
:key="record.id || index"
|
||||||
|
class="record-item"
|
||||||
|
>
|
||||||
|
<view class="record-header">
|
||||||
|
<view class="record-date">{{ formatDate(record.jcDate) }}</view>
|
||||||
|
<view class="record-status" :class="getStatusClass(record.status)">
|
||||||
|
{{ getStatusText(record.status) }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="record-content">
|
||||||
|
<view class="record-info">
|
||||||
|
<view class="record-bz">{{ record.bzmc }}</view>
|
||||||
|
<view class="record-time">{{ record.jcTime }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="record-price">¥{{ record.jfje }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="record-footer">
|
||||||
|
<view class="record-location">{{ record.jcLocation || '暂无地点信息' }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 暂无数据提示 -->
|
||||||
|
<view v-else class="empty-record-list">
|
||||||
|
<view class="empty-icon">
|
||||||
|
<u-icon name="list" size="50" color="#C8C9CC"></u-icon>
|
||||||
|
</view>
|
||||||
|
<view class="empty-text">暂无就餐记录</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, onMounted } from "vue";
|
||||||
|
import { jcGetXsJcJlApi } from "@/api/base/jcApi";
|
||||||
|
|
||||||
|
// 接收外部传入属性并设置默认值
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
xsId: string,
|
||||||
|
}>(), {
|
||||||
|
xsId: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 就餐记录列表数据
|
||||||
|
const jcRecordList = ref<any>([]);
|
||||||
|
|
||||||
|
// 获取就餐记录列表
|
||||||
|
const getJcRecordList = async () => {
|
||||||
|
try {
|
||||||
|
const res = await jcGetXsJcJlApi({
|
||||||
|
xsId: props.xsId
|
||||||
|
});
|
||||||
|
if (res.resultCode === 1) {
|
||||||
|
jcRecordList.value = res.result || [];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取就餐记录失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 格式化日期
|
||||||
|
const formatDate = (dateStr: string) => {
|
||||||
|
if (!dateStr) return '';
|
||||||
|
const date = new Date(dateStr);
|
||||||
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取状态样式类
|
||||||
|
const getStatusClass = (status: string) => {
|
||||||
|
switch (status) {
|
||||||
|
case '1': return 'status-success';
|
||||||
|
case '2': return 'status-pending';
|
||||||
|
case '3': return 'status-cancelled';
|
||||||
|
default: return 'status-default';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取状态文本
|
||||||
|
const getStatusText = (status: string) => {
|
||||||
|
switch (status) {
|
||||||
|
case '1': return '已完成';
|
||||||
|
case '2': return '进行中';
|
||||||
|
case '3': return '已取消';
|
||||||
|
default: return '未知状态';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听学生ID变更
|
||||||
|
watch(() => props.xsId, (newVal) => {
|
||||||
|
if (newVal) {
|
||||||
|
getJcRecordList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.xsId) {
|
||||||
|
getJcRecordList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.jc-record-list {
|
||||||
|
padding: 15px;
|
||||||
|
|
||||||
|
.record-item {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
.record-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
.record-date {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-status {
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 10px;
|
||||||
|
|
||||||
|
&.status-success {
|
||||||
|
background-color: #f0f9ff;
|
||||||
|
color: #3fbf72;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.status-pending {
|
||||||
|
background-color: #fff7e6;
|
||||||
|
color: #ff8c00;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.status-cancelled {
|
||||||
|
background-color: #fff2f0;
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.status-default {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
.record-info {
|
||||||
|
.record-bz {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-time {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-price {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ff6b00;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-footer {
|
||||||
|
.record-location {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暂无数据样式
|
||||||
|
.empty-record-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 60px 20px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.empty-icon {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
background-color: #f5f6f7;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #303133;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
223
src/pages/base/jc/bm.vue
Normal file
223
src/pages/base/jc/bm.vue
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
<template>
|
||||||
|
<view class="jc-bm-page">
|
||||||
|
<!-- 报名信息头部 - 固定部分 -->
|
||||||
|
<view class="selection-header">
|
||||||
|
<view class="header-content">
|
||||||
|
<!-- 学生选择部分 -->
|
||||||
|
<XsPicker :is-bar="true" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 可滚动的内容区域 -->
|
||||||
|
<view class="scrollable-content">
|
||||||
|
<JcBzList :xs-id="curXs.id" :can-selected="true" :multiple="true" @change="onJcBzChange" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 底部操作区域 -->
|
||||||
|
<view class="bottom-action">
|
||||||
|
<view class="selected-info">
|
||||||
|
<text>已选择:{{ selectedCount }} 个就餐标准</text>
|
||||||
|
<text class="total-price">总金额:¥{{ totalPrice }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="action-buttons">
|
||||||
|
<view class="cancel-btn" @click="goBack">取消</view>
|
||||||
|
<view class="confirm-btn" @click="confirmBm" :class="{ disabled: selectedCount === 0 }">确认报名</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import XsPicker from "@/pages/base/components/XsPicker/index.vue"
|
||||||
|
import JcBzList from "@/pages/base/components/JcBzList/index.vue"
|
||||||
|
import { useUserStore } from "@/store/modules/user";
|
||||||
|
import { useDataStore } from "@/store/modules/data";
|
||||||
|
import { jcBmJcBzApi } from "@/api/base/jcApi";
|
||||||
|
|
||||||
|
const { getCurXs, getUser } = useUserStore();
|
||||||
|
const { getData, setData } = useDataStore();
|
||||||
|
|
||||||
|
const curXs = computed(() => getCurXs);
|
||||||
|
|
||||||
|
// 选中的就餐标准ID列表
|
||||||
|
const selectedJcBzIds = ref<string[]>([]);
|
||||||
|
// 选中的就餐标准列表
|
||||||
|
const selectedJcBzList = ref<any[]>([]);
|
||||||
|
|
||||||
|
// 选中数量
|
||||||
|
const selectedCount = computed(() => selectedJcBzIds.value.length);
|
||||||
|
|
||||||
|
// 总金额
|
||||||
|
const totalPrice = computed(() => {
|
||||||
|
let total = 0;
|
||||||
|
selectedJcBzList.value.forEach(jcBz => {
|
||||||
|
total += jcBz.jfje || 0;
|
||||||
|
});
|
||||||
|
return total;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 就餐标准选择变更
|
||||||
|
const onJcBzChange = (ids: string[]) => {
|
||||||
|
selectedJcBzIds.value = ids;
|
||||||
|
// 这里需要根据选中的ID获取对应的就餐标准详情
|
||||||
|
// 实际项目中可能需要从本地存储或重新请求数据
|
||||||
|
updateSelectedJcBzList();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新选中的就餐标准列表
|
||||||
|
const updateSelectedJcBzList = () => {
|
||||||
|
// 这里需要根据selectedJcBzIds从本地存储或重新请求数据
|
||||||
|
// 暂时使用空数组,实际项目中需要实现
|
||||||
|
selectedJcBzList.value = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 返回上一页
|
||||||
|
const goBack = () => {
|
||||||
|
uni.navigateBack();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 确认报名
|
||||||
|
const confirmBm = async () => {
|
||||||
|
if (selectedCount.value === 0) {
|
||||||
|
uni.showToast({
|
||||||
|
title: "请选择就餐标准",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await jcBmJcBzApi({
|
||||||
|
xsId: curXs.value.id,
|
||||||
|
jcBzIds: selectedJcBzIds.value,
|
||||||
|
jzId: getUser.jzId,
|
||||||
|
userId: getUser.userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.resultCode === 1) {
|
||||||
|
uni.showToast({
|
||||||
|
title: "报名成功",
|
||||||
|
icon: "success",
|
||||||
|
});
|
||||||
|
|
||||||
|
// 保存报名数据到store
|
||||||
|
setData({
|
||||||
|
...getData,
|
||||||
|
xsId: curXs.value.id,
|
||||||
|
jcBzIds: selectedJcBzIds.value,
|
||||||
|
jcBzList: selectedJcBzList.value,
|
||||||
|
totalJe: totalPrice.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 跳转到支付页面
|
||||||
|
uni.redirectTo({
|
||||||
|
url: '/pages/base/jc/pay/index'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: res.result?.message || "报名失败",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('报名失败:', error);
|
||||||
|
uni.showToast({
|
||||||
|
title: "报名失败,请重试",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 页面卸载前清除定时器
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.jc-bm-page {
|
||||||
|
min-height: 100%;
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection-header {
|
||||||
|
background: linear-gradient(135deg, #4a90e2, #2879ff);
|
||||||
|
padding: 20px 15px;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 0 0 15px 15px;
|
||||||
|
box-shadow: 0 4px 12px rgba(40, 121, 255, 0.2);
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 可滚动内容区域样式
|
||||||
|
.scrollable-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
-webkit-overflow-scrolling: touch; // 增强iOS滚动体验
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-action {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 15px;
|
||||||
|
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
.selected-info {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
|
||||||
|
.total-price {
|
||||||
|
color: #ff6b00;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.cancel-btn,
|
||||||
|
.confirm-btn {
|
||||||
|
width: 48%;
|
||||||
|
height: 44px;
|
||||||
|
line-height: 44px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 22px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn {
|
||||||
|
background-color: #fff;
|
||||||
|
color: #333;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-btn {
|
||||||
|
background-color: #2879ff;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
background-color: #ccc;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
285
src/pages/base/jc/detail.vue
Normal file
285
src/pages/base/jc/detail.vue
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
<template>
|
||||||
|
<BasicLayout>
|
||||||
|
<view class="jc-detail">
|
||||||
|
<!-- 就餐标准信息卡片 -->
|
||||||
|
<view class="info-card">
|
||||||
|
<view class="card-title">就餐标准信息</view>
|
||||||
|
<view class="divider"></view>
|
||||||
|
|
||||||
|
<view class="jc-info">
|
||||||
|
<image
|
||||||
|
class="jc-image"
|
||||||
|
:src="jcDetail.lxtp"
|
||||||
|
mode="aspectFill"
|
||||||
|
></image>
|
||||||
|
|
||||||
|
<view class="jc-content">
|
||||||
|
<view class="jc-name">{{ jcDetail.bzmc }}</view>
|
||||||
|
<view class="jc-price">
|
||||||
|
价格:<text class="price-value">¥{{ jcDetail.jfje }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="jc-desc">{{ jcDetail.bzms || '暂无描述' }}</view>
|
||||||
|
<view class="jc-time">有效期:{{ jcDetail.yxq || '暂无有效期信息' }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 就餐标准详情 -->
|
||||||
|
<view class="info-card">
|
||||||
|
<view class="card-title">标准详情</view>
|
||||||
|
<view class="divider"></view>
|
||||||
|
|
||||||
|
<view class="content-section">
|
||||||
|
<text>{{ jcDetail.bzms || "暂无详细信息" }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 使用说明 -->
|
||||||
|
<view class="info-card">
|
||||||
|
<view class="card-title">使用说明</view>
|
||||||
|
<view class="divider"></view>
|
||||||
|
|
||||||
|
<view class="content-section">
|
||||||
|
<template v-if="usageInstructions && usageInstructions.length > 0">
|
||||||
|
<view
|
||||||
|
v-for="(instruction, index) in usageInstructions"
|
||||||
|
:key="index"
|
||||||
|
class="instruction-item"
|
||||||
|
>
|
||||||
|
<text>{{ instruction }}</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<view class="empty-data">
|
||||||
|
<u-icon name="info-circle" color="#C8C9CC" size="18"></u-icon>
|
||||||
|
<text>暂无使用说明</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 注意事项 -->
|
||||||
|
<view class="info-card">
|
||||||
|
<view class="card-title">注意事项</view>
|
||||||
|
<view class="divider"></view>
|
||||||
|
|
||||||
|
<view class="content-section">
|
||||||
|
<template v-if="precautions && precautions.length > 0">
|
||||||
|
<view
|
||||||
|
v-for="(precaution, index) in precautions"
|
||||||
|
:key="index"
|
||||||
|
class="precaution-item"
|
||||||
|
>
|
||||||
|
<text>{{ precaution }}</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<view class="empty-data">
|
||||||
|
<u-icon name="info-circle" color="#C8C9CC" size="18"></u-icon>
|
||||||
|
<text>暂无注意事项</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<template #bottom>
|
||||||
|
<view class="white-bg-color py-5">
|
||||||
|
<view class="flex-row items-center pb-10 pt-5">
|
||||||
|
<u-button
|
||||||
|
text="返回"
|
||||||
|
class="ml-15 mr-7"
|
||||||
|
:plain="true"
|
||||||
|
@click="navigateBack"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</BasicLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, computed } from "vue";
|
||||||
|
import { navigateBack } from "@/utils/uniapp";
|
||||||
|
import { useDataStore } from "@/store/modules/data";
|
||||||
|
import { storeToRefs } from "pinia";
|
||||||
|
import { imagUrl } from "@/utils";
|
||||||
|
import { jcGetJcBzDetailApi } from "@/api/base/jcApi";
|
||||||
|
|
||||||
|
// 定义就餐标准数据类型
|
||||||
|
interface JcBzData {
|
||||||
|
id?: string;
|
||||||
|
bzmc?: string;
|
||||||
|
jfje?: number;
|
||||||
|
bzms?: string;
|
||||||
|
yxq?: string;
|
||||||
|
lxtp?: string;
|
||||||
|
[key: string]: any; // 允许其他字段
|
||||||
|
}
|
||||||
|
|
||||||
|
const useData = useDataStore();
|
||||||
|
const { jcBzData } = storeToRefs(useData);
|
||||||
|
|
||||||
|
// 使用说明 - 从备注中解析
|
||||||
|
const usageInstructions = ref<string[]>([]);
|
||||||
|
|
||||||
|
// 注意事项 - 从备注中解析
|
||||||
|
const precautions = ref<string[]>([]);
|
||||||
|
|
||||||
|
const jcBzDetail = jcBzData.value as JcBzData;
|
||||||
|
if (jcBzDetail && jcBzDetail.id) {
|
||||||
|
jcGetJcBzDetailApi({
|
||||||
|
jcBzId: jcBzDetail.id,
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.resultCode == 1) {
|
||||||
|
// 这里可以根据实际数据结构处理详情信息
|
||||||
|
console.log('就餐标准详情:', res.result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 就餐标准详情数据
|
||||||
|
const jcDetail = computed(() => {
|
||||||
|
const data = (jcBzData.value as JcBzData) || {};
|
||||||
|
return {
|
||||||
|
id: data.id || "",
|
||||||
|
bzmc: data.bzmc || "暂无标准名称",
|
||||||
|
jfje: data.jfje || 0,
|
||||||
|
bzms: data.bzms || "暂无描述",
|
||||||
|
yxq: data.yxq || "暂无有效期信息",
|
||||||
|
lxtp: imagUrl(data.lxtp || ''), // 使用imagUrl处理图片路径
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 解析使用说明和注意事项
|
||||||
|
const parseInstructions = () => {
|
||||||
|
const data = (jcBzData.value as JcBzData) || {};
|
||||||
|
const bzms = data.bzms || '';
|
||||||
|
|
||||||
|
// 这里可以根据实际的数据格式来解析使用说明和注意事项
|
||||||
|
// 示例:假设备注中包含使用说明和注意事项,用特定分隔符分隔
|
||||||
|
if (bzms) {
|
||||||
|
// 这里可以根据实际的数据格式来解析
|
||||||
|
// 示例:假设使用说明和注意事项用"注意事项:"分隔
|
||||||
|
const parts = bzms.split('注意事项:');
|
||||||
|
if (parts.length > 1) {
|
||||||
|
usageInstructions.value = parts[0].split('\n').filter(item => item.trim());
|
||||||
|
precautions.value = parts[1].split('\n').filter(item => item.trim());
|
||||||
|
} else {
|
||||||
|
usageInstructions.value = bzms.split('\n').filter(item => item.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
parseInstructions();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.jc-detail {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-card {
|
||||||
|
margin: 15px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 15px;
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
height: 1px;
|
||||||
|
background-color: #eee;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.jc-info {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.jc-image {
|
||||||
|
width: 120px;
|
||||||
|
height: 138px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jc-content {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.jc-name {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jc-price {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
.price-value {
|
||||||
|
color: #ff6b00;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.jc-desc {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jc-time {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-section {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.6;
|
||||||
|
|
||||||
|
.instruction-item,
|
||||||
|
.precaution-item {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding-left: 15px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: "•";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
color: #2879ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-data {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px 0;
|
||||||
|
color: #909399;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
text {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
67
src/pages/base/jc/index.vue
Normal file
67
src/pages/base/jc/index.vue
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<template>
|
||||||
|
<view class="jc-page">
|
||||||
|
<!-- 就餐信息头部 - 固定部分 -->
|
||||||
|
<view class="selection-header">
|
||||||
|
<view class="header-content">
|
||||||
|
<!-- 学生选择部分 -->
|
||||||
|
<XsPicker :is-bar="true" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 可滚动的内容区域 -->
|
||||||
|
<view class="scrollable-content">
|
||||||
|
<JcBzList :xs-id="curXs.id" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import XsPicker from "@/pages/base/components/XsPicker/index.vue"
|
||||||
|
import JcBzList from "@/pages/base/components/JcBzList/index.vue"
|
||||||
|
import { useUserStore } from "@/store/modules/user";
|
||||||
|
|
||||||
|
const { getCurXs } = useUserStore();
|
||||||
|
|
||||||
|
const curXs = computed(() => getCurXs);
|
||||||
|
|
||||||
|
// 页面卸载前清除定时器
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.jc-page {
|
||||||
|
min-height: 100%;
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection-header {
|
||||||
|
background: linear-gradient(135deg, #4a90e2, #2879ff);
|
||||||
|
padding: 20px 15px;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 0 0 15px 15px;
|
||||||
|
box-shadow: 0 4px 12px rgba(40, 121, 255, 0.2);
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 可滚动内容区域样式
|
||||||
|
.scrollable-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
-webkit-overflow-scrolling: touch; // 增强iOS滚动体验
|
||||||
|
}
|
||||||
|
</style>
|
||||||
117
src/pages/base/jc/pay/fail.vue
Normal file
117
src/pages/base/jc/pay/fail.vue
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
<template>
|
||||||
|
<view class="payment-fail">
|
||||||
|
<!-- 失败提示卡片 -->
|
||||||
|
<view class="fail-card">
|
||||||
|
<view class="fail-icon-container">
|
||||||
|
<image
|
||||||
|
class="fail-icon"
|
||||||
|
src="/static/base/2222.png"
|
||||||
|
mode="aspectFit"
|
||||||
|
></image>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="fail-text">抱歉,报名失败!</view>
|
||||||
|
|
||||||
|
<view class="divider"></view>
|
||||||
|
|
||||||
|
<view class="tip-text">请点击下方按钮</view>
|
||||||
|
|
||||||
|
<view class="finger-icon">
|
||||||
|
<image
|
||||||
|
src="/static/base/33333.png"
|
||||||
|
mode="aspectFit"
|
||||||
|
></image>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="action-btn" @click="reselect">
|
||||||
|
重新报名
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
|
||||||
|
// 返回选课页面
|
||||||
|
const reselect = () => {
|
||||||
|
uni.redirectTo({
|
||||||
|
url: '/pages/base/jc/bm'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 实际应用中可能需要记录失败原因等信息
|
||||||
|
console.log('支付失败页面加载');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.payment-fail {
|
||||||
|
min-height: 100%;
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fail-card {
|
||||||
|
margin: 15px;
|
||||||
|
width: 90%;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 30px 15px;
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.fail-icon-container {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.fail-icon {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fail-text {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
width: 100%;
|
||||||
|
height: 1px;
|
||||||
|
background-color: #eee;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-text {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #999;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.finger-icon {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
|
||||||
|
image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
width: 200px;
|
||||||
|
height: 45px;
|
||||||
|
line-height: 45px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 22.5px;
|
||||||
|
border: 1px solid #2879ff;
|
||||||
|
color: #2879ff;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
345
src/pages/base/jc/pay/index.vue
Normal file
345
src/pages/base/jc/pay/index.vue
Normal file
@ -0,0 +1,345 @@
|
|||||||
|
<template>
|
||||||
|
<view class="payment-page">
|
||||||
|
<!-- 倒计时区域 -->
|
||||||
|
<view class="countdown-section">
|
||||||
|
<view class="countdown-icon">
|
||||||
|
<u-icon name="clock" size="22" color="#fff"></u-icon>
|
||||||
|
</view>
|
||||||
|
<view class="countdown-text">待支付</view>
|
||||||
|
<view class="countdown-timer">
|
||||||
|
<text>剩余:</text>
|
||||||
|
<text class="time-value">{{ countdownTime }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="scrollable-content">
|
||||||
|
|
||||||
|
<!-- 学生信息 -->
|
||||||
|
<view class="student-info-card">
|
||||||
|
<view class="card-title">学生信息</view>
|
||||||
|
<view class="student-info">
|
||||||
|
<view class="student-name">{{ curXs.xm }}</view>
|
||||||
|
<view class="student-class">{{ curXs.bjmc }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 就餐标准信息 -->
|
||||||
|
<view class="jc-bz-info-card">
|
||||||
|
<view class="card-title">报名就餐标准</view>
|
||||||
|
<view class="jc-bz-list">
|
||||||
|
<view
|
||||||
|
v-for="(jcBz, index) in jcBzList"
|
||||||
|
:key="index"
|
||||||
|
class="jc-bz-item"
|
||||||
|
>
|
||||||
|
<view class="jc-bz-name">{{ jcBz.bzmc }}</view>
|
||||||
|
<view class="jc-bz-price">¥{{ jcBz.jfje }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 底部支付区域 -->
|
||||||
|
<view class="payment-footer">
|
||||||
|
<view class="total-amount">
|
||||||
|
<text>总金额:</text>
|
||||||
|
<text class="amount-value">¥{{ totalJe }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="action-buttons">
|
||||||
|
<view class="cancel-btn" @click="cancelRegistration">取消报名</view>
|
||||||
|
<view class="pay-btn" @click="payNow">立即支付</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useUserStore } from "@/store/modules/user";
|
||||||
|
import { useDataStore } from "@/store/modules/data";
|
||||||
|
import { jcCancelBmJcBzApi, jcFqJcBzJfjApi, jcGetJcBzPayExpiredTimeApi } from "@/api/base/jcApi";
|
||||||
|
const { getCurXs, getUser } = useUserStore();
|
||||||
|
const { getData, setData } = useDataStore();
|
||||||
|
|
||||||
|
// 学生信息
|
||||||
|
const curXs = computed(() => getCurXs);
|
||||||
|
// 就餐标准信息
|
||||||
|
const jcBzList = computed(() => getData.jcBzList);
|
||||||
|
// 总金额
|
||||||
|
const totalJe = computed(() => {
|
||||||
|
// 循环jcBzList.value 求和
|
||||||
|
if (!jcBzList.value || !jcBzList.value.length) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let total = 0;
|
||||||
|
for (let i = 0; i < jcBzList.value.length; i++) {
|
||||||
|
total += jcBzList.value[i].jfje;
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 倒计时
|
||||||
|
const countdownTime = ref("1分20秒");
|
||||||
|
let timer: any = null;
|
||||||
|
let seconds = 1 * 60 + 20; // 1分20秒
|
||||||
|
|
||||||
|
// 开始倒计时
|
||||||
|
const startCountdown = () => {
|
||||||
|
timer = setInterval(() => {
|
||||||
|
seconds--;
|
||||||
|
if (seconds <= 0) {
|
||||||
|
clearInterval(timer);
|
||||||
|
uni.showModal({
|
||||||
|
title: "支付超时",
|
||||||
|
content: "支付已超时,请重新选课",
|
||||||
|
showCancel: false,
|
||||||
|
success: () => {
|
||||||
|
cancelRegistration();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const minutes = Math.floor(seconds / 60);
|
||||||
|
const remainSeconds = seconds % 60;
|
||||||
|
countdownTime.value = `${minutes}分${remainSeconds}秒`;
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 返回上一页
|
||||||
|
const goBack = () => {
|
||||||
|
uni.reLaunch({
|
||||||
|
url: '/pages/base/home/index'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 取消报名
|
||||||
|
const cancelRegistration = () => {
|
||||||
|
uni.showModal({
|
||||||
|
title: "取消报名",
|
||||||
|
content: "确定要取消报名吗?",
|
||||||
|
success: async (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
try {
|
||||||
|
await jcCancelBmJcBzApi({
|
||||||
|
xsId: getData.xsId,
|
||||||
|
jcBzIds: getData.jcBzIds
|
||||||
|
});
|
||||||
|
uni.showToast({
|
||||||
|
title: "已取消报名",
|
||||||
|
icon: "success",
|
||||||
|
});
|
||||||
|
goBack();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('取消报名失败:', error);
|
||||||
|
uni.showToast({
|
||||||
|
title: "取消报名失败",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 立即支付
|
||||||
|
const payNow = async () => {
|
||||||
|
try {
|
||||||
|
const res = await jcFqJcBzJfjApi({
|
||||||
|
xsId: getData.xsId,
|
||||||
|
jcBzIds: getData.jcBzIds,
|
||||||
|
jffs: "四川农信", // TODO: 目前只支持四川农信
|
||||||
|
jzId: getUser.jzId,
|
||||||
|
userId: getUser.userId,
|
||||||
|
openId: getUser.openId,
|
||||||
|
});
|
||||||
|
if (res.resultCode === 1 && res.result) {
|
||||||
|
setData({
|
||||||
|
...getData,
|
||||||
|
...res.result
|
||||||
|
});
|
||||||
|
uni.redirectTo({
|
||||||
|
url: `/pages/base/jc/pay/wait?payUrl=${encodeURIComponent(res.result.cashierPayHtml)}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
uni.showToast({
|
||||||
|
title: "发起支付失败",
|
||||||
|
icon: "error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async() => {
|
||||||
|
try {
|
||||||
|
const res = await jcGetJcBzPayExpiredTimeApi({ xsId: getCurXs.id });
|
||||||
|
console.log('获取支付倒计时', res);
|
||||||
|
if (res.resultCode === 1) {
|
||||||
|
seconds = res.result;
|
||||||
|
startCountdown();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取支付倒计时失败:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (timer) {
|
||||||
|
clearInterval(timer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.payment-page {
|
||||||
|
min-height: 100%;
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 可滚动内容区域样式
|
||||||
|
.scrollable-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
-webkit-overflow-scrolling: touch; // 增强iOS滚动体验
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.student-info-card,
|
||||||
|
.jc-bz-info-card {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.student-info {
|
||||||
|
.student-name {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.student-class {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.jc-bz-list {
|
||||||
|
.jc-bz-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px 0;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jc-bz-name {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jc-bz-price {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #ff6b00;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 15px;
|
||||||
|
background-color: #2879ff;
|
||||||
|
|
||||||
|
.countdown-icon {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-text {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #fff;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-timer {
|
||||||
|
font-size: 15px;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
.time-value {
|
||||||
|
color: #ff4d4f;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-footer {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 15px;
|
||||||
|
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
.total-amount {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
font-size: 15px;
|
||||||
|
color: #333;
|
||||||
|
|
||||||
|
.amount-value {
|
||||||
|
color: #ff6b00;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.cancel-btn,
|
||||||
|
.pay-btn {
|
||||||
|
width: 48%;
|
||||||
|
height: 44px;
|
||||||
|
line-height: 44px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 22px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn {
|
||||||
|
background-color: #fff;
|
||||||
|
color: #333;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pay-btn {
|
||||||
|
background-color: #ff8c00;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
129
src/pages/base/jc/pay/success.vue
Normal file
129
src/pages/base/jc/pay/success.vue
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
<template>
|
||||||
|
<BasicLayout>
|
||||||
|
<view class="payment-success">
|
||||||
|
<!-- 成功提示卡片 -->
|
||||||
|
<view class="success-card">
|
||||||
|
<view class="success-icon-container">
|
||||||
|
<image
|
||||||
|
class="success-icon-bg"
|
||||||
|
src="/static/base/11223.png"
|
||||||
|
mode="aspectFit"
|
||||||
|
></image>
|
||||||
|
<view class="success-icon">
|
||||||
|
<u-icon name="checkmark" size="40" color="#fff"></u-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="success-text">恭喜你,报名成功!</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 这里显示学生信息和报名的就餐标准信息 -->
|
||||||
|
|
||||||
|
</view>
|
||||||
|
<template #bottom>
|
||||||
|
<!-- <view class="white-bg-color py-5">
|
||||||
|
<view class="flex-row items-center pb-10 pt-5">
|
||||||
|
<u-button
|
||||||
|
text="返回"
|
||||||
|
class="ml-15 mr-7"
|
||||||
|
:plain="true"
|
||||||
|
@click=""
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view> -->
|
||||||
|
</template>
|
||||||
|
</BasicLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
// 返回上一页
|
||||||
|
const goBack = () => {
|
||||||
|
uni.navigateBack();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 实际应用中,应从页面参数或缓存获取课程和学生信息
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.payment-success {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 15px;
|
||||||
|
height: 44px;
|
||||||
|
background-color: #2879ff;
|
||||||
|
|
||||||
|
.nav-left {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-right {
|
||||||
|
width: 40px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-card {
|
||||||
|
margin: 15px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 30px 15px;
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.success-icon-container {
|
||||||
|
position: relative;
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.success-icon-bg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
background-color: #2879ff;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-text {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
412
src/pages/base/jc/pay/wait.vue
Normal file
412
src/pages/base/jc/pay/wait.vue
Normal file
@ -0,0 +1,412 @@
|
|||||||
|
<template>
|
||||||
|
<view class="payment-page">
|
||||||
|
<!-- 倒计时区域 -->
|
||||||
|
<view class="countdown-section">
|
||||||
|
<view class="countdown-icon">
|
||||||
|
<u-icon name="clock" size="22" color="#fff"></u-icon>
|
||||||
|
</view>
|
||||||
|
<view class="countdown-text">待支付</view>
|
||||||
|
<view class="countdown-timer">
|
||||||
|
<text>剩余:</text>
|
||||||
|
<text class="time-value">{{ countdownTime }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="scrollable-content">
|
||||||
|
<!-- H5环境下的处理 -->
|
||||||
|
<view v-if="isH5" class="h5-payment-container">
|
||||||
|
<view class="payment-info">
|
||||||
|
<view class="payment-title">支付信息</view>
|
||||||
|
<view class="payment-url">{{ payUrl }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="payment-actions">
|
||||||
|
<button class="open-payment-btn" @click="openPaymentUrl">打开支付页面</button>
|
||||||
|
<button class="copy-url-btn" @click="copyPaymentUrl">复制支付链接</button>
|
||||||
|
</view>
|
||||||
|
<view class="payment-tips">
|
||||||
|
<text>提示:</text>
|
||||||
|
<text>1. 点击"打开支付页面"按钮在新窗口打开支付</text>
|
||||||
|
<text>2. 支付完成后请返回此页面</text>
|
||||||
|
<text>3. 如无法打开,请复制链接到浏览器中打开</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 非H5环境使用web-view -->
|
||||||
|
<web-view v-else :src="payUrl" @error="handleWebViewError" class="payment-webview"></web-view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 底部支付区域 -->
|
||||||
|
<view class="payment-footer">
|
||||||
|
<view class="action-buttons">
|
||||||
|
<view class="cancel-btn" @click="cancelRegistration">取消报名</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onLoad } from "@dcloudio/uni-app";
|
||||||
|
// import { jzGetQkExpiredTime, jzXkCancelApi, jzXkJfCxjApi } from "@/api/base/server";
|
||||||
|
import { useUserStore } from "@/store/modules/user";
|
||||||
|
import { useDataStore } from "@/store/modules/data";
|
||||||
|
const { getCurXs, initWs, setWsCallback } = useUserStore();
|
||||||
|
const { getData } = useDataStore();
|
||||||
|
|
||||||
|
const payUrl = ref("");
|
||||||
|
const isH5 = ref(false);
|
||||||
|
|
||||||
|
// 检测是否为H5环境
|
||||||
|
const checkPlatform = () => {
|
||||||
|
// #ifdef H5
|
||||||
|
isH5.value = true;
|
||||||
|
// #endif
|
||||||
|
};
|
||||||
|
|
||||||
|
// 打开支付URL
|
||||||
|
const openPaymentUrl = () => {
|
||||||
|
if (payUrl.value) {
|
||||||
|
// 在新窗口打开支付页面
|
||||||
|
window.open(payUrl.value, '_blank');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 复制支付URL
|
||||||
|
const copyPaymentUrl = () => {
|
||||||
|
if (payUrl.value) {
|
||||||
|
// #ifdef H5
|
||||||
|
if (navigator.clipboard) {
|
||||||
|
navigator.clipboard.writeText(payUrl.value).then(() => {
|
||||||
|
uni.showToast({
|
||||||
|
title: '链接已复制',
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
}).catch(() => {
|
||||||
|
// 降级处理
|
||||||
|
const textArea = document.createElement('textarea');
|
||||||
|
textArea.value = payUrl.value;
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
document.body.removeChild(textArea);
|
||||||
|
uni.showToast({
|
||||||
|
title: '链接已复制',
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 降级处理
|
||||||
|
const textArea = document.createElement('textarea');
|
||||||
|
textArea.value = payUrl.value;
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
document.body.removeChild(textArea);
|
||||||
|
uni.showToast({
|
||||||
|
title: '链接已复制',
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// web-view错误处理
|
||||||
|
const handleWebViewError = (e: any) => {
|
||||||
|
console.error('web-view加载错误:', e);
|
||||||
|
uni.showModal({
|
||||||
|
title: '加载失败',
|
||||||
|
content: '支付页面加载失败,请检查网络连接或联系客服',
|
||||||
|
showCancel: false
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
setWsCallback((type: string, res: any) => {
|
||||||
|
console.log('收到WebSocket消息:', type, res.data);
|
||||||
|
// 将data从字符串转为对象
|
||||||
|
const dataObj = JSON.parse(res.data);
|
||||||
|
if (dataObj.action === 'pay') {
|
||||||
|
uni.showToast({
|
||||||
|
title: '支付成功',
|
||||||
|
icon: 'success',
|
||||||
|
});
|
||||||
|
setWsCallback((type: string, res: any) => {})
|
||||||
|
// 跳转到支付成功页面
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.reLaunch({
|
||||||
|
url: "/pages/base/xk/pay/success",
|
||||||
|
});
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 倒计时
|
||||||
|
const countdownTime = ref("1分20秒");
|
||||||
|
let timer: any = null;
|
||||||
|
let seconds = 1 * 60 + 20; // 1分20秒
|
||||||
|
|
||||||
|
// 开始倒计时
|
||||||
|
const startCountdown = () => {
|
||||||
|
timer = setInterval(() => {
|
||||||
|
seconds--;
|
||||||
|
if (seconds <= 0) {
|
||||||
|
clearInterval(timer);
|
||||||
|
uni.showModal({
|
||||||
|
title: "支付超时",
|
||||||
|
content: "支付已超时,请重新选课",
|
||||||
|
showCancel: false,
|
||||||
|
success: () => {
|
||||||
|
cancelRegistration();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const minutes = Math.floor(seconds / 60);
|
||||||
|
const remainSeconds = seconds % 60;
|
||||||
|
countdownTime.value = `${minutes}分${remainSeconds}秒`;
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 返回上一页
|
||||||
|
const goBack = () => {
|
||||||
|
uni.reLaunch({
|
||||||
|
url: getData.backUrl
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 取消报名
|
||||||
|
const cancelRegistration = () => {
|
||||||
|
// uni.showModal({
|
||||||
|
// title: "取消报名",
|
||||||
|
// content: "确定要取消报名吗?",
|
||||||
|
// success: async (res) => {
|
||||||
|
// if (res.confirm) {
|
||||||
|
// await jzXkCancelApi({
|
||||||
|
// xsId: getData.xsId,
|
||||||
|
// xkId: getData.xkId
|
||||||
|
// });
|
||||||
|
// uni.showToast({
|
||||||
|
// title: "已取消报名",
|
||||||
|
// icon: "success",
|
||||||
|
// });
|
||||||
|
// goBack();
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
};
|
||||||
|
|
||||||
|
onLoad(async (options: any) => {
|
||||||
|
// 检测平台
|
||||||
|
checkPlatform();
|
||||||
|
|
||||||
|
if (options.payUrl) {
|
||||||
|
payUrl.value = decodeURIComponent(options.payUrl);
|
||||||
|
console.log('支付URL:', payUrl.value);
|
||||||
|
|
||||||
|
// const res = await jzGetQkExpiredTime({ xsId: getCurXs.id} );
|
||||||
|
// seconds = res.result;
|
||||||
|
// initWs();
|
||||||
|
// startCountdown();
|
||||||
|
} else {
|
||||||
|
uni.showToast({ title: '缺少支付地址', icon: 'none' })
|
||||||
|
setTimeout(() => {
|
||||||
|
goBack();
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
openPaymentUrl();
|
||||||
|
// try {
|
||||||
|
// const res = await jzXkJfCxjApi({
|
||||||
|
// orderNumber: getData.orderNumber,
|
||||||
|
// xsId: getData.xsId,
|
||||||
|
// xkId: getData.xkId
|
||||||
|
// });
|
||||||
|
// // 订单状态,01-交易中,02-交易成功,03-交易失败,04-交易关闭
|
||||||
|
// const { orderStat, respCode } = res.result;
|
||||||
|
// if ("0000000000" === respCode) {
|
||||||
|
// if ("02" === orderStat) {
|
||||||
|
// uni.reLaunch({
|
||||||
|
// url: "/pages/base/xk/pay/success",
|
||||||
|
// });
|
||||||
|
// } else if ("03" === orderStat) {
|
||||||
|
// uni.reLaunch({
|
||||||
|
// url: "/pages/base/xk/pay/fail",
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// uni.showToast({
|
||||||
|
// title: "订单状态查询失败",
|
||||||
|
// icon: "error",
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// } catch (error) {
|
||||||
|
// console.log("订单查询失败", error);
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (timer) {
|
||||||
|
clearInterval(timer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.payment-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollable-content {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-webview {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
border: none !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// H5支付容器样式
|
||||||
|
.h5-payment-container {
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #fff;
|
||||||
|
margin: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-info {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.payment-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-url {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
word-break: break-all;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
flex: 1;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: 22px;
|
||||||
|
font-size: 16px;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.open-payment-btn {
|
||||||
|
background-color: #2879ff;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.copy-url-btn {
|
||||||
|
background-color: #fff;
|
||||||
|
color: #333;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-tips {
|
||||||
|
background-color: #fff7e6;
|
||||||
|
border: 1px solid #ffd591;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 15px;
|
||||||
|
|
||||||
|
text {
|
||||||
|
display: block;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #d46b08;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 15px;
|
||||||
|
background-color: #2879ff;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.countdown-icon {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-text {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #fff;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-timer {
|
||||||
|
font-size: 15px;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
.time-value {
|
||||||
|
color: #ff4d4f;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-footer {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 15px;
|
||||||
|
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
.cancel-btn {
|
||||||
|
height: 44px;
|
||||||
|
line-height: 44px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 22px;
|
||||||
|
font-size: 16px;
|
||||||
|
background-color: #fff;
|
||||||
|
color: #333;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
67
src/pages/base/jc/record.vue
Normal file
67
src/pages/base/jc/record.vue
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<template>
|
||||||
|
<view class="jc-record-page">
|
||||||
|
<!-- 记录信息头部 - 固定部分 -->
|
||||||
|
<view class="selection-header">
|
||||||
|
<view class="header-content">
|
||||||
|
<!-- 学生选择部分 -->
|
||||||
|
<XsPicker :is-bar="true" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 可滚动的内容区域 -->
|
||||||
|
<view class="scrollable-content">
|
||||||
|
<JcRecordList :xs-id="curXs.id" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import XsPicker from "@/pages/base/components/XsPicker/index.vue"
|
||||||
|
import JcRecordList from "@/pages/base/components/JcRecordList/index.vue"
|
||||||
|
import { useUserStore } from "@/store/modules/user";
|
||||||
|
|
||||||
|
const { getCurXs } = useUserStore();
|
||||||
|
|
||||||
|
const curXs = computed(() => getCurXs);
|
||||||
|
|
||||||
|
// 页面卸载前清除定时器
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.jc-record-page {
|
||||||
|
min-height: 100%;
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection-header {
|
||||||
|
background: linear-gradient(135deg, #4a90e2, #2879ff);
|
||||||
|
padding: 20px 15px;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 0 0 15px 15px;
|
||||||
|
box-shadow: 0 4px 12px rgba(40, 121, 255, 0.2);
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 可滚动内容区域样式
|
||||||
|
.scrollable-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
-webkit-overflow-scrolling: touch; // 增强iOS滚动体验
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -5,6 +5,7 @@ export const useDataStore = defineStore({
|
|||||||
state: () => ({
|
state: () => ({
|
||||||
data: {},
|
data: {},
|
||||||
kcData: {},
|
kcData: {},
|
||||||
|
jcBzData: {},
|
||||||
global: {},
|
global: {},
|
||||||
file: {},
|
file: {},
|
||||||
appCode: "JZ"
|
appCode: "JZ"
|
||||||
@ -22,6 +23,9 @@ export const useDataStore = defineStore({
|
|||||||
getKcData(): any {
|
getKcData(): any {
|
||||||
return this.kcData;
|
return this.kcData;
|
||||||
},
|
},
|
||||||
|
getJcBzData(): any {
|
||||||
|
return this.jcBzData;
|
||||||
|
},
|
||||||
getAppCode(): string {
|
getAppCode(): string {
|
||||||
return this.appCode;
|
return this.appCode;
|
||||||
},
|
},
|
||||||
@ -39,6 +43,9 @@ export const useDataStore = defineStore({
|
|||||||
setKcData(data: any) {
|
setKcData(data: any) {
|
||||||
this.kcData = data;
|
this.kcData = data;
|
||||||
},
|
},
|
||||||
|
setJcBzData(data: any) {
|
||||||
|
this.jcBzData = data;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
persist: {
|
persist: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user