hookehuyr

feat(router): 添加周期选择功能及页面

实现周期选择功能,包括:
1. 添加周期选择路由和页面组件
2. 在路由守卫中检查是否需要周期选择
3. 添加周期选择API调用和逻辑处理
4. 实现周期选择确认后的跳转逻辑
......@@ -36,6 +36,12 @@ export default [{
title: '未授权页面',
}
}, {
path: '/cycle-selection',
component: () => import('@/views/cycle-selection.vue'),
meta: {
title: '选择周期',
}
}, {
path: '/test',
component: () => import('@/views/test.vue'),
meta: {
......
/*
* @Date: 2022-05-26 13:57:28
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2024-12-23 18:26:41
* @LastEditTime: 2025-09-08 15:52:35
* @FilePath: /data-table/src/router.js
* @Description: 文件描述
*/
......@@ -12,6 +12,7 @@ import generateRoutes from './utils/generateRoute'
import { showConfirmDialog, Loading } from "vant";
import Cookies from 'js-cookie';
import { styleColor } from "@/constant.js";
import { getCycleListAPI } from '@/api/cycle';
// TAG: 路由配置表
/**
......@@ -37,7 +38,43 @@ const router = createRouter({
/**
* generateRoute 负责把后台返回数据拼接成项目需要的路由结构,动态添加到路由表里面
*/
router.beforeEach((to, from, next) => {
/**
* 检查是否需要周期选择
* @param {Object} to 目标路由
* @returns {Promise<boolean>} 是否需要周期选择
*/
const checkCycleSelection = async (to) => {
// 如果已经在周期选择页面,不需要再检查
if (to.path === '/cycle-selection') {
return false;
}
// 如果没有表单代码,不需要周期选择
if (!to.query.code) {
return false;
}
// 如果用户已经选择过周期,不需要再选择
if (to.query.cycle_selected === '1') {
return false;
}
// 如果是预览模式,不需要周期选择
if (to.query.model === 'preview') {
return false;
}
try {
const { data } = await getCycleListAPI({ form_code: to.query.code });
// 如果需要周期选择且有周期列表
return data.is_cycle && data.cycle_list && data.cycle_list.length > 0;
} catch (error) {
console.error('检查周期选择失败:', error);
return false;
}
};
router.beforeEach(async (to, from, next) => {
// 使用404为中转页面,避免动态路由没有渲染出来,控制台报警告问题
if (to.path == '/404' && to.redirectedFrom != undefined) {
// 模拟异步操作
......@@ -51,6 +88,23 @@ router.beforeEach((to, from, next) => {
next({ ...to.redirectedFrom, replace: true });
}, 1000);
} else {
// 检查是否需要周期选择
const needsCycleSelection = await checkCycleSelection(to);
if (needsCycleSelection) {
// 保存目标路由到sessionStorage
sessionStorage.setItem('cycle_target_route', JSON.stringify({
path: to.path,
query: to.query,
params: to.params
}));
// 跳转到周期选择页面
next({
path: '/cycle-selection',
query: { code: to.query.code }
});
return;
}
if (to.query.page_type === 'add' || to.query.page_type === undefined) { // 表单为新增状态, 检查是否有未完成的表单信息
const existingCookie = Cookies.get(to.query.code);
if (existingCookie && to.query.force_back !== '1') {
......
<template>
<div class="cycle-selection-page">
<van-config-provider :theme-vars="themeVars">
<div class="cycle-popup">
<div class="popup-header">
<h3>选择周期</h3>
</div>
<div class="popup-content">
<van-radio-group v-model="selectedCycle">
<div v-for="cycle in cycleList" :key="cycle.id" class="cycle-item">
<van-radio :name="cycle.id">
<div class="cycle-info">
<div class="cycle-title">{{ cycle.title }}</div>
<div class="cycle-time">
<div v-if="cycle.start_date">活动开始时间: {{ cycle.start_date }}</div>
<div v-if="cycle.end_date">活动结束时间: {{ cycle.end_date }}</div>
<div v-if="cycle.reg_begin_time">报名开始时间: {{ cycle.reg_begin_time }}</div>
<div v-if="cycle.reg_end_time">报名结束时间: {{ cycle.reg_end_time }}</div>
</div>
</div>
</van-radio>
</div>
</van-radio-group>
</div>
<div class="popup-footer">
<van-button type="primary" class="confirm-btn" @click="confirmCycleSelection" :disabled="!selectedCycle">
确认选择
</van-button>
</div>
</div>
</van-config-provider>
</div>
</template>
<script setup>
import { ref, onMounted, nextTick } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { getCycleListAPI } from '@/api/cycle';
import { styleColor } from '@/constant.js';
const $router = useRouter();
const $route = useRoute();
// 主题配置
const themeVars = {
// 主色调配置
radioCheckedIconColor: styleColor.baseColor,
buttonPrimaryBackground: styleColor.baseColor,
buttonPrimaryBorderColor: styleColor.baseColor,
};
// 周期选择相关变量
const cycleList = ref([]);
const selectedCycle = ref('');
/**
* 动态计算弹窗内容区域高度
*/
const calculatePopupContentHeight = () => {
nextTick(() => {
const popupElement = document.querySelector('.cycle-popup');
const headerElement = document.querySelector('.popup-header');
const footerElement = document.querySelector('.popup-footer');
const contentElement = document.querySelector('.popup-content');
if (popupElement && headerElement && footerElement && contentElement) {
const popupHeight = popupElement.offsetHeight;
const headerHeight = headerElement.offsetHeight;
const footerHeight = footerElement.offsetHeight;
const padding = 100; // 上下padding和margin的总和
const contentHeight = popupHeight - headerHeight - footerHeight - padding;
contentElement.style.height = `${contentHeight}px`;
}
});
};
/**
* 获取周期列表
* @param {string} form_code 表单唯一标识
*/
const getCycleList = async (form_code) => {
try {
const { data } = await getCycleListAPI({ form_code });
if (data.is_cycle) {
if (!data.cycle_list || data.cycle_list.length === 0) {
// 如果is_cycle是true但cycle_list为空,跳转到停用页面
$router.push("/stop?status=disable");
return;
}
// 设置周期列表
cycleList.value = data.cycle_list;
// 计算高度
calculatePopupContentHeight();
} else {
// 如果不需要周期选择,直接跳转到目标页面
const targetRoute = sessionStorage.getItem('cycle_target_route');
if (targetRoute) {
sessionStorage.removeItem('cycle_target_route');
$router.replace(JSON.parse(targetRoute));
} else {
$router.replace('/');
}
}
} catch (error) {
console.error('获取周期列表失败:', error);
}
};
/**
* 确认选择周期
*/
const confirmCycleSelection = () => {
if (selectedCycle.value) {
// 获取目标路由
const targetRoute = sessionStorage.getItem('cycle_target_route');
if (targetRoute) {
const route = JSON.parse(targetRoute);
// 添加周期参数
route.query = {
...route.query,
x_cycle: selectedCycle.value,
cycle_selected: '1'
};
// 清除临时存储
sessionStorage.removeItem('cycle_target_route');
// 跳转到目标页面
$router.replace(route);
} else {
// 如果没有目标路由,跳转到首页
$router.replace({
path: '/',
query: {
x_cycle: selectedCycle.value,
cycle_selected: '1'
}
});
}
}
};
onMounted(() => {
const form_code = $route.query.code;
if (form_code) {
getCycleList(form_code);
} else {
console.error('缺少表单代码参数');
$router.replace('/');
}
});
</script>
<style lang="less" scoped>
.cycle-selection-page {
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
}
.cycle-popup {
width: 100%;
max-width: 500px;
height: 70vh;
background: white;
border-radius: 12px;
display: flex;
flex-direction: column;
overflow: hidden;
}
.popup-header {
padding: 20px;
text-align: center;
border-bottom: 1px solid #eee;
flex-shrink: 0;
h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #333;
}
}
.popup-content {
flex: 1;
overflow-y: auto;
padding: 0 20px;
min-height: 0;
}
.cycle-item {
padding: 15px 0;
border-bottom: 1px solid #f5f5f5;
&:last-child {
border-bottom: none;
}
}
.cycle-info {
margin-left: 10px;
}
.cycle-title {
font-size: 16px;
font-weight: 500;
color: #333;
margin-bottom: 8px;
}
.cycle-time {
font-size: 14px;
color: #666;
line-height: 1.5;
div {
margin-bottom: 4px;
&:last-child {
margin-bottom: 0;
}
}
}
.popup-footer {
padding: 20px;
border-top: 1px solid #eee;
flex-shrink: 0;
}
.confirm-btn {
width: 100%;
height: 44px;
border-radius: 8px;
font-size: 16px;
font-weight: 500;
}
</style>