vue3 H5项目列表锚点定位问题

本文介绍了在Vue3环境下,处理H5项目中从详情页面返回列表页时如何保持滚动位置,并解决列表状态改变不能使用缓存的问题。通过监听滚动条事件和在DOM渲染后赋值实现滚动位置恢复,同时详细展示了项目的代码实现,包括搜索、筛选、下拉刷新等功能。
摘要由CSDN通过智能技术生成

最近在做VUE3的 H5项目遇到的问题是详情返回列表时依旧定位到之前浏览的位置,我第一时间想到的就是keepalive,但是我们项目里详情是可以操作的,列表的状态会改变。所以不能使用缓存。我用滚动条解决了问题。

在你点击详情的时候先获取当前点击滚动条的位置 存储起来。

在这里插入图片描述
给滚动条赋值的时候要注意一定要在DOM渲染完成以后再赋值,不然赋值会失败,这里我用了 nextTick
还得监听滚动条事件 移动了滚动条的话就移除缓存里存储的,会有问题
在这里插入图片描述

记得引入
在这里插入图片描述
在渲染完成后调用赋值方法,下拉刷新的时候也要把本地存的滚动条位置清除

下面上完整代码 写的不好,可能还有问题,也没有加动画效果,感兴趣的可以加一下

<template>
  <PageWapper :customTitle="pageTitle">
    <template #right v-if="!isCheck && typeData.indexOf(12030401) != -1">
      <Icon name="plus" size="25" @click="openAdd()" />
    </template>
    <Sticky :offset-top="remToPx(2.944)" class="panelSearch">
      <Search v-model="searchValue" placeholder="请输入姓名或电话" @search="onSearch" @update:model-value="seek" />
      <DropdownMenu style="z-index: 999">
        <DropdownItem v-model="userType" :options="userTypes" @change="onSearch" />
        <DropdownItem v-model="userState" :options="userStates" @change="onSearch" />
        <DropdownItem v-model="userOrderby" :options="userOrderbys" @change="onSearch" />
      </DropdownMenu>
    </Sticky>
    <Empty class="custom-image" :image="noDataImage" description="暂无数据" v-show="showEmpty" />
    <PullRefresh v-model="pullLoading" @refresh="onSearch">
      <List
        v-model:loading="loading"
        :finished="finished"
        finished-text="没有更多了"
        @load="onLoad"
        :immediate-check="false"
        v-show="!showEmpty"
        class="items"
      >
        <CheckboxGroup v-model="selectData">
          <Cell
            v-for="(item, index) in list"
            :key="index"
            title-class="item-detail"
            @click="onCellClick(item)"
            clickable
            center
          >
            <template #title>
              <div style="display: flex; justify-content: space-between; align-items: center">
                <div>
                  <div class="item-detail-logo">
                    <div class="logo">
                      <img :src="item.FPhoto" :onerror="defaultImg" />
                    </div>
                  </div>
                  <div class="item-detail-content">
                    <p class="content-title">
                      {{ item.FRealName }}
                      <span v-show="item.FIsEnable != 1" :class="{ userState0: item.FIsEnable != 1 }">
                        {{ item.FIsEnable == 1 ? '启用中' : '已禁用' }}
                      </span>
                    </p>
                    <!-- <p class="content-address">{{ item.FName }}</p> -->
                    <p class="content-dept">{{ item.FName }} / {{ item.FDepName }}</p>
                  </div>
                </div>

                <div class="action_btn_group" v-show="!isCheck && typeData.indexOf(12030402) != -1" style="">
                  <van-button class="button-m" type="danger" size="mini" plain v-on:click.stop="editData(item)"
                    >编辑</van-button
                  >
                </div>
              </div>
            </template>
            <template #right-icon v-if="isCheck && params.multiple">
              <Checkbox :name="item.FID" label-disabled />
            </template>
            <template #label>
              <ItemTag
                v-for="(tag, i) in getTagInfo(item.TagIDs)"
                :key="i"
                :text="tag.FName"
                :color="tag.FTextColor"
                :bgColor="tag.FBgColor"
                :borderColor="tag.FBorderColor"
              />
            </template>
          </Cell>
        </CheckboxGroup>
      </List>
    </PullRefresh>
    <ActionSheet
      v-model:show="showActionSheet"
      :actions="actions"
      cancel-text="取消"
      close-on-click-action
      :description="descAction"
      @select="selectActionSheet"
    />
  </PageWapper>
</template>

<script lang="ts" setup>
import { onMounted, ref, reactive, computed, onActivated } from 'vue';
import {
  DropdownMenu,
  DropdownItem,
  Sticky,
  Icon,
  Empty,
  Dialog,
  Toast,
  Calendar,
  Search,
  ActionBar,
  ActionBarButton,
  List,
  Cell,
  Col,
  Row,
  Checkbox,
  CheckboxGroup,
  CellGroup,
  Tag,
  ActionSheet,
  PullRefresh,
} from 'vant';
import { useRouter, useRoute } from 'vue-router';
import { $emit } from 'vue-happy-bus';
import { PageWapper } from '/@/components/Page';
import { queryBasicData, queryPageList } from '/@/api/common';
import { useModalValueStore } from '/@/store/modules/modal';
import { ItemTag } from '/@/components/ItemTag';
import noDataImage from '/@/common/images/暂无数据.png';
import defImage from '/@/common/images/暂无图片.png';
const defaultImg = 'this.src="' + defImage + '"';
import { remToPx } from '/@/utils/helper/remHelper';
import { toResetPwd, toResetIMEI, toEnableUser, toUnEnableUser, getUserBtn } from '/@/api/sys/user';
import { nextTick } from 'vue';
const route = useRoute();
const router = useRouter();
const showEmpty = ref(false);
const showActionSheet = ref(false);
const typeData = ref([]);
const userType = ref(-1);
const userState = ref(-1);
const userOrderby = ref(-1);
const userTypes = ref([
  { text: '全部类型', value: -1 },
  { text: '平台用户', value: 1 },
  { text: '经销商用户', value: 2 },
  { text: '供货商用户', value: 3 },
]);
const userStates = ref([
  { text: '全部状态', value: -1 },
  { text: '启用', value: 1 },
  { text: '禁用', value: 0 },
]);
const userOrderbys = ref([
  { text: '默认排序', value: -1 },
  { text: '姓名', value: 0 },
  { text: '最新创建', value: 1 },
  { text: '最近登录', value: 2 },
]);

const id = ref(-1);
const descAction = ref('');
const actions = ref([
  { name: '重置密码', color: '#1989FA' },
  { name: '重置串号', color: '#1989FA' },
  { name: '禁用', color: '#ee0a24' },
  { name: '启用', color: '#07C160' },
]);
const searchValue = ref('');
const params = reactive({
  title: '用户管理',
  field: '',
  multiple: true,
  custid: -1,
  type: -1,
  ids: [],
});
const filter = reactive({
  PageSize: 20,
  PageIndex: 1,
  Conditions: [],
  OrderBys: [{ Sort: 'FID', Order: 0 }],
});
const loading = ref(true);
const pullLoading = ref(false);
const finished = ref(false);
const list = ref([]);
const selectData = ref([]);
const basicData = reactive({ TagList: [] });
const total = ref(0);
const isCheck = ref(true);
const changeDomScrool = async () => {
  //这里赋值滚动条的位置,页面渲染完成调用
  await nextTick();
  if (localStorage.getItem('scrollTop')) {
    let top = Number(localStorage.getItem('scrollTop'));
    document.documentElement.scrollTop = top;
  }
};
function scrollToTop() {
  //判断 如果当前滚动条位置大于缓存中滚动条位置就移除缓存中的,不然会有问题
  if (document.documentElement.scrollTop > Number(localStorage.getItem('scrollTop'))) {
    localStorage.removeItem('scrollTop');
  }
}
window.addEventListener('scroll', scrollToTop); //监听滚动条滚动事件
const pageTitle = computed(() => {
  return `${params.title}(${total.value})`;
});
onMounted(async () => {
  const resy = await getUserBtn();
  if (resy && resy.success) {
    var arr2 = resy.response.filter((value, index, a) => {
      return (
        value.FCode == 12030401 ||
        value.FCode == 12030402 ||
        value.FCode == 12030404 ||
        value.FCode == 12030405 ||
        value.FCode == 12030407
      );
    });
    arr2.map((item) => {
      return typeData.value.push(item.FCode);
    });
  }
  if (route.path == '/userList') {
    isCheck.value = false;
  } else {
    isCheck.value = true;
  }
  selectData.value = [];
  const storeParams = useModalValueStore().UserParams;
  if (storeParams && isCheck.value) {
    params.title = storeParams.title || '选择客户';
    params.field = storeParams.field;
    params.multiple = storeParams.multiple;
    params.custid = storeParams.custid;
    params.ids = storeParams.ids;
    params.type = storeParams.type;
  }

  const resBasic = await queryBasicData({
    request: 'TagList',
  });
  if (resBasic && resBasic.success) {
    basicData.TagList = (resBasic.response.TagList || []).filter((a) => a.FType == 3) || [];
  }
  onLoad();
});

const onLoad = async () => {
  filter.Conditions = [{ Key: 'FIsManager', Value: 0, Operator: 0 }];
  //状态
  if (isCheck.value) {
    filter.Conditions.push({
      Key: 'FIsEnable',
      Value: 1,
      Operator: 0,
    });
  } else {
    if (userState.value > -1) {
      filter.Conditions.push({
        Key: 'FIsEnable',
        Value: userState.value,
        Operator: 0,
      });
    }
  }
  //类型
  if (params.type > -1) {
    filter.Conditions.push({
      Key: 'FType',
      Value: params.type,
      Operator: 0,
    });
  } else {
    if (userType.value > -1) {
      filter.Conditions.push({
        Key: 'FType',
        Value: userType.value,
        Operator: 0,
      });
    }
  }
  if ((searchValue.value || '').length > 0) {
    filter.Conditions.push({
      Key: 'FNumber,FName,FHelpCode,FRealName,FPhone',
      Value: searchValue.value,
      Operator: 1,
    });
  }
  if (params.custid > -1) {
    filter.Conditions.push({
      Key: 'FCustID',
      Value: params.custid,
      Operator: 0,
    });
  }
  if ((params.ids || []).length > 0) {
    filter.Conditions.push({
      Key: 'FID',
      Value: params.ids.toString(),
      Operator: 7,
    });
  }
  switch (userOrderby.value) {
    case 0: //名称
      filter.OrderBys = [{ Sort: 'FRealName', Order: 0 }];
      break;
    case 1: //最新创建
      filter.OrderBys = [{ Sort: 'FCreateDate', Order: 1 }];
      break;
    case 2: //最新登录
      filter.OrderBys = [{ Sort: 'FLastLoginDate', Order: 1 }];
      break;
    default:
      //默认排序
      filter.OrderBys = [{ Sort: 'FID', Order: 0 }];
      break;
  }
  try {
    const res = await queryPageList('/SysUser/GetUserPageListV2', filter);
    if (res.success) {
      if (filter.PageIndex == 1) {
        list.value = res.response.data || [];
      } else {
        list.value = list.value.concat(res.response.data || []);
      }
      filter.PageIndex += 1;
      total.value = res.response.total;
    } else {
      finished.value = true;
      total.value = 0;
    }
  } catch {
    finished.value = true;
    total.value = 0;
  } finally {
    if (list.value.length >= total.value) {
      finished.value = true;
    }
    showEmpty.value = list.value.length > 0 ? false : true;
    loading.value = false;
    pullLoading.value = false;
    changeDomScrool(); //DOM渲染完成在给滚动条赋值 不然赋值不了
  }
};
//新增
const openAdd = () => {
  let scrollTop: any = Number(document.documentElement.scrollTop); //在点击需要跳转或需要调用接口的地方存储当前页面滚动条的位置
  localStorage.setItem('scrollTop', scrollTop);
  router.push({ path: '/sys/user/Newlyuser' });
};
//编辑
const editData = (item) => {
  let scrollTop: any = Number(document.documentElement.scrollTop); //在点击需要跳转或需要调用接口的地方存储当前页面滚动条的位置
  localStorage.setItem('scrollTop', scrollTop);
  router.push({ path: '/sys/user/Newlyuser', query: { id: item.FID } });
};
//查询
const onSearch = () => {
  localStorage.removeItem('scrollTop');
  loading.value = true;
  selectData.value = [];
  list.value = [];
  filter.PageIndex = 1;
  finished.value = false;
  onLoad();
};
const seek = (value: any) => {
  if (!value) {
    loading.value = true;
    selectData.value = [];
    list.value = [];
    filter.PageIndex = 1;
    finished.value = false;
    onLoad();
  }
};
//点击Cell
const onCellClick = (item) => {
  if (isCheck.value) {
    if (!params.multiple) {
      selectData.value = [item.FID];
      onSubmit();
    } else {
      if (selectData.value.find((a) => a == item.FID)) {
        selectData.value = selectData.value.filter((a) => a != item.FID) || [];
      } else {
        selectData.value.push(item.FID);
      }
    }
  } else {
    id.value = item.FID;
    if (item.FIsEnable == 1) {
      actions.value = [];
      if (typeData.value.indexOf(12030404) != -1) {
        actions.value.push({ name: '重置密码', color: '#1989FA' });
      }
      if (typeData.value.indexOf(12030405) != -1) {
        actions.value.push({ name: '重置串号', color: '#1989FA' });
      }
      if (typeData.value.indexOf(12030407) != -1) {
        actions.value.push({ name: '禁用', color: '#ee0a24' });
      }
    } else {
      actions.value = [];
      if (typeData.value.indexOf(12030404) != -1) {
        actions.value.push({ name: '重置密码', color: '#1989FA' });
      }
      if (typeData.value.indexOf(12030405) != -1) {
        actions.value.push({ name: '重置串号', color: '#1989FA' });
      }
      if (typeData.value.indexOf(12030407) != -1) {
        actions.value.push({ name: '启用', color: '#07C160' });
      }
    }
    descAction.value = '姓名:' + item.FRealName;
    showActionSheet.value = true;
  }
};
//底部按钮保存
const onClickSubmit = () => {
  if (selectData.value.length == 0) {
    Toast.fail('不能为空');
  } else {
    onSubmit();
  }
};
const onSubmit = () => {
  const custs = list.value.filter((a) => selectData.value.indexOf(a.FID) > -1) || [];
  let obj = {};
  if (params.multiple) {
    obj[params.field] = custs;
  } else {
    obj[params.field] = custs[0];
  }
  $emit('onSelectUser', obj);
  router.go(-1);
};
//标签集合
const getTagInfo = (idsStr) => {
  if ((idsStr || '').length > 0) {
    let ids = idsStr.split(',').map((a) => parseInt(a));
    return basicData.TagList.filter((a) => ids.indexOf(a.FID) >= 0) || [];
  } else {
    return [];
  }
};
const selectActionSheet = (action, index) => {
  const toastLoading = Toast.loading({
    message: action.name + '中...',
    forbidClick: true,
    loadingType: 'spinner',
    duration: 0,
  });
  setTimeout(async () => {
    try {
      let rightRes = null;
      switch (action.name) {
        case '重置密码':
          rightRes = await toResetPwd(id.value);
          break;
        case '重置串号':
          rightRes = await toResetIMEI(id.value);
          break;
        case '启用':
          rightRes = await toEnableUser(id.value);
          break;
        case '禁用':
          rightRes = await toUnEnableUser(id.value);
          break;
      }
      if (rightRes) {
        if (action.name == '启用') {
          let curUser = list.value.find((a) => a.FID == id.value);
          curUser.FIsEnable = 1;
        }
        if (action.name == '禁用') {
          let curUser = list.value.find((a) => a.FID == id.value);
          curUser.FIsEnable = 0;
        }
        Toast.success(action.name + '成功');
      } else {
        Toast.fail(rightRes.msg);
      }
    } finally {
      toastLoading.clear();
    }
  }, 500);
  //编辑
  // function editBill(item) {
  //   router.push({ path: '/hr/entryApplyEdit', query: { id: item.FGUID } });
  // }
};
</script>

<style lang="less" scoped>
.item-detail {
  position: relative;
  .item-detail-logo {
    position: absolute;
    top: var(--van-cell-vertical-padding);
    left: var(--van-cell-horizontal-padding);
    text-align: center;
    .logo {
      color: #fff;
      background-color: #fff;
      height: 44px;
      width: 44px;
      border-radius: 3rem;
      text-align: center;
      line-height: 3rem;
      font-size: 0.9rem;
      overflow: hidden;
      text-overflow: clip;
      img {
        height: 3rem;
        width: 3rem;
      }
    }
  }
  .item-detail-content {
    padding: 0 0.5rem 0 3.5rem;
    min-height: 3rem;
    p {
      margin: 0.1rem 0 0.25rem 0;
      padding: 0;
      line-height: 1.2rem;
    }
    p.content-title {
      font-size: 15px;
      font-weight: bold;
      color: #111;
      .userState1 {
        font-size: 0.8rem;
        color: #07c160;
      }
      .userState0 {
        font-size: 0.8rem;
        color: #ee0a24;
      }
    }
    p.content-address {
      font-size: 0.8rem;
      color: #969799;
    }
    p.content-dept {
      font-size: 12px;
      color: #888;
      .userState1 {
        font-size: 0.8rem;
        color: #07c160;
      }
      .userState0 {
        font-size: 0.8rem;
        color: #ee0a24;
      }
    }
    .action_btn_group {
      float: right;
    }
  }
  // .van-tag {
  //   margin: 0 0.5rem 0.5rem 0;
  // }
}
.items .van-list__finished-text {
  margin-bottom: var(--van-action-bar-height);
}
</style>
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值