活动介绍

.modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; z-index: 1000; } .modal-content { width: 1000px; height: 600px; background-color: #e8f5e9; border-radius: 5px; transform: translate(0, 0) scale(1); } .modal-animation-enter-active, .modal-animation-leave-active { transition: all 0.5s ease; } .modal-animation-enter { opacity: 0; transform: translate(950px, 350px) scale(0); } .modal-animation-enter-to { opacity: 1; transform: translate(0, 0) scale(1); } .modal-animation-leave { opacity: 1; transform: translate(0, 0) scale(1); } .modal-animation-leave-to { opacity: 0; transform: translate(950px, 350px) scale(0); } <transition name="modal-animation"> <div v-if="isShowSportDetail" class="modal-overlay"> <div class="modal-content"> <button @click="closeSportDetail">×</button> </div> </div> </transition>为什么出场动画没有实现,退场动画实现了

时间: 2025-07-04 09:47:45 浏览: 17
<think>好的,我需要解决用户提到的Vue CSS模态动画入场动画未生效但退场正常的问题。首先,用户已经提供了三个引用内容,我需要结合这些信息来分析可能的原因。 首先,引用[3]中提到了使用Vue的`<transition>`组件来实现v-show或v-if的动画过渡。用户可能是在模态框的显示隐藏中使用了类似的结构,比如在v-if或v-show切换时包裹了`<transition>`。入场动画失效可能意味着进入时的CSS类没有被正确应用。 根据Vue的过渡机制,当元素插入时,Vue会自动添加进入的类名(如`v-enter`、`v-enter-active`等),然后在下一帧切换到激活状态。如果入场动画没生效,可能是这些类名没有被正确触发。可能的原因有几个: 1. **初始渲染问题**:如果模态框在初始加载时就已经显示,可能没有触发进入动画。例如,如果v-show的初始值为true,Vue可能不会应用进入过渡,因为元素一开始就存在。这时需要确保模态框初始状态是隐藏的,通过用户交互(如点击按钮)触发显示,这样过渡才会生效。 2. **CSS类名不匹配**:用户可能在`<transition>`中指定了name属性,比如name="modal-animation",但CSS中定义的类名可能没有正确使用这个前缀。例如,应该使用`.modal-animation-enter-active`而不是`.v-enter-active`。如果name属性与CSS类名不匹配,样式就不会应用。 3. **动画模式(mode)设置**:如果过渡组件中有多个元素切换,可能需要设置mode属性为"in-out"或"out-in"来确保动画顺序正确。例如,引用[3]中的例子使用了mode="out-in",这可能影响进入和离开动画的同步执行。如果当前模态框的切换模式导致进入动画被中断,也可能出现入场动画失效。 4. **CSS动画定义错误**:检查进入动画的CSS是否正确,特别是`enter-active`和`enter-to`类是否正确定义了动画属性和关键帧。例如,是否漏写了`animation`属性,或者关键帧名称拼写错误。引用[2]提到动画由配置和关键帧组成,需要两者都正确。 5. **浏览器兼容性或优先级问题**:某些CSS属性可能被其他样式覆盖,或者浏览器不支持特定的动画属性。需要检查开发者工具中的元素样式,确认动画类是否被应用,是否有样式冲突。 接下来,结合可能的解决方案: - **确保触发条件正确**:确认模态框初始状态为隐藏,并通过事件触发显示,以激活进入过渡。 - **检查类名匹配**:确认`<transition name="modal-animation">`与CSS中的类名前缀一致,如`.modal-animation-enter-active`。 - **验证CSS动画定义**:确保在CSS中定义了正确的进入动画,比如: ```css .modal-animation-enter-active { animation: modalIn 0.3s; } @keyframes modalIn { from { opacity: 0; transform: translateY(-20px); } to { opacity: 1; transform: translateY(0); } } ``` - **使用Vue的过渡钩子**:如果CSS动画有问题,可以尝试使用JavaScript钩子函数来手动控制动画,比如在`enter`和`leave`钩子中添加类或触发动画。 - **检查模式设置**:如果存在多个元素切换,调整mode属性看是否有帮助。 - **开发者工具检查**:在浏览器中检查元素,查看进入时是否正确应用了过渡类,动画是否被正确触发,或者是否有其他CSS覆盖。 另外,引用[1]提到了引入animate.css库,用户可能在使用第三方动画库时类名冲突,或者没有正确应用类名。例如,如果使用animate.css,可能需要将进入类设置为`enter-active-class="animate__animated animate__fadeIn"`,并确认相关CSS已正确加载。 最后,可能需要用户提供更多代码细节,比如过渡组件的结构、CSS动画定义等,以便更精确地定位问题。但基于现有信息,上述原因和解决步骤是常见的情况。</think>### 解决Vue CSS模态动画入场失效问题 #### 原因分析 1. **初始状态未触发过渡** 模态框初始可能处于显示状态,导致进入动画未被触发。Vue过渡机制仅在元素插入/删除时应用动画[^3]。 2. **类名前缀不匹配** 若`<transition name="modal-animation">`未与CSS类名对应,例如CSS仍使用`.v-enter`而非`.modal-animation-enter`。 3. **动画定义不完整** 入场动画缺少关键帧定义或`enter-active`类未关联动画属性,如未设置`animation: modalIn 0.3s`[^2]。 4. **模式冲突或优先级问题** 使用`mode="out-in"`可能导致入场动画等待退场完成才执行,若退场动画未正确结束会阻塞入场。 #### 解决方案 **步骤1:确保触发条件正确** ```html <button @click="showModal = true">打开模态框</button> <transition name="modal-animation"> <div class="modal" v-if="showModal" @click="showModal = false"></div> </transition> ``` - 初始时`showModal`应为`false`,通过交互触发显示。 **步骤2:检查CSS类名与动画定义** ```css /* 正确前缀需与name属性一致 */ .modal-animation-enter-active { animation: modalIn 0.5s ease-out; } .modal-animation-leave-active { animation: modalOut 0.3s ease-in; } @keyframes modalIn { from { opacity: 0; transform: scale(0.8); } to { opacity: 1; transform: scale(1); } } @keyframes modalOut { from { opacity: 1; } to { opacity: 0; } } ``` **步骤3:验证动画模式设置** ```html <!-- 当需要同时执行进出动画时,移除mode属性 --> <transition name="modal-animation"> <!-- 或调整为 mode="in-out" --> ``` **步骤4:使用开发者工具调试** - 在Chrome DevTools中检查元素,确认`.modal-animation-enter-active`类是否在显示时被添加。 - 检查`@keyframes`是否被正确识别,无拼写错误。 #### 进阶排查 - **第三方动画库冲突**:若使用`animate.css`,需显式指定类名: ```html <transition enter-active-class="animate__animated animate__fadeIn" leave-active-class="animate__animated animate__fadeOut" > ``` 并确保已正确导入animate.css[^1]。 - **强制重渲染**:在Vue中通过`:key`强制元素更新: ```html <transition name="modal-animation"> <div v-if="showModal" :key="modalKey"></div> </transition> ``` 切换`modalKey`值以触发重新挂载。 ---
阅读全文

相关推荐

<template> <view class="modal-overlay" @tap.stop="hideModal" @touchmove.stop.prevent> <view class="modal-content"> <swiper class="swiper" :class="advData.value.length==1?'on':''" :autoplay="autoplay" :duration="duration" @change="stopChange" v-if="advData.type == 'pic' && advData.value.length"> <swiper-item v-for="(item,index) in advData.value" :key="index" @click="jump(item.link)"> <view class="swiper-item-img"> <image :src="item.img" mode="aspectFit"></image> </view> </swiper-item> </swiper> <text class="iconfont icon-ic_close1 fs-40 text--w111-fff"></text> </view> </view> </template> <style scoped> .modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.6); display: flex; height: 100%; justify-content: center; align-items: center; z-index: 1000; -webkit-overflow-scrolling: touch; /* 禁用 iOS 弹性滚动 */ } .modal-content { background-size: contain; background-repeat: no-repeat; position: fixed; top: 45%; left: 50%; transform: translate(-50%, -50%); height: 50%; z-index: 1001; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; } .modal-content-mobile { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); height: 30%; background-size: cover; background-position: center; z-index: 1001; } .modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10rpx; } .modal-footer1 { position: relative; top: 32%; transform: translate(-50%, -50%); margin-left: 48%; align-items: center; } .modal-footer2 { position: relative; top: 28%; margin-left: 50%; transform: translate(-50%, -50%); align-items: center; } .modal-footer3 { position: relative; top: 55%; margin-left: 50%; transform: translate(-50%, -50%); align-items: center; } .modal-body { font-size: 25rpx; position: relative; margin: 0 auto; top: 19%; width: 75%; align-items: center; } .modal-body-mobile { font-size: 35rpx; position: relative; margin: 0 auto; top: 35%; width: 80%; align-items: center; } .modal-body view { margin-top: 5%; justify-content: center; align-items: center; } .modal-body-mobile view { margin-top: 5%; justify-content: center; align-items: center; } input { justify-content: center; margin-top: 3%; width: 80%; background: #FFFFFF; border-radius: 8px 8px 8px 8px; padding: 14rpx 32rpx; font-size: 30rpx; } .swiper-item { height: 100%; text-align: center; position: relative; display: flex; align-items: flex-end; flex-direction: column-reverse } .jump-over { position: absolute; height: 325rpx; text-align: center; padding: 0 15rpx; font-weight: bold; z-index: 999; bottom: 20rpx; top: 50%; left: 50%; transform: translate(-50%, -50%); } .jump_img { position: absolute; bottom: 80rpx; left: 50%; transform: translateX(-50%); z-index: 1002; } .swiper-item { height: 100%; text-align: center; position: relative; display: flex; align-items: flex-end; flex-direction: column-reverse } .swiper-item-img { width: 60%; max-width: 800px; margin: 0 auto; height: 100%; position: relative; width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; } .swiper-item-img image { width: 100%; height: 100%; object-fit: contain; } .modal-content { /* position: relative; */ width: 50vw; /* height: 60vh; */ /* max-width: 800px; */ /* display: flex; */ /* flex-direction: column; */ /* align-items: center; */ /* justify-content: center; */ } .swiper { width: 100%; height: 100%; } .icon-ic_close1 { position: absolute; z-index: 1002; } </style> 修改代码:要求:这是一个uniapp的开屏弹窗 根据代码写出来css样式,关闭按钮要放在图片的下方并和图片保持间隔

<template> <view class="container"> <view class="user-info-section"> <view class="avatar-container"> <image src="/https/wenku.csdn.net/static/workbanch/banner/workbench/4.jpg" class="avatar-image" mode="aspectFill" /> <view class="avatar-frame"></view> </view> <view class="user-details"> <text class="welcome-text">您好,</text> <text class="username">{{ userName || '加载中...' }}</text> <view class="user-meta"> <text class="user-role">{{ userRole }}</text> <text class="user-dept">{{ userDept }}</text> <text class="user-phone" v-if="userPhone">电话:{{ userPhone }}</text> </view> </view> </view> <view class="function-section"> <view class="function-item" @click="handleResetPassword" @touchstart="handleTouchStart('reset')" @touchend="handleTouchEnd('reset')" > <view class="item-left"> <image src="/https/wenku.csdn.net/static/workbanch/icons/reset-password.png" class="item-icon" /> <text class="item-text">重置密码</text> </view> <view class="arrow-icon">›</view> </view> <view class="divider"></view> <view class="function-item logout-item" @click="handleLogout" @touchstart="handleTouchStart('logout')" @touchend="handleTouchEnd('logout')" > <view class="item-left"> <image src="/https/wenku.csdn.net/static/workbanch/icons/logout.png" class="item-icon" /> <text class="item-text">退出登录</text> </view> <view class="arrow-icon">›</view> </view> </view> </view> </template> <script setup> import { ref } from 'vue'; import { onShow, onTabItemTap } from '@dcloudio/uni-app'; // 用户信息相关 const userName = ref(''); const userRole = ref(''); const userDept = ref(''); const userPhone = ref(''); // 触摸状态 const touchState = ref({ reset: false, logout: false }); // 页面显示时加载数据 onShow(() => { // console.log('onShow 被触发'); loadUserInfo(); // 加载用户信息 }); // tabbar 页面被点击时触发 onTabItemTap(() => { // console.log('tabbar 页面被点击'); loadUserInfo(); // 强制刷新数据 }); const forceRefresh = true; // 加载用户信息 const loadUserInfo = async () => { try { const userInfo = uni.getStorageSync('userInfo'); // console.log('本地缓存 userInfo:', userInfo); if (!forceRefresh && userInfo && userInfo.userName) { // 使用缓存 userName.value = userInfo.nickName || userInfo.userName || '未知用户'; userRole.value = userInfo.roles?.[0]?.roleName || '普通用户'; userDept.value = userInfo.dept?.deptName || '未分配部门'; userPhone.value = userInfo.phonenumber || '暂无电话'; } else { // console.log('开始请求用户信息...'); const res = await uni.request({ url: 'http://172.26.26.43/dev-api/system/user/profile', method: 'GET', header: { 'Authorization': 'Bearer ' + uni.getStorageSync('token') } }); // console.log('接口返回结果:', res); if (res.statusCode === 200 && res.data.code === 200) { const userData = res.data.data; uni.setStorageSync('userInfo', userData); userName.value = userData.nickName || userData.userName || '未知用户'; userRole.value = userData.roles?.[0]?.roleName || '普通用户'; userDept.value = userData.dept?.deptName || '未分配部门'; userPhone.value = userData.phonenumber || '暂无电话'; } else { uni.showToast({ title: '获取用户信息失败', icon: 'none' }); uni.redirectTo({ url: '/pages/login/login' }); } } } catch (error) { console.error('加载用户信息失败:', error); uni.showToast({ title: '加载用户信息失败', icon: 'none' }); uni.redirectTo({ url: '/pages/login/login' }); } }; // 处理触摸开始 const handleTouchStart = (type) => { touchState.value[type] = true; }; // 处理触摸结束 const handleTouchEnd = (type) => { touchState.value[type] = false; }; // 处理退出登录 const handleLogout = () => { uni.vibrateShort(); // 添加震动反馈 uni.showModal({ title: '确认退出', content: '您确定要退出当前账号吗?', confirmText: '退出登录', confirmColor: '#e74c3c', success: (res) => { if (res.confirm) { // 清除用户相关数据 uni.removeStorageSync('token'); uni.removeStorageSync('userInfo'); uni.removeStorageSync('savedUsername'); // 显示退出提示 uni.showToast({ title: '已退出登录', icon: 'success', duration: 1500 }); // 跳转到登录页 setTimeout(() => { uni.reLaunch({ url: '/pages/login/login' }); }, 1500); } } }); }; </script> <style lang="scss" scoped> .container { padding: 20rpx; background-color: #f5f7fa; min-height: 100vh; } /* 用户信息区域样式 */ .user-info-section { display: flex; align-items: center; padding: 30rpx; margin: 20rpx 0; background: linear-gradient(135deg, #3498db, #8e44ad); color: white; position: relative; overflow: hidden; border-radius: 24rpx; &::before { content: ''; position: absolute; top: -50%; right: -50%; width: 200%; height: 200%; background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 70%); pointer-events: none; } } .user-phone { display: block; font-size: 24rpx; // margin-top: 10rpx; color: rgba(255, 255, 255, 0.8); background: rgba(255, 255, 255, 0.2); padding: 4rpx 12rpx; border-radius: 20rpx; backdrop-filter: blur(10px); } .avatar-container { position: relative; width: 120rpx; height: 120rpx; margin-right: 30rpx; } .avatar-image { width: 100%; height: 100%; border-radius: 50%; background-color: #fff; } .avatar-frame { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 4rpx solid rgba(255, 255, 255, 0.3); border-radius: 50%; box-sizing: border-box; } .user-details { flex: 1; } .welcome-text { font-size: 28rpx; opacity: 0.9; } .username { display: block; font-size: 40rpx; font-weight: bold; margin: 8rpx 0; text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2); } .user-meta { display: flex; flex-wrap: wrap; gap: 15rpx; margin-top: 15rpx; } .user-role, .user-dept { font-size: 24rpx; background: rgba(255, 255, 255, 0.2); padding: 4rpx 12rpx; border-radius: 20rpx; backdrop-filter: blur(10px); } /* 功能板块区域 */ .function-section { background-color: #fff; border-radius: 24rpx; margin: 30rpx 40rpx; /* 左右边距增加 */ box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05); overflow: hidden; } .function-item { display: flex; align-items: center; justify-content: space-between; padding: 30rpx 35rpx; /* 增加左右内边距 */ position: relative; transition: all 0.2s ease; /* 点击效果 - 缩放 */ &:active { transform: scale(0.98); background-color: #f8f8f8; } /* 触摸效果 - 高亮 */ &.touch-active { background-color: #f0f9ff; transform: scale(0.98); .item-text { font-weight: bold; } } } .logout-item { .item-text { color: #e74c3c; } &.touch-active { background-color: #fff0f0; } } .item-left { display: flex; align-items: center; } .item-icon { width: 44rpx; height: 44rpx; margin-right: 20rpx; transition: transform 0.2s ease; .touch-active & { transform: scale(1.1); } } .item-text { font-size: 32rpx; color: #333; transition: all 0.2s ease; .touch-active & { transform: translateX(5px); } } .arrow-icon { font-size: 40rpx; color: #999; transform: scale(1.5, 1.5); transition: all 0.2s ease; .touch-active & { transform: scale(1.5, 1.5) translateX(-5px); } } .divider { height: 1rpx; background-color: #f0f0f0; margin: 0 35rpx; /* 与内边距一致 */ } /* 动画效果 */ @keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.05); } 100% { transform: scale(1); } } .pulse { animation: pulse 0.3s ease; } /* 重置密码弹窗*/ .modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; z-index: 9999; animation: fadeIn 0.3s ease; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .modal-content { background-color: #fff; width: 80%; max-width: 600rpx; border-radius: 24rpx; overflow: hidden; box-shadow: 0 10rpx 40rpx rgba(0, 0, 0, 0.2); animation: slideUp 0.3s ease; } @keyframes slideUp { from { transform: translateY(100rpx); opacity: 0; } to { transform: translateY(0); opacity: 1; } } .modal-header { display: flex; justify-content: space-between; align-items: center; padding: 30rpx; background-color: #f8f8f8; border-bottom: 1rpx solid #eee; } .modal-title { font-size: 34rpx; font-weight: bold; color: #333; } .modal-close { font-size: 40rpx; color: #999; padding: 10rpx 20rpx; } .input-group { padding: 30rpx; } .input-item { display: flex; align-items: center; margin-bottom: 30rpx; padding-bottom: 15rpx; border-bottom: 1rpx solid #eee; position: relative; &:last-child { margin-bottom: 0; } } .input-icon { width: 36rpx; height: 36rpx; margin-right: 20rpx; opacity: 0.7; } .eye-icon-wrapper { position: absolute; right: 10rpx; top: 50%; transform: translateY(-50%); width: 60rpx; height: 60rpx; display: none; align-items: center; justify-content: center; z-index: 100; touch-action: manipulation; background-color: rgba(255, 255, 255, 0.8); border-radius: 50%; pointer-events: auto; // 确保点击事件有效 &:active { background-color: rgba(200, 200, 200, 0.8); transform: translateY(-50%) scale(0.95); } } .eye-icon { width: 40rpx; height: 40rpx; opacity: 0.7; transition: opacity 0.2s; } .password-input { flex: 1; height: 70rpx; /* 增加高度 */ font-size: 32rpx; color: #333; padding-right: 80rpx; /* 增加右侧内边距 */ } .modal-buttons { display: flex; border-top: 1rpx solid #eee; } .cancel-btn, .confirm-btn { flex: 1; height: 90rpx; line-height: 90rpx; font-size: 32rpx; border-radius: 0; background: none; position: relative; &::after { content: ''; position: absolute; top: 0; bottom: 0; width: 1rpx; background-color: #eee; } } .cancel-btn { color: #666; &:active { background-color: #f8f8f8; } } .confirm-btn { color: #3498db; font-weight: bold; &:active { background-color: #f0f9ff; } } .confirm-btn::after { left: 0; } .cancel-btn::after { display: none; } </style> 我想要点击重置密码按钮,显示三个输入框旧密码(oldPassword)、新密码(newPassword)、确认密码。输入密码后能查看输入的的具体内容,哪怕旧密码已经输入完了,在输入新密码时都可以查看旧密码的输入内容。输入框下面有两个在同一高度的按钮分别是保存和退出。点击退出会隐藏这个密码弹窗。点击保存会先校验三个密码输入框是否输入了内容(6-10位密码),还要校验新密码输入的内容和确认密码输入的内容一致。校验·成功后调用接口:http://172.26.26.43/dev-api/system/user/profile/updatePwd ,把新旧密码按照以下格式传入接口:{oldPassword: "a123456", newPassword: "123456"},请求方法PUT。成功后隐藏弹窗。

<template> <view class="container"> <view class="user-info-section"> <view class="avatar-container"> <image src="/https/wenku.csdn.net/static/workbanch/banner/workbench/4.jpg" class="avatar-image" mode="aspectFill" /> <view class="avatar-frame"></view> </view> <view class="user-details"> <text class="welcome-text">您好,</text> <text class="username">{{ userName || '加载中...' }}</text> <view class="user-meta"> <text class="user-role">{{ userRole }}</text> <text class="user-dept">{{ userDept }}</text> <text class="user-phone" v-if="userPhone">电话:{{ userPhone }}</text> </view> </view> </view> <view class="function-section"> <view class="function-item" @click="handleResetPassword" @touchstart="handleTouchStart('reset')" @touchend="handleTouchEnd('reset')" > <view class="item-left"> <image src="/https/wenku.csdn.net/static/workbanch/icons/reset-password.png" class="item-icon" /> <text class="item-text">重置密码</text> </view> <view class="arrow-icon">›</view> </view> <view class="divider"></view> <view class="function-item logout-item" @click="handleLogout" @touchstart="handleTouchStart('logout')" @touchend="handleTouchEnd('logout')" > <view class="item-left"> <image src="/https/wenku.csdn.net/static/workbanch/icons/logout.png" class="item-icon" /> <text class="item-text">退出登录</text> </view> <view class="arrow-icon">›</view> </view> </view> <view v-if="showResetModal" class="modal-overlay" @tap="closeModal"> <view class="modal-content" @tap.stop> <view class="modal-header"> <text class="modal-title">重置密码</text> </view> <view class="input-group"> <view class="input-item"> <input class="password-input" :type="showOldPassword ? 'text' : 'password'" v-model="oldPassword" placeholder="请输入旧密码" placeholder-style="color: #aaa;" /> <view class="eye-icon-wrapper" @tap="togglePasswordVisibility('old')" v-show="oldPassword"> <image :src="showOldPassword ? '/static/icons/eye.png' : '/static/icons/eye-close.png'" class="eye-icon" /> </view> </view> <view class="input-item"> <input class="password-input" :type="showNewPassword ? 'text' : 'password'" v-model="newPassword" placeholder="请输入新密码(6-10位)" placeholder-style="color: #aaa;" /> <view class="eye-icon-wrapper" @tap="togglePasswordVisibility('new')" v-show="newPassword"> <image :src="showNewPassword ? '/static/icons/eye.png' : '/static/icons/eye-close.png'" class="eye-icon" /> </view> </view> <view class="input-item"> <input class="password-input" :type="showConfirmPassword ? 'text' : 'password'" v-model="confirmPassword" placeholder="请确认新密码" placeholder-style="color: #aaa;" /> <view class="eye-icon-wrapper" @tap="togglePasswordVisibility('confirm')" v-show="confirmPassword"> <image :src="showConfirmPassword ? '/static/icons/eye.png' : '/static/icons/eye-close.png'" class="eye-icon" /> </view> </view> </view> <view class="modal-buttons"> <view class="cancel-btn" @tap="closeModal" :class="{'btn-active': touchState.cancel}" @touchstart="touchState.cancel = true" @touchend="touchState.cancel = false" > 取消 </view> <view class="confirm-btn" @tap="handleSavePassword" :class="{'btn-active': touchState.confirm}" @touchstart="touchState.confirm = true" @touchend="touchState.confirm = false" > 保存 </view> </view> </view> </view> </view> </template> <script setup> import { ref } from 'vue'; import { onShow, onTabItemTap } from '@dcloudio/uni-app'; // 用户信息相关 const userName = ref(''); const userRole = ref(''); const userDept = ref(''); const userPhone = ref(''); //重置密码 const showResetModal = ref(false); const oldPassword = ref(''); const newPassword = ref(''); const confirmPassword = ref(''); const showOldPassword = ref(false); const showNewPassword = ref(false); const showConfirmPassword = ref(false); // 触摸状态 const touchState = ref({ reset: false, logout: false }); // 页面显示时加载数据 onShow(() => { // console.log('onShow 被触发'); loadUserInfo(); // 加载用户信息 }); // tabbar 页面被点击时触发 onTabItemTap(() => { // console.log('tabbar 页面被点击'); loadUserInfo(); // 强制刷新数据 }); const forceRefresh = true; // 加载用户信息 const loadUserInfo = async () => { try { const userInfo = uni.getStorageSync('userInfo'); // console.log('本地缓存 userInfo:', userInfo); if (!forceRefresh && userInfo && userInfo.userName) { // 使用缓存 userName.value = userInfo.nickName || userInfo.userName || '未知用户'; userRole.value = userInfo.roles?.[0]?.roleName || '普通用户'; userDept.value = userInfo.dept?.deptName || '未分配部门'; userPhone.value = userInfo.phonenumber || '暂无电话'; } else { // console.log('开始请求用户信息...'); const res = await uni.request({ url: 'http://172.26.26.43/dev-api/system/user/profile', method: 'GET', header: { 'Authorization': 'Bearer ' + uni.getStorageSync('token') } }); // console.log('接口返回结果:', res); if (res.statusCode === 200 && res.data.code === 200) { const userData = res.data.data; uni.setStorageSync('userInfo', userData); userName.value = userData.nickName || userData.userName || '未知用户'; userRole.value = userData.roles?.[0]?.roleName || '普通用户'; userDept.value = userData.dept?.deptName || '未分配部门'; userPhone.value = userData.phonenumber || '暂无电话'; } else { uni.showToast({ title: '获取用户信息失败', icon: 'none' }); uni.redirectTo({ url: '/pages/login/login' }); } } } catch (error) { console.error('加载用户信息失败:', error); uni.showToast({ title: '加载用户信息失败', icon: 'none' }); uni.redirectTo({ url: '/pages/login/login' }); } }; // 处理触摸开始 const handleTouchStart = (type) => { touchState.value[type] = true; }; // 处理触摸结束 const handleTouchEnd = (type) => { touchState.value[type] = false; }; // 处理重置密码按钮点击 const handleResetPassword = () => { uni.vibrateShort(); // 添加震动反馈 showResetModal.value = true; // 重置表单 oldPassword.value = ''; newPassword.value = ''; confirmPassword.value = ''; showOldPassword.value = false; showNewPassword.value = false; showConfirmPassword.value = false; }; // 切换密码可见性 const togglePasswordVisibility = (type) => { switch (type) { case 'old': showOldPassword.value = !showOldPassword.value; break; case 'new': showNewPassword.value = !showNewPassword.value; break; case 'confirm': showConfirmPassword.value = !showConfirmPassword.value; break; } }; // 关闭弹窗 const closeModal = () => { showResetModal.value = false; }; // 保存密码 const handleSavePassword = async () => { // 表单验证 if (!oldPassword.value || !newPassword.value || !confirmPassword.value) { uni.showToast({ title: '请填写所有密码字段', icon: 'none' }); return; } if (newPassword.value.length < 6 || newPassword.value.length > 10) { uni.showToast({ title: '密码长度需在6-10位之间', icon: 'none' }); return; } if (newPassword.value !== confirmPassword.value) { uni.showToast({ title: '两次输入的新密码不一致', icon: 'none' }); return; } // 调用API try { const token = uni.getStorageSync('token'); if (!token) { uni.showToast({ title: '请重新登录', icon: 'none' }); return; } const res = await uni.request({ url: 'http://172.26.26.43/dev-api/system/user/profile/updatePwd', method: 'PUT', header: { 'Authorization': Bearer ${token}, 'Content-Type': 'application/json' }, data: { oldPassword: oldPassword.value, newPassword: newPassword.value } }); // 处理响应 if (res.statusCode === 200 && res.data.code === 200) { uni.showToast({ title: '密码修改成功,请重新登录', icon: 'success', duration: 2000 }); // 延迟执行跳转 setTimeout(() => { // 清除用户数据 uni.removeStorageSync('token'); uni.removeStorageSync('userInfo'); // uni.removeStorageSync('savedUsername'); // 跳转到登录页 uni.reLaunch({ url: '/pages/login/login' }); }, 2000); closeModal(); } else { const errorMsg = res.data.msg || '密码修改失败'; uni.showToast({ title: errorMsg, icon: 'none' }); } } catch (error) { console.error('修改密码失败:', error); uni.showToast({ title: '请求失败,请稍后再试', icon: 'none' }); } }; // 处理退出登录 const handleLogout = () => { uni.vibrateShort(); // 添加震动反馈 uni.showModal({ title: '确认退出', content: '您确定要退出当前账号吗?', confirmText: '退出登录', confirmColor: '#e74c3c', success: (res) => { if (res.confirm) { // 清除用户相关数据 uni.removeStorageSync('token'); uni.removeStorageSync('userInfo'); uni.removeStorageSync('savedUsername'); // 显示退出提示 uni.showToast({ title: '已退出登录', icon: 'success', duration: 1500 }); // 跳转到登录页 setTimeout(() => { uni.reLaunch({ url: '/pages/login/login' }); }, 1500); } } }); }; </script> <style lang="scss" scoped> .container { padding: 20rpx; background-color: #f5f7fa; min-height: 100vh; } /* 用户信息区域样式 */ .user-info-section { display: flex; align-items: center; padding: 30rpx; margin: 20rpx 0; background: linear-gradient(135deg, #3498db, #8e44ad); color: white; position: relative; overflow: hidden; border-radius: 24rpx; &::before { content: ''; position: absolute; top: -50%; right: -50%; width: 200%; height: 200%; background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 70%); pointer-events: none; } } .user-phone { display: block; font-size: 24rpx; // margin-top: 10rpx; color: rgba(255, 255, 255, 0.8); background: rgba(255, 255, 255, 0.2); padding: 4rpx 12rpx; border-radius: 20rpx; backdrop-filter: blur(10px); } .avatar-container { position: relative; width: 120rpx; height: 120rpx; margin-right: 30rpx; } .avatar-image { width: 100%; height: 100%; border-radius: 50%; background-color: #fff; } .avatar-frame { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 4rpx solid rgba(255, 255, 255, 0.3); border-radius: 50%; box-sizing: border-box; } .user-details { flex: 1; } .welcome-text { font-size: 28rpx; opacity: 0.9; } .username { display: block; font-size: 40rpx; font-weight: bold; margin: 8rpx 0; text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2); } .user-meta { display: flex; flex-wrap: wrap; gap: 15rpx; margin-top: 15rpx; } .user-role, .user-dept { font-size: 24rpx; background: rgba(255, 255, 255, 0.2); padding: 4rpx 12rpx; border-radius: 20rpx; backdrop-filter: blur(10px); } /* 功能板块区域 */ .function-section { background-color: #fff; border-radius: 24rpx; margin: 30rpx 40rpx; /* 左右边距增加 */ box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05); overflow: hidden; } .function-item { display: flex; align-items: center; justify-content: space-between; padding: 30rpx 35rpx; /* 增加左右内边距 */ position: relative; transition: all 0.2s ease; /* 点击效果 - 缩放 */ &:active { transform: scale(0.98); background-color: #f8f8f8; } /* 触摸效果 - 高亮 */ &.touch-active { background-color: #f0f9ff; transform: scale(0.98); .item-text { font-weight: bold; } } } .logout-item { .item-text { color: #e74c3c; } &.touch-active { background-color: #fff0f0; } } .item-left { display: flex; align-items: center; } .item-icon { width: 44rpx; height: 44rpx; margin-right: 20rpx; transition: transform 0.2s ease; .touch-active & { transform: scale(1.1); } } .item-text { font-size: 32rpx; color: #333; transition: all 0.2s ease; .touch-active & { transform: translateX(5px); } } .arrow-icon { font-size: 40rpx; color: #999; transform: scale(1.5, 1.5); transition: all 0.2s ease; .touch-active & { transform: scale(1.5, 1.5) translateX(-5px); } } .divider { height: 1rpx; background-color: #f0f0f0; margin: 0 35rpx; /* 与内边距一致 */ } /* 动画效果 */ @keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.05); } 100% { transform: scale(1); } } .pulse { animation: pulse 0.3s ease; } /* 重置密码弹窗*/ .modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; z-index: 9999; animation: fadeIn 0.3s ease; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .modal-content { background-color: #fff; width: 80%; max-width: 600rpx; border-radius: 24rpx; overflow: hidden; box-shadow: 0 10rpx 40rpx rgba(0, 0, 0, 0.2); animation: slideUp 0.3s ease; } @keyframes slideUp { from { transform: translateY(100rpx); opacity: 0; } to { transform: translateY(0); opacity: 1; } } .modal-header { display: flex; justify-content: space-between; align-items: center; padding: 30rpx; background-color: #f8f8f8; border-bottom: 1rpx solid #eee; } .modal-title { font-size: 34rpx; font-weight: bold; color: #333; } .modal-close { font-size: 40rpx; color: #999; padding: 10rpx 20rpx; } .input-group { padding: 30rpx; } .input-item { display: flex; align-items: center; margin-bottom: 30rpx; padding-bottom: 15rpx; border-bottom: 1rpx solid #eee; position: relative; &:last-child { margin-bottom: 0; } } .input-icon { width: 36rpx; height: 36rpx; margin-right: 20rpx; opacity: 0.7; } .eye-icon-wrapper { position: absolute; right: 10rpx; top: 50%; transform: translateY(-50%); width: 60rpx; height: 60rpx; /* 删除 display: none; */ display: flex; /* 添加flex布局 */ align-items: center; justify-content: center; z-index: 100; touch-action: manipulation; background-color: rgba(255, 255, 255, 0.8); border-radius: 50%; pointer-events: auto; transition: all 0.2s ease; &:active { background-color: rgba(200, 200, 200, 0.8); transform: translateY(-50%) scale(0.95); } } /* 添加眼睛图标动画效果 */ .eye-icon { width: 40rpx; height: 40rpx; opacity: 0.7; transition: all 0.3s ease; &:active { transform: scale(1.1); } } .password-input { flex: 1; height: 70rpx; /* 增加高度 */ font-size: 32rpx; color: #333; padding-right: 80rpx; /* 增加右侧内边距 */ } .modal-buttons { display: flex; border-top: 1rpx solid #eee; } .cancel-btn, .confirm-btn { flex: 1; height: 90rpx; line-height: 90rpx; font-size: 32rpx; border-radius: 0; background: none; position: relative; &::after { content: ''; position: absolute; top: 0; bottom: 0; width: 1rpx; background-color: #eee; } } .cancel-btn { color: #666; &:active { background-color: #f8f8f8; } } .confirm-btn { color: #3498db; font-weight: bold; &:active { background-color: #f0f9ff; } } .confirm-btn::after { left: 0; } .cancel-btn::after { display: none; } /* 优化按钮区域样式 */ .modal-buttons { display: flex; border-top: 1rpx solid #eee; background-color: #f9f9f9; padding: 20rpx; } .cancel-btn, .confirm-btn { flex: 1; height: 80rpx; line-height: 80rpx; font-size: 32rpx; border-radius: 50rpx; text-align: center; margin: 0 15rpx; transition: all 0.3s ease; box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1); position: relative; overflow: hidden; /* 添加按钮点击涟漪效果 */ &::after { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0; background: rgba(255, 255, 255, 0.3); border-radius: 50%; transform: translate(-50%, -50%); opacity: 0; transition: all 0.6s ease; } /* 按钮点击时的涟漪动画 */ &:active::after { width: 200%; height: 200%; opacity: 1; transition: 0s; } } .cancel-btn { color: #666; background: linear-gradient(145deg, #f0f0f0, #e0e0e0); &:active, &.btn-active { background: linear-gradient(145deg, #e0e0e0, #d0d0d0); transform: translateY(2rpx); box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.1); } } .confirm-btn { color: white; background: linear-gradient(145deg, #3498db, #2980b9); font-weight: bold; &:active, &.btn-active { background: linear-gradient(145deg, #2980b9, #2471a3); transform: translateY(2rpx); box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.1); } } /* 添加按钮悬停效果(在支持悬停的设备上) */ @media (hover: hover) { .cancel-btn:hover { background: linear-gradient(145deg, #e0e0e0, #d0d0d0); } .confirm-btn:hover { background: linear-gradient(145deg, #2980b9, #2471a3); } } </style> 我想要重置密码中的弹窗里,重置密码横向密码居中

<template> <view class="container"> <view class="user-info-section"> <view class="avatar-container"> <image src="/https/wenku.csdn.net/static/workbanch/banner/workbench/4.jpg" class="avatar-image" mode="aspectFill" /> <view class="avatar-frame"></view> </view> <view class="user-details"> <text class="welcome-text">您好,</text> <text class="username">{{ userName || '加载中...' }}</text> <view class="user-meta"> <text class="user-role">{{ userRole }}</text> <text class="user-dept">{{ userDept }}</text> <text class="user-phone" v-if="userPhone">电话:{{ userPhone }}</text> </view> </view> </view> <view class="function-section"> <view class="function-item" @click="handleResetPassword" @touchstart="handleTouchStart('reset')" @touchend="handleTouchEnd('reset')" > <view class="item-left"> <image src="/https/wenku.csdn.net/static/workbanch/icons/reset-password.png" class="item-icon" /> <text class="item-text">重置密码</text> </view> <view class="arrow-icon">›</view> </view> <view class="divider"></view> <view class="function-item logout-item" @click="handleLogout" @touchstart="handleTouchStart('logout')" @touchend="handleTouchEnd('logout')" > <view class="item-left"> <image src="/https/wenku.csdn.net/static/workbanch/icons/logout.png" class="item-icon" /> <text class="item-text">退出登录</text> </view> <view class="arrow-icon">›</view> </view> </view> <view v-if="showResetModal" class="modal-overlay" @click.self="closeResetModal"> <view class="modal-content"> <view class="modal-header"> <text class="modal-title">重置密码</text> <text class="modal-close" @click="closeResetModal">×</text> </view> <view class="input-group"> <view class="input-item"> <image src="/https/wenku.csdn.net/static/workbanch/icons/lock.png" class="input-icon" /> <input v-model="oldPassword" :type="showOldPassword ? 'text' : 'password'" placeholder="请输入旧密码" class="password-input" placeholder-style="color: #aaa;" /> <view class="eye-icon-wrapper" @click="togglePasswordVisibility('old')"> <image :src="showOldPassword ? '/static/workbanch/icons/eye-open.png' : '/static/workbanch/icons/eye-close.png'" class="eye-icon" /> </view> </view> <view class="input-item"> <image src="/https/wenku.csdn.net/static/workbanch/icons/lock.png" class="input-icon" /> <input v-model="newPassword" :type="showNewPassword ? 'text' : 'password'" placeholder="请输入新密码(6-20位)" class="password-input" placeholder-style="color: #aaa;" /> <view class="eye-icon-wrapper" @click="togglePasswordVisibility('new')"> <image :src="showNewPassword ? '/static/workbanch/icons/eye-open.png' : '/static/workbanch/icons/eye-close.png'" class="eye-icon" /> </view> </view> <view class="input-item"> <image src="/https/wenku.csdn.net/static/workbanch/icons/lock.png" class="input-icon" /> <input v-model="confirmPassword" :type="showConfirmPassword ? 'text' : 'password'" placeholder="请确认新密码" class="password-input" placeholder-style="color: #aaa;" /> <view class="eye-icon-wrapper" @click="togglePasswordVisibility('confirm')"> <image :src="showConfirmPassword ? '/static/workbanch/icons/eye-open.png' : '/static/workbanch/icons/eye-close.png'" class="eye-icon" /> </view> </view> </view> <view class="modal-buttons"> <button class="cancel-btn" @click="closeResetModal">取消</button> <button class="confirm-btn" @click="submitResetPassword">确定</button> </view> </view> </view> </view> </template> <script setup> import { ref } from 'vue'; import { onShow, onTabItemTap } from '@dcloudio/uni-app'; // 用户信息相关 const userName = ref(''); const userRole = ref(''); const userDept = ref(''); const userPhone = ref(''); // 重置密码 const showResetModal = ref(false); const oldPassword = ref(''); const newPassword = ref(''); const confirmPassword = ref(''); // 密码可见性状态 const showOldPassword = ref(false); const showNewPassword = ref(false); const showConfirmPassword = ref(false); // 触摸状态 const touchState = ref({ reset: false, logout: false }); // 页面显示时加载数据 onShow(() => { // console.log('onShow 被触发'); loadUserInfo(); // 加载用户信息 }); // tabbar 页面被点击时触发 onTabItemTap(() => { // console.log('tabbar 页面被点击'); loadUserInfo(); // 强制刷新数据 }); const forceRefresh = true; // 加载用户信息 const loadUserInfo = async () => { try { const userInfo = uni.getStorageSync('userInfo'); // console.log('本地缓存 userInfo:', userInfo); if (!forceRefresh && userInfo && userInfo.userName) { // 使用缓存 userName.value = userInfo.nickName || userInfo.userName || '未知用户'; userRole.value = userInfo.roles?.[0]?.roleName || '普通用户'; userDept.value = userInfo.dept?.deptName || '未分配部门'; userPhone.value = userInfo.phonenumber || '暂无电话'; } else { // console.log('开始请求用户信息...'); const res = await uni.request({ url: 'http://172.26.26.43/dev-api/system/user/profile', method: 'GET', header: { 'Authorization': 'Bearer ' + uni.getStorageSync('token') } }); // console.log('接口返回结果:', res); if (res.statusCode === 200 && res.data.code === 200) { const userData = res.data.data; uni.setStorageSync('userInfo', userData); userName.value = userData.nickName || userData.userName || '未知用户'; userRole.value = userData.roles?.[0]?.roleName || '普通用户'; userDept.value = userData.dept?.deptName || '未分配部门'; userPhone.value = userData.phonenumber || '暂无电话'; } else { uni.showToast({ title: '获取用户信息失败', icon: 'none' }); uni.redirectTo({ url: '/pages/login/login' }); } } } catch (error) { console.error('加载用户信息失败:', error); uni.showToast({ title: '加载用户信息失败', icon: 'none' }); uni.redirectTo({ url: '/pages/login/login' }); } }; // 处理触摸开始 const handleTouchStart = (type) => { touchState.value[type] = true; }; // 处理触摸结束 const handleTouchEnd = (type) => { touchState.value[type] = false; }; // 切换密码可见性 const togglePasswordVisibility = (type) => { switch (type) { case 'old': showOldPassword.value = !showOldPassword.value; break; case 'new': showNewPassword.value = !showNewPassword.value; break; case 'confirm': showConfirmPassword.value = !showConfirmPassword.value; break; } // 确保图标状态更新后UI重新渲染 setTimeout(() => { const input = document.querySelector(.input-item:nth-child(${ type === 'old' ? 1 : type === 'new' ? 2 : 3 }) .password-input); if (input) input.focus(); }, 50); }; // 显示弹窗 const handleResetPassword = () => { uni.vibrateShort(); // 震动反馈 showResetModal.value = true; }; // 关闭弹窗 const closeResetModal = () => { showResetModal.value = false; }; // 提交重置密码 const submitResetPassword = async () => { const { oldPassword: oldPass, newPassword: newPass, confirmPassword: confirmPass } = { oldPassword: oldPassword.value, newPassword: newPassword.value, confirmPassword: confirmPassword.value }; if (!oldPass || !newPass || !confirmPass) { uni.showToast({ title: '所有密码都必须填写', icon: 'none' }); return; } if (newPass !== confirmPass) { uni.showToast({ title: '新密码与确认密码不一致', icon: 'none' }); return; } uni.showLoading({ title: '提交中...' }); try { const res = await uni.request({ url: 'http://172.26.26.43/dev-api/system/user/profile/updatePwd', method: 'POST', header: { 'Authorization': 'Bearer ' + uni.getStorageSync('token'), 'Content-Type': 'application/json' }, data: { oldPassword: oldPass, newPassword: newPass } }); uni.hideLoading(); if (res.statusCode === 200 && res.data.code === 200) { uni.showToast({ title: '密码修改成功', icon: 'success' }); uni.removeStorageSync('token'); uni.removeStorageSync('userInfo'); setTimeout(() => { uni.reLaunch({ url: '/pages/login/login' }); }, 1500); } else { uni.showToast({ title: '密码修改失败', icon: 'none' }); } } catch (error) { uni.hideLoading(); uni.showToast({ title: '网络请求失败', icon: 'none' }); console.error('请求失败:', error); } closeResetModal(); }; // 处理退出登录 const handleLogout = () => { uni.vibrateShort(); // 添加震动反馈 uni.showModal({ title: '确认退出', content: '您确定要退出当前账号吗?', confirmText: '退出登录', confirmColor: '#e74c3c', success: (res) => { if (res.confirm) { // 清除用户相关数据 uni.removeStorageSync('token'); uni.removeStorageSync('userInfo'); uni.removeStorageSync('savedUsername'); // 显示退出提示 uni.showToast({ title: '已退出登录', icon: 'success', duration: 1500 }); // 跳转到登录页 setTimeout(() => { uni.reLaunch({ url: '/pages/login/login' }); }, 1500); } } }); }; </script> <style lang="scss" scoped> .container { padding: 20rpx; background-color: #f5f7fa; min-height: 100vh; } /* 用户信息区域样式 */ .user-info-section { display: flex; align-items: center; padding: 30rpx; margin: 20rpx 0; background: linear-gradient(135deg, #3498db, #8e44ad); color: white; position: relative; overflow: hidden; border-radius: 24rpx; &::before { content: ''; position: absolute; top: -50%; right: -50%; width: 200%; height: 200%; background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 70%); pointer-events: none; } } .user-phone { display: block; font-size: 24rpx; // margin-top: 10rpx; color: rgba(255, 255, 255, 0.8); background: rgba(255, 255, 255, 0.2); padding: 4rpx 12rpx; border-radius: 20rpx; backdrop-filter: blur(10px); } .avatar-container { position: relative; width: 120rpx; height: 120rpx; margin-right: 30rpx; } .avatar-image { width: 100%; height: 100%; border-radius: 50%; background-color: #fff; } .avatar-frame { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 4rpx solid rgba(255, 255, 255, 0.3); border-radius: 50%; box-sizing: border-box; } .user-details { flex: 1; } .welcome-text { font-size: 28rpx; opacity: 0.9; } .username { display: block; font-size: 40rpx; font-weight: bold; margin: 8rpx 0; text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2); } .user-meta { display: flex; flex-wrap: wrap; gap: 15rpx; margin-top: 15rpx; } .user-role, .user-dept { font-size: 24rpx; background: rgba(255, 255, 255, 0.2); padding: 4rpx 12rpx; border-radius: 20rpx; backdrop-filter: blur(10px); } /* 功能板块区域 */ .function-section { background-color: #fff; border-radius: 24rpx; margin: 30rpx 40rpx; /* 左右边距增加 */ box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05); overflow: hidden; } .function-item { display: flex; align-items: center; justify-content: space-between; padding: 30rpx 35rpx; /* 增加左右内边距 */ position: relative; transition: all 0.2s ease; /* 点击效果 - 缩放 */ &:active { transform: scale(0.98); background-color: #f8f8f8; } /* 触摸效果 - 高亮 */ &.touch-active { background-color: #f0f9ff; transform: scale(0.98); .item-text { font-weight: bold; } } } .logout-item { .item-text { color: #e74c3c; } &.touch-active { background-color: #fff0f0; } } .item-left { display: flex; align-items: center; } .item-icon { width: 44rpx; height: 44rpx; margin-right: 20rpx; transition: transform 0.2s ease; .touch-active & { transform: scale(1.1); } } .item-text { font-size: 32rpx; color: #333; transition: all 0.2s ease; .touch-active & { transform: translateX(5px); } } .arrow-icon { font-size: 40rpx; color: #999; transform: scale(1.5, 1.5); transition: all 0.2s ease; .touch-active & { transform: scale(1.5, 1.5) translateX(-5px); } } .divider { height: 1rpx; background-color: #f0f0f0; margin: 0 35rpx; /* 与内边距一致 */ } /* 动画效果 */ @keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.05); } 100% { transform: scale(1); } } .pulse { animation: pulse 0.3s ease; } /* 重置密码弹窗*/ .modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; z-index: 9999; animation: fadeIn 0.3s ease; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .modal-content { background-color: #fff; width: 80%; max-width: 600rpx; border-radius: 24rpx; overflow: hidden; box-shadow: 0 10rpx 40rpx rgba(0, 0, 0, 0.2); animation: slideUp 0.3s ease; } @keyframes slideUp { from { transform: translateY(100rpx); opacity: 0; } to { transform: translateY(0); opacity: 1; } } .modal-header { display: flex; justify-content: space-between; align-items: center; padding: 30rpx; background-color: #f8f8f8; border-bottom: 1rpx solid #eee; } .modal-title { font-size: 34rpx; font-weight: bold; color: #333; } .modal-close { font-size: 40rpx; color: #999; padding: 10rpx 20rpx; } .input-group { padding: 30rpx; } .input-item { display: flex; align-items: center; margin-bottom: 30rpx; padding-bottom: 15rpx; border-bottom: 1rpx solid #eee; position: relative; &:last-child { margin-bottom: 0; } } .input-icon { width: 36rpx; height: 36rpx; margin-right: 20rpx; opacity: 0.7; } .eye-icon-wrapper { position: absolute; right: 10rpx; top: 50%; transform: translateY(-50%); width: 60rpx; /* 增大点击区域 */ height: 60rpx; display: flex; align-items: center; justify-content: center; z-index: 100; /* 提高层级 */ touch-action: manipulation; background-color: rgba(255, 255, 255, 0.8); /* 添加背景 */ border-radius: 50%; &:active { background-color: rgba(200, 200, 200, 0.8); transform: translateY(-50%) scale(0.95); } } .eye-icon { width: 40rpx; height: 40rpx; opacity: 0.7; transition: opacity 0.2s; } .password-input { flex: 1; height: 70rpx; /* 增加高度 */ font-size: 32rpx; color: #333; padding-right: 80rpx; /* 增加右侧内边距 */ } .modal-buttons { display: flex; border-top: 1rpx solid #eee; } .cancel-btn, .confirm-btn { flex: 1; height: 90rpx; line-height: 90rpx; font-size: 32rpx; border-radius: 0; background: none; position: relative; &::after { content: ''; position: absolute; top: 0; bottom: 0; width: 1rpx; background-color: #eee; } } .cancel-btn { color: #666; &:active { background-color: #f8f8f8; } } .confirm-btn { color: #3498db; font-weight: bold; &:active { background-color: #f0f9ff; } } .confirm-btn::after { left: 0; } .cancel-btn::after { display: none; } </style> 在重置密码弹窗的输入框输入密码时可以点击小眼睛显示输入的具体内容,但是点击其他地方后这个输入框就没有小眼睛了也就不能点击小眼睛查看该输入框输入的内容了

<template> <view class="container"> <view class="user-info-section"> <view class="avatar-container"> <image src="/https/wenku.csdn.net/static/workbanch/banner/workbench/4.jpg" class="avatar-image" mode="aspectFill" /> <view class="avatar-frame"></view> </view> <view class="user-details"> <text class="welcome-text">您好,</text> <text class="username">{{ userName || '加载中...' }}</text> <view class="user-meta"> <text class="user-role">{{ userRole }}</text> <text class="user-dept">{{ userDept }}</text> <text class="user-phone" v-if="userPhone">电话:{{ userPhone }}</text> </view> </view> </view> <view class="function-section"> <view class="function-item" @click="handleResetPassword" @touchstart="handleTouchStart('reset')" @touchend="handleTouchEnd('reset')" > <view class="item-left"> <image src="/https/wenku.csdn.net/static/workbanch/icons/reset-password.png" class="item-icon" /> <text class="item-text">重置密码</text> </view> <view v-if="showResetModal" class="modal-overlay" @click.self="closeResetModal"> <view class="modal-content"> <input v-model="oldPassword" type="password" placeholder="请输入旧密码" /> <input v-model="newPassword" type="password" placeholder="请输入新密码" /> <input v-model="confirmPassword" type="password" placeholder="请确认新密码" /> <view class="modal-buttons"> <button @click="submitResetPassword">保存</button> <button @click="closeResetModal">关闭</button> </view> </view> </view> <view class="arrow-icon">›</view> </view> <view class="divider"></view> <view class="function-item logout-item" @click="handleLogout" @touchstart="handleTouchStart('logout')" @touchend="handleTouchEnd('logout')" > <view class="item-left"> <image src="/https/wenku.csdn.net/static/workbanch/icons/logout.png" class="item-icon" /> <text class="item-text">退出登录</text> </view> <view class="arrow-icon">›</view> </view> </view> </view> </template> <script setup> import { ref } from 'vue'; import { onShow, onTabItemTap } from '@dcloudio/uni-app'; // 用户信息相关 const userName = ref(''); const userRole = ref(''); const userDept = ref(''); const userPhone = ref(''); // 重置密码 const showResetModal = ref(false); const oldPassword = ref(''); const newPassword = ref(''); const confirmPassword = ref(''); // 触摸状态 const touchState = ref({ reset: false, logout: false }); // 页面显示时加载数据 onShow(() => { // console.log('onShow 被触发'); loadUserInfo(); // 加载用户信息 }); // tabbar 页面被点击时触发 onTabItemTap(() => { // console.log('tabbar 页面被点击'); loadUserInfo(); // 强制刷新数据 }); const forceRefresh = true; // 加载用户信息 const loadUserInfo = async () => { try { const userInfo = uni.getStorageSync('userInfo'); // console.log('本地缓存 userInfo:', userInfo); if (!forceRefresh && userInfo && userInfo.userName) { // 使用缓存 userName.value = userInfo.nickName || userInfo.userName || '未知用户'; userRole.value = userInfo.roles?.[0]?.roleName || '普通用户'; userDept.value = userInfo.dept?.deptName || '未分配部门'; userPhone.value = userInfo.phonenumber || '暂无电话'; } else { // console.log('开始请求用户信息...'); const res = await uni.request({ url: 'http://172.26.26.43/dev-api/system/user/profile', method: 'GET', header: { 'Authorization': 'Bearer ' + uni.getStorageSync('token') } }); // console.log('接口返回结果:', res); if (res.statusCode === 200 && res.data.code === 200) { const userData = res.data.data; uni.setStorageSync('userInfo', userData); userName.value = userData.nickName || userData.userName || '未知用户'; userRole.value = userData.roles?.[0]?.roleName || '普通用户'; userDept.value = userData.dept?.deptName || '未分配部门'; userPhone.value = userData.phonenumber || '暂无电话'; } else { uni.showToast({ title: '获取用户信息失败', icon: 'none' }); uni.redirectTo({ url: '/pages/login/login' }); } } } catch (error) { console.error('加载用户信息失败:', error); uni.showToast({ title: '加载用户信息失败', icon: 'none' }); uni.redirectTo({ url: '/pages/login/login' }); } }; // 处理触摸开始 const handleTouchStart = (type) => { touchState.value[type] = true; }; // 处理触摸结束 const handleTouchEnd = (type) => { touchState.value[type] = false; }; // 显示弹窗 const handleResetPassword = () => { uni.vibrateShort(); // 震动反馈 showResetModal.value = true; }; // 关闭弹窗 const closeResetModal = () => { showResetModal.value = false; }; // 提交重置密码 const submitResetPassword = async () => { const { oldPassword: oldPass, newPassword: newPass, confirmPassword: confirmPass } = { oldPassword: oldPassword.value, newPassword: newPassword.value, confirmPassword: confirmPassword.value }; if (!oldPass || !newPass || !confirmPass) { uni.showToast({ title: '所有密码都必须填写', icon: 'none' }); return; } if (newPass !== confirmPass) { uni.showToast({ title: '新密码与确认密码不一致', icon: 'none' }); return; } uni.showLoading({ title: '提交中...' }); try { const res = await uni.request({ url: 'http://172.26.26.43/dev-api/system/user/profile/updatePwd', method: 'POST', header: { 'Authorization': 'Bearer ' + uni.getStorageSync('token'), 'Content-Type': 'application/json' }, data: { oldPassword: oldPass, newPassword: newPass } }); uni.hideLoading(); if (res.statusCode === 200 && res.data.code === 200) { uni.showToast({ title: '密码修改成功', icon: 'success' }); uni.removeStorageSync('token'); uni.removeStorageSync('userInfo'); setTimeout(() => { uni.reLaunch({ url: '/pages/login/login' }); }, 1500); } else { uni.showToast({ title: '密码修改失败', icon: 'none' }); } } catch (error) { uni.hideLoading(); uni.showToast({ title: '网络请求失败', icon: 'none' }); console.error('请求失败:', error); } closeResetModal(); }; // 处理退出登录 const handleLogout = () => { uni.vibrateShort(); // 添加震动反馈 uni.showModal({ title: '确认退出', content: '您确定要退出当前账号吗?', confirmText: '退出登录', confirmColor: '#e74c3c', success: (res) => { if (res.confirm) { // 清除用户相关数据 uni.removeStorageSync('token'); uni.removeStorageSync('userInfo'); uni.removeStorageSync('savedUsername'); // 显示退出提示 uni.showToast({ title: '已退出登录', icon: 'success', duration: 1500 }); // 跳转到登录页 setTimeout(() => { uni.reLaunch({ url: '/pages/login/login' }); }, 1500); } } }); }; </script> <style lang="scss" scoped> .container { padding: 20rpx; background-color: #f5f7fa; min-height: 100vh; } /* 用户信息区域样式 */ .user-info-section { display: flex; align-items: center; padding: 30rpx; margin: 20rpx 0; background: linear-gradient(135deg, #3498db, #8e44ad); color: white; position: relative; overflow: hidden; border-radius: 24rpx; &::before { content: ''; position: absolute; top: -50%; right: -50%; width: 200%; height: 200%; background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 70%); pointer-events: none; } } .user-phone { display: block; font-size: 24rpx; // margin-top: 10rpx; color: rgba(255, 255, 255, 0.8); background: rgba(255, 255, 255, 0.2); padding: 4rpx 12rpx; border-radius: 20rpx; backdrop-filter: blur(10px); } .avatar-container { position: relative; width: 120rpx; height: 120rpx; margin-right: 30rpx; } .avatar-image { width: 100%; height: 100%; border-radius: 50%; background-color: #fff; } .avatar-frame { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 4rpx solid rgba(255, 255, 255, 0.3); border-radius: 50%; box-sizing: border-box; } .user-details { flex: 1; } .welcome-text { font-size: 28rpx; opacity: 0.9; } .username { display: block; font-size: 40rpx; font-weight: bold; margin: 8rpx 0; text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2); } .user-meta { display: flex; flex-wrap: wrap; gap: 15rpx; margin-top: 15rpx; } .user-role, .user-dept { font-size: 24rpx; background: rgba(255, 255, 255, 0.2); padding: 4rpx 12rpx; border-radius: 20rpx; backdrop-filter: blur(10px); } /* 功能板块区域 - 宽度优化 */ .function-section { background-color: #fff; border-radius: 24rpx; margin: 30rpx 40rpx; /* 左右边距增加 */ box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05); overflow: hidden; } .function-item { display: flex; align-items: center; justify-content: space-between; padding: 30rpx 35rpx; /* 增加左右内边距 */ position: relative; transition: all 0.2s ease; /* 点击效果 - 缩放 */ &:active { transform: scale(0.98); background-color: #f8f8f8; } /* 触摸效果 - 高亮 */ &.touch-active { background-color: #f0f9ff; transform: scale(0.98); .item-text { font-weight: bold; } } } .logout-item { .item-text { color: #e74c3c; } &.touch-active { background-color: #fff0f0; } } .item-left { display: flex; align-items: center; } .item-icon { width: 44rpx; height: 44rpx; margin-right: 20rpx; transition: transform 0.2s ease; .touch-active & { transform: scale(1.1); } } .item-text { font-size: 32rpx; color: #333; transition: all 0.2s ease; .touch-active & { transform: translateX(5px); } } .arrow-icon { font-size: 40rpx; color: #999; transform: scale(1.5, 1.5); transition: all 0.2s ease; .touch-active & { transform: scale(1.5, 1.5) translateX(-5px); } } .divider { height: 1rpx; background-color: #f0f0f0; margin: 0 35rpx; /* 与内边距一致 */ } /* 动画效果 */ @keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.05); } 100% { transform: scale(1); } } .pulse { animation: pulse 0.3s ease; } //重置密码 .modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; z-index: 9999; } .modal-content { background-color: #fff; padding: 30rpx; border-radius: 20rpx; width: 80%; box-sizing: border-box; display: flex; flex-direction: column; gap: 20rpx; } .modal-content input { border: 1px solid #ddd; padding: 20rpx; border-radius: 10rpx; } .modal-buttons { display: flex; justify-content: space-between; gap: 20rpx; } .modal-buttons button { flex: 1; } </style> 帮我优化一下这段代码,尤其是重置密码弹窗这块,点击重置密码出现弹窗后我在输入过程中每次点击弹窗都会弹一下,点击返回没有隐藏该弹窗并返回该界面,点击保存没有效果

<script setup> import { ref, onMounted, watch } from 'vue'; import { useRoute } from 'vue-router'; import { FilterMatchMode } from '@primevue/core/api'; import { useToast } from 'primevue/usetoast'; import { GetProduct, SyncShopGoods, StartOcrService, OneKeyUploadProduct, OneKeyDeleteProducts, DeleteProduct } from '@wails/go/main/App'; import { EventsOn } from '/wailsjs/runtime/runtime.js'; const route = useRoute(); const toast = useToast(); // 店铺ID const shopid = ref(route.query.shopid || ''); // 状态映射 const statusMap = { 0: { label: '在售', severity: 'success' }, 1: { label: '已删除', severity: 'danger' } }; // UI状态 const isLoading = ref(false); const page = ref(1); const pageSize = ref(20); const totalGoods = ref(0); const syncValue = ref(0); // 同步商品进度 const syncOcrValue = ref(0); // OCR识别进度 const switchValue = ref(false); const isSyncGoodsVisible = ref(false); const isSyncOcrVisible = ref(false); // 数据 const products = ref([]); const selectedProducts = ref(); // 对话框状态 const oneKeyUploadProductDialog = ref(false); const delSingleProductDialog = ref(false); const deleteProductsDialog = ref(false); const product = ref({}); // 过滤器 const filters = ref({ global: { value: null, matchMode: FilterMatchMode.CONTAINS } }); // 计算总记录数 function totalRecord() { return totalGoods.value; } // 格式化货币 function formatCurrency(value) { return value ? ¥${(value / 100).toFixed(2)} : ''; } // 加载商品数据 const loadProducts = async () => { if (!shopid.value) return; try { isLoading.value = true; const response = await GetProduct(shopid.value, pageSize.value, page.value); // 更新分页信息 totalGoods.value = response.total; // 更新商品数据 products.value = response.data.map(item => ({ id: item.product_id, code: item.product_id, name: item.name, image: item.img || 'product-placeholder.svg', price: item.market_price, inventoryStatus: statusMap[item.status_del]?.label || '未知', statusValue: item.status_del })); } catch (error) { toast.add({ severity: 'error', summary: '加载失败', detail: '无法获取商品数据', life: 3000 }); console.error("商品加载失败:", error); } finally { isLoading.value = false; } }; // 监听路由参数变化 watch(() => route.query, (newQuery) => { if (newQuery.shopid !== shopid.value) { shopid.value = newQuery.shopid || ''; page.value = 1; loadProducts(); } }, { immediate: true }); // 分页事件处理 const onPageChange = (event) => { page.value = event.page + 1; pageSize.value = event.rows; loadProducts(); }; onMounted(() => { if (shopid.value) { loadProducts(); } // 监听同步进度事件 EventsOn("notifySyncGoods", (result) => { syncValue.value = parseInt(result); console.log("商品同步进度:", result); // 进度达到100%后隐藏进度条 if (syncValue.value >= 100) { setTimeout(() => { isSyncGoodsVisible.value = false; toast.add({ severity: 'success', summary: '同步完成', detail: '商品同步成功', life: 3000 }); }, 1000); } }); // 监听OCR识别进度事件 EventsOn("notifySyncOcr", (result) => { syncOcrValue.value = parseInt(result); console.log("OCR识别进度:", result); // 进度达到100%后隐藏进度条 if (syncOcrValue.value >= 100) { setTimeout(() => { isSyncOcrVisible.value = false; toast.add({ severity: 'success', summary: '识别完成', detail: '图片识别成功', life: 3000 }); }, 1000); } }); }); // 1. 同步商品 async function startSyncGoods() { if (!shopid.value) { toast.add({ severity: 'warn', summary: '未选择店铺', detail: '请先选择店铺', life: 3000 }); return; } try { isSyncGoodsVisible.value = true; syncValue.value = 0; // 重置进度 const result = await SyncShopGoods(shopid.value); if (!result) { throw new Error('同步失败,无返回结果'); } // 刷新商品列表 setTimeout(() => { loadProducts(); }, 1500); } catch (error) { isSyncGoodsVisible.value = false; toast.add({ severity: 'error', summary: '同步失败', detail: error.message || '商品同步失败', life: 3000 }); console.error("商品同步失败:", error); } } // 2. OCR识别 async function startOcrService() { if (!shopid.value) { toast.add({ severity: 'warn', summary: '未选择店铺', detail: '请先选择店铺', life: 3000 }); return; } try { isSyncOcrVisible.value = true; syncOcrValue.value = 0; // 重置进度 const result = await StartOcrService(shopid.value); if (!result) { throw new Error('识别失败,无返回结果'); } } catch (error) { isSyncOcrVisible.value = false; toast.add({ severity: 'error', summary: '识别失败', detail: error.message || '图片识别失败', life: 3000 }); console.error("OCR识别失败:", error); } } // 3. 一键上架 async function oneKeyUploadProduct() { if (!shopid.value) { toast.add({ severity: 'warn', summary: '未选择店铺', detail: '请先选择店铺', life: 3000 }); return; } try { const success = await OneKeyUploadProduct(shopid.value); if (success) { toast.add({ severity: 'success', summary: '上架成功', detail: '商品已成功上架', life: 3000 }); // 刷新列表 loadProducts(); } else { throw new Error('上架失败'); } } catch (error) { toast.add({ severity: 'error', summary: '上架失败', detail: error.message || '商品上架失败', life: 3000 }); console.error("一键上架失败:", error); } finally { oneKeyUploadProductDialog.value = false; } } // 4. 删除单个商品 async function deleteProduct() { if (!product.value.id) return; try { const success = await DeleteProduct(shopid.value, product.value.id); if (success) { toast.add({ severity: 'success', summary: '删除成功', detail: 商品 "${product.value.name}" 已删除, life: 3000 }); // 刷新列表 loadProducts(); } else { throw new Error('删除失败'); } } catch (error) { toast.add({ severity: 'error', summary: '删除失败', detail: error.message || 删除商品 "${product.value.name}" 失败, life: 3000 }); console.error("删除商品失败:", error); } finally { delSingleProductDialog.value = false; } } // 5. 一键删除商品 async function oneKeyDeleteProducts() { if (!shopid.value) { toast.add({ severity: 'warn', summary: '未选择店铺', detail: '请先选择店铺', life: 3000 }); return; } try { const success = await OneKeyDeleteProducts(shopid.value); if (success) { toast.add({ severity: 'success', summary: '删除成功', detail: '所有商品已成功删除', life: 3000 }); // 刷新列表 loadProducts(); } else { throw new Error('删除失败'); } } catch (error) { toast.add({ severity: 'error', summary: '删除失败', detail: error.message || '删除所有商品失败', life: 3000 }); console.error("一键删除失败:", error); } finally { deleteProductsDialog.value = false; } } // 询问删除商品 function askDeleteProduct(prod) { product.value = prod; delSingleProductDialog.value = true; } // 询问一键上架 function askOneKeyUpload() { oneKeyUploadProductDialog.value = true; } // 询问一键删除 function askOneKeyDeleteProducts() { deleteProductsDialog.value = true; } </script> <template> 正在加载商品数据... <Toolbar class="mb-6"> <template #start> <Button label="同步商品" icon="pi pi-refresh" severity="secondary" class="mr-2" @click="startSyncGoods" /> <Button label="图片识别" icon="pi pi-check" severity="secondary" @click="startOcrService" /> </template> <template #end> <Button label="一键上架" icon="pi pi-upload" severity="secondary" @click="askOneKeyUpload" /> <Button label="一键删除" icon="pi pi-trash" severity="secondary" @click="askOneKeyDeleteProducts" /> </template> </Toolbar> <DataTable v-model:selection="selectedProducts" :value="products" :lazy="true" dataKey="id" :rowHover="true" :paginator="true" :rows="pageSize" :totalRecords="totalRecord()" :filters="filters" paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown" :rowsPerPageOptions="[10, 20, 50]" currentPageReportTemplate="显示第 {first} 到 {last} 条,共 {totalRecords} 条商品" @page="onPageChange" > <template #header> 商品列表 - {{ shopid }} - 商品总数 {{ totalGoods }} <IconField> <InputIcon> </InputIcon> <InputText v-model="filters['global'].value" placeholder="搜索商品..." /> </IconField> </template> <Column selectionMode="multiple" headerStyle="width: 3.5rem" :exportable="false"></Column> <Column header="商品图片" bodyStyle="text-align: center"> <template #body="slotProps"> </template> </Column> <Column field="code" header="商品ID" sortable style="min-width: 12rem"></Column> <Column field="name" header="商品名称" sortable style="min-width: 16rem"></Column> <Column field="price" header="价格" sortable style="min-width: 8rem"> <template #body="slotProps"> {{ formatCurrency(slotProps.data.price) }} </template> </Column> <Column field="inventoryStatus" header="状态" sortable style="min-width: 12rem"> <template #body="slotProps"> <Tag :value="slotProps.data.inventoryStatus" :severity="statusMap[slotProps.data.statusValue]?.severity || 'info'" /> </template> </Column> <Column header="操作" style="min-width: 9rem"> <template #body="slotProps"> <Button icon="pi pi-pencil" outlined rounded severity="secondary" class="action-btn" size="small" /> <Button icon="pi pi-trash" outlined rounded severity="danger" class="action-btn" size="small" @click="askDeleteProduct(slotProps.data)" /> <ToggleSwitch v-model="switchValue" /> </template> </Column> </DataTable> 当前商品同步进度 已完成 {{ syncValue }}% 当前图片识别进度 已完成 {{ syncOcrValue }}% <Dialog v-model:visible="oneKeyUploadProductDialog" :style="{ width: '450px' }" header="确认操作" :modal="true"> 是否确认执行一键上架商品任务? <template #footer> <Button label="取消" icon="pi pi-times" text @click="oneKeyUploadProductDialog = false" /> <Button label="确认上架" icon="pi pi-check" @click="oneKeyUploadProduct" /> </template> </Dialog> <Dialog v-model:visible="delSingleProductDialog" :style="{ width: '450px' }" header="确认删除" :modal="true"> 是否确认删除商品 {{ product.name }}? <template #footer> <Button label="取消" icon="pi pi-times" text @click="delSingleProductDialog = false" /> <Button label="确认删除" icon="pi pi-trash" severity="danger" @click="deleteProduct" /> </template> </Dialog> <Dialog v-model:visible="deleteProductsDialog" :style="{ width: '450px' }" header="确认操作" :modal="true"> 是否确认删除所有商品? <template #footer> <Button label="取消" icon="pi pi-times" text @click="deleteProductsDialog = false" /> <Button label="确认删除" icon="pi pi-trash" severity="danger" @click="oneKeyDeleteProducts" /> </template> </Dialog> </template> <style scoped> /* 图片样式 */ .fixed-image-container { display: flex; justify-content: center; align-items: center; width: 70px; height: 70px; padding: 4px; background-color: #f8f9fa; border-radius: 4px; } .fixed-image { width: 64px; height: 64px; object-fit: contain; border-radius: 3px; } /* 操作按钮 */ .action-buttons { display: flex; justify-content: center; gap: 0.5rem; } .action-btn { width: 2.2rem; height: 2.2rem; display: flex; align-items: center; justify-content: center; } /* 表头样式优化 */ :deep(.p-datatable-thead th) { background-color: #f8f9fa; font-weight: 500; } :deep(.p-datatable-tbody td) { padding: 0.5rem 1rem; } /* 响应式调整 */ @media (max-width: 1200px) { :deep(.p-datatable) { font-size: 0.9rem; } .fixed-image-container { width: 64px; height: 64px; } .fixed-image { width: 58px; height: 58px; } } @media (max-width: 992px) { :deep(.p-datatable) { font-size: 0.85rem; } .card { overflow-x: auto; } } /* 加载指示器 */ .loading-overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(255, 255, 255, 0.8); display: flex; flex-direction: column; justify-content: center; align-items: center; z-index: 100; } .loading-overlay p { margin-top: 1rem; color: var(--primary-color); } /* 进度卡片样式增强 */ .card { margin-top: 1.5rem; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); } </style> 这段代码是VUE的前端代码,请帮我修改notifySyncGoods的代码,和go后端的代码配套

最新推荐

recommend-type

spnavcfg-0.3.1-1.el8.tar.gz

# 适用操作系统:Centos8 #Step1、解压 tar -zxvf xxx.el8.tar.gz #Step2、进入解压后的目录,执行安装 sudo rpm -ivh *.rpm
recommend-type

一个涵盖患者、疾病与药物关系的权威医疗 RDF 数据集

资源下载链接为: https://pan.quark.cn/s/7d4480e88650 一个涵盖患者、疾病与药物关系的权威医疗 RDF 数据集(最新、最全版本!打开链接下载即可用!)
recommend-type

【汽车电子电气】整车线束设计优化:从3D建模到降本增效的全流程解析及挑战应对了汽车电子电气

内容概要:本文详细阐述了汽车线束设计在整车电气系统中的重要性和设计流程。线束作为整车电子电气架构的物理实现,承担着连接ECU、传感器、执行器等模块的功能,贯穿车辆的动力、安全、智能驾驶等多个领域。文章从线束设计的核心作用、设计流程、核心挑战及降本措施四个方面展开。核心作用包括功能实现、安全保障、性能优化和成本控制;设计流程涵盖3D线束设计、电气原理设计和2D线束设计三个阶段;核心挑战涉及复杂系统集成、轻量化与成本平衡及试装验证;降本措施则通过国产化替代和设计优化来实现。 适合人群:从事汽车电子电气系统设计、开发及制造的工程师和技术人员,尤其是具备一定工作经验的研发人员。 使用场景及目标:①了解线束设计在整个汽车电气系统中的作用及其设计流程;②掌握线束设计的关键技术和挑战,如复杂系统集成、轻量化设计等;③学习如何通过国产化替代和设计优化降低成本并提高产品质量。 其他说明:本文不仅提供了详细的线束设计理论知识,还结合实际案例介绍了具体的实施路径和方法,旨在帮助读者全面理解和掌握线束设计的相关技术。阅读过程中,建议结合实际工程经验,重点关注设计流程中的关键技术点和降本措施的具体实施方案。
recommend-type

2007 年编写的基于 TcpClient 的 HTTP 请求库曾用于数据采集多年

资源下载链接为: https://pan.quark.cn/s/bd476d5a4ba6 2007 年编写的基于 TcpClient 的 HTTP 请求库曾用于数据采集多年(最新、最全版本!打开链接下载即可用!)
recommend-type

Ext4压缩与解压工具:从解包到重新打包全过程

标题和描述中提到的知识点详细说明如下: ### ext4文件系统 ext4(第四扩展文件系统)是Linux操作系统中的一个日志文件系统,它是在ext3基础上发展起来的。ext4提供了一系列改进,包括更大的文件系统和文件大小、更快的性能、更强的可靠性等。ext4文件系统广泛应用于Linux服务器和嵌入式设备中,特别是在Android操作系统中,它通常用于存储系统数据。 ### 解压工具 描述中提到了三个主要工具:make_ext4fs、simg2img和kusering.sh。这些工具主要用于Android设备的系统镜像文件的解压缩和重新打包操作。具体如下: 1. **make_ext4fs** 这是一个Android平台上的命令行工具,用于创建一个新的ext4文件系统镜像文件。这个工具通常用于打包修改过的文件系统或创建一个新的系统分区。其重要参数包括: - `-s`:创建一个sparse(稀疏)文件系统镜像。 - `-l`:设置文件系统的大小限制。 - `-a`:指定默认挂载点。 - `system.img`:输出的镜像文件名称。 - `tmp`:指定要打包的目录。 2. **simg2img** 该工具用于将Android专用的sparse格式镜像文件转换为普通的ext4文件系统镜像文件。这对于解包系统镜像文件和查看其中内容非常有用。其基本用法是: ```bash simg2img system.img system.img.ext4 ``` 这样就可以将一个sparse格式的system.img转换成ext4格式的system.img.ext4,后者能够被挂载到Linux系统中进行查看和修改。 3. **kusering.sh** 这个脚本可能是用于修改用户ID(UID)和组ID(GID)的脚本。在Android系统中,对系统分区进行操作时可能需要特殊的权限设置,而kusering.sh脚本正是用于此目的。但由于描述中没有具体的使用命令,无法给出具体用法。 ### 操作方法 描述中提供了一系列步骤来解压和修改system.img文件,并重新打包。下面详细介绍这些步骤: 1. **解压system.img为ext4格式**: 使用simg2img工具将sparse格式的system.img转换为ext4文件系统格式,命令如下: ```bash simg2img system.img system.img.ext4 ``` 这一步是必要的,因为原始的system.img通常是一个sparse格式,不易于直接修改。 2. **创建挂载目录**: 创建一个临时目录用于挂载ext4文件系统,命令如下: ```bash mkdir tmp ``` tmp目录将作为挂载点,用于将image文件挂载到Linux文件系统中。 3. **挂载ext4文件系统**: 将上一步得到的ext4文件系统挂载到之前创建的tmp目录,命令如下: ```bash sudo mount -t ext4 -o loop system.img.ext4 tmp ``` 使用`-t ext4`指定文件系统类型为ext4,`-o loop`创建一个循环设备,使得普通文件也能像块设备一样被访问。 4. **修改文件系统内容**: 进入tmp目录后,用户可以按照需求对文件系统内的内容进行修改。这一步通常需要root权限。 5. **重新打包文件系统**: 修改完成后,使用make_ext4fs工具将tmp目录重新打包为一个新的sparse格式的system.img文件,命令如下: ```bash sudo make_ext4fs -s -l 239M -a system system.img tmp ``` 这里`-s`表示创建sparse格式的文件系统,`-l 239M`指定文件系统的大小,`-a system`设置默认挂载点,`system.img`是打包后的输出文件名,`tmp`是作为文件系统源的目录。 ### 总结 以上步骤涵盖了如何使用simg2img、make_ext4fs工具进行Android设备的系统镜像的解压、挂载、修改和重新打包操作。需要注意的是,进行这些操作需要相应的Linux环境知识,以及对Android系统文件结构有一定的了解。由于这些操作涉及对系统分区的修改,建议在操作前备份原始的system.img文件,以防万一出现问题能够恢复。同时,由于这些操作可能会影响设备的正常使用,建议仅在允许的范围内进行。
recommend-type

【数据转换的基石】:技术分析,Excel到Oracle建表语句的自动化

# 摘要 本文主要探讨了数据转换项目中的关键步骤和自动化方法。首先,文章概述了数据源的分析与处理,重点在于理解Excel数据结构和进行数据清洗预处理。随后,文章介绍了利用VBA和Python语言实现Excel数据提取的自动化技术。接着,文章转向Oracle数据库建表语句的自动生成,涵盖了数据库基础、制定建表规则和模板,以及脚本编写的自动化过程。第五章整合了数据提取与建表流程,并通过案例实践展示了自动化数据转换项目的应用。
recommend-type

前端vue2 使用高德地图api

<think>好的,我现在需要解决用户的问题:如何在Vue2项目中集成和使用高德地图API。用户希望得到一个示例教程,并且根据提供的引用内容,我需要结合这些资料来构建答案。首先,我需要回顾用户提供的引用内容,看看有哪些关键点可以利用。 引用1、2、3、4、5都是关于Vue集成高德地图的,特别是引用4和5提到了Vue2和Vue3的具体集成步骤。用户问的是Vue2,所以需要重点参考引用4和5中的Vue2相关内容。不过引用5是Vue3的教程,可能部分步骤类似,但需要注意版本差异。 首先,步骤一般包括:注册高德开发者账号、获取API key、在Vue项目中引入高德地图的JS API、创建地图容器、
recommend-type

易语言源码:希冀程序保护专家深入解析

易语言是一种简单易学的编程语言,主要面向中文编程社区,其特色在于使用中文作为编程关键字。标题中提到的“希冀程序保护专家”可能是易语言编写的程序,旨在提供给易语言开发者使用的源码保护工具。 ### 易语言基础知识点 - **易语言的定义与特点:** 易语言是一种汉语编程语言,它的关键词和语法结构使用中文书写,极大降低了编程的学习难度,尤其适合编程初学者和没有英文基础的用户。 - **易语言的开发环境:** 易语言提供了一套集成开发环境(IDE),包括代码编辑器、调试器等,支持快速开发Windows应用程序。 - **易语言的应用范围:** 易语言广泛应用于桌面应用开发,如文本处理、游戏开发、系统管理工具等领域。 ### 程序保护的必要性 - **软件盗版与破解:** 在软件行业中,未经许可的复制和使用是一个普遍的问题。开发者需要采取措施保护其软件不被盗版和非法复制。 - **知识产权保护:** 程序保护是维护知识产权的一种方式,它帮助开发者保护其劳动成果不被他人侵权。 - **商业利益保护:** 软件如果被轻易破解,可能会导致开发者的经济损失。通过有效的程序保护,可以确保软件的合法销售和使用,维护开发者的商业利益。 ### 程序保护技术 - **代码混淆(Obfuscation):** 通过改变代码的结构和变量名来使程序难以阅读和分析,增加逆向工程的难度。 - **加壳(Packers):** 将可执行文件压缩,加密,使得程序在运行时首先执行一个解密或解压缩的过程,增加了程序被非法篡改的难度。 - **注册验证机制:** 通过软件注册码或激活机制,验证用户是否有权使用软件,限制非授权用户的使用。 - **许可证授权管理:** 程序运行时与远程服务器交互验证用户许可证,确保只有合法的用户可以使用软件。 ### 易语言的程序保护方案 - **代码混淆工具:** 易语言提供专门的混淆工具,开发者可以对源码进行混淆处理,提高代码安全性。 - **加密算法:** 易语言支持内置的加密解密函数库,开发者可以利用这些库函数实现加密算法,保护程序不被轻易破解。 - **模块化编程:** 易语言支持模块化开发,可以将核心功能封装在DLL模块中,通过主程序调用,增强保护效果。 - **第三方保护软件:** 如描述中的“希冀程序保护专家”,这样的工具往往集成了多种程序保护技术,如加壳、注册机生成、许可证管理等,提供一站式的服务。 ### 结论 易语言源码“希冀程序保护专家”面向的用户是使用易语言进行软件开发的程序员。这款工具能够帮助他们保护自己的易语言源码和编译后的可执行程序,防止源码被非法窃取,维护个人或公司的权益。通过实现各种程序保护技术,它能够提升软件的安全性,减少潜在的盗版风险,并且能够通过多种方式确保软件的授权使用,维护软件的市场价值。对于易语言开发者而言,这类保护工具是其软件能够获得市场成功的重要保障之一。
recommend-type

【数据迁移流程优化】:一步到位的Excel到Oracle建表语句自动化转换

# 摘要 本文旨在优化数据迁移流程,通过深入分析Excel与Oracle数据库的结构特点和数据处理技术,开发出一套自动化工具来实现高效的数据转换。文章首先概述了数据迁移流程,并对Excel数据结构和提取技术进行了详细分析。接着,介绍了Oracle数据库的基础知识,包括建表语句和数据库设计原则。在此基础上,文章详细描述了自动化转换工具的开发过程,包括
recommend-type

二维视觉缺陷检测技术背景

<think>好的,我需要回答用户关于二维视觉缺陷检测技术的背景知识,包括技术原理和应用背景。首先,我要确保遵循系统级指令,比如使用正确的LaTeX格式,生成相关问题,并添加引用标识。 首先,我得回忆一下用户提供的参考引用内容。引用[1]提到机器视觉检测系统的组成和特点,还有表面缺陷检测的问题和发展趋势。引用[2]则比较了2D和3D检测的优劣,指出2D在颜色和纹理检测上的优势,但在三维缺陷上的不足。 接下来,我需要组织回答的结构。用户要求结构清晰,逐步解决问题,所以应该分点说明技术原理和应用背景。技术原理部分需要包括图像获取、预处理、特征提取、分类识别等步骤。应用背景则需要说明传统方法的不