1、调整退费跳转逻辑

2、完善退费申请逻辑
This commit is contained in:
ywyonui 2025-09-15 21:06:37 +08:00
parent b34ca07a45
commit d46c17386d
22 changed files with 1187 additions and 138 deletions

View File

@ -86,4 +86,36 @@ export const xkkclxFindAllApi = async () => {
*/
export const getXkkcDetailByIdApi = async (id: string) => {
return await get("/api/xkkc/getXkkcDetailById?id=" + id);
};
};
/**
* 退退使
*/
export const checkXkTfApi = async (params: any) => {
return await get("/mobile/jz/checkXkTf", params);
};
/**
* 退
* @param params
* @returns
*/
export const findPageXkTfApi = async (params: any) => {
return await get("/api/xkTf/findPage", params);
};
/**
* 退
* @param params
* @returns
*/
export const getXkTfDetailByIdApi = async (id: string) => {
return await get("/api/xkTf/getDetailById?id=" + id);
};
/**
* 退
*/
export const xkTfSqApi = async (params: any) => {
return await post("/api/xkTf/sq", params);
};

View File

@ -43,4 +43,4 @@ function change(e: string) {
}
}
</script>
<style lang="less" scoped></style>
<style lang="scss" scoped></style>

View File

@ -21,7 +21,7 @@
const attrs = useAttrs()
const blob = ref<string | null>(null)
const popup = ref<{ open: (v: string) => void } | null>(null)
const popup = ref<{ open: (v: string) => void, close: () => void }>({ open: () => { }, close: () => { }})
const emits = defineEmits(['select', 'close'])

View File

@ -1,25 +1,20 @@
<template>
<view class="wh-full">
<u-upload
:fileList="data.fileList"
@afterRead="afterRead"
@delete="deletePic"
v-bind="attrs"
:deletable="!attrs.disabled"
/>
</view>
<view class="wh-full">
<u-upload :fileList="data.fileList" @oversize="oversize" @afterRead="afterRead" @delete="deletePic" v-bind="attrs"
:deletable="!attrs.disabled" />
</view>
</template>
<script setup lang="ts">
//https://uiadmin.net/uview-plus/components/upload.html'
import {hideLoading, showLoading} from "@/utils/uniapp";
import {attachmentUpload} from "@/api/system/upload";
import {imagUrl} from "@/utils";
import { hideLoading, showLoading } from "@/utils/uniapp";
import { attachmentUpload } from "@/api/system/upload";
import { imagUrl } from "@/utils";
const attrs: any = useAttrs()
const data: any = reactive({
fileList: []
fileList: []
})
let urlList: string[] = []
let isUpdate = false
@ -27,58 +22,70 @@ let isUpdate = false
const emit = defineEmits(['update:modelValue'])
function deletePic(event: any) {
if (!attrs.disabled) {
isUpdate = true
data.fileList.splice(event.index, 1)
urlList.splice(event.index, 1)
emit('update:modelValue', urlList.join(','))
}
if (!attrs.disabled) {
isUpdate = true
data.fileList.splice(event.index, 1)
urlList.splice(event.index, 1)
emit('update:modelValue', urlList.join(','))
}
}
const props = defineProps(['modelValue'])
watchEffect(() => {
if (props.modelValue) {
if (!isUpdate) {
let urlListModel = props.modelValue.split(',')
for (let key in urlListModel) {
if (urlListModel[key] && urlListModel[key] != 'undefined') {
data.fileList.push({
url: imagUrl(urlListModel[key]),
})
urlList.push(urlListModel[key])
}
}
if (props.modelValue) {
if (!isUpdate) {
let urlListModel = props.modelValue.split(',')
for (let key in urlListModel) {
if (urlListModel[key] && urlListModel[key] != 'undefined') {
data.fileList.push({
url: imagUrl(urlListModel[key]),
})
urlList.push(urlListModel[key])
}
}
}
}
})
async function afterRead(event: any) {
isUpdate = true
// mutiple true , file
let lists: any = [].concat(event.file)
let fileListLen = data.fileList.length
lists.map((item: any) => {
data.fileList.push({
...item,
status: 'uploading',
message: '上传中'
})
})
async function oversize(file: any) {
//
const maxSize = attrs.maxSize || 5 * 1024 * 1024; // 5MB
const maxSizeMB = (maxSize / 1024 / 1024).toFixed(2);
for (let i = 0; i < lists.length; i++) {
showLoading({title: '上传中'})
const {result} = await attachmentUpload(lists[i].url)
hideLoading()
let item = data.fileList[fileListLen]
data.fileList.splice(fileListLen, 1, Object.assign(item, {
status: 'success',
message: '',
url: imagUrl(result[0].filePath)
}))
urlList.push(result[0].filePath)
fileListLen++
}
emit('update:modelValue', urlList.join(','))
uni.showToast({
title: `文件大小不能超过${maxSizeMB}MB`,
icon: 'none',
duration: 3000
});
}
async function afterRead(event: any) {
isUpdate = true
// mutiple true , file
let lists: any = [].concat(event.file)
let fileListLen = data.fileList.length
lists.map((item: any) => {
data.fileList.push({
...item,
status: 'uploading',
message: '上传中'
})
})
for (let i = 0; i < lists.length; i++) {
showLoading({ title: '上传中' })
const { result } = await attachmentUpload(lists[i].url) as any
hideLoading()
let item = data.fileList[fileListLen]
data.fileList.splice(fileListLen, 1, Object.assign(item, {
status: 'success',
message: '',
url: imagUrl(result[0].filePath)
}))
urlList.push(result[0].filePath)
fileListLen++
}
emit('update:modelValue', urlList.join(','))
}
</script>

View File

@ -0,0 +1,134 @@
<template>
<view class="image-preview-container">
<view class="preview-title" v-if="title">{{ title }}</view>
<view class="image-list" v-if="imageList && imageList.length > 0">
<view
class="image-item"
v-for="(image, index) in imageList"
:key="index"
@click="previewImages(index)"
:style="{ width: width + 'rpx', height: height + 'rpx' }"
>
<image
:src="getImageUrl(image)"
class="preview-image"
mode="aspectFill"
/>
<view class="image-overlay" v-if="showOverlay">
<uni-icons type="eye" size="24" color="#fff"></uni-icons>
</view>
</view>
</view>
<view class="no-image" v-else>
{{ emptyText || '暂无图片' }}
</view>
</view>
</template>
<script setup lang="ts">
import { imagUrl } from '@/utils'
const props = defineProps({
imageList: {
type: Array as () => string[],
default: () => []
},
title: {
type: String,
default: ''
},
emptyText: {
type: String,
default: '暂无图片'
},
showOverlay: {
type: Boolean,
default: true
},
width: {
type: Number,
default: 120
},
height: {
type: Number,
default: 120
}
})
const getImageUrl = (url: string) => {
if (url && (url.startsWith('http://') || url.startsWith('https://'))) {
return url
}
return imagUrl(url)
}
const previewImages = (currentIndex: number) => {
if (!props.imageList || props.imageList.length === 0) return
const urls = props.imageList.map(item => getImageUrl(item))
uni.previewImage({
current: currentIndex,
urls: urls as string[]
})
}
</script>
<style scoped>
.image-preview-container {
padding: 20rpx;
background-color: #fff;
}
.preview-title {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 20rpx;
color: #333;
}
.image-list {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
}
.image-item {
position: relative;
border-radius: 10rpx;
overflow: hidden;
border: 1rpx solid #eee;
}
.image-item:hover .image-overlay {
opacity: 1;
}
.preview-image {
width: 100%;
height: 100%;
display: block;
}
.image-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.3s;
}
.no-image {
text-align: center;
color: #999;
padding: 40rpx 0;
font-size: 28rpx;
}
</style>

View File

@ -227,6 +227,20 @@
"enablePullDownRefresh": false
}
},
{
"path": "pages/base/xk/tf/sq",
"style": {
"navigationBarTitleText": "选课退费申请",
"enablePullDownRefresh": false
}
},
{
"path": "pages/base/xk/tf/detail",
"style": {
"navigationBarTitleText": "选课退费详情",
"enablePullDownRefresh": false
}
},
{
"path": "pages/base/xk/qk/wks",
"style": {
@ -286,6 +300,13 @@
"enablePullDownRefresh": false
}
},
{
"path": "pages/base/gzs/tf",
"style": {
"navigationBarTitleText": "退费告知书",
"enablePullDownRefresh": false
}
},
{
"path": "pages/base/jc/index",
"style": {

View File

@ -63,6 +63,10 @@ onLoad(async (options: any) => {
async function submit() {
//
const data = await signCompRef.value.getSyncSignature();
if (!data) {
console.log("请签名");
return;
}
sign_file.value = data.base64;
setFile({
sign_file: sign_file.value,

127
src/pages/base/gzs/tf.vue Normal file
View File

@ -0,0 +1,127 @@
<template>
<BasicLayout>
<view class="p-15">
<view class="white-bg-color p-15 r-md" v-if="notice">
<RichTextContent :content="notice" />
<!-- <view v-html="notice" />" -->
</view>
<BasicSign ref="signCompRef" title="签名"></BasicSign>
</view>
<template #bottom>
<view class="white-bg-color py-5">
<view class="flex-row items-center pb-10 pt-5">
<u-button
text="下一步"
class="mx-15"
type="primary"
@click="submit"
/>
</view>
</view>
</template>
</BasicLayout>
</template>
<script lang="ts" setup>
import { xkgzsApi } from "@/api/base/server";
import { useDataStore } from "@/store/modules/data";
import { showLoading } from "@/utils/uniapp";
import { onLoad } from "@dcloudio/uni-app";
import RichTextContent from "@/components/RichTextContent/RichTextContent.vue";
const signCompRef = ref<any>(null);
const sign_file = ref<any>(null);
const { setFile } = useDataStore();
const notice = ref("");
const lxId = ref("");
onLoad(async (options: any) => {
lxId.value = options.lxId || '';
let kcLx = "";
switch (lxId.value) {
case "JC": {
kcLx = "就餐";
} break;
case "816059832": {
kcLx = "俱乐部退费";
} break;
case "962488654": {
kcLx = "兴趣课退费";
} break;
default: {
uni.reLaunch({ url: '/pages/base/home/index' });
return;
}
}
showLoading({ title: "加载中..." });
const res = await xkgzsApi({ kcLx: kcLx });
notice.value = res.rows?.[0]?.content || "";
uni.hideLoading();
});
async function submit() {
//
const data = await signCompRef.value.getSyncSignature();
if (!data) {
console.log("请签名");
return;
}
sign_file.value = data.base64;
setFile({
sign_file: sign_file.value,
});
switch (lxId.value) {
case "JC": {
uni.reLaunch({
url: "/pages/base/jc/tf",
});
} break;
default: {
uni.reLaunch({
url: "/pages/base/xk/tf/sq?xklxId=" + lxId.value,
});
}
}
}
</script>
<style lang="scss" scoped>
//
.white-bg-color {
background-color: #fff;
}
.r-md {
border-radius: 8px;
}
.p-15 {
padding: 15px;
}
.py-5 {
padding: 5px 0;
}
.pb-10 {
padding-bottom: 10px;
}
.pt-5 {
padding-top: 5px;
}
.flex-row {
display: flex;
flex-direction: row;
}
.items-center {
align-items: center;
}
.mx-15 {
margin: 0 15px;
}
</style>

View File

@ -223,6 +223,7 @@ const menuItems = ref([
icon: "/static/base/home/file-text-line.png",
path: "/pages/base/gzs/index",
permissionKey: "school-xqkxk", //
action: 'jf',
lxId: '962488654',
},
{
@ -230,6 +231,7 @@ const menuItems = ref([
icon: "/static/base/home/contacts-book-3-line.png",
path: "/pages/base/gzs/index",
permissionKey: "school-jlbxk", //
action: 'jf',
lxId: '816059832',
},
{
@ -237,13 +239,24 @@ const menuItems = ref([
icon: "/static/base/home/contacts-book-3-line.png",
path: "/pages/base/gzs/index",
permissionKey: "school-jcjf",
action: 'jf',
lxId: 'JC',
},
{
title: "退费申请",
title: "兴趣课退费",
icon: "/static/base/home/contacts-book-3-line.png",
path: "/pages/base/tf/index",
permissionKey: "school-jlb",
path: "/pages/base/gzs/tf",
permissionKey: "school-xqk-tf",
action: "tf",
lxId: "962488654",
},
{
title: "俱乐部退费",
icon: "/static/base/home/contacts-book-3-line.png",
path: "/pages/base/gzs/tf",
permissionKey: "school-jlb-tf",
action: "tf",
lxId: "816059832",
},
]);
@ -268,13 +281,13 @@ const goToGlxs = () => {
//
const handleMenuClick = debounce(async (item: any) => {
if (item.path) {
if (!item.lxId) {
if (item.lxId) {
setGlobal({ lxId: item.lxId, action: item.action, from: 'home' });
PageUtils.toHome(item.lxId, item.action);
} else {
uni.navigateTo({
url: item.path,
});
} else {
setGlobal({ lxId: item.lxId, from: 'home' });
PageUtils.toHome(item.lxId);
}
}
});

View File

@ -23,7 +23,7 @@ const switchXs = (xs: any) => {
//
setXsPickerInitialized(true);
//
PageUtils.checkLogicPage(xs.lxId);
PageUtils.checkLogicPage(getGlobal.lxId, getGlobal.action);
}
</script>

View File

@ -1,8 +1,20 @@
<template>
<view class="xkqd-list">
<!-- 课程信息卡片 -->
<view class="info-card" v-for="(item, index) in dataList" :key="index">
<view class="card-title">课程信息</view>
<view class="info-card" v-for="(item, index) in dataList" :key="index" @click="handleClick(item)">
<view class="card-header">
<view class="card-title">课程信息</view>
<view class="card-actions" v-if="canSelected">
<view class="radio-container">
<uni-icons
:type="item.selected ? 'checkbox-filled' : 'circle'"
:color="item.selected ? '#3FBF72' : '#ccc'"
size="30"
></uni-icons>
</view>
</view>
</view>
<view class="divider"></view>
<view class="course-info">
@ -27,72 +39,108 @@
<script setup lang="ts">
import { imagUrl } from "@/utils";
//
const props = defineProps<{
dataList: any
}>();
//
const props = withDefaults(defineProps<{
dataList: any[];
canSelected: boolean,
}>(), {
xk: () => ({}),
canSelected: false,
});
const handleClick = (item: any) => {
if (!props.canSelected) {
return;
}
item.selected = !item.selected;
};
//
const getSelectedList = () => {
const list = props.dataList || [];
return list.filter((item: any) => item.selected);
};
defineExpose({
getSelectedList,
});
</script>
<style lang="scss" scoped>
.xkqd-list {
padding: 0 15px;
}
.info-card {
margin: 15px;
background-color: #fff;
border-radius: 8px;
border-radius: 10px;
padding: 15px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
margin-bottom: 20px;
}
.card-title {
font-size: 16px;
font-weight: bold;
color: #333;
margin-bottom: 10px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.divider {
height: 1px;
background-color: #eee;
margin-bottom: 15px;
}
.card-title {
font-size: 18px;
font-weight: bold;
}
.divider {
height: 1px;
background-color: #eee;
margin: 10px 0;
}
.course-info {
display: flex;
.course-image {
width: 120px;
height: 120px;
border-radius: 8px;
margin-right: 15px;
}
.course-details {
flex: 1;
.course-name {
font-size: 18px;
font-weight: 500;
color: #333;
margin-bottom: 10px;
}
.course-teacher,
.course-location {
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
.course-price {
font-size: 14px;
color: #666;
.price-value {
color: #ff6b00;
font-weight: bold;
}
}
}
align-items: center;
}
.course-image {
width: 100px;
height: 100px;
border-radius: 5px;
margin-right: 15px;
}
.course-details {
flex: 1;
}
.course-name {
font-size: 16px;
font-weight: bold;
margin-bottom: 5px;
}
.course-teacher,
.course-location {
font-size: 14px;
color: #666;
margin-bottom: 5px;
}
.course-price {
font-size: 14px;
color: #ff6600;
}
.price-value {
font-size: 16px;
font-weight: bold;
}
.radio-container {
display: flex;
align-items: center;
justify-content: center;
}
.radio-container uni-icons {
transition: all 0.3s ease;
}
</style>

View File

@ -173,7 +173,7 @@ const submit = debounce(async () => {
title: res.message,
icon: "none",
});
PageUtils.toHome(xklxId.value);
PageUtils.toHome(xklxId.value, "jf");
}
} catch (error: any) {
uni.hideLoading();
@ -182,7 +182,7 @@ const submit = debounce(async () => {
title: error.message || "报名失败",
icon: "none",
});
PageUtils.toHome(xklxId.value);
PageUtils.toHome(xklxId.value, "jf");
}
});
@ -204,7 +204,7 @@ onLoad((options:any) => {
if (dataObj.action === 'qk') {
if (dataObj.code === 1 && dataObj.data === "qk") {
clearXkkcSelected();
PageUtils.toHome(xklxId.value);
PageUtils.toHome(xklxId.value, "jf");
} else if (dataObj.code === 2) { //
uni.hideLoading();
setTimeout(() => {

View File

@ -136,7 +136,7 @@ const submit = async () => {
xkId: curXk.value.id,
xkkcIds: selectedXkkcIds.value,
jzId: getUser.jzId,
qmFile: sign_file ? sign_file.value : "",
qmFile: sign_file ? sign_file : "",
};
const res = await jzXkQkjApi(params);
uni.hideLoading();

View File

@ -136,7 +136,7 @@ const submit = async () => {
xkId: curXk.value.id,
xkkcIds: selectedXkkcIds.value,
jzId: getUser.jzId,
qmFile: sign_file ? sign_file.value : "",
qmFile: sign_file ? sign_file : "",
};
const res = await jzXkQkjApi(params);
uni.hideLoading();

View File

@ -0,0 +1,276 @@
<template>
<view class="approval-progress">
<view class="progress-title">
<text class="applicant-name">审批进度</text>
</view>
<view class="divider"></view>
<view class="progress-list">
<view class="progress-item" v-for="(approver, index) in approvalList" :key="index">
<view class="progress-item-row">
<view class="item-avatar">
<image
:src="approver.avatar || '/static/base/home/11222.png'"
class="w-full h-full"
></image>
</view>
<view class="item-middle">
<text class="item-name">{{ approver.userName }}</text>
<text class="item-detail">{{ getSpTypeText(approver.spType) }}</text>
</view>
<view class="item-right">
<text class="item-time" v-if="approver.approveTime">{{ formatTime(approver.approveTime) }}</text>
<text class="item-status" :class="getStatusClass(approver.approveStatus)">
{{ getStatusText(approver.approveStatus) }}
</text>
</view>
</view>
<view class="progress-item-line" v-if="index < approvalList.length - 1"></view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import dayjs from "dayjs";
import { getXsQjApprovalProcessApi } from "@/api/base/xsQjApi";
//
const props = withDefaults(defineProps<{
xkTfId: string
}>(), {
xkTfId: ''
});
//
const approvalList = ref<any[]>([]);
//
const loadApprovalProcess = async () => {
if (!props.xkTfId) return;
try {
// API
const res = await getXsQjApprovalProcessApi(props.xkTfId, 'XK_TF');
if (res.resultCode === 1 && res.result) {
// sort
approvalList.value = res.result.map((item: any) => ({
userName: item.userName || getDefaultUserName(item.spType),
spType: item.spType,
approveStatus: item.approveStatus,
approveTime: item.approveTime,
approveRemark: item.approveRemark,
avatar: item.avatar || '/static/base/home/11222.png'
}));
} else {
loadMockData();
}
} catch (error) {
console.error('获取审批流程失败:', error);
// API使
loadMockData();
}
};
//
const getDefaultUserName = (spType: string) => {
switch (spType) {
case 'SQ': return '学生';
case 'SP': return '班主任';
case 'CC': return '家长';
default: return '未知';
}
};
//
const loadMockData = () => {
const mockData = [
{
userName: '学生',
spType: 'SQ',
approveStatus: 'approved',
approveTime: new Date(),
approveRemark: '申请人提交',
avatar: '/static/base/home/11222.png'
},
{
userName: '班主任',
spType: 'SP',
approveStatus: 'pending',
approveTime: null,
approveRemark: '待审批',
avatar: '/static/base/home/11222.png'
}
];
approvalList.value = mockData;
};
//
const getSpTypeText = (spType: string) => {
switch (spType) {
case 'SQ': return '申请人';
case 'SP': return '审批人';
case 'CC': return '抄送人';
default: return '';
}
};
//
const getStatusText = (status: string) => {
switch (status) {
case 'apply': return '已申请';
case 'pending': return '待处理';
case 'approved': return '已同意';
case 'rejected': return '已拒绝';
case 'cc_sent': return '已抄送';
default: return '未知';
}
};
//
const getStatusClass = (status: string) => {
switch (status) {
case 'pending': return 'status-pending';
case 'approved': return 'status-approved';
case 'rejected': return 'status-rejected';
case 'cc_sent': return 'status-cc';
default: return '';
}
};
//
const formatTime = (time: string | Date) => {
if (!time) return '';
return dayjs(time).format('YYYY-MM-DD HH:mm');
};
// xkTfId
watch(() => props.xkTfId, (newVal) => {
if (newVal) {
loadApprovalProcess();
}
});
//
if (props.xkTfId) {
loadApprovalProcess();
}
</script>
<style lang="scss" scoped>
.approval-progress {
margin: 15px;
background-color: #fff;
border-radius: 8px;
padding: 15px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
.progress-title {
font-size: 16px;
font-weight: bold;
color: #333;
margin-bottom: 10px;
.applicant-name {
font-size: 16px;
font-weight: bold;
color: #333;
}
}
.divider {
height: 1px;
background-color: #eee;
margin-bottom: 15px;
}
.progress-list {
display: flex;
flex-direction: column;
.progress-item {
position: relative;
.progress-item-row {
display: flex;
align-items: center;
padding: 10px 0;
.item-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
overflow: hidden;
margin-right: 12px;
flex-shrink: 0;
}
.item-middle {
flex: 1;
display: flex;
flex-direction: column;
.item-name {
font-size: 14px;
color: #333;
font-weight: 500;
margin-bottom: 4px;
}
.item-detail {
font-size: 12px;
color: #999;
}
}
.item-right {
display: flex;
flex-direction: column;
align-items: flex-end;
.item-time {
font-size: 12px;
color: #999;
margin-bottom: 4px;
}
.item-status {
font-size: 12px;
padding: 2px 8px;
border-radius: 10px;
&.status-pending {
background-color: #fff7e6;
color: #fa8c16;
}
&.status-approved {
background-color: #f6ffed;
color: #52c41a;
}
&.status-rejected {
background-color: #fff2f0;
color: #ff4d4f;
}
&.status-cc {
background-color: #f0f5ff;
color: #1890ff;
}
}
}
}
.progress-item-line {
height: 20px;
width: 2px;
background-color: #e8e8e8;
margin-left: 19px;
margin-top: -10px;
margin-bottom: -10px;
}
}
}
}
</style>

View File

@ -0,0 +1,128 @@
<template>
<BasicLayout>
<view class="xkTf-info">
<!-- 学生信息卡片 -->
<XkPayXs />
<!-- 课程信息卡片 -->
<XkPaySuccessXkkc :dataList="xkTfQdList" />
<!-- 退费信息卡片 -->
<view class="xkTf-card">
<view class="card-body">
<view class="info-column">
<text class="label">退费说明:</text>
<text class="value">{{ xkTf.tfSm }}</text>
</view>
<view class="info-column">
<text class="label">缴费凭证:</text>
<text class="value">
<PreviewImage :image-list="jfPzList" empty-text="无缴费凭证" />
</text>
</view>
</view>
</view>
<!-- 审批流程 -->
<ProgressList :qjId="xkTf.id" />
</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 { onLoad } from "@dcloudio/uni-app";
import { navigateBack } from "@/utils/uniapp";
import { useDataStore } from "@/store/modules/data";
import { imagUrl } from "@/utils";
import XkPayXs from "@/pages/base/xk/components/XkPayXs/index.vue"
import XkPaySuccessXkkc from "@/pages/base/xk/components/XkPaySuccessXkkc/index.vue"
import ProgressList from "./components/progressList.vue";
import PreviewImage from "@/components/PreviewImage/index.vue";
const { getTf } = useDataStore();
const xkTf = ref<any>({});
const xkTfQdList = ref<any>([]);
const jfPzList = ref<any>([]);
//
const initData = (tf: any, tfQdList: any[]) => {
xkTf.value = tf;
xkTfQdList.value = tfQdList;
const jfPz = tf.jfPz || '';
//
jfPzList.value = jfPz.split(',') || [];
};
onLoad((options: any) => {
// 退
if (getTf && getTf.xkTf) {
initData(getTf.xkTf, getTf.xkTfQdList);
} else if (options.xkTfId) {
}
});
</script>
<style lang="scss" scoped>
.xkTf-info {
background-color: #f5f7fa;
.xkTf-card {
margin: 15px;
background-color: #fff;
border-radius: 8px;
padding: 15px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
.card-body {
.info-row {
display: flex;
margin-bottom: 10px;
.label {
font-size: 14px;
color: #bbb;
width: 70px;
flex-shrink: 0;
margin-right: 8px;
}
.value {
font-size: 14px;
color: #333;
flex: 1;
}
}
.info-column {
display: flex;
flex-direction: column;
.label {
font-size: 14px;
color: #bbb;
flex-shrink: 0;
margin-right: 8px;
width: 100%;
margin-bottom: 5px;
}
.value {
font-size: 14px;
color: #333;
flex: 1;
margin-bottom: 10px;
}
}
}
}
}
</style>

197
src/pages/base/xk/tf/sq.vue Normal file
View File

@ -0,0 +1,197 @@
<template>
<BasicLayout>
<view class="xkTf-info">
<!-- 学生信息卡片 -->
<XkPayXs />
<!-- 课程信息卡片 -->
<XkPaySuccessXkkc :dataList="dataList" :can-selected="true" ref="xkQdRef" />
</view>
<template #bottom>
<view class="white-bg-color py-5">
<!-- 表单卡片 -->
<view class="form-card">
<BasicForm @register="register" />
</view>
<view class="flex-row items-center pb-10 pt-5">
<u-button
text="返回"
class="ml-15 mr-7"
:plain="true"
@click="goBack"
/>
<u-button
:text="isSubmitting ? '申请中...' : '提交申请'"
class="ml-7 mr-15"
type="primary"
:plain="true"
@click="submit"
/>
</view>
</view>
</template>
</BasicLayout>
</template>
<script setup lang="ts">
import { useForm } from "@/components/BasicForm/hooks/useForm";
import { onLoad } from "@dcloudio/uni-app";
import XkPayXs from "@/pages/base/xk/components/XkPayXs/index.vue"
import XkPaySuccessXkkc from "@/pages/base/xk/components/XkPaySuccessXkkc/index.vue"
import { checkXkTfApi, xkTfSqApi } from "@/api/base/xkApi";
import { useUserStore } from "@/store/modules/user";
import { useDataStore } from "@/store/modules/data";
import { useDebounce } from "@/utils/debounce";
const { getCurXs, getUser } = useUserStore();
const { getTf, getFile } = useDataStore();
const { sign_file } = getFile;
// isSubmitting useDebounce
const { isProcessing: isSubmitting, debounce } = useDebounce(2000);
const dataList = ref<any>([]);
const xkQdRef = ref<any>(null);
const [register, { getValue, setValue }] = useForm({
schema: [
{
field: "tfSm",
label: "退费说明",
component: "BasicInput",
required: true,
itemProps: {
labelPosition: "top",
},
componentProps: {
type: "textarea",
},
},
{
field: "jfPz",
label: "缴费凭证",
component: "BasicUpload",
required: true,
itemProps: {
labelPosition: "top",
},
componentProps: {
maxSize: 1024 * 1024 * 5,
},
},
],
});
//
const goBack = () => {
uni.reLaunch({ url: "/pages/base/home/index" });
};
// 退
const submit = debounce(async () => {
const selectedList = xkQdRef.value.getSelectedList();
if (selectedList.length <= 0) {
uni.showToast({
title: "请选择退费课程",
icon: "none",
});
return;
}
const fd = await getValue();
const params = {
...fd,
xsId: getCurXs.id,
jzId: getUser.jzId,
qmFile: sign_file ? sign_file : "",
xkId: selectedList[0].xkId,
xkqdIdList: selectedList.map((item:any) => item.id),
}
uni.showLoading({
title: "提交中...",
});
const res = await xkTfSqApi(params);
if (res.resultCode === 1) {
setTimeout(() => {
uni.showToast({
title: "退费申请成功!",
icon: "none",
duration: 2000,
});
}, 100);
uni.reLaunch({
url: "/pages/base/home/index",
});
uni.hideLoading();
} else {
uni.hideLoading();
setTimeout(() => {
uni.showToast({
title: res.message,
icon: "none",
});
}, 100);
}
});
const loadYxXkList = async (xklxId:string) => {
const res = await checkXkTfApi({
xsId: getCurXs.id,
njmcId: getCurXs.njmcId,
xklxId: xklxId,
});
const result = res.result || {};
dataList.value = result.xkqdList || [];
};
onLoad((options:any) => {
dataList.value = getTf.xkqdList || [];
if(!dataList.value.length) {
loadYxXkList(options.xklxId);
}
});
</script>
<style lang="scss" scoped>
.xkTf-info {
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;
}
}
.form-card {
margin: 0 15px;
}
</style>

View File

@ -37,13 +37,24 @@ const initGlobalData = (data: any) => {
if (!lxId && gData.type) {
switch (gData.type + '') {
case "1":
lxId = '962488654';
lxId = '962488654';
gData.action = "jf";
break;
case "2":
lxId = '816059832';
lxId = '816059832';
gData.action = "jf";
break;
case "3":
lxId = 'JC';
lxId = 'jf';
gData.action = "jf";
break;
case "4":
lxId = '962488654';
gData.action = "tf";
break;
case "5":
lxId = '816059832';
gData.action = "tf";
break;
}
}
@ -72,7 +83,7 @@ onLoad(async (data: any) => {
}
}
//
PageUtils.toHome(gData.lxId);
PageUtils.toHome(gData.lxId, gData.action);
return;
}
} catch (err) {

View File

@ -309,7 +309,7 @@ async function submit() {
refreshPermissionCache(currentPermissions, res.result.changeTime);
}
}
PageUtils.toHome(getGlobal.lxId);
PageUtils.toHome(getGlobal.lxId, getGlobal.action);
} else {
showToast({ title: res.message || "提交失败", icon: "none" });
}

View File

@ -10,7 +10,8 @@ export const useDataStore = defineStore({
file: {},
params: {},
appCode: "JZ",
qk: {}
qk: {},
tf: {}
}),
getters: {
getData(): any {
@ -36,6 +37,9 @@ export const useDataStore = defineStore({
},
getQk(): any {
return this.qk;
},
getTf(): any {
return this.tf;
}
},
actions: {
@ -47,6 +51,7 @@ export const useDataStore = defineStore({
this.file = {};
this.params = {};
this.qk = {};
this.tf = {};
},
setData(data: any) {
this.data = data;
@ -69,6 +74,9 @@ export const useDataStore = defineStore({
setQk(data: any) {
this.qk = data;
},
setTf(data: any) {
this.tf = data;
},
},
persist: {
enabled: true,

View File

@ -1,4 +1,4 @@
import { checkXsXkApi } from "@/api/base/xkApi";
import { checkXsXkApi, checkXkTfApi } from "@/api/base/xkApi";
import { checkXsJcApi } from "@/api/base/jcApi";
import { useUserStore } from "@/store/modules/user";
import { useDataStore } from "@/store/modules/data";
@ -15,8 +15,9 @@ export const PageUtils = {
/**
*
* @param lxId JC: 就餐816059832: 俱乐部962488654: 兴趣课
* @param action jf: 缴费tf: 退费
*/
async toHome(lxId?: string) {
async toHome(lxId: string, action: string) {
// 没有类型,则跳转首页
if (!lxId) {
uni.reLaunch({
@ -38,12 +39,12 @@ export const PageUtils = {
}
}
// 判断业务逻辑,并跳转界面
await this.checkLogicPage(lxId);
await this.checkLogicPage(lxId, action);
},
/**
*
*/
async checkLogicPage(lxId: string) {
async checkLogicPage(lxId: string, action: string) {
switch (lxId) {
// 就餐逻辑单独处理
case "JC": {
@ -51,7 +52,11 @@ export const PageUtils = {
} break;
// 默认当作 选课的选课类型IDxkLxId处理
default: {
await this.checkQkLogic(lxId);
if (action === "jf") {
await this.checkQkLogic(lxId);
} else if (action === "tf") {
await this.checkXkTfLogic(lxId);
}
}
}
},
@ -113,5 +118,43 @@ export const PageUtils = {
} break;
}
},
// 判断退费页面切换逻辑
async checkXkTfLogic(xklxId: string) {
const res = await checkXkTfApi({
xsId: userStore.getCurXs.id,
njmcId: userStore.getCurXs.njmcId,
xklxId: xklxId,
});
console.log('checkXkTfLogic', res);
if (res.resultCode != 1) {
uni.showToast({
title: res.message,
icon: 'none',
duration: 2000,
});
uni.reLaunch({
url: "/pages/base/home/index",
});
return;
}
const result = res.result || {};
// 记录到缓存数据中
dataStore.setTf(result);
// 状态判断
switch (result.status) {
case 'KTF': { // KTF可退费
uni.reLaunch({
url: "/pages/base/gzs/tf?lxId=" + xklxId,
});
} break;
case 'TFZ': { // TFZ退费中
uni.reLaunch({
url: "/pages/base/xk/tf/detail?xklxId=" + xklxId,
});
} break;
}
},
}