feat(router): 添加周期选择功能及页面
实现周期选择功能,包括: 1. 添加周期选择路由和页面组件 2. 在路由守卫中检查是否需要周期选择 3. 添加周期选择API调用和逻辑处理 4. 实现周期选择确认后的跳转逻辑
Showing
3 changed files
with
304 additions
and
2 deletions
| ... | @@ -36,6 +36,12 @@ export default [{ | ... | @@ -36,6 +36,12 @@ export default [{ |
| 36 | title: '未授权页面', | 36 | title: '未授权页面', |
| 37 | } | 37 | } |
| 38 | }, { | 38 | }, { |
| 39 | + path: '/cycle-selection', | ||
| 40 | + component: () => import('@/views/cycle-selection.vue'), | ||
| 41 | + meta: { | ||
| 42 | + title: '选择周期', | ||
| 43 | + } | ||
| 44 | +}, { | ||
| 39 | path: '/test', | 45 | path: '/test', |
| 40 | component: () => import('@/views/test.vue'), | 46 | component: () => import('@/views/test.vue'), |
| 41 | meta: { | 47 | meta: { | ... | ... |
| 1 | /* | 1 | /* |
| 2 | * @Date: 2022-05-26 13:57:28 | 2 | * @Date: 2022-05-26 13:57:28 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2024-12-23 18:26:41 | 4 | + * @LastEditTime: 2025-09-08 15:52:35 |
| 5 | * @FilePath: /data-table/src/router.js | 5 | * @FilePath: /data-table/src/router.js |
| 6 | * @Description: 文件描述 | 6 | * @Description: 文件描述 |
| 7 | */ | 7 | */ |
| ... | @@ -12,6 +12,7 @@ import generateRoutes from './utils/generateRoute' | ... | @@ -12,6 +12,7 @@ import generateRoutes from './utils/generateRoute' |
| 12 | import { showConfirmDialog, Loading } from "vant"; | 12 | import { showConfirmDialog, Loading } from "vant"; |
| 13 | import Cookies from 'js-cookie'; | 13 | import Cookies from 'js-cookie'; |
| 14 | import { styleColor } from "@/constant.js"; | 14 | import { styleColor } from "@/constant.js"; |
| 15 | +import { getCycleListAPI } from '@/api/cycle'; | ||
| 15 | 16 | ||
| 16 | // TAG: 路由配置表 | 17 | // TAG: 路由配置表 |
| 17 | /** | 18 | /** |
| ... | @@ -37,7 +38,43 @@ const router = createRouter({ | ... | @@ -37,7 +38,43 @@ const router = createRouter({ |
| 37 | /** | 38 | /** |
| 38 | * generateRoute 负责把后台返回数据拼接成项目需要的路由结构,动态添加到路由表里面 | 39 | * generateRoute 负责把后台返回数据拼接成项目需要的路由结构,动态添加到路由表里面 |
| 39 | */ | 40 | */ |
| 40 | -router.beforeEach((to, from, next) => { | 41 | +/** |
| 42 | + * 检查是否需要周期选择 | ||
| 43 | + * @param {Object} to 目标路由 | ||
| 44 | + * @returns {Promise<boolean>} 是否需要周期选择 | ||
| 45 | + */ | ||
| 46 | +const checkCycleSelection = async (to) => { | ||
| 47 | + // 如果已经在周期选择页面,不需要再检查 | ||
| 48 | + if (to.path === '/cycle-selection') { | ||
| 49 | + return false; | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + // 如果没有表单代码,不需要周期选择 | ||
| 53 | + if (!to.query.code) { | ||
| 54 | + return false; | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + // 如果用户已经选择过周期,不需要再选择 | ||
| 58 | + if (to.query.cycle_selected === '1') { | ||
| 59 | + return false; | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + // 如果是预览模式,不需要周期选择 | ||
| 63 | + if (to.query.model === 'preview') { | ||
| 64 | + return false; | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + try { | ||
| 68 | + const { data } = await getCycleListAPI({ form_code: to.query.code }); | ||
| 69 | + // 如果需要周期选择且有周期列表 | ||
| 70 | + return data.is_cycle && data.cycle_list && data.cycle_list.length > 0; | ||
| 71 | + } catch (error) { | ||
| 72 | + console.error('检查周期选择失败:', error); | ||
| 73 | + return false; | ||
| 74 | + } | ||
| 75 | +}; | ||
| 76 | + | ||
| 77 | +router.beforeEach(async (to, from, next) => { | ||
| 41 | // 使用404为中转页面,避免动态路由没有渲染出来,控制台报警告问题 | 78 | // 使用404为中转页面,避免动态路由没有渲染出来,控制台报警告问题 |
| 42 | if (to.path == '/404' && to.redirectedFrom != undefined) { | 79 | if (to.path == '/404' && to.redirectedFrom != undefined) { |
| 43 | // 模拟异步操作 | 80 | // 模拟异步操作 |
| ... | @@ -51,6 +88,23 @@ router.beforeEach((to, from, next) => { | ... | @@ -51,6 +88,23 @@ router.beforeEach((to, from, next) => { |
| 51 | next({ ...to.redirectedFrom, replace: true }); | 88 | next({ ...to.redirectedFrom, replace: true }); |
| 52 | }, 1000); | 89 | }, 1000); |
| 53 | } else { | 90 | } else { |
| 91 | + // 检查是否需要周期选择 | ||
| 92 | + const needsCycleSelection = await checkCycleSelection(to); | ||
| 93 | + if (needsCycleSelection) { | ||
| 94 | + // 保存目标路由到sessionStorage | ||
| 95 | + sessionStorage.setItem('cycle_target_route', JSON.stringify({ | ||
| 96 | + path: to.path, | ||
| 97 | + query: to.query, | ||
| 98 | + params: to.params | ||
| 99 | + })); | ||
| 100 | + // 跳转到周期选择页面 | ||
| 101 | + next({ | ||
| 102 | + path: '/cycle-selection', | ||
| 103 | + query: { code: to.query.code } | ||
| 104 | + }); | ||
| 105 | + return; | ||
| 106 | + } | ||
| 107 | + | ||
| 54 | if (to.query.page_type === 'add' || to.query.page_type === undefined) { // 表单为新增状态, 检查是否有未完成的表单信息 | 108 | if (to.query.page_type === 'add' || to.query.page_type === undefined) { // 表单为新增状态, 检查是否有未完成的表单信息 |
| 55 | const existingCookie = Cookies.get(to.query.code); | 109 | const existingCookie = Cookies.get(to.query.code); |
| 56 | if (existingCookie && to.query.force_back !== '1') { | 110 | if (existingCookie && to.query.force_back !== '1') { | ... | ... |
src/views/cycle-selection.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <div class="cycle-selection-page"> | ||
| 3 | + <van-config-provider :theme-vars="themeVars"> | ||
| 4 | + <div class="cycle-popup"> | ||
| 5 | + <div class="popup-header"> | ||
| 6 | + <h3>选择周期</h3> | ||
| 7 | + </div> | ||
| 8 | + <div class="popup-content"> | ||
| 9 | + <van-radio-group v-model="selectedCycle"> | ||
| 10 | + <div v-for="cycle in cycleList" :key="cycle.id" class="cycle-item"> | ||
| 11 | + <van-radio :name="cycle.id"> | ||
| 12 | + <div class="cycle-info"> | ||
| 13 | + <div class="cycle-title">{{ cycle.title }}</div> | ||
| 14 | + <div class="cycle-time"> | ||
| 15 | + <div v-if="cycle.start_date">活动开始时间: {{ cycle.start_date }}</div> | ||
| 16 | + <div v-if="cycle.end_date">活动结束时间: {{ cycle.end_date }}</div> | ||
| 17 | + <div v-if="cycle.reg_begin_time">报名开始时间: {{ cycle.reg_begin_time }}</div> | ||
| 18 | + <div v-if="cycle.reg_end_time">报名结束时间: {{ cycle.reg_end_time }}</div> | ||
| 19 | + </div> | ||
| 20 | + </div> | ||
| 21 | + </van-radio> | ||
| 22 | + </div> | ||
| 23 | + </van-radio-group> | ||
| 24 | + </div> | ||
| 25 | + <div class="popup-footer"> | ||
| 26 | + <van-button type="primary" class="confirm-btn" @click="confirmCycleSelection" :disabled="!selectedCycle"> | ||
| 27 | + 确认选择 | ||
| 28 | + </van-button> | ||
| 29 | + </div> | ||
| 30 | + </div> | ||
| 31 | + </van-config-provider> | ||
| 32 | + </div> | ||
| 33 | +</template> | ||
| 34 | + | ||
| 35 | +<script setup> | ||
| 36 | +import { ref, onMounted, nextTick } from 'vue'; | ||
| 37 | +import { useRouter, useRoute } from 'vue-router'; | ||
| 38 | +import { getCycleListAPI } from '@/api/cycle'; | ||
| 39 | +import { styleColor } from '@/constant.js'; | ||
| 40 | + | ||
| 41 | +const $router = useRouter(); | ||
| 42 | +const $route = useRoute(); | ||
| 43 | + | ||
| 44 | +// 主题配置 | ||
| 45 | +const themeVars = { | ||
| 46 | + // 主色调配置 | ||
| 47 | + radioCheckedIconColor: styleColor.baseColor, | ||
| 48 | + buttonPrimaryBackground: styleColor.baseColor, | ||
| 49 | + buttonPrimaryBorderColor: styleColor.baseColor, | ||
| 50 | +}; | ||
| 51 | + | ||
| 52 | +// 周期选择相关变量 | ||
| 53 | +const cycleList = ref([]); | ||
| 54 | +const selectedCycle = ref(''); | ||
| 55 | + | ||
| 56 | +/** | ||
| 57 | + * 动态计算弹窗内容区域高度 | ||
| 58 | + */ | ||
| 59 | +const calculatePopupContentHeight = () => { | ||
| 60 | + nextTick(() => { | ||
| 61 | + const popupElement = document.querySelector('.cycle-popup'); | ||
| 62 | + const headerElement = document.querySelector('.popup-header'); | ||
| 63 | + const footerElement = document.querySelector('.popup-footer'); | ||
| 64 | + const contentElement = document.querySelector('.popup-content'); | ||
| 65 | + | ||
| 66 | + if (popupElement && headerElement && footerElement && contentElement) { | ||
| 67 | + const popupHeight = popupElement.offsetHeight; | ||
| 68 | + const headerHeight = headerElement.offsetHeight; | ||
| 69 | + const footerHeight = footerElement.offsetHeight; | ||
| 70 | + const padding = 100; // 上下padding和margin的总和 | ||
| 71 | + | ||
| 72 | + const contentHeight = popupHeight - headerHeight - footerHeight - padding; | ||
| 73 | + contentElement.style.height = `${contentHeight}px`; | ||
| 74 | + } | ||
| 75 | + }); | ||
| 76 | +}; | ||
| 77 | + | ||
| 78 | +/** | ||
| 79 | + * 获取周期列表 | ||
| 80 | + * @param {string} form_code 表单唯一标识 | ||
| 81 | + */ | ||
| 82 | +const getCycleList = async (form_code) => { | ||
| 83 | + try { | ||
| 84 | + const { data } = await getCycleListAPI({ form_code }); | ||
| 85 | + if (data.is_cycle) { | ||
| 86 | + if (!data.cycle_list || data.cycle_list.length === 0) { | ||
| 87 | + // 如果is_cycle是true但cycle_list为空,跳转到停用页面 | ||
| 88 | + $router.push("/stop?status=disable"); | ||
| 89 | + return; | ||
| 90 | + } | ||
| 91 | + // 设置周期列表 | ||
| 92 | + cycleList.value = data.cycle_list; | ||
| 93 | + // 计算高度 | ||
| 94 | + calculatePopupContentHeight(); | ||
| 95 | + } else { | ||
| 96 | + // 如果不需要周期选择,直接跳转到目标页面 | ||
| 97 | + const targetRoute = sessionStorage.getItem('cycle_target_route'); | ||
| 98 | + if (targetRoute) { | ||
| 99 | + sessionStorage.removeItem('cycle_target_route'); | ||
| 100 | + $router.replace(JSON.parse(targetRoute)); | ||
| 101 | + } else { | ||
| 102 | + $router.replace('/'); | ||
| 103 | + } | ||
| 104 | + } | ||
| 105 | + } catch (error) { | ||
| 106 | + console.error('获取周期列表失败:', error); | ||
| 107 | + } | ||
| 108 | +}; | ||
| 109 | + | ||
| 110 | +/** | ||
| 111 | + * 确认选择周期 | ||
| 112 | + */ | ||
| 113 | +const confirmCycleSelection = () => { | ||
| 114 | + if (selectedCycle.value) { | ||
| 115 | + // 获取目标路由 | ||
| 116 | + const targetRoute = sessionStorage.getItem('cycle_target_route'); | ||
| 117 | + if (targetRoute) { | ||
| 118 | + const route = JSON.parse(targetRoute); | ||
| 119 | + // 添加周期参数 | ||
| 120 | + route.query = { | ||
| 121 | + ...route.query, | ||
| 122 | + x_cycle: selectedCycle.value, | ||
| 123 | + cycle_selected: '1' | ||
| 124 | + }; | ||
| 125 | + // 清除临时存储 | ||
| 126 | + sessionStorage.removeItem('cycle_target_route'); | ||
| 127 | + // 跳转到目标页面 | ||
| 128 | + $router.replace(route); | ||
| 129 | + } else { | ||
| 130 | + // 如果没有目标路由,跳转到首页 | ||
| 131 | + $router.replace({ | ||
| 132 | + path: '/', | ||
| 133 | + query: { | ||
| 134 | + x_cycle: selectedCycle.value, | ||
| 135 | + cycle_selected: '1' | ||
| 136 | + } | ||
| 137 | + }); | ||
| 138 | + } | ||
| 139 | + } | ||
| 140 | +}; | ||
| 141 | + | ||
| 142 | +onMounted(() => { | ||
| 143 | + const form_code = $route.query.code; | ||
| 144 | + if (form_code) { | ||
| 145 | + getCycleList(form_code); | ||
| 146 | + } else { | ||
| 147 | + console.error('缺少表单代码参数'); | ||
| 148 | + $router.replace('/'); | ||
| 149 | + } | ||
| 150 | +}); | ||
| 151 | +</script> | ||
| 152 | + | ||
| 153 | +<style lang="less" scoped> | ||
| 154 | +.cycle-selection-page { | ||
| 155 | + width: 100vw; | ||
| 156 | + height: 100vh; | ||
| 157 | + background-color: rgba(0, 0, 0, 0.5); | ||
| 158 | + display: flex; | ||
| 159 | + align-items: center; | ||
| 160 | + justify-content: center; | ||
| 161 | +} | ||
| 162 | + | ||
| 163 | +.cycle-popup { | ||
| 164 | + width: 100%; | ||
| 165 | + max-width: 500px; | ||
| 166 | + height: 70vh; | ||
| 167 | + background: white; | ||
| 168 | + border-radius: 12px; | ||
| 169 | + display: flex; | ||
| 170 | + flex-direction: column; | ||
| 171 | + overflow: hidden; | ||
| 172 | +} | ||
| 173 | + | ||
| 174 | +.popup-header { | ||
| 175 | + padding: 20px; | ||
| 176 | + text-align: center; | ||
| 177 | + border-bottom: 1px solid #eee; | ||
| 178 | + flex-shrink: 0; | ||
| 179 | + | ||
| 180 | + h3 { | ||
| 181 | + margin: 0; | ||
| 182 | + font-size: 18px; | ||
| 183 | + font-weight: 600; | ||
| 184 | + color: #333; | ||
| 185 | + } | ||
| 186 | +} | ||
| 187 | + | ||
| 188 | +.popup-content { | ||
| 189 | + flex: 1; | ||
| 190 | + overflow-y: auto; | ||
| 191 | + padding: 0 20px; | ||
| 192 | + min-height: 0; | ||
| 193 | +} | ||
| 194 | + | ||
| 195 | +.cycle-item { | ||
| 196 | + padding: 15px 0; | ||
| 197 | + border-bottom: 1px solid #f5f5f5; | ||
| 198 | + | ||
| 199 | + &:last-child { | ||
| 200 | + border-bottom: none; | ||
| 201 | + } | ||
| 202 | +} | ||
| 203 | + | ||
| 204 | +.cycle-info { | ||
| 205 | + margin-left: 10px; | ||
| 206 | +} | ||
| 207 | + | ||
| 208 | +.cycle-title { | ||
| 209 | + font-size: 16px; | ||
| 210 | + font-weight: 500; | ||
| 211 | + color: #333; | ||
| 212 | + margin-bottom: 8px; | ||
| 213 | +} | ||
| 214 | + | ||
| 215 | +.cycle-time { | ||
| 216 | + font-size: 14px; | ||
| 217 | + color: #666; | ||
| 218 | + line-height: 1.5; | ||
| 219 | + | ||
| 220 | + div { | ||
| 221 | + margin-bottom: 4px; | ||
| 222 | + | ||
| 223 | + &:last-child { | ||
| 224 | + margin-bottom: 0; | ||
| 225 | + } | ||
| 226 | + } | ||
| 227 | +} | ||
| 228 | + | ||
| 229 | +.popup-footer { | ||
| 230 | + padding: 20px; | ||
| 231 | + border-top: 1px solid #eee; | ||
| 232 | + flex-shrink: 0; | ||
| 233 | +} | ||
| 234 | + | ||
| 235 | +.confirm-btn { | ||
| 236 | + width: 100%; | ||
| 237 | + height: 44px; | ||
| 238 | + border-radius: 8px; | ||
| 239 | + font-size: 16px; | ||
| 240 | + font-weight: 500; | ||
| 241 | +} | ||
| 242 | +</style> |
-
Please register or login to post a comment