This commit is contained in:
hrx 2025-12-15 17:46:47 +08:00
parent 1f9057a38a
commit 00bb3e5653
13 changed files with 1136 additions and 206 deletions

View File

@ -6,7 +6,7 @@
<transition name="slide">
<div v-show="windowsHidden" style="font-size: 14px">
<div class="new-floating" style="z-index: 9999;">
<div class="new-floating" style="z-index: 99;">
<img src="./img/head.png" alt="">
<div class="cloud-contact-us " @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave">
<!-- <span class="cloud-contact-us-i"></span>-->
@ -789,7 +789,7 @@ export default {
top: -28px;
width: 40px;
height: 40px;
z-index: 9999;
z-index: 99;
}
position: relative;

View File

@ -171,7 +171,7 @@ Vue.use(HappyScroll)
// });
// console.log(element);
// console.clear(); // 清除测试日志
// console.clear(); // 清除测试日志
// }
// // 方法4: 检查Eruda等移动端调试工具
@ -345,6 +345,77 @@ window.addEventListener('beforeunload', function () {
Object.keys(filters).forEach(key => {
Vue.filter(key, filters[key])
})
// 在 main.js 的 router.beforeEach 中添加
router.beforeEach((to, from, next) => {
// 清空面包屑状态的代码
// store.commit('tagsView/resetBreadcrumbState');
// 新增:检测是否为移动设备
const userAgent = navigator.userAgent;
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent);
// 如果是移动设备且访问的是根路径,重定向到移动端首页
if (isMobile && to.path === '/') {
next('/h5HomePage');
return;
}
// 如果是移动设备且访问的不是移动端页面,重定向到移动端首页
if (isMobile && !to.meta?.isMobile && to.path !== '/h5HomePage' && !to.path.startsWith('/h5HomePage/')) {
next('/h5HomePage');
return;
}
// 如果已登录且有token但Vuex状态丢失从sessionStorage恢复
if (store.getters.token && (!store.getters.user || !store.getters.userType)) {
console.log("检测到状态丢失从sessionStorage恢复用户状态");
const user = sessionStorage.getItem('user');
const auths = sessionStorage.getItem('auths');
const userType = sessionStorage.getItem('userType');
const orgType = sessionStorage.getItem('orgType');
if (user) {
store.commit('user/SET_USER', user);
}
if (auths) {
store.commit('user/SET_AUTHS', JSON.parse(auths));
}
if (userType) {
store.commit('user/SET_USER_TYPE', userType);
}
if (orgType) {
store.commit('user/SET_ORG_TYPE', parseInt(orgType));
}
// 重新生成路由
try {
const accessRoutes = store.dispatch('permission/generateRoutes', {
user: store.getters.user,
auths: store.getters.auths,
userType: store.getters.userType,
orgType: store.getters.orgType
});
// 重新添加路由
router.addRoutes(accessRoutes);
// 重定向到当前路由以确保路由更新
next({ ...to, replace: true });
return;
} catch (error) {
console.error('重新生成路由失败:', error);
}
}
onOverflow.forEach(element => {
if (to.path == element) {
document.querySelector("body").setAttribute("style", "overflow: auto !important;")
}
});
next();
});
Vue.config.productionTip = false

View File

@ -6,8 +6,83 @@ const userAgent = window.navigator.userAgent;
// 判断是否为移动设备
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent);
// 如果是移动设备,添加移动端首页路由和根路径重定向
if (isMobile) {
console.log("检测到移动设备,添加移动端路由");
// 先添加根路径重定向到移动端首页
constantRoutes.unshift({
path: '/',
redirect: '/h5HomePage',
hidden: true
});
// 添加移动端首页路由
constantRoutes.push({
path: '/h5HomePage',
name: 'H5HomePage',
title: 'H5首页',
component: () => import('@/views/H5/index.vue'),
hidden: true,
redirect: "/h5HomePage/index",
meta: { isMobile: true },
children: [
{
path: "index",
title: 'H5首页',
component: () => import('@/views/H5/official/index.vue'),
meta: {
title: "H5首页",
fullPath: "/h5HomePage/index",
isMobile: true
},
},
{
path: "cloud",
title: '云',
component: () => import('@/views/H5/cloud/index.vue'),
meta: {
title: "云",
fullPath: "/h5HomePage/cloud",
isMobile: true
},
},
{
path: "calculate",
title: '算',
component: () => import('@/views/H5/calculate/index.vue'),
meta: {
title: "算",
fullPath: "/h5HomePage/calculate",
isMobile: true
},
},
{
path: "net",
title: '网',
component: () => import('@/views/H5/net/index.vue'),
meta: {
title: "网",
fullPath: "/h5HomePage/net",
isMobile: true
},
},
{
path: "use",
title: '用',
component: () => import('@/views/H5/use/index.vue'),
meta: {
title: "用",
fullPath: "/h5HomePage/use",
isMobile: true
},
},
]
});
}
// 修复:更全面的路由过滤逻辑
function filterAsyncRoutes(routes, permissions, userRoles = []) {
function filterAsyncRoutes(routes, permissions, userRoles = [], deviceType = 'pc') {
const res = [];
// 定义需要客户角色才能访问的路由
@ -35,6 +110,14 @@ function filterAsyncRoutes(routes, permissions, userRoles = []) {
return; // 跳过当前路由
}
// 新增:根据设备类型过滤路由
if (deviceType === 'mobile' && !(route.meta?.isMobile || route.meta?.isMobile === true)) {
return; // 移动设备跳过非移动端路由
}
if (deviceType === 'pc' && route.meta?.isMobile === true) {
return; // PC设备跳过移动端路由
}
// 如果当前路由有权限,则加入结果
if (hasPermission) {
res.push(tmpRoute);
@ -45,7 +128,7 @@ function filterAsyncRoutes(routes, permissions, userRoles = []) {
}
// 如果没有直接权限,但有子路由,递归处理子路由
else if (tmpRoute.children) {
const filteredChildren = filterAsyncRoutes(tmpRoute.children, permissions, userRoles);
const filteredChildren = filterAsyncRoutes(tmpRoute.children, permissions, userRoles, deviceType);
if (filteredChildren.length > 0) {
tmpRoute.children = filteredChildren;
res.push(tmpRoute); // 即使父路由本身没有权限,只要有子路由有权限,也要保留父路由
@ -57,7 +140,7 @@ function filterAsyncRoutes(routes, permissions, userRoles = []) {
}
// 新增:为普通用户添加订单管理和资源管理路由
function addUserRoutes(routes, userType, orgType, userRoles = []) {
function addUserRoutes(routes, userType, orgType, userRoles = [], deviceType = 'pc') {
console.log("addUserRoutes - userType:", userType, "orgType:", orgType, "userRoles:", userRoles);
const userRoutes = [];
@ -67,12 +150,13 @@ function addUserRoutes(routes, userType, orgType, userRoles = []) {
const orderManagementRoute = routes.find(route => route.path === "/orderManagement");
const resourceManagementRoute = routes.find(route => route.path === "/resourceManagement");
if (orderManagementRoute) {
// 新增:根据设备类型过滤
if (orderManagementRoute && (deviceType === 'pc' || orderManagementRoute.meta?.isMobile === true)) {
console.log("添加订单管理路由");
userRoutes.push(JSON.parse(JSON.stringify(orderManagementRoute))); // 深拷贝
}
if (resourceManagementRoute) {
if (resourceManagementRoute && (deviceType === 'pc' || resourceManagementRoute.meta?.isMobile === true)) {
console.log("添加资源管理路由");
userRoutes.push(JSON.parse(JSON.stringify(resourceManagementRoute))); // 深拷贝
}
@ -85,10 +169,10 @@ function addUserRoutes(routes, userType, orgType, userRoles = []) {
routes.find(route => route.path === "/rechargeManagement"),
routes.find(route => route.path === "/invoiceManagement"),
routes.find(route => route.path === "/workOrderManagement")
].filter(route => {
// 过滤掉undefined并且只有客户角色才能看到这些路由
return route && userRoles.includes('客户');
return route && userRoles.includes('客户') &&
(deviceType === 'pc' || route.meta?.isMobile === true);
});
console.log("添加新的客户菜单路由:", newCustomerRoutes.map(r => r.path));
@ -97,38 +181,11 @@ function addUserRoutes(routes, userType, orgType, userRoles = []) {
return userRoutes;
}
function filterRoutesMobile(routes) {
return routes.filter(route => {
if (route.children && route.children.length) {
route.children = filterRoutesMobile(route.children);
return route.children.length > 0;
}
if (route.meta?.isMobile || route.meta?.isMobile === true) {
return true;
} else {
return false;
}
});
}
function filterRoutesPc(routes) {
return routes.filter(route => {
if (route.children && route.children.length) {
route.children = filterRoutesPc(route.children);
return route.children.length > 0;
}
if (!route.meta?.isMobile || route.meta?.isMobile === false) {
return true;
} else {
return false;
}
});
}
const state = {
routes: [],
addRoutes: [],
users: []
users: [],
isMobile: isMobile // 保存设备类型状态
};
const mutations = {
@ -136,11 +193,15 @@ const mutations = {
console.log("MUTATION SET_ROUTES - received routes:", routes);
state.addRoutes = routes;
sessionStorage.setItem("routes", JSON.stringify(routes));
// 将移动端首页路由也包含在内
state.routes = constantRoutes.concat(routes);
console.log("MUTATION SET_ROUTES - final state.routes:", state.routes);
},
SETUSERS: (state, user) => {
state.users = user;
},
SET_DEVICE_TYPE: (state, isMobile) => {
state.isMobile = isMobile;
}
};
@ -161,7 +222,7 @@ const actions = {
* @param {Object} [params.user] - 用户信息对象
* @returns {Promise<Array>} 解析后的动态路由数组
*/
generateRoutes({ commit, rootState }, params) {
generateRoutes({ commit, rootState, state }, params) {
console.log("ACTION generateRoutes - params:", params);
return new Promise((resolve) => {
let accessedRoutes;
@ -176,10 +237,18 @@ const actions = {
console.log("用户类型:", userType, "orgType:", orgType);
// 确定设备类型
const deviceType = state.isMobile ? 'mobile' : 'pc';
console.log("设备类型:", deviceType);
// 修复:包含 orgType 为 2 和 3 的情况
if (params.user && params.user.includes("admin") && orgType != 2 && orgType != 3) {
// 管理员:只显示超级管理员菜单
accessedRoutes = asyncRoutes.filter(item => item.path === '/superAdministrator');
// 管理员只显示超级管理员菜单仅PC端
if (deviceType === 'pc') {
accessedRoutes = asyncRoutes.filter(item => item.path === '/superAdministrator');
} else {
accessedRoutes = [];
}
} else {
const auths = params.auths ? JSON.parse(JSON.stringify(params.auths)) : [];
console.log("ACTION generateRoutes - auths:", auths);
@ -195,8 +264,8 @@ const actions = {
// 如果权限列表包含空路径,认为用户有所有权限
accessedRoutes = asyncRoutes || [];
} else {
// 使用修复后的过滤函数,传入用户角色
accessedRoutes = filterAsyncRoutes(asyncRoutes, auths, userRoles);
// 使用修复后的过滤函数,传入用户角色和设备类型
accessedRoutes = filterAsyncRoutes(asyncRoutes, auths, userRoles, deviceType);
}
} else {
// 如果没有权限列表,不显示任何动态路由
@ -205,7 +274,7 @@ const actions = {
// 新增:为普通用户添加订单管理和资源管理路由以及新的五个客户菜单
console.log("为用户添加特定路由");
const userSpecificRoutes = addUserRoutes(asyncRoutes, userType, orgType, userRoles);
const userSpecificRoutes = addUserRoutes(asyncRoutes, userType, orgType, userRoles, deviceType);
// 确保不重复添加路由,同时检查角色权限
userSpecificRoutes.forEach(route => {

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 KiB

View File

@ -2,19 +2,29 @@
<div class="h5-container">
<!-- 主要内容区域 -->
<div ref="mainContent" class="main" @scroll="handleScroll">
<router-view></router-view>
<transition :name="transitionName" mode="out-in">
<router-view></router-view>
</transition>
</div>
<div style="height: 1.4rem;"></div>
<!-- 返回顶部按钮 -->
<transition name="fade">
<transition name="fade-scale">
<div
v-show="showBackToTop"
class="back-to-top"
@click="scrollToTop"
>
<div class="back-to-top-icon"></div>
<div class="back-to-top-text">顶部</div>
<div class="back-to-top-icon">
<svg viewBox="0 0 24 24" fill="none">
<path d="M12 5L12 19M12 5L6 11M12 5L18 11"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"/>
</svg>
</div>
<div class="back-to-top-text">回顶部</div>
</div>
</transition>
@ -27,14 +37,20 @@
@click="switchTab('index')"
>
<div class="item-img">
<img
:src="activeTab === 'index' ? require('./images/tabBar/homeColor.png') : require('./images/tabBar/home.png')"
alt="首页"
/>
<transition name="icon-bounce" mode="out-in">
<img
:key="activeTab === 'index'"
:src="activeTab === 'index' ? require('./images/tabBar/homeColor.png') : require('./images/tabBar/home.png')"
alt="首页"
/>
</transition>
</div>
<div class="item-text">
首页
<transition name="text-slide" mode="out-in">
<span :key="activeTab === 'index'">首页</span>
</transition>
</div>
<div class="active-indicator" v-if="activeTab === 'index'"></div>
</div>
<!-- -->
@ -44,14 +60,20 @@
@click="switchTab('cloud')"
>
<div class="item-img">
<img
:src="activeTab === 'cloud' ? require('./images/tabBar/cloudColor.png') : require('./images/tabBar/cloud.png')"
alt="云"
/>
<transition name="icon-bounce" mode="out-in">
<img
:key="activeTab === 'cloud'"
:src="activeTab === 'cloud' ? require('./images/tabBar/cloudColor.png') : require('./images/tabBar/cloud.png')"
alt="云"
/>
</transition>
</div>
<div class="item-text">
<transition name="text-slide" mode="out-in">
<span :key="activeTab === 'cloud'"></span>
</transition>
</div>
<div class="active-indicator" v-if="activeTab === 'cloud'"></div>
</div>
<!-- -->
@ -61,14 +83,20 @@
@click="switchTab('calculate')"
>
<div class="item-img">
<img
:src="activeTab === 'calculate' ? require('./images/tabBar/calculateColor.png') : require('./images/tabBar/calculate.png')"
alt="算"
/>
<transition name="icon-bounce" mode="out-in">
<img
:key="activeTab === 'calculate'"
:src="activeTab === 'calculate' ? require('./images/tabBar/calculateColor.png') : require('./images/tabBar/calculate.png')"
alt="算"
/>
</transition>
</div>
<div class="item-text">
<transition name="text-slide" mode="out-in">
<span :key="activeTab === 'calculate'"></span>
</transition>
</div>
<div class="active-indicator" v-if="activeTab === 'calculate'"></div>
</div>
<!-- -->
@ -78,14 +106,20 @@
@click="switchTab('net')"
>
<div class="item-img">
<img
:src="activeTab === 'net' ? require('./images/tabBar/netColor.png') : require('./images/tabBar/net.png')"
alt="网"
/>
<transition name="icon-bounce" mode="out-in">
<img
:key="activeTab === 'net'"
:src="activeTab === 'net' ? require('./images/tabBar/netColor.png') : require('./images/tabBar/net.png')"
alt="网"
/>
</transition>
</div>
<div class="item-text">
<transition name="text-slide" mode="out-in">
<span :key="activeTab === 'net'"></span>
</transition>
</div>
<div class="active-indicator" v-if="activeTab === 'net'"></div>
</div>
<!-- -->
@ -95,14 +129,20 @@
@click="switchTab('use')"
>
<div class="item-img">
<img
:src="activeTab === 'use' ? require('./images/tabBar/userColor.png') : require('./images/tabBar/user.png')"
alt="用"
/>
<transition name="icon-bounce" mode="out-in">
<img
:key="activeTab === 'use'"
:src="activeTab === 'use' ? require('./images/tabBar/userColor.png') : require('./images/tabBar/user.png')"
alt="用"
/>
</transition>
</div>
<div class="item-text">
<transition name="text-slide" mode="out-in">
<span :key="activeTab === 'use'"></span>
</transition>
</div>
<div class="active-indicator" v-if="activeTab === 'use'"></div>
</div>
</div>
</div>
@ -124,6 +164,8 @@ export default {
],
showBackToTop: false, //
scrollThreshold: 200, //
transitionName: 'fade', //
isSwitching: false, //
};
},
watch: {
@ -142,8 +184,11 @@ export default {
* @param {string} tabId - 标签ID
*/
switchTab(tabId) {
//
if (this.activeTab === tabId) return;
//
if (this.isSwitching || this.activeTab === tabId) return;
//
this.isSwitching = true;
//
this.activeTab = tabId;
@ -151,7 +196,39 @@ export default {
// tabId
const tab = this.tabList.find(item => item.id === tabId);
if (tab) {
this.$router.push(tab.path);
//
this.animateTabClick(tabId);
//
setTimeout(() => {
this.$router.push(tab.path);
//
setTimeout(() => {
this.isSwitching = false;
}, 300);
}, 150);
} else {
this.isSwitching = false;
}
},
/**
* 执行Tab点击动画
* @param {string} tabId - 标签ID
*/
animateTabClick(tabId) {
// tab
const tabIndex = this.tabList.findIndex(item => item.id === tabId);
const tabs = document.querySelectorAll('.tabBar-item');
if (tabs[tabIndex]) {
//
tabs[tabIndex].classList.add('click-animation');
//
setTimeout(() => {
tabs[tabIndex].classList.remove('click-animation');
}, 300);
}
},
@ -186,6 +263,15 @@ export default {
*/
scrollToTop() {
if (this.$refs.mainContent) {
//
const btn = document.querySelector('.back-to-top');
if (btn) {
btn.classList.add('clicked');
setTimeout(() => {
btn.classList.remove('clicked');
}, 300);
}
this.$refs.mainContent.scrollTo({
top: 0,
behavior: 'smooth' //
@ -208,120 +294,5 @@ window.onresize = adapter()
</script>
<style lang="less" scoped>
.h5-container {
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
position: relative;
}
.main {
flex: 1;
overflow-y: auto;
-webkit-overflow-scrolling: touch; /* 为iOS添加平滑滚动 */
}
/* 返回顶部按钮样式 */
.back-to-top {
position: fixed;
right: 0.4rem;
bottom: 1.5rem; /* 在底部导航栏上方 */
z-index: 999;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 1.1rem;
height: 1.1rem;
background: linear-gradient(90deg, #275AFF 0%, #2EBDFA 100%);
border-radius: 50%;
box-shadow: 0 0.04rem 0.12rem rgba(0, 0, 0, 0.3);
cursor: pointer;
transition: all 0.3s ease;
&:active {
transform: scale(0.95);
box-shadow: 0 0.02rem 0.06rem rgba(0, 0, 0, 0.2);
}
&:hover {
background: linear-gradient(90deg, #275AFF 0%, #2EBDFA 100%);
}
.back-to-top-icon {
color: white;
font-size: 0.4rem;
font-weight: bold;
// line-height: 0.5rem;
}
.back-to-top-text {
color: white;
font-size: 0.26rem;
line-height: 0.2rem;
// margin-top: -0.04rem;
}
}
/* 淡入淡出动画 */
.fade-enter-active, .fade-leave-active {
transition: opacity 0.3s, transform 0.3s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
transform: translateY(0.2rem);
}
.tabBar {
padding: .2rem 0;
background-color: #fff;
width: 100%;
display: flex;
justify-content: space-around;
align-items: center;
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 1000;
box-shadow: 0 -0.02rem .1rem rgba(0, 0, 0, 0.1);
.tabBar-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
opacity: 0.8;
}
&.active {
.item-text {
color: #1890ff;
font-weight: bold;
}
}
}
.item-img {
margin-bottom: .1rem;
img {
width: .5rem;
height: .5rem;
display: block;
transition: transform 0.3s ease;
}
}
.item-text {
font-size: .24rem;
color: #666;
transition: color 0.3s ease;
}
}
@import url('./less/home/index.less');
</style>

View File

@ -95,7 +95,7 @@
width: var(--dialog-width, 6rem) !important;
max-width: 90%;
border-radius: 0.08rem;
z-index: 99999;
z-index: 9999;
}
::v-deep .product-consult-dialog.el-dialog .el-dialog__header {
padding: 0.2rem 0.3rem 0.1rem;

View File

@ -108,7 +108,7 @@
width: var(--dialog-width, 6rem) !important;
max-width: 90%;
border-radius: .08rem;
z-index: 99999;
z-index: 9999;
.el-dialog__header {
padding: .2rem .3rem .1rem;

View File

@ -0,0 +1,365 @@
.h5-container {
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
position: relative;
}
.main {
flex: 1;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
/* 为iOS添加平滑滚动 */
}
/* 页面切换动画 */
.fade-enter-active,
.fade-leave-active {
transition: all 0.3s ease;
}
.fade-enter-from {
opacity: 0;
transform: translateY(0.2rem);
}
.fade-leave-to {
opacity: 0;
transform: translateY(-0.2rem);
}
/* 返回顶部按钮样式 */
.back-to-top {
position: fixed;
right: 0.4rem;
bottom: 1.5rem;
/* 在底部导航栏上方 */
z-index: 999;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 1.1rem;
height: 1.1rem;
background: linear-gradient(90deg, #275AFF 0%, #2EBDFA 100%);
border-radius: 50%;
box-shadow: 0 0.08rem 0.24rem rgba(102, 126, 234, 0.4);
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
user-select: none;
-webkit-tap-highlight-color: transparent;
/* 悬停效果 */
/* 激活效果 */
/* 点击动画 */
}
.back-to-top:hover {
transform: translateY(-0.1rem);
box-shadow: 0 0.12rem 0.36rem rgba(102, 126, 234, 0.6);
background: linear-gradient(90deg, #275AFF 0%, #2EBDFA 100%);
}
.back-to-top:active {
transform: scale(0.95);
}
.back-to-top.clicked {
animation: pulse 0.3s ease;
}
.back-to-top .back-to-top-icon {
width: 0.45rem;
height: 0.45rem;
color: white;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 0.04rem;
}
.back-to-top .back-to-top-icon svg {
width: 100%;
height: 100%;
stroke: currentColor;
transition: transform 0.3s ease;
}
.back-to-top .back-to-top-text {
color: white;
font-size: 0.24rem;
font-weight: 500;
line-height: 1;
letter-spacing: 0.02rem;
text-shadow: 0 0.02rem 0.04rem rgba(0, 0, 0, 0.2);
}
/* 脉冲动画 */
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(0.9);
}
100% {
transform: scale(1);
}
}
/* 返回顶部按钮动画 */
.fade-scale-enter-active,
.fade-scale-leave-active {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.fade-scale-enter-from,
.fade-scale-leave-to {
opacity: 0;
transform: scale(0.8) translateY(0.2rem);
}
/* TabBar 样式 */
.tabBar {
padding: 0.2rem 0;
background-color: rgba(255, 255, 255, 0.95);
width: 100%;
display: flex;
justify-content: space-around;
align-items: center;
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 1000;
box-shadow: 0 -0.02rem 0.2rem rgba(0, 0, 0, 0.08);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-top: 1px solid rgba(0, 0, 0, 0.05);
}
.tabBar .tabBar-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
padding: 0.1rem 0.15rem;
border-radius: 0.3rem;
position: relative;
overflow: hidden;
user-select: none;
/* 悬停效果 */
/* 激活状态 */
/* 点击动画 */
/* 激活指示器 */
}
.tabBar .tabBar-item:hover {
background-color: rgba(24, 144, 255, 0.08);
transform: translateY(-0.05rem);
}
.tabBar .tabBar-item.active .item-text {
color: #1890ff;
font-weight: 600;
}
.tabBar .tabBar-item.click-animation {
animation: tabClick 0.3s ease;
}
.tabBar .tabBar-item .active-indicator {
position: absolute;
bottom: 0.05rem;
left: 50%;
transform: translateX(-50%);
width: 0.3rem;
height: 0.04rem;
border-radius: 0.02rem;
animation: indicatorAppear 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
.tabBar .item-img {
margin-bottom: 0.08rem;
position: relative;
width: 0.5rem;
height: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
}
.tabBar .item-img img {
width: 100%;
height: 100%;
display: block;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.tabBar .item-text {
font-size: 0.24rem;
color: #666;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
height: 0.3rem;
display: flex;
align-items: center;
justify-content: center;
}
/* 图标动画 */
.icon-bounce-enter-active {
animation: iconBounceIn 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
.icon-bounce-leave-active {
animation: iconBounceOut 0.3s ease;
}
@keyframes iconBounceIn {
0% {
opacity: 0;
transform: scale(0.3) rotate(-30deg);
}
50% {
transform: scale(1.2) rotate(10deg);
}
70% {
transform: scale(0.9) rotate(-5deg);
}
100% {
opacity: 1;
transform: scale(1) rotate(0deg);
}
}
@keyframes iconBounceOut {
0% {
opacity: 1;
transform: scale(1) rotate(0deg);
}
100% {
opacity: 0;
transform: scale(0.5) rotate(30deg);
}
}
/* 文字动画 */
.text-slide-enter-active {
animation: textSlideIn 0.3s ease-out;
}
.text-slide-leave-active {
animation: textSlideOut 0.2s ease-in;
}
@keyframes textSlideIn {
0% {
opacity: 0;
transform: translateY(0.1rem);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
@keyframes textSlideOut {
0% {
opacity: 1;
transform: translateY(0);
}
100% {
opacity: 0;
transform: translateY(-0.1rem);
}
}
/* Tab点击动画 */
@keyframes tabClick {
0% {
transform: scale(1);
}
50% {
transform: scale(0.95);
}
100% {
transform: scale(1);
}
}
/* 指示器出现动画 */
@keyframes indicatorAppear {
0% {
opacity: 0;
transform: translateX(-50%) scaleX(0);
}
100% {
opacity: 1;
transform: translateX(-50%) scaleX(1);
}
}
/* 响应式调整 */
@media (max-width: 480px) {
.back-to-top {
right: 0.3rem;
bottom: 1.6rem;
width: 1rem;
height: 1rem;
}
.back-to-top .back-to-top-icon {
width: 0.4rem;
height: 0.4rem;
margin-bottom: 0.03rem;
}
.back-to-top .back-to-top-text {
font-size: 0.22rem;
}
.tabBar {
padding: 0.15rem 0;
}
.tabBar .tabBar-item {
padding: 0.08rem 0.12rem;
}
.tabBar .item-img {
width: 0.45rem;
height: 0.45rem;
}
.tabBar .item-text {
font-size: 0.22rem;
}
}
@media (min-width: 768px) {
.back-to-top {
right: 0.5rem;
bottom: 2rem;
width: 1.2rem;
height: 1.2rem;
}
.back-to-top .back-to-top-icon {
width: 0.5rem;
height: 0.5rem;
margin-bottom: 0.05rem;
}
.back-to-top .back-to-top-text {
font-size: 0.26rem;
}
}
/* 深色模式支持 */
@media (prefers-color-scheme: dark) {
.back-to-top {
background: linear-gradient(90deg, #275AFF 0%, #2EBDFA 100%);
box-shadow: 0 0.08rem 0.24rem rgba(138, 43, 226, 0.4);
}
.back-to-top:hover {
background: linear-gradient(90deg, #275AFF 0%, #2EBDFA 100%);
box-shadow: 0 0.12rem 0.36rem rgba(138, 43, 226, 0.6);
}
.tabBar {
background-color: rgba(30, 30, 30, 0.95);
border-top: 1px solid rgba(255, 255, 255, 0.05);
}
.tabBar .tabBar-item .item-text {
color: #b0b0b0;
}
.tabBar .tabBar-item:hover {
background-color: rgba(100, 181, 246, 0.08);
}
.tabBar .tabBar-item.active .item-text {
color: #64b5f6;
}
.tabBar .tabBar-item.active .active-indicator {
background: linear-gradient(90deg, #64b5f6, #81c784);
}
}
/* 安全区域适配iPhone X及以上机型 */
@supports (padding: max(0px)) {
.back-to-top {
bottom: calc(1.5rem + env(safe-area-inset-bottom, 0));
}
.tabBar {
padding-bottom: calc(0.2rem + env(safe-area-inset-bottom, 0));
}
@media (max-width: 480px) {
.back-to-top {
bottom: calc(1.6rem + env(safe-area-inset-bottom, 0));
}
.tabBar {
padding-bottom: calc(0.15rem + env(safe-area-inset-bottom, 0));
}
}
@media (min-width: 768px) {
.back-to-top {
bottom: calc(2rem + env(safe-area-inset-bottom, 0));
}
}
}

View File

@ -0,0 +1,438 @@
.h5-container {
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
position: relative;
}
.main {
flex: 1;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
/* 为iOS添加平滑滚动 */
}
/* 页面切换动画 */
.fade-enter-active,
.fade-leave-active {
transition: all 0.3s ease;
}
.fade-enter-from {
opacity: 0;
transform: translateY(0.2rem);
}
.fade-leave-to {
opacity: 0;
transform: translateY(-0.2rem);
}
/* 返回顶部按钮样式 */
.back-to-top {
position: fixed;
right: 0.4rem;
bottom: 1.5rem;
/* 在底部导航栏上方 */
z-index: 999;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 1.1rem;
height: 1.1rem;
background: linear-gradient(90deg, #275AFF 0%, #2EBDFA 100%);
border-radius: 50%;
box-shadow: 0 0.08rem 0.24rem rgba(102, 126, 234, 0.4);
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
user-select: none;
-webkit-tap-highlight-color: transparent;
/* 悬停效果 */
&:hover {
transform: translateY(-0.1rem);
box-shadow: 0 0.12rem 0.36rem rgba(102, 126, 234, 0.6);
background: linear-gradient(90deg, #275AFF 0%, #2EBDFA 100%);
}
/* 激活效果 */
&:active {
transform: scale(0.95);
}
/* 点击动画 */
&.clicked {
animation: pulse 0.3s ease;
}
.back-to-top-icon {
width: 0.45rem;
height: 0.45rem;
color: white;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 0.04rem;
svg {
width: 100%;
height: 100%;
stroke: currentColor;
transition: transform 0.3s ease;
}
}
.back-to-top-text {
color: white;
font-size: 0.24rem;
font-weight: 500;
line-height: 1;
letter-spacing: 0.02rem;
text-shadow: 0 0.02rem 0.04rem rgba(0, 0, 0, 0.2);
}
}
/* 脉冲动画 */
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(0.9);
}
100% {
transform: scale(1);
}
}
/* 返回顶部按钮动画 */
.fade-scale-enter-active,
.fade-scale-leave-active {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.fade-scale-enter-from,
.fade-scale-leave-to {
opacity: 0;
transform: scale(0.8) translateY(0.2rem);
}
/* TabBar 样式 */
.tabBar {
padding: 0.2rem 0;
background-color: rgba(255, 255, 255, 0.95);
width: 100%;
display: flex;
justify-content: space-around;
align-items: center;
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 1000;
box-shadow: 0 -0.02rem 0.2rem rgba(0, 0, 0, 0.08);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-top: 1px solid rgba(0, 0, 0, 0.05);
.tabBar-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
padding: 0.1rem 0.15rem;
border-radius: 0.3rem;
position: relative;
overflow: hidden;
user-select: none;
/* 悬停效果 */
&:hover {
background-color: rgba(24, 144, 255, 0.08);
transform: translateY(-0.05rem);
}
/* 激活状态 */
&.active {
.item-text {
color: #1890ff;
font-weight: 600;
}
}
/* 点击动画 */
&.click-animation {
animation: tabClick 0.3s ease;
}
/* 激活指示器 */
.active-indicator {
position: absolute;
bottom: 0.05rem;
left: 50%;
transform: translateX(-50%);
width: 0.3rem;
height: 0.04rem;
// background: linear-gradient(90deg, #1890ff);
border-radius: 0.02rem;
animation: indicatorAppear 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
}
.item-img {
margin-bottom: 0.08rem;
position: relative;
width: 0.5rem;
height: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
img {
width: 100%;
height: 100%;
display: block;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
}
.item-text {
font-size: 0.24rem;
color: #666;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
height: 0.3rem;
display: flex;
align-items: center;
justify-content: center;
}
}
/* 图标动画 */
.icon-bounce-enter-active {
animation: iconBounceIn 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
.icon-bounce-leave-active {
animation: iconBounceOut 0.3s ease;
}
@keyframes iconBounceIn {
0% {
opacity: 0;
transform: scale(0.3) rotate(-30deg);
}
50% {
transform: scale(1.2) rotate(10deg);
}
70% {
transform: scale(0.9) rotate(-5deg);
}
100% {
opacity: 1;
transform: scale(1) rotate(0deg);
}
}
@keyframes iconBounceOut {
0% {
opacity: 1;
transform: scale(1) rotate(0deg);
}
100% {
opacity: 0;
transform: scale(0.5) rotate(30deg);
}
}
/* 文字动画 */
.text-slide-enter-active {
animation: textSlideIn 0.3s ease-out;
}
.text-slide-leave-active {
animation: textSlideOut 0.2s ease-in;
}
@keyframes textSlideIn {
0% {
opacity: 0;
transform: translateY(0.1rem);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
@keyframes textSlideOut {
0% {
opacity: 1;
transform: translateY(0);
}
100% {
opacity: 0;
transform: translateY(-0.1rem);
}
}
/* Tab点击动画 */
@keyframes tabClick {
0% {
transform: scale(1);
}
50% {
transform: scale(0.95);
}
100% {
transform: scale(1);
}
}
/* 指示器出现动画 */
@keyframes indicatorAppear {
0% {
opacity: 0;
transform: translateX(-50%) scaleX(0);
}
100% {
opacity: 1;
transform: translateX(-50%) scaleX(1);
}
}
/* 响应式调整 */
@media (max-width: 480px) {
.back-to-top {
right: 0.3rem;
bottom: 1.6rem;
width: 1rem;
height: 1rem;
.back-to-top-icon {
width: 0.4rem;
height: 0.4rem;
margin-bottom: 0.03rem;
}
.back-to-top-text {
font-size: 0.22rem;
}
}
.tabBar {
padding: 0.15rem 0;
.tabBar-item {
padding: 0.08rem 0.12rem;
}
.item-img {
width: 0.45rem;
height: 0.45rem;
}
.item-text {
font-size: 0.22rem;
}
}
}
@media (min-width: 768px) {
.back-to-top {
right: 0.5rem;
bottom: 2rem;
width: 1.2rem;
height: 1.2rem;
.back-to-top-icon {
width: 0.5rem;
height: 0.5rem;
margin-bottom: 0.05rem;
}
.back-to-top-text {
font-size: 0.26rem;
}
}
}
/* 深色模式支持 */
@media (prefers-color-scheme: dark) {
.back-to-top {
background: linear-gradient(90deg, #275AFF 0%, #2EBDFA 100%);
box-shadow: 0 0.08rem 0.24rem rgba(138, 43, 226, 0.4);
&:hover {
background: linear-gradient(90deg, #275AFF 0%, #2EBDFA 100%);
box-shadow: 0 0.12rem 0.36rem rgba(138, 43, 226, 0.6);
}
}
.tabBar {
background-color: rgba(30, 30, 30, 0.95);
border-top: 1px solid rgba(255, 255, 255, 0.05);
.tabBar-item {
.item-text {
color: #b0b0b0;
}
&:hover {
background-color: rgba(100, 181, 246, 0.08);
}
&.active {
.item-text {
color: #64b5f6;
}
.active-indicator {
background: linear-gradient(90deg, #64b5f6, #81c784);
}
}
}
}
}
/* 安全区域适配iPhone X及以上机型 */
@supports (padding: max(0px)) {
.back-to-top {
bottom: calc(1.5rem + env(safe-area-inset-bottom, 0));
}
.tabBar {
padding-bottom: calc(0.2rem + env(safe-area-inset-bottom, 0));
}
@media (max-width: 480px) {
.back-to-top {
bottom: calc(1.6rem + env(safe-area-inset-bottom, 0));
}
.tabBar {
padding-bottom: calc(0.15rem + env(safe-area-inset-bottom, 0));
}
}
@media (min-width: 768px) {
.back-to-top {
bottom: calc(2rem + env(safe-area-inset-bottom, 0));
}
}
}

View File

@ -72,13 +72,23 @@
.journey-box .content .item-box,
.latitude-box .content .item-box {
width: 48%;
background-color: #fff;
border-radius: 0.1rem;
padding: 0.2rem;
box-shadow: 0 0.02rem 0.08rem rgba(0, 0, 0, 0.05);
box-shadow: 0 0.02rem 0.08rem rgba(39, 90, 255, 0.08);
margin-bottom: 0.2rem;
display: flex;
flex-direction: column;
background: linear-gradient(135deg, #f0f7ff 0%, #ffffff 100%);
border: 1px solid rgba(39, 90, 255, 0.1);
transition: all 0.3s ease;
}
.base-box .content .item-box:hover,
.journey-box .content .item-box:hover,
.latitude-box .content .item-box:hover {
box-shadow: 0 0.04rem 0.16rem rgba(39, 90, 255, 0.12);
transform: translateY(-2px);
border-color: rgba(39, 90, 255, 0.2);
background: linear-gradient(135deg, #e8f2ff 0%, #ffffff 100%);
}
.base-box .content .item-box .item-title,
.journey-box .content .item-box .item-title,

View File

@ -78,13 +78,22 @@
.item-box {
width: 48%;
background-color: #fff;
border-radius: 0.1rem;
padding: 0.2rem;
box-shadow: 0 .02rem .08rem rgba(0, 0, 0, 0.05);
box-shadow: 0 .02rem .08rem rgba(39, 90, 255, 0.08);
margin-bottom: 0.2rem;
display: flex;
flex-direction: column;
background: linear-gradient(135deg, #f0f7ff 0%, #ffffff 100%);
border: 1px solid rgba(39, 90, 255, 0.1);
transition: all 0.3s ease;
&:hover {
box-shadow: 0 .04rem .16rem rgba(39, 90, 255, 0.12);
transform: translateY(-2px);
border-color: rgba(39, 90, 255, 0.2);
background: linear-gradient(135deg, #e8f2ff 0%, #ffffff 100%);
}
.item-title {
font-size: 0.26rem;
@ -107,8 +116,6 @@
display: block;
font-size: 0.156rem;
color: #666;
// margin-bottom: 0.08rem;
// line-height: 1.4;
.advantage-icon {
width: 0.18rem;

View File

@ -21,6 +21,7 @@
<!-- 内容 -->
<div class="content">
<div class="item-box" v-for="(item, key) in baseData" :key="key">
<div class="item-title">{{ item.title }}</div>
<div class="item-description">{{ item.description }}</div>

View File

@ -13,13 +13,11 @@
</el-table-column>
<el-table-column prop="content" label="内容" min-width="180">
</el-table-column>
<el-table-column prop="create_time" label="咨询时间" min-width="180">
<el-table-column prop="update_time" label="咨询时间" min-width="180">
</el-table-column>
<el-table-column prop="update_time" label="联系时间" min-width="180">
</el-table-column>
<el-table-column prop="update_time" label="操作" min-width="180">
<el-table-column prop="update_time" label="反馈状态" min-width="180">
<template slot-scope="scope">
<el-button type="text" @click="handleEdit(scope.row)">已回复</el-button>
<el-button type="text" >{{ scope.row.feedback === '1' ? '已回复' : '未回复' }}</el-button>
</template>
</el-table-column>
</el-table>