【原生小程序使用webview添加横向滚动】

<view class="container">
  <web-view wx:if="{{showWebview}}" src="{{templateUrl}}" bindload="onWebViewLoad">
    <cover-view class="floating-btn-container">
      <cover-view class="btn-content">
        <cover-view class="btn" bindtap="bindFile">
          <cover-image class="btn-icon" src="//static.solarbe.com/talentNetwork/miniProgram/resume_preview/download_icon.png" />
          <cover-view class="btn-text">保存至附件</cover-view>
          <cover-view class="divider"></cover-view>
        </cover-view>
        <cover-view class="btn" bindtap="handleBack">
          <cover-image class="btn-icon" src="//static.solarbe.com/talentNetwork/miniProgram/resume_preview/detail_icon.png" />
          <cover-view class="btn-text">更换样式</cover-view>
          <cover-view class="divider"></cover-view>
        </cover-view>
        <cover-view class="btn" bindtap="uploadFile">
          <cover-image class="btn-icon" src="//static.solarbe.com/talentNetwork/miniProgram/resume_preview/upload_icon.png" />
          <cover-view class="btn-text">上传简历</cover-view>
          <cover-view class="divider"></cover-view>
        </cover-view>
        <cover-view class="btn btn-last" bindtap="goToEdit">
          <cover-image class="btn-icon" src="//static.solarbe.com/talentNetwork/miniProgram/resume_preview/edit_icon.png" />
          <cover-view class="btn-text">编辑</cover-view>
        </cover-view>
      </cover-view>
    </cover-view>
  </web-view>

  <cover-view class="tpl-content" wx:if="{{show}}">
    <cover-view class="menu-overlay">
      <cover-view class="menu-content" style="transform: translateX({{translateX}}px)" bind:touchstart="onTouchStart" bind:touchmove="onTouchMove">
        <block wx:if="{{isIOS}}">
          <cover-view wx:for="{{pdfList}}" wx:key="{{item.id}}" class="items" data-id="{{item.id}}" data-imgUrl="{{item.img_src}}"  id="item-{{item.id}}" data-title="{{item.name}}" bind:tap="choose">
            <cover-image class="items-image" src="{{item.img_src}}" mode="" />
            <cover-view class="items-text">{{item.name}}</cover-view>
            <cover-image wx:if="{{item.id==id}}" class="choose-image" src="//static.solarbe.com/talentNetwork/miniProgram/resume_preview/choose-icon.png" mode="" />
            <cover-image wx:else class="choose-image" src="//static.solarbe.com/talentNetwork/miniProgram/resume_preview/no-choose-icon.png" mode="" />
          </cover-view>
        </block>
        <block wx:else>
          <cover-view wx:for="{{pdfList}}" wx:key="{{item.id}}" id="item-{{item.id}}" class="items {{item.id==id?'selected-box': 'un-selected-box'}}" data-id="{{item.id}}" data-imgUrl="{{item.img_src}}" data-title="{{item.name}}" bind:tap="choose">
            <cover-image class="items-image" src="{{item.img_src}}" mode="" />
            <cover-view class="items-text">{{item.name}}</cover-view>
          </cover-view>
        </block>
      </cover-view>
    </cover-view>
    <cover-view class="tpl-btn" bind:tap="chooseClose">使用此模版</cover-view>
  </cover-view>
</view>
.mask {
  position: fixed;
  top: 0;
  left: 0;
  background: rgba(0, 0, 0, .3);
  width: 100%;
  height: 100%;
}

/* 容器样式 */
.container {
  height: 100vh;
  background: #f3f3f3;
}

/* 悬浮按钮容器 */
.floating-btn-container {
  position: fixed;
  bottom: 0;
  left: 0;
  width: 100%;
  z-index: 9999;
  background: #f3f3f3;
  padding: 30rpx 0;
}

/* 按钮内容区域 */
.btn-content {
  display: flex;
  justify-content: space-around;
  /* 横向均匀分布 */
  align-items: center;
  width: 90%;
  height: 120rpx;
  /* 增加高度容纳图标+文字 */
  margin: 0 auto;
  background: #fff;
  border-radius: 56rpx;
  box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.1);
  padding: 0 40rpx;
  box-sizing: border-box;
}


.btn {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 25%;
}

.btn-last {
  width: 20%;
}

/* 图标样式 */
.btn-icon {
  width: 48rpx;
  height: 48rpx;
  margin-bottom: 8rpx;
  /* 图标与文字间距 */
}

/* 文字样式 */
.btn-text {
  font-size: 24rpx;
  color: #333;
  white-space: nowrap;
  /* 防止文字换行 */
}

.divider {
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 1rpx;
  height: 60%;
  /* 控制竖线高度为按钮区域的60% */
  background: #e5e5e5;
}

.items {
  flex-shrink: 0;
  /* 禁止收缩 */
  display: flex;
  flex-direction: column;
  position: relative;
  width: 240rpx;
  /* 与图片宽度一致 */
  margin-right: 10rpx;
}

.choose-image {
  width: 32rpx;
  height: 34rpx;
  position: absolute;
  z-index: 10002;
  right: 20rpx;
  top: 20rpx;
}

.selected-box {
  border: 2px solid #1787FB !important;
  border-radius: 8rpx;
}

.un-selected-box {
  border: 0px solid #1787FB !important;
  border-radius: 8rpx;
}

.ios-style {
  border: none;
  border-radius: 0;
}

.items-image {
  width: 240rpx;
  height: 360rpx;
}

.items-text {
  font-size: 24rpx;
  color: #333333;
  line-height: 34rpx;
  text-align: center;
}

.tpl-content {
  position: fixed;
  left: 0;
  bottom: 0;
  height: 610rpx;
  background: rgba(205, 231, 255, 1);
  z-index: 10000;
  width: 100%;
}

.tpl-btn {
  height: 88rpx;
  background: #1787FB;
  border-radius: 8rpx;
  width: 90%;
  font-weight: 500;
  font-size: 28rpx;
  color: #FFFFFF;
  line-height: 88rpx;
  text-align: center;
  position: fixed;
  bottom: 60rpx;
  left: 50%;
  transform: translate(-50%, 0);
  width: 90%;
  z-index: 10001;
}

/* 覆盖层容器 */
.menu-overlay {
  position: absolute;
  top: 0;
  visibility: visible !important;
  overflow: scroll;
  left: 0;
  height: 450rpx;
  padding: 30rpx 30rpx 0rpx 30rpx;
  z-index: 10001;
}

/* 菜单内容容器 */
.menu-content {
  display: flex;
  flex-direction: row;
  width: 100%;
  height: 400rpx;
  padding-bottom: 10rpx;
  transition: transform 0.3s;
  will-change: transform; /* 提前声明动画属性 */
  -webkit-overflow-scrolling: touch; /* 启用 iOS 弹性滚动 */
  transform: translateZ(0); /* 强制开启 GPU 加速 */
  /* 平滑动画 */
}
var $ = getApp();
Page({

  /**
   * 页面的初始数据
   */
  data: {
    showWebview: true,
    resumeUrl: '',
    title: '',
    id: '0', //模版id
    resumeId: 0, //简历id
    loading: !1,
    pdfList: [],
    templateUrl: '',
    show: false,
    translateX: 0,
    maxScroll: 0,
    startX: 0,
    currentIndex: 0,
    lastUpdate: 0,
    isIOS: false,
    currentX: 0
  },
  onTouchStart(e) {
    this.setData({
      startX: this.data.isIOS ? e.touches[0].clientX : e.touches[0].pageX,
      currentX: this.data.translateX
    });
  },
  onTouchMove(e) {
    const deltaX = (this.data.isIOS ? e.touches[0].clientX : e.touches[0].pageX) - this.data.startX;
    let newTranslateX = this.data.currentX + deltaX;
    // 限制滚动范围(示例值需根据内容宽度调整)
    const maxScroll = -(140 * this.data.pdfList.length - 375)
    newTranslateX = Math.min(0, Math.max(maxScroll, newTranslateX))
    // 节流更新频率(每 16ms 更新一次)
    if (Date.now() - this.data.lastUpdate > 16) {
      this.setData({
        translateX: newTranslateX,
        lastUpdate: Date.now()
      })
    }
  },
  onLoad(options) {
    const systemInfo = wx.getSystemInfoSync()
    const isIOS = systemInfo.system.includes('iOS')
    this.setData({
      isIOS: isIOS
    })
    this.getMakePDF()
    if (options?.id) {
      this.setData({
        id: options.id
      })
    }
    if (options?.resumeId) {
      this.setData({
        resumeId: options.resumeId
      })
    }
    if (options?.title) {
      this.setData({
        title: options.title
      })
    }
    this.setData({
      templateUrl: url
    })
    this.getResumePdfUrl()
    // 动态计算高度(示例:屏幕高度 - 按钮区域高度)
    const buttonHeight = 60; // 根据实际按钮高度调整
    this.setData({
      webviewHeight: systemInfo.windowHeight - buttonHeight
    });
  },
  centerSelectedItem() {
    wx.createSelectorQuery()
      .select(`#item-${this.data.id}`)
      .boundingClientRect(res => {
        if (res) {
          const screenWidth = wx.getSystemInfoSync().screenWidth
          const translateX = screenWidth / 2 - res.left - res.width / 2
          this.setData({
            translateX
          })
        }
      }).exec()
  },
  getMakePDF(type) {
    $.http('home/resume_tpl/getTplList', (r) => {
      this.setData({
        pdfList: r.data
      }, () => {
        setTimeout(() => {
          // this.centerSelectedItem()
        }, 200)
      })
      if (type == 'init') {
        this.setData({
          show: true
        })
      }
    })
  },
  onWebViewLoad() {
    // 再次强制设置标题
    wx.setNavigationBarTitle({
      title: this.data.title + "简历模版-预览"
    });
  },
  getResumePdfUrl() {
    let params = {
        uid: $.visitor.uid,
        tpl_id: this.data.id
      },
      params2 = {
        id: this.data.resumeId,
        is_export: 0
      }
    let url = this.data.id == -1 ? 'home/resume/exportPdfByPhp' : 'home/poster_two/makePdf'
    if (this.data.id == -1) {
      wx.showLoading({
        title: '加载中...',
      })
    }
    $.http(url, this.data.id == -1 ? params2 : params, (r) => {
      if (this.data.id == -1) {
        wx.downloadFile({
          url: r.data.url, // 文件的本身url
          filePath: wx.env.USER_DATA_PATH + "/" + this.data.title, // 本地自定义的文件名
          success: function (res) {
            let filePath = res.filePath; // 微信临时文件路径(这里要使用自定义的名字文件名,否则打开的文件名是乱码)
            wx.openDocument({
              filePath: filePath,
              fileType: 'pdf',
              showMenu: true, // 是否显示右上角菜单按钮 默认为false(看自身需求,可要可不要。后期涉及到右上角分享功能)
              success: function () {
                wx.hideLoading()
              },
              fail: function (e) {
                wx.hideLoading()
                $.msg(0, '网络繁忙,请稍后重试~')
              }
            });
          },
          fail: function (r) {
            wx.hideLoading()
            $.msg(0, '网络繁忙,请稍后重试~')
          }
        });
        return
      }
      this.setData({
        resumeUrl: r.data
      })
    }, 'post')
  },
  choose(e) {
    let id = e.currentTarget.dataset.id,
      title = e.currentTarget.dataset.title
    wx.setNavigationBarTitle({
      title: title + "简历模版-预览"
    })

    this.setData({
      id,
      title
    }, () => {
      this.setData({})
    })
    console.log(this.data.id)
  },
  chooseClose() {
    this.setData({
      showWebview: false
    }, () => {
      setTimeout(() => {
        this.setData({
          show: false,
          showWebview: true,
          templateUrl: `https://www.hsolar.com/member/resume/template/index${this.data.id}?id=${$.visitor.uid}`
        })
      }, 200)
      this.getResumePdfUrl()
    })
  },
  // 换样式
  handleBack() {
    if (this.data.pdfList.length) {
      this.setData({
        show: true
      })
    } else {
      this.getMakePDF('init')
    }
  },
  // 跳转
  goToEdit() {
    wx.redirectTo({
      url: '/pages/personal/resume_edit/resume_edit',
    })
  },
  // 上传附件
  uploadFile() {
    wx.navigateTo({
      url: '/subPackages/pages/personal/resume_accessory/resume_accessory',
    })
  },
  detail(f) {
    $.http('personal/resume_two/detail', function (r) {
      let d = r.data
      f && f(d)
    })
  },
  bindFile() {
    wx.showLoading({
      title: '生成中',
    })
    this.setData({
      loading: !0
    })
    let that = this
    wx.downloadFile({
      url: this.data.resumeUrl, // 文件的本身url
      filePath: wx.env.USER_DATA_PATH + "/" + this.data.title, // 本地自定义的文件名
      success: function (res) {
        let filePath = res.filePath; // 微信临时文件路径(这里要使用自定义的名字文件名,否则打开的文件名是乱码)
        wx.openDocument({
          filePath: filePath,
          fileType: 'pdf',
          showMenu: true, // 是否显示右上角菜单按钮 默认为false(看自身需求,可要可不要。后期涉及到右上角分享功能)
          success: function () {
            wx.hideLoading()
            that.setData({
              loading: !1
            })
            $.msg(0, '生成成功')
          },
          fail: function (e) {
            wx.hideLoading()
            that.setData({
              loading: !1
            })
            $.msg(0, '网络繁忙,请稍后重试~')
          }
        });
      },
      fail: function (r) {
        wx.hideLoading()
        that.setData({
          loading: !1
        })
        $.msg(0, '网络繁忙,请稍后重试~')
      }
    });
  },
  /**
   * 生命周期函数--监听页面显示
   */
  onShow() {
    // wx.onAppRoute((route) => {
    //   const pages = getCurrentPages();
    //   console.log(route, pages, '===')
    //   if (pages.length > 1) {
    //     // 覆盖左上角返回按钮行为
    //     // wx.enableAlertBeforeUnload({
    //     //   message: '是否直接返回上一页?',
    //     //   success: (res) => {
    //     //     console.log(res,999)
    //     //     if (res.errMsg=='enableAlertBeforeUnload:ok') {
    //     //       wx.redirectTo({
    //     //         url: '/subPackagesC/pages/resumeTetemplate/resumeTetemplate',
    //     //       })
    //     //     }
    //     //   },
    //     // });
    //   }
    // });
  }
})
### 如何在 UniApp 中禁用页面滑动返回功能 #### Android 物理返回键全局禁止 为了实现安卓设备上的物理返回键全局禁止,在应用初始化时可以使用 `app.mixin` 方法来监听并处理返回按键事件: ```javascript // main.js 或者其他入口文件 import Vue from 'vue'; Vue.mixin({ methods: { onBackPress(event) { // 阻止默认行为,即不执行返回操作 event.preventDefault(); return true; } }, created() { plus.key.addEventListener('backbutton', this.onBackPress, false); }, destroyed() { plus.key.removeEventListener('backbutton'); } }); ``` 此代码片段展示了如何利用 `app.mixin` 来拦截所有的返回按钮点击事件,并通过 `return true` 明确告知系统已处理该事件而不触发默认的行为[^1]。 #### 禁止单页面侧滑返回 对于单个页面而言,如果希望关闭其侧滑手势返回的功能,则可以通过设置当前页面 Webview 的样式属性 `popGesture` 实现这一目的: ```javascript // 页面生命周期函数内 export default { onLoad() { const page = this.$mp.page.$getAppWebview(); if (page) { page.setStyle({ popGesture: 'none' }); } } } ``` 上述 JavaScript 代码适用于 HBuilderX 编译环境下的原生插件调用方式。这段脚本会在页面加载完成后立即生效,从而达到禁用手势的效果[^2]。 #### 抖音小程序特殊处理 针对抖音平台的小程序开发场景下,当涉及到自定义右滑逻辑而不想让其触发页面切换时,可采用特定 API 进行干预: ```html <template> <view class="container" @touchstart="handleTouchStart"> <!-- 组件内容 --> </view> </template> <script> export default { methods: { handleTouchStart() { tt.setSwipeBackMode(0); // 关闭右侧边缘滑动手势返回上一页模式 } } }; </script> ``` 这里展示了一个简单的模板结构以及对应的交互逻辑,其中 `tt.setSwipeBackMode()` 是由字节跳动官方提供的用于控制是否允许用户通过屏幕边缘横向拖拽的方式退出当前界面的方法[^3]。 #### 使用 Scroll-View 控制滚动行为 另外一种情况是在某些情况下需要完全锁定整个视图区域内的任何方向上的触控移动,这时可以在 HTML 结构中包裹一层 `<scroll-view>` 并调整相应 CSS 属性以达成目标: ```html <template> <scroll-view scroll-x="false" scroll-y="false" style="position: absolute; bottom: 0px; top: 0px;"> <!-- 子元素 --> </scroll-view> </template> ``` 这种方法能够有效防止因误碰或其他原因造成的不必要的滚动现象发生,同时也间接实现了对页面回退动作的有效抑制[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值