一、需求背景
为了提升交互,在前端开发中经常会碰到,滚动/滑动页面时菜单回显的效果。有一天,产品找到我说uniapp中这个页面可以做到滑动后在上边按钮回显吗?我说能能,特此记录锚点定位的问题。
二、需求分析
实现锚点定位我们需要关注这几点:
1、当前各个分组dom的id
2、各个分组的dom的初始位置
3、获取当前滚动条的滚动距离
4、监听滚动事件,根据监听的的滚动距离和初始位置做比较,回显菜单
三、实现代码
1、我们先实现一个简单的例子。
先上效果图:
代码如下:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<meta name="viewport" content="width=device-width, viewport-fit=cover, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
<script type="text/javascript" src="https://cdn.bootcss.com/vConsole/3.3.0/vconsole.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16"></script>
<style>
.page-box {
box-sizing: border-box;
background-color: #fff;
padding: 10px;
}
.container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.header {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.button-style {
height: 32px;
line-height: 32px;
width: 50px;
text-align: center;
font-size: 14px;
border: 1px solid #189fff;
border-radius: 3px;
background-color: #fff;
color: #189fff;
cursor: pointer;
}
.main {
height: 200px;
overflow-y: scroll;
width: 200px;
border: 1px solid #a3a3a3;
border-radius: 3px;
display: flex;
flex-direction: column;
gap: 10px;
position: relative;
}
.good-style {
display: flex;
justify-content: center;
align-items: center;
margin: 0 10px;
flex: none;
height: 150px;
width: 150px;
text-align: center;
font-size: 14px;
border-radius: 3px;
color: #ffffff;
}
</style>
</head>
<body>
<div id="app" class="page-box">
<div class="container">
<div class="header">
<div class="button-style" :style="showStatus(index)" v-for="(item, index) in buttonList" :key="item.code" @click="handleButtonClick(index)">
{{item.name}}
</div>
</div>
<div id="main" class="main">
<div :id="'good_' + item.code" class="good-style" :style="{ 'background': item.background }" v-for="(item, index) in goodList" :key="item.code">
{{item.name}}
</div>
</div>
</div>
</div>
<script>
let vue = new Vue({
el: '#app',
data() {
return {
activeButton: 0, // 当前选中index
buttonList: [
{
name: '萝卜',
code: 1
},
{
name: '白菜',
code: 2
},
{
name: '鸡蛋',
code: 3
}
],
goodList: [
{
name: '一个萝卜',
code: 1,
background: '#dc143c'
},
{
name: '一个白菜',
code: 2,
background: '#3e82f7'
},
{
name: '一个鸡蛋',
code: 3,
background: '#ffa500'
}
],
// 初始信息
baseInfoList: []
}
},
created() {
},
mounted() {
// 获取dom的信息
this.getDomInfo()
let dom = document.getElementById('main')
// 监听滚动事件
dom.addEventListener('scroll', this.handleScroll, false)
},
beforeDestroy() {
let dom = document.getElementById('main')
// 移除监听
dom.removeEventListener('scroll', this.handleScroll)
},
methods: {
// 激活的按钮添加颜色提示
showStatus(index) {
if(index === this.activeButton) {
return {
background: '#189fff',
color: '#ffffff'
}
} else {
return {}
}
},
// 点击按钮回调
handleButtonClick(data) {
this.activeButton = data
let dom = document.getElementById('main')
dom.scrollTo({
top: this.baseInfoList[data].top,
behavior: 'smooth'
})
},
// 获取dom原始信息
getDomInfo() {
this.baseInfoList = []
this.goodList.forEach(x => {
let dom = document.getElementById('good_' + x.code)
this.baseInfoList.push({
top: dom.offsetTop,
height: dom.offsetHeight,
id: 'good_' + x.code,
code: x.code
})
})
},
// 处理滚动事件
handleScroll(data) {
console.dir(data.currentTarget.scrollTop)
let scrollTop = data.currentTarget.scrollTop
this.comparePosition(scrollTop)
},
// 比较滚动距离和dom初始位置
comparePosition(scrollTop) {
for (let i = 0; i < this.baseInfoList.length; i++) {
if (i === 0) {
if (scrollTop <= (this.baseInfoList[i].height * 1) / 2) {
this.activeButton = i
}
} else {
if ((scrollTop <= this.baseInfoList[i].top + (this.baseInfoList[i].height * 1) / 2) &&
(scrollTop > this.baseInfoList[i].top - this.baseInfoList[i - 1].height / 2)) {
this.activeButton = i
}
}
}
}
}
})
</script>
</body>
</html>
2、uniapp页面开发时需要注意和h5的不同之处:
1) uniapp有自带的监听页面滚动的方法 onPageScroll
2) uniapp创建的app不支持浏览器端专属的window、document、navigator等对象,需要
let query = uni.createSelectorQuery().in(this)
通过该方法查询dom信息
3)exec() 方法仅需要最后执行一次,不要多次执行。
示例代码:(仅供比对参考,具体冗余业务代码没删除,复制后无法单独运行)
<template>
<view class="page-box">
<navigator-bar :showBack="true">
<template #content>
<tab-list :tabList="tabList"></tab-list>
</template>
</navigator-bar>
<view class="container" :style="{ paddingTop: (statusbarHeight + navHeight) + 'px' }">
<view>
<view class="plan-detail">
<view class="title">
<view class="left">
<text class="text">{{ !validateNull(userModel.cons_name) ? userModel.cons_name : "" }}</text>
<u-icon name="edit-pen" class="edit" size="24" color="#ff5c26" @click="showEditModel"></u-icon>
</view>
<u-button v-if="recheck" size="mini" class="last-btn" shape="circle" @click="lastCheck">上次安检记录</u-button>
</view>
<u-row gutter="10">
<u-col span="12">
<view class="item-col">
<image mode="widthFix" class="list-icon" :src="userImg" width="30rpx" height="30rpx"></image>
<text class="text">用户号:{{ !validateNull(userModel.cons_no) ? userModel.cons_no : "" }}</text>
</view>
</u-col>
</u-row>
<u-row gutter="10">
<u-col span="6">
<view class="item-col">
<image mode="widthFix" class="list-icon" :src="telImg" width="30rpx" height="30rpx"></image>
<text class="text">电话:{{ !validateNull(userModel.cons_tel) ? userModel.cons_tel : "" }}</text>
</view>
</u-col>
<u-col span="6">
<view class="item-col">
<image mode="widthFix" class="list-icon" :src="statusImg" width="30rpx" height="30rpx"></image>
<text class="text">用户状态:</text>
<text class="text"
:class="{ created: userModel.cons_status === 1, closed: userModel.cons_status !== 1 }">{{
userModel.cons_status
=== 1 ? "正常" : "异常" }}</text>
</view>
</u-col>
</u-row>
<u-row gutter="10">
<u-col span="12">
<view class="item-col">
<image mode="widthFix" class="list-icon" :src="addressImg" width="30rpx" height="30rpx"></image>
<text class="text">详细地址:{{ !validateNull(userModel.cons_addr) ? userModel.cons_addr : "" }}</text>
</view>
</u-col>
</u-row>
<u-row gutter="10">
<u-col span="12">
<view class="item-col">
<image mode="widthFix" class="list-icon" :src="countImg" width="30rpx" height="30rpx"></image>
<text class="text">备注:{{ !validateNull(userModel.description) ? userModel.description : "" }}</text>
</view>
</u-col>
</u-row>
</view>
<!-- 点击按钮滚动到指定区域 -->
<scroll-view class="model-list" v-if="modelCode.groupInfoList && modelCode.groupInfoList.length" scroll-x="true"
:scroll-with-animation="true" :scroll-left="scrollLeft"
:style="{ position: 'sticky', top: (statusbarHeight + navHeight) + 'px' }" @scroll="scroll">
<view class="model-item" v-for="(item, index) in modelCode.groupInfoList" :key="item.id"
:class="{ active: index === currentIndex }" @click="checkTab(item, index)">
<view class="img">
<u--image :src="baseUrl + item.group_image" :lazy-load="true" mode="widthFix" width="55rpx"
height="55rpx"></u--image>
</view>
<text class="name">{{ item.group_name }}</text>
</view>
</scroll-view>
<template v-if="modelCode.groupInfoList && modelCode.groupInfoList.length">
<view class="model-group" v-for="(item, index) in modelCode.groupInfoList" :key="item.id"
:id="'into' + index">
<view class="title" :class="{ active: index === currentIndex }">
<view class="left">
<u-badge :type="index === currentIndex ? 'error' : 'primary'" max="99" :value="index + 1"
shape="horn"></u-badge>
<text class="text">{{ item.group_name }}</text>
</view>
<view class="right">
<u-switch size="20" v-if="getInstallItemVal(item.itemList) !== '2'"
@change="e => changeInstallVal(e, item.itemList)" :value="getInstallItemVal(item.itemList)"
activeValue="1" inactiveValue="0" activeColor="#61C589"></u-switch>
</view>
</view>
<view v-if="getInstallItemVal(item.itemList) !== '0'">
<view class="cell-box" v-for="(param, idx) in filterItemList(item.itemList)" :key="param.id"
@click="cellTap(index, idx, param.item_type)">
<view class="show-box">
<view class="u-slot-title">
<text v-if="param.required_flag" class="isMust">*</text>
<text class="u-cell-text">{{ param.item_name }}</text>
</view>
<view class="right-model">
<model-params :paramValue.sync="param.paramValue" :showModel.sync="showModel"
:paramCheckValue.sync="param.paramCheckValue" :type="param.item_type"
:defaultVal="param.default_value" :plh="param.placeholder" :options="param.item_option"
@updateModelVal="val => updateModelVal(val, param)"></model-params>
</view>
</view>
<view class="hidden-box" v-if="showHiddenBox(param)">
<view class="hidden-cell">
<view class="u-slot-title">
<text class="u-cell-text">整改措施:</text>
</view>
<view class="right-model">
{{ param.rectification_method }}
</view>
</view>
<view class="hidden-cell">
<view class="u-slot-title">
<text class="u-cell-text">处理方式:</text>
</view>
<view class="right-model">
<picker @change="e => confirmPicker(e, param)" :value="param.hiddenObj.rectMethod"
range-key="label" :range="reportList">
<view class="model-select">
<text v-if="reportList[param.hiddenObj.rectMethod]">{{
reportList[param.hiddenObj.rectMethod].label }}</text>
<text v-else class="plh">请选择</text>
<u-icon name="arrow-right" color="#c6c6c6" size="18"></u-icon>
</view>
</picker>
</view>
</view>
<view class="hidden-cell">
<view class="u-slot-title">
<text class="u-cell-text">备注:</text>
</view>
<view class="right-model">
<u--textarea class="model-area" v-model="param.hiddenObj.desc" placeholder="请输入备注"
height="50"></u--textarea>
</view>
</view>
<view class="hidden-cell">
<view class="u-slot-title">
<text class="u-cell-text">隐患拍照:</text>
</view>
<view class="right-model">
<view class="upload-box">
<u-upload :fileList="param.hiddenObj.hiddenImg" :capture="['camera']"
@afterRead="e => afterRead(e, item, param, 'hidden')" :previewImage="true"
@delete="e => deleteHiddenPic(e, param, 'hidden')" name="hidden" multiple
:maxCount="6"></u-upload>
</view>
</view>
</view>
<view v-if="param.hiddenObj.rectMethod === 0" class="hidden-cell">
<view class="u-slot-title">
<text class="u-cell-text"><text style="color: red; padding-right: 3px;">*</text>整改后拍照:</text>
</view>
<view class="right-model">
<view class="upload-box">
<u-upload :fileList="param.hiddenObj.hiddenAfterImg" :capture="['camera']"
@afterRead="e => afterRead(e, item, param, 'hiddenAfter')" :previewImage="true"
@delete="e => deleteHiddenPic(e, param, 'hiddenAfter')" name="hidden" multiple
:maxCount="6"></u-upload>
</view>
</view>
</view>
</view>
</view>
<view class="cell-box" @click="d(item)">
<view class="show-box">
<view class="u-slot-title">
<text class="u-cell-text"><span style="color: red;padding-right: 3px;">*</span>拍照:</text>
</view>
<view class="right-model">
<view class="upload-box">
<u-upload name="photo" multiple :previewImage="true" :fileList="item.imgList" :maxCount="6"
@afterRead="e => afterRead(e, item)" :capture="['camera']"
@delete="e => deletePic(e, index)"></u-upload>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<dy-nodata v-else showType="default"></dy-nodata>
</view>
</view>
<view class="submit">
<u-button size="mini" class="btn" shape="circle" @click="signAndSubmit" :loading="loading">提 交</u-button>
</view>
<u-modal :show="showEdit" @confirm="confirmEdit" ref="uModal" :asyncClose="true" title="修改客户信息"
:showCancelButton="true" @cancel="showEdit = false" :borderBottom="false" confirmColor="#F4801A">
<u--form labelPosition="left" :model="editForm" :rules="rules" ref="editForm" class="edit-form">
<u-form-item label="" prop="consName" borderBottom ref="consName">
<u--input placeholder="请输入用户名称" border="surround" prefixIcon="account"
prefixIconStyle="font-size: 32rpx;color: #ef1e0b" v-model="editForm.consName"></u--input>
</u-form-item>
<!-- <u-form-item label="" prop="consAddr" borderBottom ref="consAddr">
<u--input placeholder="请填写地址" border="surround" prefixIcon="map"
prefixIconStyle="font-size: 32rpx;color: #ef1e0b" v-model="editForm.consAddr"></u--input>
</u-form-item> -->
<u-form-item label="" prop="consTel" borderBottom ref="consTel">
<u--input placeholder="请输入手机号码" border="surround" prefixIcon="phone"
prefixIconStyle="font-size: 32rpx;color: #ef1e0b" v-model="editForm.consTel"></u--input>
</u-form-item>
<u-form-item label="" prop="consAddr" borderBottom ref="consAddr">
<u--input placeholder="请输入详细地址" border="surround" prefixIcon="map"
prefixIconStyle="font-size: 32rpx;color: #ef1e0b" v-model="editForm.consAddr"></u--input>
</u-form-item>
<u-form-item label="" prop="description" borderBottom ref="description">
<u--input placeholder="请填写备注信息" border="surround" prefixIcon="list-dot"
prefixIconStyle="font-size: 32rpx;color: #ef1e0b" v-model="editForm.description"></u--input>
</u-form-item>
</u--form>
</u-modal>
<sign-in v-if="showMap" :signType="signType" @close="closeMapSign" @sign="submitCheck"></sign-in>
</view>
</template>
<script>
import {
hideTabBar,
validateNull,
parseTime
} from '@/common/utils.js'
import {
BASE_FILE_URL
} from "@/common/api.js"
import statusImg from "@/static/security/status.png"
import userImg from "@/static/security/user.png"
import telImg from "@/static/security/tel.png"
import addressImg from "@/static/security/address.png"
import countImg from "@/static/security/count.png"
export default {
data() {
return {
statusImg,
userImg,
telImg,
addressImg,
countImg,
// 顶部状态栏高度
statusbarHeight: uni.getSystemInfoSync().statusBarHeight,
// 顶部导航栏高度
navHeight: getApp().globalData.ifIOS ? 44 : 48,
// 顶部tab切换列表
tabList: [{
name: "入户",
title: ""
}],
baseUrl: BASE_FILE_URL,
modelCode: {}, // 根据模板ID查询的模板代码列表
userModel: {}, // 根据用户ID查询的用户信息
consId: 0, // 客户id
templateId: 0, // 模板ID
planId: 0, // 安检计划id
intoView: "", // 锚点跳转
scrollLeft: 0,
oldScroll: 0,
currentIndex: 0, // 选中的模型
// indexs: [0, 0], // 当前点击安检项的索引
// 现场整改、上报维修、下达整改通知
reportList: [{
name: 0,
label: "现场整改"
}, {
name: 1,
label: "上报维修"
}, {
name: 2,
label: "下达整改通知"
}],
showModel: "", // 是否显示数据项组件
showEdit: false, // 是否显示编辑
editForm: {
consName: "",
consAddr: "",
consTel: "",
description: ""
},
rules: {
'consName': {
type: 'string',
required: true,
message: '请输入用户名称',
trigger: ['blur']
},
'consAddr': {
type: 'string',
required: true,
message: '请填写地址',
trigger: ['blur']
},
'consTel': [{
required: true,
message: '请输入手机号码',
trigger: ['blur']
}, {
// 自定义验证函数,见上说明
validator: (rule, value, callback) => {
// 上面有说,返回true表示校验通过,返回false表示不通过
// uni.$u.test.mobile()就是返回true或者false的
return uni.$u.test.mobile(value);
},
message: '手机号码不正确',
// 触发器可以同时用blur和change
trigger: ['blur']
}]
},
recheck: "", // 是否是复检
// 是否显示签到弹窗
showMap: false,
// 签到类型
signType: '1', // 安检
// 为了适应现状临时用到的变量 start
sign_address: '',
longitude: '',
latitude: '',
// 为了适应现状临时用到的变量 end
// 提交按钮loading
loading: false,
// dom的初始位置
itemTopBaseList: [],
// dom的id列表
idList: [],
// 是否处理监听
scrollFlag: true,
// 存储第一个未填必填项的位置
firstNoticeItemInfo: null
}
},
created() {
// 打开页面时隐藏掉tabbar 使用自定义tabbar
hideTabBar()
},
computed: {
// 过滤出不带是否安装的项
filterItemList() {
return function(list) {
return list.filter(x => x.item_code !== 'installed')
}
}
},
onLoad(option) {
if (option.templateId) {
this.templateId = option.templateId
}
if (option.consId) {
this.consId = option.consId
}
if (option.planId) {
this.planId = option.planId
}
if (option.recheck) {
this.recheck = option.recheck
}
// 页面每次显示时走一下数据
this.resetSearch()
this.getUserModel()
},
onShow() {},
onPageScroll(data) {
if (this.scrollFlag) {
let info = this.compareScroll(data.scrollTop)
if (info !== null) {
let index = info.index
this.scrollLeft = this.oldScroll + ((index - Math.floor(this.modelCode.groupInfoList.length / 2)) * 74)
this.currentIndex = index
this.intoView = info.id
}
}
},
methods: {
compareScroll(scrollTop) {
if (this.itemTopBaseList.length) {
for (let i = 0; i < this.itemTopBaseList.length; i++) {
if (i === 0) {
if (scrollTop < this.itemTopBaseList[0].top - 244 + Number(((this.itemTopBaseList[0].height * 3) / 4).toFixed(0))) {
return this.itemTopBaseList[0]
}
} else {
if (scrollTop < this.itemTopBaseList[i].top - 244 + Number(((this.itemTopBaseList[i].height * 3) / 4).toFixed(0)) &&
scrollTop >= this.itemTopBaseList[i - 1].top - 244 + Number(((this.itemTopBaseList[i - 1].height * 3) / 4).toFixed(0))) {
return this.itemTopBaseList[i]
}
}
}
}
return null
},
d(item) {
},
// 获取列表中是否安装 项 的值
getInstallItemVal(arr) {
let itemArr = arr.filter(x => x.item_code === "installed")
return itemArr.length ? itemArr[0].paramValue : "2"
},
// 切换是否安装
changeInstallVal(e, arr) {
let itemArr = arr.filter(x => x.item_code === "installed")
if (itemArr.length) {
this.$set(itemArr[0], 'paramValue', itemArr[0].paramValue === "1" ? "0" : "1")
}
},
scroll: function(e) {
this.oldScroll = e.detail.scrollLeft
},
validateNull(val) {
return validateNull(val)
},
// 重置查询
resetSearch() {
this.getModelCode()
},
// 根据用户ID查询用户详情
getUserModel() {
let param = {
cons_id: this.consId
}
this.$http("GET", "mobile/securityPlan/qryConsDetails", param)
.then(res => {
if (res.success) {
this.userModel = res.result
} else {
this.$showToast(res.message)
}
})
.catch(err => {
this.$showToast(err.message)
})
},
// 获取巡检组和定位父级的距离信息
getNodeBaseTop() {
let query = uni.createSelectorQuery().in(this)
this.$nextTick(() => {
this.idList = []
this.itemTopBaseList = [] // 距离定位父级的距离
if (this.modelCode.groupInfoList && this.modelCode.groupInfoList.length > 0) {
for(let i = 0; i < this.modelCode.groupInfoList.length; i++) {
this.idList.push({id: 'into' + i, key: this.modelCode.groupInfoList[i].id, index: i})
let nodeDom = query.select(`#into${i}`)
nodeDom.fields({
id: true,
rect: true,
size: true,
scrollOffset: true
}, (data) => {
this.itemTopBaseList.push({
id: data.id,
top: data.top,
height: data.height,
index: i
})
})
}
}
query.exec()
})
},
// 根据模板id查询模板代码项
getModelCode() {
let param = {
id: this.templateId
}
this.$http("GET", "mobile/securityTemplate/load", param)
.then(res => {
if (res.success) {
this.modelCode = res.result
this.modelCode.groupInfoList.forEach(item => {
// 添加照片集合字段
this.$set(item, "imgList", [])
// 是否展示子项
// this.$set(item, "showFlag", true)
item.itemList.forEach(opt => {
// 添加默认赋值属性 paramValue
if (opt.item_type === "checkbox") {
this.$set(opt, "paramCheckValue", opt.default_value ? opt.default_value.split(",") : [])
} else {
this.$set(opt, "paramValue", opt.default_value ? opt.default_value : "")
}
// 添加隐患信息字段
this.$set(opt, "hiddenObj", {
desc: "",
rectMethod: 0, // 整改方式 0、现场整改 1、上报维修 2、下达隐患通知
hiddenImg: [],
hiddenAfterImg: []
})
})
})
this.getNodeBaseTop() // 获取巡检组的基础dom信息
} else {
this.$showToast(res.message)
}
})
.catch(err => {
this.$showToast(err.message)
})
},
// 触顶
upperHandle() {
console.log("触顶了")
},
// 触底 分页增加 重新查询
lowerHandle() {
console.log("触底了")
},
// 点击模型列表
checkTab(item, index) {
this.scrollFlag = false
this.scrollLeft = this.oldScroll + ((index - Math.floor(this.modelCode.groupInfoList.length / 2)) * 74)
this.currentIndex = index
this.intoView = 'into' + index
this.point()
},
// 点击cell列表
cellTap(index, idx, type) {
this.showModel = type
// this.indexs = [index, idx]
},
// 更新数据
updateModelVal(val, param) {
this.$set(param, "paramValue", val)
},
// 确认选择 处理方式
confirmPicker(e, param) {
this.$set(param.hiddenObj, "rectMethod", e.target.value)
},
// 删除图片
deletePic(event, index) {
this.modelCode.groupInfoList[index].imgList.splice(event.index, 1)
// this[`fileList${event.name}`].splice(event.index, 1)
},
// 删除图片
deleteHiddenPic(event, param, type) {
if (type === 'hiddenAfter') {
param.hiddenObj.hiddenAfterImg.splice(event.index, 1)
} else if (type === 'hidden') {
param.hiddenObj.hiddenImg.splice(event.index, 1)
}
},
// 新增安检图片 | 隐患照片上传
async afterRead(event, item, param, type) {
// 当设置 mutiple 为 true 时, file 为数组格式,否则为对象格式
let lists = [].concat(event.file)
let arr
if (validateNull(param)) {
arr = item.imgList
} else {
if (type === 'hidden') {
arr = param.hiddenObj.hiddenImg
} else if (type === 'hiddenAfter') {
arr = param.hiddenObj.hiddenAfterImg
}
}
let fileListLen = arr.length
lists.map((item) => {
arr.push({
url: item.url
})
})
for (let i = 0; i < lists.length; i++) {
const result = await this.uploadFilePromise(lists[i].url)
let item = arr[fileListLen]
arr.splice(fileListLen, 1, Object.assign(item, {
status: 'success',
message: '',
photo_url: result,
url: BASE_FILE_URL + result
}))
fileListLen++
}
},
uploadFilePromise(url) {
return this.$uploadFile(url, "mobile/fileUpload")
},
// 是否显示隐患项
showHiddenBox(item) {
let flag = item.hidden_danger_flag === 1
let great = item.hidden_danger_judge === "greater" && item.paramValue > item.hidden_danger_value
let equal = item.hidden_danger_judge === "equal" && item.paramValue === item.hidden_danger_value
let less = item.hidden_danger_judge === "less" && item.paramValue < item.hidden_danger_value
let notNull = item.hidden_danger_judge === "notNull" && !validateNull(item.paramValue)
if (item.item_type === "radio") {
return flag && equal
} else if (item.item_type === "checkbox") {
let arr = item.hidden_danger_value ? item.hidden_danger_value.split(",") : []
if (item.paramCheckValue && item.paramCheckValue.length) {
return item.paramCheckValue.some(c => arr.indexOf(c) > -1)
} else {
return false
}
}
return flag && (great || equal || less || notNull)
},
signAndSubmit() {
// this.showMap = true // 临时禁用
// 临时启用 start
this.getLocation()
// 临时启用 end
},
// 提交
submitCheck(signInfo) {
this.firstNoticeItemInfo = null
// 处理提交参数
let param = {
planId: this.planId,
accountId: this.consId,
repetitionFlag: 0,
state: "normal",
elecSignature: "",
description: "",
securityCheckItemValueList: [],
securityCheckRecordPhotoList: [],
hiddenDangerList: []
}
let state // 状态 normal | danger
let checkItems = [] // 安检项集合
let checkPhotos = [] // 安检照片集合参数
let hiddenDangers = [] // 隐患信息
let isValidate = true
for (let item of this.modelCode.groupInfoList) {
item.imgList.forEach(m => {
checkPhotos.push({
group_id: item.id,
photo_url: m.photo_url
})
})
let installItemVal = this.getInstallItemVal(item.itemList)
for(let opt of item.itemList){
if (opt.required_flag && installItemVal !== '0') {
if ((opt.item_type === "checkbox" && !opt.paramCheckValue.length) || (opt.item_type !== "checkbox" &&
!opt.paramValue)) {
let temp = this.idList.filter(idFlag => {
return idFlag.key === item.id
})
if (temp && temp.length > 0) {
this.firstNoticeItemInfo = temp[0]
this.checkTab({}, this.firstNoticeItemInfo.index)
}
isValidate = false
}
}
let isHidden = this.showHiddenBox(opt)
if (opt.item_type === "checkbox" && opt.paramCheckValue.length) {
checkItems.push({
item_id: opt.id,
item_value: opt.paramCheckValue.join(",")
})
} else if (opt.paramValue) {
checkItems.push({
item_id: opt.id,
item_value: opt.paramValue
})
}
if (isHidden) {
state = true
let hiddenPhotos = []
let hiddenAfterPhotos = [] // 现场整改完成后的拍照
let dangerObj = {
currentItem: opt,
groupId: item.id,
groupName: item.group_name,
itemId: opt.id,
infoSource: "item",
handleMethod: this.reportList[opt.hiddenObj.rectMethod].label,
// handleMethodCode: opt.hiddenObj.rectMethod,
description: opt.hiddenObj.desc
}
opt.hiddenObj.hiddenImg.forEach(h => {
hiddenPhotos.push({
photo_url: h.photo_url,
hidden_danger_photo_type: 'UnhandledPhoto'
})
})
if (opt.hiddenObj.hiddenAfterImg && opt.hiddenObj.hiddenAfterImg.length < 1 && opt.hiddenObj.rectMethod === 0) {
let temp = this.idList.filter(idFlag => {
return idFlag.key === item.id
})
if (temp && temp.length > 0) {
this.firstNoticeItemInfo = temp[0]
this.checkTab({}, this.firstNoticeItemInfo.index)
}
this.$showToast("现场整改的隐患项需要上传整改后照片!")
return
}
opt.hiddenObj.hiddenAfterImg.forEach(h => {
hiddenAfterPhotos.push({
photo_url: h.photo_url,
hidden_danger_photo_type: 'HandledPhoto'
})
})
if (opt.hiddenObj.rectMethod === 1) {
dangerObj.workOrderRepair = {
work_content: opt.hidden_danger_judge === "notNull" ? opt.paramValue : opt.rectification_method
}
}
dangerObj.hiddenDangerInfoPhotoList = [...hiddenPhotos, ...hiddenAfterPhotos]
// dangerObj.hiddenAfterDangerInfoPhotoList = hiddenAfterPhotos
hiddenDangers.push(dangerObj)
}
}
}
if (!isValidate) {
this.$showToast("请完善必填字段!")
return
}
param.state = state ? "danger" : "normal"
param.securityCheckItemValueList = checkItems
param.securityCheckRecordPhotoList = checkPhotos
param.hiddenDangerList = hiddenDangers
if (signInfo) {
param.clockInRecord = signInfo
} else {
delete param.clockInRecord
}
let homeInParams = encodeURIComponent(JSON.stringify(param))
uni.setStorageSync("homeInParams", homeInParams)
this.$navigatorPage('/pages/sc-confirm/sc-confirm')
},
lastCheck() {
this.$navigatorPage("/pages/security-view/security-view?&templateId=" + this.templateId + "&accountId=" + this
.userModel.id + "&recheck=" + this.recheck)
},
showEditModel() {
this.showEdit = true
this.editForm.consName = this.userModel.cons_name
this.editForm.consAddr = this.userModel.cons_addr
this.editForm.consTel = this.userModel.cons_tel
},
// 确认编辑 保存
confirmEdit() {
this.$refs.editForm.validate().then(res => {
let editData = {
id: this.userModel.id,
cons_name: this.editForm.consName,
cons_addr: this.editForm.consAddr,
cons_tel: this.editForm.consTel,
description: this.editForm.description
}
this.$http('POST', 'mobile/consAccount/update', editData).then(res => {
this.$showToast(res.message)
this.showEdit = false
this.getUserModel()
})
}).catch(errors => {
})
},
point(index) { //锚点滑动
uni.createSelectorQuery().select(`#${this.intoView}`).boundingClientRect(data => { //目标位置节点 类或者 id
uni.createSelectorQuery().select(".model-group").boundingClientRect((res) => { //最外层盒子节点类或者 id
uni.pageScrollTo({
duration: 300, //过渡时间
scrollTop: data.top - res.top + 160, //到达距离顶部的top值
complete: () => {
setTimeout(() => {
this.scrollFlag = true
}, 400)
}
})
}).exec()
}).exec();
},
/**
* 关闭签到弹窗的回调
*/
closeMapSign() {
this.showMap = false
},
// 为了适应现状进行的妥协,写下屈辱的方法, 临时启用,后续会禁用 start
getLocation() {
this.sign_address = ''
this.longitude = ''
this.latitude = ''
let temp = ''
this.loading = true
uni.getLocation({
type: 'gcj02',
geocode: true,
isHighAccuracy: true,
highAccuracyExpireTime: 5000,
success: (data) => {
if (data.address) {
this.sign_address = data.address.province + data.address.city + data.address.district + data.address.street + data.address.streetNum + data.address.poiName
}
this.longitude = data.longitude
this.latitude = data.latitude
// 签到时间 signTime
let nowDate = new Date().getTime()
this.signTime = parseTime(nowDate, 'zh')
if(this.signType && this.longitude && this.latitude) {
temp = {
clock_in_type: this.signType,
clock_in_addr: this.sign_address,
longitude: this.longitude,
latitude: this.latitude
}
}
},
fail: () => {
console.log('获取位置失败');
},
complete: () => {
this.loading = false
console.log('完成位置获取工作');
this.submitCheck(temp)
}
})
},
// 为了适应现状进行的妥协,写下屈辱的方法, 临时启用,后续会禁用 end
}
}
</script>
<style lang="scss" scoped>
page {
background-color: #f5f5f5;
}
.placeholder {
height: 510rpx;
width: 100%;
}
.container {
width: 100%;
padding: 0 0 16rpx;
background-color: #f5f5f5;
margin-bottom: 110rpx;
.search {
width: 100%;
padding: 20rpx 30rpx;
background: #fff;
}
.scroll-box {
height: 100%;
scroll-behavior: smooth;
}
}
.plan-detail {
width: 100%;
padding: 30rpx 30rpx 10rpx;
background: #f5f5f5;
display: flex;
flex-direction: column;
justify-content: center;
.title {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
font-size: 28rpx;
color: #333;
.left {
display: flex;
align-items: center;
flex-direction: row;
.text {
margin-right: 10rpx;
}
}
.u-button {
width: 160rpx;
margin: 0;
color: $normal_red;
border: none;
}
.last-btn {
width: 260rpx;
height: 56rpx;
background: #fff;
border: 1px solid $normal_red;
color: $normal_red;
font-size: 28rpx;
}
}
.item-col {
width: 100%;
display: flex;
align-items: center;
flex-direction: row;
justify-content: flex-start;
margin-bottom: 20rpx;
.text {
font-size: 28rpx;
color: #333;
&.closed {
color: $normal_red;
}
&.created {
color: #61C589;
}
}
.list-icon {
width: 30rpx;
height: 30rpx;
margin-right: 10rpx;
}
}
}
.model-list {
white-space: nowrap;
scroll-behavior: smooth;
height: 200rpx;
padding: 25rpx 30rpx;
background-color: #fff;
box-sizing: border-box;
// position: fixed!important;
// top: 402rpx;
z-index: 10 !important;
.model-item {
display: inline-block;
width: 100rpx;
margin-right: 48rpx;
text-align: center;
&:last-child {
margin: 0;
}
.img {
overflow: hidden;
width: 100rpx;
height: 100rpx;
margin-bottom: 10rpx;
display: flex;
justify-content: center;
align-items: center;
background-color: #F4F4F7;
border-radius: 14rpx;
}
.name {
font-size: 28rpx;
color: #232832;
}
&.active {
.img {
background-color: $normal_red;
}
.name {
color: $normal_red;
}
}
}
}
.model-group {
padding: 0 0 22rpx;
margin-top: 16rpx;
background-color: #fff;
overflow-y: overlay;
.title {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 30rpx;
color: #333;
.left {
display: flex;
justify-content: space-between;
align-items: center;
}
.text {
margin-left: 10rpx;
}
&.active {
color: $normal_red;
}
}
// .unable {
// pointer-events: none;
// opacity: 0.4;
// }
.isMust {
color: $normal_red;
}
}
.cell-box {
position: relative;
width: 100%;
padding: 0 30rpx 1px;
&::after {
content: "";
position: absolute;
left: 30rpx;
right: 30rpx;
bottom: 1px;
height: 1px;
background-color: #c6c6c6;
}
&:last-child {
&::after {
background-color: #fff;
}
}
.show-box {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
min-height: 40rpx;
padding: 25rpx 0;
background: #fff;
gap: 0rpx;
}
.hidden-box {
padding: 10rpx 20rpx;
background-color: #f4f4f4;
.hidden-cell {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
min-height: 40rpx;
padding: 25rpx 0;
}
}
}
.u-slot-title,
.right-model {
white-space: nowrap;
color: #333;
font-size: 28rpx;
.model-area {
width: 300rpx;
}
}
.upload-box {
width: 360rpx;
/deep/ .u-upload__wrap {
justify-content: flex-end;
}
/deep/ .u-upload__wrap__preview__image {
width: 100rpx !important;
height: 100rpx !important;
}
}
.model-select {
display: flex;
flex-direction: row;
align-items: center;
}
/deep/ .u-upload__button {
width: 100rpx !important;
height: 100rpx !important;
background-color: #fff;
border: 1px solid #c6c6c6;
border-radius: 12rpx;
}
.submit {
width: 100%;
height: 120rpx;
text-align: center;
padding: 30rpx 0 36rpx;
background-color: #fff;
position: fixed;
bottom: 0;
.btn {
width: 160rpx;
height: 56rpx;
background: $normal_red;
border-color: $normal_red;
color: #fff;
font-size: 28rpx;
}
}
.edit-form {
width: 100%;
}
/deep/ .u-modal__title {
height: 80rpx;
line-height: 80rpx;
padding-top: 0;
background: linear-gradient(180deg, #F98665 0%, $normal_red 100%);
color: #fff;
}
</style>