最近在做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>