孙旺 1 year ago
parent
commit
b4a10a0c7f

+ 124 - 0
src/components/ba-tree-picker/README.md

@@ -0,0 +1,124 @@
+## 树形层级选择器
+### 简介
+为统一样式而生,树形层级选择器,picker弹窗形式的,样式和比例参照uniapp的picker和uni-data-picker组件
+* 支持单选、多选、父级选择,当然也支持单层选择
+* 支持Object对象属性自定义映射
+* 支持显示全部选中、部分选中、未选中三种状态
+* 支持快速自定义简单样式(分割线、按钮、标题、对齐等),深入样式可复写css
+
+### 使用方法
+在 `script` 中引入组件
+``` javascript
+	import baTreePicker from "@/components/ba-tree-picker/ba-tree-picker.vue"
+	export default {
+		components: {
+			baTreePicker
+		}
+```
+在 `template` 中使用组件
+``` javascript
+	<ba-tree-picker ref="treePicker" :multiple='false' @select-change="selectChange" title="选择城市"
+		:localdata="listData" valueKey="value" textKey="label" childrenKey="children" />
+```
+在 `script` 中定义打开方法,和选择监听
+``` javascript
+		methods: {
+			// 显示选择器
+			showPicker() {
+				this.$refs.treePicker._show();
+			},
+			//监听选择(ids为数组)
+			selectChange(ids, names) {
+				console.log(ids, names)
+			}
+		}
+```
+在 `template` 中调用打开
+``` javascript
+	<view @click="showPicker">调用选择器</view>
+```
+
+### 属性
+|属性名|类型|默认值|说明|
+|:-|:-:|:--:|-:|
+|localdata|Array|[]|源数据,目前支持tree结构,后续会考虑支持扁平化结构|
+|valueKey|String|id|指定 Object 中 key 的值作为节点数据id|
+|textKey|String|name|指定 Object 中 key 的值作为节点显示内容|
+|childrenKey|String|children|指定 Object 中 key 的值作为节点子集|
+|multiple|Boolean|false|是否多选,默认单选|
+|selectParent|Boolean|true|是否可以选父级,默认可以|
+|title|String| |标题|
+|titleColor|String||标题颜色|
+|confirmColor|String|#0055ff|确定按钮颜色|
+|cancelColor|String|#757575|取消按钮颜色|
+|switchColor|String|#666|节点切换图标颜色|
+|border|Boolean|false|是否有分割线,默认无|
+
+
+
+###  数据格式
+
+注意:必须有id、name(id可通过valueKey来配置为其它键值,如value)字段,且唯一
+
+``` json
+[
+    {
+        id: 1,
+        name: '公司1',
+        children: [{
+            id: 11,
+            name: '研发部',
+            children: [{
+                id: 111,
+                name: '张三',
+                
+            },{
+                id: 112,
+                name: '李四',
+                
+            }]
+        },{
+            id: 12,
+            name: '综合部',
+            
+        } ]
+    },
+    {
+        id: 2,
+        name: '公司2',
+        children: [{
+            id: 21,
+            name: '研发部',
+            
+        },{
+            id: 22,
+            name: '综合部',
+            
+        },{
+            id: 23,
+            name: '财务部',
+            
+        }, ]
+    },
+    {
+        id: 3,
+        name: '公司3'
+    },
+    {
+        id: 4,
+        name: '公司4',
+        children: [{
+            id: 41,
+            name: '研发部',
+            
+        }]
+    }
+]
+```
+</details>
+
+### 方法
+|方法名|参数|默认值|说明|
+|:-|:-:|:--:|-:|
+|_show()| | |显示选择器|
+|_hide()| | |隐藏选择器|

+ 619 - 0
src/components/ba-tree-picker/ba-tree-picker.vue

@@ -0,0 +1,619 @@
+<!-- 树形层级选择器-->
+<!-- 1、支持单选、多选 -->
+<template>
+	<view>
+		<view class="tree-cover" :class="{'show':showDialog}" @tap="_cancel"></view>
+		<view class="tree-dialog" :class="{'show':showDialog}">
+			<view class="tree-bar">
+				<view class="tree-bar-cancel" :style="{'color':cancelColor}" hover-class="hover-c" @tap="_cancel">取消
+				</view>
+				<view class="tree-bar-title" :style="{'color':titleColor}">{{title}}</view>
+				<view class="tree-bar-confirm" :style="{'color':confirmColor}" hover-class="hover-c" @tap="_confirm">
+					{{multiple?'确定':''}}
+				</view>
+			</view>
+			<view class="tree-view">
+				<scroll-view class="tree-list" :scroll-y="true">
+					<block v-for="(item, index) in treeList" :key="index">
+						<view class="tree-item" :style="[{
+							paddingLeft: item.level*30 + 'rpx'
+						}]" :class="{
+							itemBorder: border === true,
+							show: item.isShow
+						}">
+							<view class="item-label">
+								<view class="item-icon uni-inline-item" @tap.stop="_onItemSwitch(item, index)">
+									<view v-if="!item.isLastLevel&&item.isShowChild" class="switch-on"
+										:style="{'border-left-color':switchColor}">
+									</view>
+									<view v-else-if="!item.isLastLevel&&!item.isShowChild" class="switch-off"
+										:style="{'border-top-color':switchColor}">
+									</view>
+									<view v-else class="item-last-dot" :style="{'border-top-color':switchColor}">
+									</view>
+								</view>
+								<view class="flex flex-wrap justify-between align-center uni-flex-item uni-inline-item" @tap.stop="_onItemSelect(item, index)">
+									<view class="item-name flex"> {{item.name+(item.childCount?"("+item.childCount+")":'')}}
+									</view>
+									<view class="item-check  flex" v-if="selectParent?true:item.isLastLevel">
+										<view class="item-check-yes" v-if="item.checkStatus==1"
+											:class="{'radio':!multiple}" :style="{'border-color':confirmColor}">
+											<view class="item-check-yes-part"
+												:style="{'background-color':confirmColor}">
+											</view>
+										</view>
+										<view class="item-check-yes" v-else-if="item.checkStatus==2"
+											:class="{'radio':!multiple}" :style="{'border-color':confirmColor}">
+											<view class="item-check-yes-all" :style="{'background-color':confirmColor}">
+											</view>
+										</view>
+										<view class="item-check-no" v-else :class="{'radio':!multiple}"
+											:style="{'border-color':confirmColor}"></view>
+									</view>
+								</view>
+							</view>
+
+						</view>
+					</block>
+				</scroll-view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		emits: ['select-change'],
+		name: "ba-tree-picker",
+		props: {
+			valueKey: {
+				type: String,
+				default: 'id'
+			},
+			textKey: {
+				type: String,
+				default: 'name'
+			},
+			childrenKey: {
+				type: String,
+				default: 'children'
+			},
+			localdata: {
+				type: Array,
+				default: function() {
+					return []
+				}
+			},
+			localTreeList: { //在已经格式化好的数据
+				type: Array,
+				default: function() {
+					return []
+				}
+			},
+			selectedData: {
+				type: Array,
+				default: function() {
+					return []
+				}
+			},
+			title: {
+				type: String,
+				default: ''
+			},
+			multiple: { // 是否可以多选
+				type: Boolean,
+				default: true
+			},
+			selectParent: { //是否可以选父级
+				type: Boolean,
+				default: true
+			},
+			confirmColor: { // 确定按钮颜色
+				type: String,
+				default: '' // #0055ff
+			},
+			cancelColor: { // 取消按钮颜色
+				type: String,
+				default: '' // #757575
+			},
+			titleColor: { // 标题颜色
+				type: String,
+				default: '' //
+			},
+			switchColor: { // 节点切换图标颜色
+				type: String,
+				default: '' // #666
+			},
+			border: { // 是否有分割线
+				type: Boolean,
+				default: false
+			},
+		},
+		data() {
+			return {
+				showDialog: false,
+				treeList: []
+			}
+		},
+		computed: {},
+		methods: {
+			_show() {
+				this.showDialog = true
+			},
+			_hide() {
+				this.showDialog = false
+			},
+			_cancel() {
+				this._hide()
+				this.$emit("cancel", '');
+			},
+			_confirm() { //多选
+				let selectedList = []; //如果子集全部选中,只返回父级 id
+				let selectedNames;
+				let currentLevel = -1;
+				this.treeList.forEach((item, index) => {
+					if (currentLevel >= 0 && item.level > currentLevel) {
+
+					} else {
+						if (item.checkStatus === 2) {
+							currentLevel = item.level;
+							selectedList.push(item.id);
+							selectedNames = selectedNames ? selectedNames + ' / ' + item.name : item.name;
+						} else {
+							currentLevel = -1;
+						}
+					}
+				})
+				//console.log('_confirm', selectedList);
+				this._hide()
+				this.$emit("select-change", selectedList, selectedNames);
+			},
+			//格式化原数据(原数据为tree结构)
+			_formatTreeData(list = [], level = 0, parentItem, isShowChild = true) {
+				let nextIndex = 0;
+				let parentId = -1;
+				let initCheckStatus = 0;
+				if (parentItem) {
+					nextIndex = this.treeList.findIndex(item => item.id === parentItem.id) + 1;
+					parentId = parentItem.id;
+					if (!this.multiple) { //单选
+						initCheckStatus = 0;
+					} else
+						initCheckStatus = parentItem.checkStatus == 2 ? 2 : 0;
+				}
+				list.forEach(item => {
+					let isLastLevel = true;
+					if (item && item[this.childrenKey]) {
+						let children = item[this.childrenKey];
+						if (Array.isArray(children) && children.length > 0) {
+							isLastLevel = false;
+						}
+					}
+
+					let itemT = {
+						id: item[this.valueKey],
+						name: item[this.textKey],
+						level,
+						isLastLevel,
+						isShow: isShowChild,
+						isShowChild: false,
+						checkStatus: initCheckStatus,
+						orCheckStatus: 0,
+						parentId,
+						children: item[this.childrenKey],
+						childCount: item[this.childrenKey] ? item[this.childrenKey].length : 0,
+						childCheckCount: 0,
+						childCheckPCount: 0
+					};
+
+					if (this.selectedData.indexOf(itemT.id) >= 0) {
+						itemT.checkStatus = 2;
+						itemT.orCheckStatus = 2;
+						itemT.childCheckCount = itemT.children ? itemT.children.length : 0;
+						this._onItemParentSelect(itemT, nextIndex);
+					}
+
+					this.treeList.splice(nextIndex, 0, itemT);
+					nextIndex++;
+				})
+				//console.log(this.treeList);
+			},
+			// 节点打开、关闭切换
+			_onItemSwitch(item, index) {
+				// console.log(item)
+				//console.log('_itemSwitch')
+				if (item.isLastLevel === true) {
+					return;
+				}
+				item.isShowChild = !item.isShowChild;
+				if (item.children) {
+					this._formatTreeData(item.children, item.level + 1, item);
+					item.children = undefined;
+				} else {
+					this._onItemChildSwitch(item, index);
+				}
+			},
+			_onItemChildSwitch(item, index) {
+				//console.log('_onItemChildSwitch')
+				const firstChildIndex = index + 1;
+				if (firstChildIndex > 0)
+					for (var i = firstChildIndex; i < this.treeList.length; i++) {
+						let itemChild = this.treeList[i];
+						if (itemChild.level > item.level) {
+							if (item.isShowChild) {
+								if (itemChild.parentId === item.id) {
+									itemChild.isShow = item.isShowChild;
+									if (!itemChild.isShow) {
+										itemChild.isShowChild = false;
+									}
+								}
+							} else {
+								itemChild.isShow = item.isShowChild;
+								itemChild.isShowChild = false;
+							}
+						} else {
+							return;
+						}
+					}
+			},
+			// 节点选中、取消选中
+			_onItemSelect(item, index) {
+				//console.log('_onItemSelect')
+				//console.log(item)
+				if (!this.multiple) { //单选
+					item.checkStatus = item.checkStatus == 0 ? 2 : 0;
+
+					this.treeList.forEach((v, i) => {
+						if (i != index) {
+							this.treeList[i].checkStatus = 0
+						} else {
+							this.treeList[i].checkStatus = 2
+						}
+					})
+
+					let selectedList = [];
+					let selectedNames;
+					selectedList.push(item.id);
+					selectedNames = item.name;
+					this._hide()
+					this.$emit("select-change", selectedList, selectedNames);
+					return
+				}
+
+				let oldCheckStatus = item.checkStatus;
+				switch (oldCheckStatus) {
+					case 0:
+						item.checkStatus = 2;
+						item.childCheckCount = item.childCount;
+						item.childCheckPCount = 0;
+						break;
+					case 1:
+					case 2:
+						item.checkStatus = 0;
+						item.childCheckCount = 0;
+						item.childCheckPCount = 0;
+						break;
+					default:
+						break;
+				}
+				//子节点 全部选中
+				this._onItemChildSelect(item, index);
+				//父节点 选中状态变化
+				this._onItemParentSelect(item, index, oldCheckStatus);
+			},
+			_onItemChildSelect(item, index) {
+				//console.log('_onItemChildSelect')
+				let allChildCount = 0;
+				if (item.childCount && item.childCount > 0) {
+					index++;
+					while (index < this.treeList.length && this.treeList[index].level > item.level) {
+						let itemChild = this.treeList[index];
+						itemChild.checkStatus = item.checkStatus;
+						if (itemChild.checkStatus == 2) {
+							itemChild.childCheckCount = itemChild.childCount;
+							itemChild.childCheckPCount = 0;
+						} else if (itemChild.checkStatus == 0) {
+							itemChild.childCheckCount = 0;
+							itemChild.childCheckPCount = 0;
+						}
+						// console.log('>>>>index:', index, 'item:', itemChild.name, '  status:', itemChild
+						// 	.checkStatus)
+						index++;
+					}
+				}
+			},
+			_onItemParentSelect(item, index, oldCheckStatus) {
+				//console.log('_onItemParentSelect')
+				//console.log(item)
+				const parentIndex = this.treeList.findIndex(itemP => itemP.id == item.parentId);
+				//console.log('parentIndex:' + parentIndex)
+				if (parentIndex >= 0) {
+					let itemParent = this.treeList[parentIndex];
+					let count = itemParent.childCheckCount;
+					let oldCheckStatusParent = itemParent.checkStatus;
+
+					if (oldCheckStatus == 1) {
+						itemParent.childCheckPCount -= 1;
+					} else if (oldCheckStatus == 2) {
+						itemParent.childCheckCount -= 1;
+					}
+					if (item.checkStatus == 1) {
+						itemParent.childCheckPCount += 1;
+					} else if (item.checkStatus == 2) {
+						itemParent.childCheckCount += 1;
+					}
+
+					if (itemParent.childCheckCount <= 0 && itemParent.childCheckPCount <= 0) {
+						itemParent.childCheckCount = 0;
+						itemParent.childCheckPCount = 0;
+						itemParent.checkStatus = 0;
+					} else if (itemParent.childCheckCount >= itemParent.childCount) {
+						itemParent.childCheckCount = itemParent.childCount;
+						itemParent.childCheckPCount = 0;
+						itemParent.checkStatus = 2;
+					} else {
+						itemParent.checkStatus = 1;
+					}
+					//console.log('itemParent:', itemParent)
+					this._onItemParentSelect(itemParent, parentIndex, oldCheckStatusParent);
+				}
+			},
+			// 重置数据
+			_reTreeList() {
+				this.treeList.forEach((v, i) => {
+					this.treeList[i].checkStatus = v.orCheckStatus
+				})
+			},
+			_initTree() {
+				this.treeList = [];
+				this._formatTreeData(this.localdata);
+			}
+		},
+		watch: {
+			localdata() {
+				this._initTree();
+			},
+			localTreeList() {
+				this.treeList = this.localTreeList;
+			}
+		},
+		mounted() {
+			this._initTree();
+		}
+	}
+</script>
+
+<style scoped>
+	.tree-cover {
+		position: fixed;
+		top: 0rpx;
+		right: 0rpx;
+		bottom: 0rpx;
+		left: 0rpx;
+		z-index: 100;
+		background-color: rgba(0, 0, 0, .4);
+		opacity: 0;
+		transition: all 0.3s ease;
+		visibility: hidden;
+	}
+
+	.tree-cover.show {
+		visibility: visible;
+		opacity: 1;
+	}
+
+	.tree-dialog {
+		position: fixed;
+		top: 0rpx;
+		right: 0rpx;
+		bottom: 0rpx;
+		left: 0rpx;
+		background-color: #fff;
+		border-top-left-radius: 10px;
+		border-top-right-radius: 10px;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+		z-index: 102;
+		top: 20%;
+		transition: all 0.3s ease;
+		transform: translateY(100%);
+	}
+
+	.tree-dialog.show {
+		transform: translateY(0);
+	}
+
+	.tree-bar {
+		/* background-color: #fff; */
+		height: 90rpx;
+		padding-left: 25rpx;
+		padding-right: 25rpx;
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		box-sizing: border-box;
+		border-bottom-width: 1rpx !important;
+		border-bottom-style: solid;
+		border-bottom-color: #f5f5f5;
+		font-size: 32rpx;
+		color: #757575;
+		line-height: 1;
+	}
+
+	.tree-bar-confirm {
+		color: #0055ff;
+		padding: 15rpx;
+	}
+
+	.tree-bar-title {}
+
+	.tree-bar-cancel {
+		color: #757575;
+		padding: 15rpx;
+	}
+
+	.tree-view {
+		flex: 1;
+		padding: 20rpx;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+		overflow: hidden;
+		height: 100%;
+	}
+
+	.tree-list {
+		flex: 1;
+		height: 100%;
+		overflow: hidden;
+	}
+
+	.tree-item {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		line-height: 1;
+		height: 0;
+		opacity: 0;
+		transition: 0.2s;
+		overflow: hidden;
+	}
+
+	.tree-item.show {
+		height: 90rpx;
+		opacity: 1;
+	}
+
+	.tree-item.showchild:before {
+		transform: rotate(90deg);
+	}
+
+	.tree-item.last:before {
+		opacity: 0;
+	}
+
+	.switch-on {
+		width: 0;
+		height: 0;
+		border-left: 10rpx solid transparent;
+		border-right: 10rpx solid transparent;
+		border-top: 15rpx solid #666;
+	}
+
+	.switch-off {
+		width: 0;
+		height: 0;
+		border-bottom: 10rpx solid transparent;
+		border-top: 10rpx solid transparent;
+		border-left: 15rpx solid #666;
+	}
+
+	.item-last-dot {
+		position: absolute;
+		width: 10rpx;
+		height: 10rpx;
+		border-radius: 100%;
+		background: #666;
+	}
+
+	.item-icon {
+		width: 26rpx;
+		height: 26rpx;
+		margin-right: 8rpx;
+		padding-right: 20rpx;
+		padding-left: 20rpx;
+	}
+
+	.item-label {
+		flex: 1;
+		display: flex;
+		align-items: center;
+		height: 100%;
+		line-height: 1.2;
+	}
+
+	.item-name {
+		flex: 1;
+		overflow: hidden;
+		text-overflow: ellipsis;
+		white-space: nowrap;
+		/*width: 450rpx;*/
+	}
+
+	.item-check {
+		width: 40px;
+		height: 40px;
+		display: flex;
+		justify-content: center;
+		align-items: center;
+	}
+
+	.item-check-yes,
+	.item-check-no {
+		width: 20px;
+		height: 20px;
+		border-top-left-radius: 20%;
+		border-top-right-radius: 20%;
+		border-bottom-right-radius: 20%;
+		border-bottom-left-radius: 20%;
+		border-top-width: 1rpx;
+		border-left-width: 1rpx;
+		border-bottom-width: 1rpx;
+		border-right-width: 1rpx;
+		border-style: solid;
+		border-color: #0055ff;
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		box-sizing: border-box;
+	}
+
+	.item-check-yes-part {
+		width: 12px;
+		height: 12px;
+		border-top-left-radius: 20%;
+		border-top-right-radius: 20%;
+		border-bottom-right-radius: 20%;
+		border-bottom-left-radius: 20%;
+		background-color: #0055ff;
+	}
+
+	.item-check-yes-all {
+		margin-bottom: 5px;
+		border: 2px solid #007aff;
+		border-left: 0;
+		border-top: 0;
+		height: 12px;
+		width: 6px;
+		transform-origin: center;
+		/* #ifndef APP-NVUE */
+		transition: all 0.3s;
+		/* #endif */
+		transform: rotate(45deg);
+	}
+
+	.item-check .radio {
+		border-top-left-radius: 50%;
+		border-top-right-radius: 50%;
+		border-bottom-right-radius: 50%;
+		border-bottom-left-radius: 50%;
+	}
+
+	.item-check .radio .item-check-yes-b {
+		border-top-left-radius: 50%;
+		border-top-right-radius: 50%;
+		border-bottom-right-radius: 50%;
+		border-bottom-left-radius: 50%;
+	}
+
+	.hover-c {
+		opacity: 0.6;
+	}
+
+	.itemBorder {
+		border-bottom: 1px solid #e5e5e5;
+	}
+</style>

+ 134 - 0
src/components/da-tree/changelog.md

@@ -0,0 +1,134 @@
+# 1.3.1
+
+修复
+
+1. 修复方法`setExpandedKeys`没联动展开上级父子节点
+
+# 1.3.0
+
+优化
+
+1. `field`新增字段 `append` 用于在标签后面显示小提示
+2. 新增支持点击标签也能选中节点
+3. 方法`setExpandedKeys`支持加载动态数据
+4. 修复父节点禁用,则不能展开及图标展开显示
+5. 修复动态加载数据时,末级节点的 `children` 为 `null` 时仍显示展开图标
+
+# 1.2.6
+
+新增
+
+1. 新增支持主题换色
+2. 支持单选的`onlyRadioLeaf`为`true`时可点父节点展开/收起
+3. 优化`expandChecked`调整为不展开无子节点的节点
+
+# 1.2.5
+
+新增
+
+1. 新增 `expandChecked`,控制选择时是否展开当前已选的所有下级节点
+
+# 1.2.4
+
+修复
+
+1. 修复动态数据展开状态异常问题
+
+# 1.2.3
+
+新增
+
+1. 新增 `checkedDisabled`,是否渲染禁用值
+2. 新增 `packDisabledkey`,是否返回已禁用并选中的 key
+3. 修复选择父级时,子级已禁用但仍被选中的问题
+
+# 1.2.2
+
+优化
+
+1. 调整动态数据载入处理方式
+2. 修复节点数据因动态数据引起的状态异常
+3. 修复初始节点数据默认选中
+
+# 1.2.1
+
+修复
+
+1. 修复切换`选中状态`被重复选中问题
+2. 修复动态数据引起的重复选择问题
+
+# 1.2.0
+
+新增
+
+1. 新增方法调用
+   > - 新增`setCheckedKeys`,方法设置指定 key 的节点选中状态
+   > - 新增`setExpandedKeys`,方法设置指定 key 的节点展开状态
+2. 修复小程序重复插槽一直刷报错问题
+3. 优化展开时,会展开子级所以下级节点
+
+# 1.1.1
+
+新增
+
+1. 新增`data`的`disabled`,支持节点禁用状态
+2. 新增`field`的`disabled`,可自定`disabled`字段值
+
+# 1.1.0
+
+新增
+
+1. 新增`loadMode`、`loadApi`,支持展开时加载异步数据
+2. 新增方法调用
+   > - 新增`getCheckedKeys`,方法返回已选的 key
+   > - 新增`getHalfCheckedKeys`,方法返回半选的 key
+   > - 新增`getExpandedKeys`,方法返回已展开的 key
+   > - 新增`getCheckedNodes`,方法返回已选的节点
+   > - 新增`getHalfCheckedNodes`,方法返回半选的节点
+   > - 新增`getExpandedNodes`,方法返回已展开的节点
+3. 对代码进行重构,更易于后期拓展
+4. 此次更新后,页面多个的 DaTee 组件间的数据不再关联
+
+# 1.0.6
+
+新增
+
+1. 新增`checkStrictly`,多选模式下选中时是否父子不关联
+
+# 1.0.5
+
+修复
+
+1. 修复多选时已选数据重复问题
+
+# 1.0.4
+
+修复
+
+1. 修复 `change` 事件回调数据的问题
+
+# 1.0.3
+
+优化
+
+1. 优化文档及示例说明
+
+# 1.0.2
+
+新增
+
+1. 新增 `onlyRadioLeaf` ,单选时只允许选中末级
+2. 优化默认展开及默认选择的展开问题
+
+# 1.0.1
+
+新增
+
+1. 支持展开/收起回调事件`@expand`
+
+# 1.0.0
+
+初始版本 1.0.0,基于 Vue3 进行开发,支持单选、多选,兼容各大平台
+
+1. 支持单选
+2. 支持多选

File diff suppressed because it is too large
+ 956 - 0
src/components/da-tree/index.vue


+ 303 - 0
src/components/da-tree/readme.md

@@ -0,0 +1,303 @@
+# da-tree
+
+一个基于 Vue3 的 tree(树)组件,同时支持主题换色,可能是最适合你的 tree(树)组件
+
+组件一直在更新,遇到问题可在下方讨论。
+
+### 关于使用
+
+可在右侧的`使用 HBuilderX 导入插件`或`下载示例项目ZIP`,方便快速上手。
+
+可通过下方的示例及文档说明,进一步了解使用组件相关细节参数。
+
+插件地址:https://ext.dcloud.net.cn/plugin?id=12384
+
+### 组件示例
+
+```jsx
+<template>
+  <div>多选</div>
+  <DaTree
+    ref="DaTreeRef"
+    :data="roomTreeData"
+    :field="{ label: 'name', key: 'id' }"
+    defaultExpandAll
+    showCheckbox
+    :defaultCheckedKeys="defaultCheckedKeysValue"
+    @change="handleTreeChange"
+    @expand="handleExpandChange"></DaTree>
+  <div><button @click="doCheckedTree(['2'],true)">全选</button></div>
+  <div><button @click="doCheckedTree(['2'],false)">取消全选</button></div>
+  <div><button @click="doCheckedTree(['211','222'],true)">选中指定节点</button></div>
+  <div><button @click="doCheckedTree(['211','222'],false)">取消选中指定节点</button></div>
+  <div><button @click="doExpandTree(['22','23'],true)">展开节点</button></div>
+  <div><button @click="doExpandTree(['22','23'],false)">收起节点</button></div>
+  <div>单选</div>
+  <DaTree
+    :data="roomTreeData"
+    :field="{ label: 'name', key: 'id' }"
+    defaultExpandAll
+    :defaultCheckedKeys="defaultCheckedKeysValue2"
+    @change="handleTreeChange"
+    @expand="handleExpandChange"></DaTree>
+  <div>默认展开指定节点</div>
+  <DaTree
+    :data="roomTreeData"
+    :field="{ label: 'name', key: 'id' }"
+    showCheckbox
+    :defaultExpandedKeys="defaultExpandKeysValue3"
+    @change="handleTreeChange"
+    @expand="handleExpandChange"></DaTree>
+  <div>异步加载数据</div>
+  <DaTree
+    :data="roomTreeData"
+    :field="{ label: 'name', key: 'id' }"
+    showCheckbox
+    loadMode
+    :loadApi="GetApiData"
+    defaultExpandAll
+    @change="handleTreeChange"
+    @expand="handleExpandChange"></DaTree>
+</template>
+```
+
+```js
+import { defineComponent, ref } from 'vue'
+
+/**
+ * 模拟创建一个接口数据
+ */
+function GetApiData(currentNode) {
+  const { key } = currentNode
+
+  return new Promise((resolve) => {
+    setTimeout(() => {
+      // 模拟返回空数据
+      if (key.indexOf('-') > -1) {
+        // return resolve(null)
+        return resolve([])
+      }
+
+      return resolve([
+        {
+          id: `${key}-1`,
+          name: `行政部X${key}-1`,
+          append: 'children为空数组,仍有展开图标',
+          children: [],
+        },
+        {
+          id: `${key}-2`,
+          name: `财务部X${key}-2`,
+          append: '无children字段,无展开图标',
+        },
+        {
+          id: `${key}-3`,
+          name: `资源部X${key}-3`,
+          append: 'children字段为null或undefined,无展开图标',
+          children: null,
+        },
+        {
+          id: `${key}-4`,
+          name: `资源部X${key}-3`,
+          append: '被禁用,无展开图标',
+          children: [],
+          disabled: true,
+        },
+      ])
+    }, 2000)
+  })
+}
+
+import DaTree from '@/components/da-tree/index.vue'
+export default defineComponent({
+  components: { DaTree },
+  setup() {
+    const DaTreeRef = ref()
+    // key的类型必须对应树数据key的类型
+    const defaultCheckedKeysValue = ref(['211', '222'])
+    const defaultCheckedKeysValue2 = ref('222')
+    const defaultExpandKeysValue3 = ref(['212', '231'])
+    const roomTreeData = ref([
+      {
+        id: '2',
+        name: '行政中心',
+        children: [
+          {
+            id: '21',
+            name: '行政部',
+            children: [
+              {
+                id: '211',
+                name: '行政一部',
+                children: null,
+              },
+              {
+                id: '212',
+                name: '行政二部',
+                children: [],
+                disabled: true,
+              },
+            ],
+          },
+          {
+            id: '22',
+            name: '财务部',
+            children: [
+              {
+                id: '221',
+                name: '财务一部',
+                children: [],
+                disabled: true,
+              },
+              {
+                id: '222',
+                name: '财务二部',
+                children: [],
+              },
+            ],
+          },
+          {
+            id: '23',
+            name: '人力资源部',
+            children: [
+              {
+                id: '231',
+                name: '人力一部',
+                children: [],
+              },
+              {
+                id: '232',
+                name: '人力二部',
+                append: '更多示例,请下载示例项目查看',
+              },
+            ],
+          },
+        ],
+      },
+    ])
+    function doExpandTree(keys, expand) {
+      DaTreeRef.value?.setExpandedKeys(keys, expand)
+
+      const gek = DaTreeRef.value?.getExpandedKeys()
+      console.log('当前已展开的KEY ==>', gek)
+    }
+    function doCheckedTree(keys, checked) {
+      DaTreeRef.value?.setCheckedKeys(keys, checked)
+
+      const gek = DaTreeRef.value?.getCheckedKeys()
+      console.log('当前已选中的KEY ==>', gek)
+    }
+    function handleTreeChange(allSelectedKeys, currentItem) {
+      console.log('handleTreeChange ==>', allSelectedKeys, currentItem)
+    }
+    function handleExpandChange(expand, currentItem) {
+      console.log('handleExpandChange ==>', expand, currentItem)
+    }
+    return {
+      DaTreeRef,
+      roomTreeData,
+      defaultCheckedKeysValue,
+      defaultCheckedKeysValue2,
+      defaultExpandKeysValue3,
+      handleTreeChange,
+      handleExpandChange,
+      GetApiData,
+      doExpandTree,
+      doCheckedTree,
+    }
+  },
+})
+```
+
+** 更多示例请下载/导入示例项目 ZIP 查看 **
+
+### 组件参数
+
+| 属性                | 类型                            | 默认值    | 必填 | 说明                                               |
+| :------------------ | :------------------------------ | :-------- | :--- | :------------------------------------------------- |
+| data                | `Array`                         | -         | 是   | 树的数据                                           |
+| themeColor          | `String`                        | `#007aff` | 否   | 主题色,十六进制                                   |
+| defaultCheckedKeys  | `Array` \| `Number` \| `String` | -         | 否   | 默认选中的节点,单选为单个 key,多选为 key 的数组  |
+| showCheckbox        | `Boolean`                       | `false`   | 否   | 是否开启多选,默认单选                             |
+| checkStrictly       | `Boolean`                       | `false`   | 否   | 多选时,是否执行父子不关联的任意勾选,默认父子关联 |
+| field               | `Object`                        | -         | 否   | 字段对应内容,格式参考下方                         |
+| showRadioIcon       | `Boolean`                       | `true`    | 否   | 是否显示单选图标,默认显示                         |
+| onlyRadioLeaf       | `Boolean`                       | `true`    | 否   | 单选时只允许选中末级,默认可随意选中               |
+| defaultExpandAll    | `Boolean`                       | `false`   | 否   | 是否默认展开全部                                   |
+| defaultExpandedKeys | `Array`                         | -         | 否   | 默认展开的节点                                     |
+| indent              | `Number`                        | `40`      | 否   | 子项缩进距离,单位 rpx                             |
+| checkboxPlacement   | `String`                        | `left`    | 否   | 选择框的位置,可选 left/right                      |
+| loadMode            | `Boolean`                       | `false`   | 否   | 为 true 时,空的 children 数组会显示展开图标       |
+| loadApi             | `Function`                      | -         | 否   | 选择框的位置,可选 left/right                      |
+| checkedDisabled     | `Boolean`                       | `false`   | 否   | 是否渲染禁用值,默认不渲染                         |
+| packDisabledkey     | `Boolean`                       | `true`    | 否   | 是否返回已禁用的但已选中的 key,默认返回禁用已选值 |
+| expandChecked       | `Boolean`                       | `false`   | 否   | 选择时是否展开当前已选的所有下级节点,默认不展开   |
+
+**field 格式**
+
+```js
+{
+  label: 'label',
+  key: 'key',
+  children: 'children',
+  disabled: 'disabled',
+  append: 'append'
+}
+```
+
+**data 数组格式**,如果数组格式不一致,请通过`field`对象指定对应的 key 值
+
+```js
+  {
+    label: '',
+    value: '',
+    disabled: false,
+    children: [],
+  }
+```
+
+### 组件事件
+
+| 事件名称 | 回调参数                                | 说明            |
+| :------- | :-------------------------------------- | :-------------- |
+| change   | `(allCheckedKeys, currentItem) => void` | 选中时回调      |
+| expand   | `(expandState, currentItem) => void`    | 展开/收起时回调 |
+
+### 组件方法
+
+| 方法名称            | 参数             | 说明                                                                               |
+| :------------------ | :--------------- | :--------------------------------------------------------------------------------- |
+| setCheckedKeys      | `(keys,checked)` | 设置指定 key 的节点选中/取消选中的状态。注: keys 单选时为 key,多选时为 key 的数组 |
+| getCheckedKeys      | -                | 返回已选的 key                                                                     |
+| getHalfCheckedKeys  | -                | 返回半选的 key                                                                     |
+| setExpandedKeys     | `(keys,expand)`  | 设置指定 key 的节点展开/收起的状态。注:keys 为数组                                |
+| getExpandedKeys     | -                | 返回已展开的 key                                                                   |
+| getCheckedNodes     | -                | 返回已选的节点                                                                     |
+| getHalfCheckedNodes | -                | 返回半选的节点                                                                     |
+| getExpandedNodes    | -                | 返回已展开的节点                                                                   |
+
+### 组件版本
+
+v1.3.1
+
+### 差异化
+
+已通过测试
+
+> - H5 页面
+> - 微信小程序
+> - 支付宝、钉钉小程序
+> - 字节跳动、抖音、今日头条小程序
+> - 百度小程序
+> - 飞书小程序
+> - QQ 小程序
+> - 飞书小程序
+
+未测试
+
+> - 京东小程序、快手小程序由于非企业用户暂无演示
+> - 快应用、360 小程序因 Vue3 支持的原因暂无演示
+
+### 开发组
+
+[@CRLANG](https://crlang.com)

+ 76 - 0
src/components/da-tree/utils.ts

@@ -0,0 +1,76 @@
+/** 未选 */
+export const unCheckedStatus = 0
+/** 半选 */
+export const halfCheckedStatus = 1
+/** 选中 */
+export const isCheckedStatus = 2
+
+/**
+ * 深拷贝内容
+ * @param obj 拷贝对象
+ * @author crlang(https://crlang.com)
+ */
+export function deepClone(obj) {
+  if (typeof obj !== 'object' || obj == null) {
+    return obj
+  }
+  let res
+  if (obj instanceof Array) {
+    res = []
+  } else {
+    res = {}
+  }
+  for (const key in obj) {
+    // eslint-disable-next-line no-prototype-builtins
+    if (obj.hasOwnProperty(key)) {
+      res[key] = deepClone(obj[key])
+    }
+  }
+  return res
+}
+
+/**
+ * 获取所有指定的节点
+ * @param type
+ * @param value
+ */
+export function getAllNodes(list, type, value, packDisabledkey = true) {
+  if (!list || list.length === 0) {
+    return []
+  }
+
+  const res = []
+  for (let i = 0; i < list.length; i++) {
+    const item = list[i]
+    if (item[type] === value) {
+      if ((packDisabledkey && item.disabled) || !item.disabled) {
+        res.push(item)
+      }
+    }
+  }
+
+  return res
+}
+
+/**
+ * 获取所有指定的key值
+ * @param type
+ * @param value
+ */
+export function getAllNodeKeys(list, type, value, packDisabledkey = true) {
+  if (!list || list.length === 0) {
+    return []
+  }
+
+  const res = []
+  for (let i = 0; i < list.length; i++) {
+    const item = list[i]
+    if (item[type] === value) {
+      if ((packDisabledkey && item.disabled) || !item.disabled) {
+        res.push(item.key)
+      }
+    }
+  }
+
+  return res
+}

+ 6 - 5
src/config.js

@@ -1,9 +1,10 @@
 // 应用全局配置
 export default {
-	baseUrl: process.env.NODE_ENV == 'production' ?
-		'https://api.mxrvending.com:9050' :
-		'https://ai.tanbin.vip',
-	// baseUrl: 'https://api.mxrvending.com:9050',
+	// baseUrl: process.env.NODE_ENV == 'production' ?
+	baseUrl:'https://api.mxrvending.com:9050',
+	// 	'https://ai.tanbin.vip',
+	 //baseUrl: 'https://api.mxrvending.com:9050',
+	//baseUrl:'http://119.96.194.20:9050',
 	//mock数据
 	// baseUrl: 'http://119.96.213.127:9010/mock',
 	// 应用信息
@@ -17,4 +18,4 @@ export default {
 		// 官方网站
 		site_url: 'http://www.xyvending.com/',
 	},
-}
+}

+ 3 - 3
src/manifest.json

@@ -50,7 +50,7 @@
     "quickapp" : {},
     /* 小程序特有相关 */
     "mp-weixin" : {
-        "appid" : "wxde6edfb4b2c1e7db",
+        "appid" : "wxb322e2d1719d170b",
         "setting" : {
             "urlCheck" : false
         },
@@ -65,8 +65,8 @@
     "mp-toutiao" : {
         "usingComponents" : true
     },
-    "uniStatistics": {  
-        "enable": false
+    "uniStatistics" : {
+        "enable" : false
     },
     "vueVersion" : "3"
 }

+ 19 - 0
src/package.json

@@ -0,0 +1,19 @@
+{
+    "id": "da-tree",
+    "name": "da-tree 树组件(支持单选、多选、无限级、主题色,Vue3版) ",
+    "displayName": "da-tree 树组件(支持单选、多选、无限级、主题色,Vue3版) ",
+    "version": "1.3.1",
+    "description": "一个基于 Vue3 的tree(树)组件,支持主题换色,可能是最适合你的tree(树)组件",
+    "keywords": [
+        "tree",
+        "树",
+        "树组件",
+        "da系列"
+    ],
+    "dcloudext": {
+        "category": [
+            "前端组件",
+            "通用组件"
+        ]
+    }
+}

+ 45 - 4
src/pages.json

@@ -1,9 +1,9 @@
 {
   "easycom": {
 		// 注意一定要放在custom里,否则无效,https://ask.dcloud.net.cn/question/131175
-		"custom": {  
+		"custom": {
 	        "^u-(.*)": "uview-plus/components/u-$1/u-$1.vue",
-			"^xy-(.*)": "@/components/xy-$1/xy-$1.vue" 
+			"^xy-(.*)": "@/components/xy-$1/xy-$1.vue"
 		}
 	},
 	"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
@@ -34,8 +34,49 @@
 					"navigationBarTitleText": "设备列表",
 					"navigationStyle": "custom"
 				}
-			}]
-		}
+			},{
+				"path": "devices/deviceDetail",
+				"style": {
+					"navigationBarTitleText": "设备详情",
+					"navigationStyle": "custom"
+				}
+			},{
+				"path": "devices/deviceNetHistory",
+				"style": {
+					"navigationBarTitleText": "联网记录",
+					"navigationStyle": "custom"
+				}
+			},{
+				"path": "devices/deviceEvents",
+				"style": {
+					"navigationBarTitleText": "设备事件",
+					"navigationStyle": "custom"
+				}
+			},{
+				"path": "logs/deviceLogs",
+				"style": {
+					"navigationBarTitleText": "设备日志",
+					"navigationStyle": "custom"
+				}
+			},{
+				"path": "logs/logFile",
+				"style": {
+					"navigationBarTitleText": "日志文件",
+					"navigationStyle": "custom"
+				}
+			}
+			]
+		}, {
+		  "root": "pages/xy_merc_be", //商户管理
+		  "pages": [{
+				  "path": "mercManager/merc",
+				  "style": {
+					  "navigationBarTitleText": "商户管理",
+					  "navigationStyle": "custom"
+				  }
+			  }
+		  ]
+	  }
 	],
 	"globalStyle": {
 		"navigationBarTextStyle": "black",

+ 12 - 3
src/pages/globalPages/components/account.vue

@@ -11,7 +11,7 @@
 					</view>
 					<view class="menu-list-content flex flex-wrap justify-start">
 						<block v-for="item1 in item.children" :key="item1.id">
-							<view class="menu-item flex flex-direction align-center">
+							<view class="menu-item flex flex-direction align-center" @click="clickMenu(item1)">
 								<view class="menu-img">
 									<xy-svg width="60" height="60" :url="`https://cdn.ossfile.mxrvending.com/assets/xy_mana_mini/images/icons/svg/${item1.meta.icon}.svg`"></xy-svg>
 								</view>
@@ -66,6 +66,15 @@
 		}
 		return tempList
 	})
+
+  /**
+   * 点击菜单
+   */
+  function clickMenu (item) {
+    console.log("888********",`/pages${item.path}`)
+
+    proxy.$tab.navigateTo(`/pages${item.path}`)
+  }
 </script>
 
 <style lang="scss" scoped>
@@ -99,7 +108,7 @@
 							height: 60rpx;
 							margin-bottom: 18rpx;
 						}
-						
+
 						.menu-name{
 							font-size: 26rpx;
 							color: #333;
@@ -113,4 +122,4 @@
 			margin-top: 50%;
 		}
 	}
-</style>
+</style>

+ 4 - 3
src/pages/globalPages/components/home.vue

@@ -56,7 +56,7 @@ const {
 } = proxy.$useDict("mqtt_cmd_templet_task");
 
 onMounted(() => {
-  console.log('平台运维', mqtt_cmd_templet_task.value)
+  console.log('平台运维', mqtt_cmd_templet_task)
 })
 
 const list1 = ref([
@@ -79,7 +79,8 @@ const list = computed(() => {
  * 点击菜单
  */
 function clickMenu (item) {
-  console.log(item)
+  console.log("888********",`/pages${item.path}`)
+
   proxy.$tab.navigateTo(`/pages${item.path}`)
 }
 </script>
@@ -129,4 +130,4 @@ function clickMenu (item) {
     margin-top: 50%;
   }
 }
-</style>
+</style>

+ 612 - 0
src/pages/xy_merc_be/mercManager/merc.vue

@@ -0,0 +1,612 @@
+<template>
+  <view class="container">
+    <u-navbar title="商户管理"
+              @rightClick="rightClick" titleStyle="color:#fff;fontSize:36rpx;"   bgColor="#2C6FF3"
+              :autoBack="true" leftIconColor="#fff" :placeholder="true">
+    </u-navbar>
+
+    <view class="content">
+
+      <view class="topbar flex flex-wrap justify-between">
+        <view class="data_total flex align-center">
+          共 {{data.total}} 条记录
+        </view>
+        <view class="search flex" @tap="openSearchMenu" style="margin-left: 20rpx;">
+          筛选<u-icon name="arrow-right" style="margin-left: 6rpx;" size="14"></u-icon>
+        </view>
+      </view>
+
+      <scroll-view  class="scrollview" :scroll-with-animation="true" scroll-y lower-threshold="100"
+                    @scrolltolower="scrolltolower" :style="{height:data.fullHeight}">
+        <view v-if="data.dataList.length>0">
+          <view class="eq-item"
+                v-for="(item1,index1) in data.dataList" :key="item1.deviceId">
+            <view class="eq-content">
+              <view class="eq-wrap">
+                <view class="eq-name flex justify-between">
+                  <view class="eq-title" v-if="item1.name">{{item1.name}}<text style="color: #666;">({{item1.mercCode}})</text></view>
+                  <view class="eq-title" v-else>{{item1.mercCode}}</view>
+                </view>
+
+                <view class="eqeq-type">
+                  <view>
+                    上级商户:
+                  </view>
+                  <view>
+                    {{item1.parentName}}({{item1.parentId}})
+                  </view>
+                </view>
+                <view class="eqeq-type">
+                  <view>
+                    登录名:
+                  </view>
+                  <view>
+                    {{item1.loginName}}
+                  </view>
+                </view>
+                <view class="eqeq-type">
+                  <view>
+                    联系人:
+                  </view>
+                  <view>
+                    {{item1.contact}}({{item1.contactPhone}})
+                  </view>
+                </view>
+                <view class="eqeq-type">
+                  <view>
+                    类型:
+                  </view>
+                  <view>
+                    {{item1.mercType}}
+                  </view>
+                </view>
+                <view class="eqeq-type">
+                  <view>
+                    更新时间:
+                  </view>
+                  <view>
+                    {{item1.createTime}}
+                  </view>
+                </view>
+                <view class="eq-toolbar flex flex-wrap justify-between">
+                  <view class="flex">
+
+                  </view>
+                  <view class="flex">
+                    <u-button size="mini" type="primary"  text="管理" @click="gotoMerc(item1)"></u-button>
+                  </view>
+                </view>
+              </view>
+            </view>
+          </view>
+          <view class="load-more" style="padding:24rpx;">
+            <u-loadmore v-if="data.dataList.length>0" :status="data.loadmoreStatus" />
+          </view>
+        </view>
+        <view v-else class='empty'>
+          <u-empty mode="data" text="数据为空"></u-empty>
+        </view>
+      </scroll-view>
+    </view>
+
+    <u-popup :show="searchMenu.showPopMenu"  duration="0" :customStyle="searchMenu.style" :round="10" mode="right" :duration="300" :overlay="false"  >
+      <view class="searchMenu popMenu flex" :style="searchMenu.searchMenu_body_style">
+        <scroll-view scroll-y class="searchMenu_body flex-wrap justify-between">
+          <view>
+            <view class="label">
+              商户名称编号
+            </view>
+            <view>
+              <u--input
+                  placeholder="商户名称/编号"
+                  border="surround"
+                  v-model="searchMenu.params.searchKey"
+              ></u--input>
+            </view>
+          </view>
+          <view>
+            <view class="label">
+              联系人
+            </view>
+            <view>
+              <u--input
+                  placeholder="联系人"
+                  border="surround"
+                  v-model="searchMenu.params.contact"
+              ></u--input>
+            </view>
+          </view>
+          <view>
+            <view class="label">
+              联系电话
+            </view>
+            <view>
+              <u--input
+                  placeholder="联系电话"
+                  border="surround"
+                  v-model="searchMenu.params.contactPhone"
+              ></u--input>
+            </view>
+          </view>
+
+          <view>
+            <view class="label">
+              机器ID号查找商家
+            </view>
+            <view class="flex flex-wrap justify-between">
+              <view class="flex">
+                <u--input
+                    placeholder="机器ID号"
+                    border="surround"
+                    v-model="searchMenu.params.deviceId"
+                ></u--input>
+              </view>
+              <view class="flex">
+                <u-button size="normal" :plain="true" text="查找" @click="doSearchMerc()"></u-button>
+              </view>
+
+            </view>
+          </view>
+          <view class="eq-item"
+                v-for="(item2,index1) in searchMenu.dataList" :key="item2.id">
+            <view class=" flex flex-wrap justify-between">
+              <view class="simple_merc" @tap="searchMenu.params.searchKey=item2.name">
+                {{item2.name}}/{{item2.mercCode}}
+              </view>
+<!--              <view>-->
+<!--                <u-button size="mini" :plain="true" text="选择" @click="searchMenu.params.searchKey=item2.name"></u-button>-->
+<!--              </view>-->
+            </view>
+          </view>
+
+        </scroll-view>
+        <view class="searchMenu_bottom ">
+          <view class="flex flex-wrap justify-between">
+            <view>
+              <u-button size="normal" :plain="true" text="关闭" @click="doSearch(0)"></u-button>
+            </view>
+
+            <view class="flex">
+              <view>
+                <u-button size="normal"  type="primary" :plain="true" text="重置" @click="resetSearchMenu"></u-button>
+              </view>
+              <view style="margin-left: 20rpx">
+                <u-button size="normal"  type="primary"  text="查找" @click="doSearch(1)"></u-button>
+              </view>
+            </view>
+          </view>
+        </view>
+       </view>
+    </u-popup>
+
+  </view>
+</template>
+
+<script setup>
+
+import {getCurrentInstance, onMounted, reactive, ref} from "vue";
+const { proxy } = getCurrentInstance();
+const instance = getCurrentInstance();
+const {
+  device_type,device_online_status,device_busy_status,device_active_status
+} = proxy.$useDict("device_type","device_online_status",'device_busy_status','device_active_status');
+const dicts = reactive({
+  device_type:[],
+  online_status:[],
+  busy_status:[],
+  active_status:[]
+})
+
+const searchMenu = reactive({
+  showPopMenu:false,
+  selStatus:0,
+  style:{
+    marginTop :'174rpx'
+  },
+  searchMenu_body_style:{
+    height:'600rpx'
+  },
+  params:{
+    searchKey:undefined,
+    contact:undefined,
+    contactPhone:undefined,
+    deviceId:undefined,
+  },
+  reqUrls:{
+    searchMerc:'/merc/be/merc/searchTreeList',
+  },
+  dataList:[],
+});
+
+function openSearchMenu(){
+  searchMenu.showPopMenu = true;
+}
+function resetSearchMenu(){
+  searchMenu.params.searchKey = undefined;
+  searchMenu.params.contact = undefined;
+  searchMenu.params.contactPhone = undefined;
+}
+function doSearch(o){
+  if(o==0)resetSearchMenu();
+
+  setTimeout(function(){
+    searchMenu.showPopMenu = false;
+    data.curPage = 1;
+    getList()
+  }, 500);
+}
+
+const data = reactive({
+  reqUrls:{
+    list:'/merc/be/merc/page',
+    token:'/merc/mini/adminToMerc',
+    tree:'/sys/sys-code-configure/tree'
+  },
+  curPage:1,
+  loadmoreStatus:'nomore',
+  params:{
+    page: {
+      current: 1,
+      size: 10,
+      orders: [
+        {
+          asc: false,
+          column: 'create_time'
+        }]
+    },
+  },
+  fullHeight:0,
+  screenHeight:1920,
+  // screenWidht:1080,
+  dataList:[],
+  total:0,
+})
+
+
+onMounted(() => {
+  dicts.device_type = ([{value:null,label:'全部'}].concat(device_type.value));
+  dicts.online_status = ([{value:null,label:'全部'}].concat(device_online_status.value));
+
+  data.screenHeight = uni.getSystemInfoSync().windowHeight;//-uni.upx2px(100);
+  searchMenu.searchMenu_body_style.height = (data.screenHeight-90)+"px";
+
+  getList();
+
+  const query = uni.createSelectorQuery().in(instance);
+  query.select(".scrollview").boundingClientRect((r) => {
+    uni.getSystemInfo({
+      success(res) {
+        // 针对iPhone X等机型底部安全距离做适配
+        const model = res.model;
+        const modelInclude = [
+          "iPhone X",
+          'iPhone XR',
+          "iPhone XS",
+          "iPhone XS MAX",
+          "iPhone 12/13 mini",
+          "iPhone 12/13 (Pro)",
+          "iPhone 12/13 Pro Max",
+          "iPhone 14 Pro Max"
+        ];
+        let safeDistance = modelInclude.includes(model)
+        //动态设置商品区域高度
+        console.log(res.windowHeight, r.top)
+        // data.screenWidht = data1.right;
+
+        let h = 0;
+        if (safeDistance) {
+
+          h = res.windowHeight - r.top;
+        } else {
+          h = res.windowHeight - r.top;
+        }
+        data.fullHeight+=h+"px";
+
+
+        //console.log("--------",searchMenu.searchMenu_body_style);
+      },
+    });
+  }).exec();
+
+});
+function scrolltolower(){
+  if (data.loadmoreStatus == 'nomore') return
+  data.curPage++
+  getList()
+}
+function getList () {
+  return new Promise((resolve, reject) => {
+    data.params.page.current = data.curPage;
+    data.params.searchKey = searchMenu.params.searchKey;
+    data.params.contact = searchMenu.params.contact;
+    data.params.contactPhone = searchMenu.params.contactPhone
+    if(data.curPage == 1){
+      data.dataList = [];
+    }
+    proxy.$request({
+      url: data.reqUrls.list,
+      data: data.params,
+      method:'post'
+    }).then(res => {
+      console.log('***',res);
+
+      if (res.records) {
+        data.dataList = data.dataList.concat(res.records)
+      }
+      data.total = res.total;
+      if (res.records.length < 10) {
+        data.loadmoreStatus = "nomore"
+      } else {
+        data.loadmoreStatus = "loadmore"
+      }
+
+      resolve(res)
+    }).catch(err => {
+      reject(err)
+    })
+  })
+}
+
+function doSearchMerc(){
+  return new Promise((resolve, reject) => {
+
+    proxy.$request({
+      url: searchMenu.reqUrls.searchMerc,
+      data: {searchKey:searchMenu.params.deviceId},
+      method:'post'
+    }).then(data => {
+      searchMenu.dataList = data;
+      console.log('机器号查找商户', searchMenu.dataList)
+      resolve(res)
+    }).catch(err => {
+      reject(err)
+    })
+  })
+}
+
+function gotoMerc(merc){
+  proxy.$request({
+    url: data.reqUrls.token,
+    data: {mercId:merc.id},
+    method:'post'
+  }).then(data => {
+    console.log('token',data);
+    wx.navigateToMiniProgram({
+      appId:'wxde6edfb4b2c1e7db',
+      path:'pages/login?mercId='+merc.id,
+      success(res){
+        console.log(res);
+      }
+    })
+    resolve(res)
+  }).catch(err => {
+    reject(err)
+  })
+  //proxy.$tab.navigateTo(`/pages/xy_system/devices/deviceDetail?id=${o}`)
+}
+function rightClick () {
+  console.log('点击了右侧')
+}
+</script>
+
+<style lang="scss" scoped>
+.container {
+  .content {
+    font-size: 28rpx;
+    font-weight: 500;
+    text-align: center;
+    //margin-top: 135rpx;
+  }
+
+  .empty {
+    margin-top: 50%;
+  }
+  .search{
+    margin: 20rpx 10rpx;
+  }
+  .popMenu{
+    background-color: white;
+    padding: 20rpx 20rpx 20rpx 20rpx;
+  }
+  .searchMenu{
+    width: 600rpx;
+    font-size: 28rpx;
+    flex-direction: column;
+    position: relative;
+    .searchMenu_body{
+      overflow-y: scroll;
+      margin-bottom: 100rpx;
+    }
+    .label{
+      text-align: left;
+      margin: 20rpx 10rpx 10rpx 10rpx;
+      width: 600rpx;
+    }
+    .searchMenu_bottom{
+      width: 100%;
+      padding: 20rpx;
+      position: absolute;
+      bottom:0rpx;
+      left:0;
+    }
+  }
+  .simple_merc{
+    padding: 14rpx;
+    background-color: #F7F7F7;
+    margin-bottom: 2rpx;
+    border-radius: 8rpx;
+  }
+  .topbar{
+    background-color: white;
+    padding: 0rpx 20rpx 0rpx 20rpx;
+  }
+  .data_total{
+    padding-left: 20rpx;
+    background-color: white;
+    text-align: center;
+    font-size: 28rpx;
+  }
+  .tab-list {
+    width: 100%;
+    background-color: #fff;
+    padding: 0 26rpx;
+    .tab-item{
+      padding:0 20rpx;
+      //width: 200rpx;
+      height: 62rpx;
+      background: #F7F7F7;
+      border-radius: 10rpx;
+      font-size: 28rpx;
+      font-weight: 500;
+      color: #777777;
+      line-height: 62rpx;
+      margin-bottom: 14rpx;
+      text-align:center;
+      &.tab-show{
+        background: #F4F8FF;
+        color:#2C6FF3;
+      }
+    }
+  }
+
+  .scrollview{
+    padding: 16rpx 16rpx 16rpx 16rpx;
+    padding-bottom:calc(20rpx + env(safe-area-inset-bottom) / 2);
+
+    .xy-card {
+      margin-bottom: 24rpx;
+    }
+
+    .eq-item {
+      position: relative;
+
+      &+.eq-item {
+        padding-top: 12rpx;
+      }
+
+      .eq-content {
+
+        .eq-wrap {
+          border-radius: 8rpx;
+          background-color: white;//rgb(245, 248, 251);
+          box-sizing: border-box;
+          padding: 24rpx 12rpx;
+          font-size: 26rpx;
+
+          .eq-name {
+            text-align: left;
+            font-size: 32rpx;
+            font-weight: bold;
+            margin-bottom: 12rpx;
+            position: relative;
+
+            >.eq-title{
+              width: 420rpx;
+              >text{
+                font-size: 30rpx;
+                color: #666;
+                font-weight: normal;
+              }
+            }
+
+            .eq-status-box{
+              float: right;
+              position: absolute;
+              right:0;
+              top:0;
+            }
+
+            .eq-status {
+              font-size: 28rpx;
+              color: #666;
+              font-weight: normal;
+              margin-left: 12rpx;
+
+              >text {
+                display: inline-block;
+                background-color: #666;
+                width: 16rpx;
+                height: 16rpx;
+                border-radius: 16rpx;
+                margin-right: 12rpx;
+
+              }
+
+              &.online {
+                color: #f56c6c;
+
+                >text {
+                  background-color: green;
+                }
+              }
+            }
+          }
+          .eq-toolbar{
+            margin-top: 10rpx;
+          }
+        }
+
+        .eqeq-type {
+          display: flex;
+          flex-direction: row;
+          align-items: center;
+          font-size: 28rpx;
+
+          >view:nth-child(1) {
+            text-align: left;
+            color: #000;
+            width: 200rpx;
+          }
+
+          >view:nth-child(2) {
+            color: #666;
+            padding-left: 6rpx;
+          }
+        }
+
+        .eqeq-type+.eqeq-type{
+          margin-top: 8rpx;
+        }
+        .message{
+          min-height: 100rpx;
+          border-style: solid;
+          border-width: 2rpx;
+          border-color: #cccccc;
+          border-radius: 10rpx;
+          margin: 20rpx 10rpx;
+          padding: 10rpx 10rpx;
+          background-color:#FFF0F5;
+          text-align: left;
+        }
+        .status {
+          width: 130rpx;
+          height: 120rpx;
+          box-sizing: border-box;
+          border-radius: 120rpx;
+          // border: 6rpx solid #2C6FF3;
+          text-align: center;
+          display: flex;
+          flex-flow: column;
+          justify-content: space-around;
+          align-items: center;
+          position: absolute;
+          right: 12rpx;
+          bottom: 24rpx;
+
+          .s-name {
+            font-size: 28rpx;
+            padding-top: 20rpx;
+            font-weight: bold;
+          }
+
+          .s-num {
+            font-size: 32rpx;
+            padding-bottom: 20rpx;
+          }
+        }
+      }
+    }
+  }
+
+}
+</style>

+ 1120 - 0
src/pages/xy_system/devices/deviceDetail.vue

@@ -0,0 +1,1120 @@
+<template>
+	<view class="container">
+    <u-navbar title="设备详情"
+              @rightClick="rightClick" titleStyle="color:#fff;fontSize:36rpx;"   bgColor="#2C6FF3"
+              :autoBack="true" leftIconColor="#fff" :placeholder="true">
+    </u-navbar>
+		<view class="content">
+			<view class="xy-card">
+				<view class="top">
+					<view>
+						<view class="t-left">
+							<view v-if="detail.deviceName">{{detail.deviceName}}<text
+									style="color: #666;font-size: 26rpx;">({{detail.deviceId}})</text></view>
+							<view v-else>{{detail.deviceId}}</view>
+						</view>
+
+						<view class="flex" style="white-space: nowrap;">
+							<view :class="[detail.netStateName=='在线'?'t-right':'t-right off']">
+								{{detail.netStateName||'离线'}}
+							</view>
+							<view :class="[detail.busyState==1?'t-right':'t-right off']">
+								{{detail.busyState==1?'运营中':'已停运'}}
+							</view>
+						</view>
+
+					</view>
+					<view class="flex flex-between" style="margin-top: 24rpx;">
+						<view>
+							<xbutton v-show="activityState.length>0" round='25rpx' padding='4rpx 10rpx' size="mini" style="margin-right:12rpx;" :bgColor="activityState=='异常'?'#ff0000':'#66CC00'"
+								color="#fff" >{{activityState}}</xbutton>
+							<xbutton round='25rpx' padding='4rpx 10rpx' size="mini" style="margin-right:12rpx;" :bgColor="detail.deviceStatus.doorStateL==2?'#66CC00':'#FFCC33'"
+								color="#fff" >{{detail.deviceStatus.doorStateL==2?'已关门':'已开门'}}</xbutton>
+
+						</view>
+
+						<view class="flex-between">
+							<xbutton size="mini" style="margin-right:12rpx;" bgColor="#fff" borderColor="#2C6FF3"
+								color="#2C6FF3" @click="downloadQr">下载柜门二维码</xbutton>
+							<xbutton size="mini" @click="btnClick('编辑')">编辑</xbutton>
+						</view>
+
+					</view>
+				</view>
+				<view :class="[isMore?'center center-more':'center']">
+					<view class="d-line">
+						<view class="c-item">
+							<view class="name">
+								编号:
+							</view>
+							<view class="val">
+								{{detail.deviceId||'/'}}
+								<text @click="copy(detail.deviceId)" v-if="detail.mercDeviceCode">复制</text>
+							</view>
+						</view>
+						<view class="c-item">
+							<view class="name">
+								商户:
+							</view>
+							<view class="val">
+								{{detail.mercName||'/'}}
+							</view>
+						</view>
+					</view>
+					<view class="d-line">
+						<view class="c-item">
+							<view class="name">
+								温度:
+							</view>
+							<view class="val">
+								{{detail.deviceStatus.tempValue}}
+							</view>
+						</view>
+						<view class="c-item">
+							<view class="name">
+								信号强度:
+							</view>
+							<view class="val net">
+								{{detail.deviceStatus.netDbm||'/'}}d<text
+									v-if="detail.deviceStatus.netDbm<-90">(弱)</text>
+								<text
+									v-else-if="detail.deviceStatus.netDbm>-90&&detail.deviceStatus.netDbm<-40">(中)</text>
+								<text
+									v-else-if="detail.deviceStatus.netDbm>-40&&detail.deviceStatus.netDbm<0">(强)</text>
+								<text v-else-if="detail.deviceStatus.netDbm==0">(无)</text>
+							</view>
+						</view>
+					</view>
+					<view class="c-item">
+						<view class="name">
+							流量卡号:
+						</view>
+						<view class="val">
+							{{detail.deviceSysinfo.simIccid||'/'}} <text @tap="detail.deviceSysinfo.simIccid"> 复制</text>
+						</view>
+					</view>
+					<view class="c-item">
+						<view class="name">
+							软件版本:
+						</view>
+						<view class="val">
+							{{detail.deviceSysinfo.appUpmVersion||'/'}}
+						</view>
+					</view>
+					<view class="c-item">
+						<view class="name">
+							最后更新时间:
+						</view>
+						<view class="val">
+							{{detail.updateTime||'/'}}
+						</view>
+					</view>
+					<view class="c-item">
+						<view class="name">
+							激活状态:
+						</view>
+						<view class="val" v-if="detail.activeState==1">
+							已激活
+						</view>
+						<view class="val" v-else>
+							未激活
+						</view>
+					</view>
+					<view class="c-item">
+						<view class="name">
+							激活时间:
+						</view>
+						<view class="val">
+							{{detail.activeTime||'/'}}
+						</view>
+					</view>
+					<view class="c-item">
+						<view class="name">
+							区域:
+						</view>
+						<view class="val">
+							{{detail.districtName||'/'}}
+						</view>
+					</view>
+					<view class="d-line">
+						<view class="c-item">
+							<view class="name">
+								点位:
+							</view>
+							<view class="val">
+								{{detail.placeName||'/'}}
+							</view>
+						</view>
+						<view class="c-item">
+							<view class="name">
+								线路:
+							</view>
+							<view class="val">
+								{{detail.placeLineName||'/'}}
+							</view>
+						</view>
+					</view>
+
+					<view class="c-item">
+						<view class="name">
+							设备位置:
+						</view>
+						<view class="val" @click="showPos" style="width:120rpx;">
+							<u-icon slot="right" size="20" name="map"></u-icon>
+						</view>
+					</view>
+
+					<view class="unfold" @click="isMore=!isMore">
+						<u-icon :name="isMore?'arrow-down':'arrow-up'" color="#999" size="20"></u-icon>
+					</view>
+				</view>
+
+				<view class="bot">
+					<view>
+						<button class="cu-btn cu-btn1" style="width:150rpx;" @click="btnClick('设备重启')">设备重启</button>
+					</view>
+					<view>
+						<button class="cu-btn cu-btn2" style="width:140rpx;" @click="btnClick('声音设置')">声音设置</button>
+					</view>
+					<view>
+						<button class="cu-btn cu-btn2" style="width: 140rpx;" @click="btnClick('温度设置')">温度设置</button>
+					</view>
+					<view>
+						<button class="cu-btn cu-btn2" style="width: 140rpx;" @click="btnClick('灯光设置')">灯光设置</button>
+					</view>
+
+				</view>
+				<view class="bot">
+					<view>
+						<button class="cu-btn cu-btn2" style="width: 140rpx;" @click="btnClick('清除故障')">清除故障</button>
+					</view>
+					<view>
+						<button class="cu-btn cu-btn3" style="width: 140rpx;"
+							@click="btnClick(btnState)">{{btnState}}</button>
+					</view>
+				</view>
+			</view>
+
+			<view class="xy-card" style="width:750rpx;margin-left: -24rpx;border-radius: 0;">
+				<u-tabs :list="chartTab" :scrollable="false" @click="tabClick" lineColor="#2C6FF3"></u-tabs>
+			</view>
+			<view class="xy-card">
+				<view class="chart-content">
+					<view class="total" v-if="current=='经营数据'">
+						<view style="width:200rpx;">
+							<u-subsection activeColor="#2C6FF3" :list="totalTab" :current="time" @change="totalChange">
+							</u-subsection>
+						</view>
+						<view class="t-content">
+							<view class="t-item">
+								<view class="t-name">
+									销售额
+								</view>
+								<view class="t-num">
+									¥<text>{{$xy.delMoney(countData.salesMoney)}}</text>
+								</view>
+							</view>
+							<view class="t-item">
+								<view class="t-name">
+									订单数
+								</view>
+								<view class="t-num">
+									<text>{{countData.salesCount||0}}</text>
+								</view>
+							</view>
+							<view class="t-item">
+								<view class="t-name">
+									退款
+								</view>
+								<view class="t-num">
+									¥<text>{{$xy.delMoney(countData.refundMoney)}}</text>
+								</view>
+							</view>
+						</view>
+
+						<view class="more-data">
+							<xbutton width="140" @click="orderDetails">
+								交易明细
+							</xbutton>
+							<xbutton style="margin-left: 20rpx;" width="140" @click="more">
+								销售统计
+							</xbutton>
+						</view>
+					</view>
+					<view class="chart" style="height: 600rpx;" v-else>
+						<qiun-data-charts canvasId="canvasString" :canvas2d="true" :ontouch="true" type="line"
+							:opts="opts" :chartData="chartData" :errorMessage="errorMessage" />
+					</view>
+				</view>
+			</view>
+
+			<view class="xy-card">
+				<view class="title">
+					商品管理
+				</view>
+				<view class="t-content">
+					<view class="t-item">
+						<view class="t-name">
+							在售商品种类
+						</view>
+						<view class="t-num">
+							<text>{{goodsManageData.categoryNum||0}}</text>种
+						</view>
+					</view>
+					<view class="t-item">
+						<view class="t-name">
+							在售库存
+						</view>
+						<view class="t-num">
+							<text>{{goodsManageData.stock||0}}</text>
+						</view>
+					</view>
+					<view class="t-item">
+						<view class="t-name">
+							上次补货后库存
+						</view>
+						<view class="t-num">
+							<text>{{goodsManageData.afterFillStock||0}}</text>
+						</view>
+					</view>
+				</view>
+				<view class="bot bot1">
+					<button class="cu-btn cu-btn2" style="width:140rpx;"
+						@click="$tab.navigateTo(`/pages/equipment/comManage?id=${id}&deviceName=${detail.deviceName}`)">管理商品</button>
+					<button class="cu-btn cu-btn2" style="width: 140rpx;margin:0 12rpx;"
+						@click="$tab.navigateTo(`/pages/replenish/replenishmentRecord?id=${id}`)">补货记录</button>
+					<button class="cu-btn cu-btn3" style="width: 140rpx;"
+						@click="$tab.navigateTo(`/pages/replenish/replenishmentHomePage?id=${id}&&deviceName=${detail.deviceName}`)">补货</button>
+				</view>
+			</view>
+
+			<xpopup :show="qrcodeShow" mode="center" @close="qrcodeClose" :showBtn="false">
+				<view class="qrcode-content flex flex-direction align-center">
+					<view class="canvas-box">
+						<u--image width="400rpx" height="400rpx" :src="qrCodeImg" mode="aspectFit"
+							:lazy-load="true"></u--image>
+					</view>
+					<view class="save-qrcode" slot="botton">
+						<xbutton @click="handleImgSave">保存二维码</xbutton>
+					</view>
+				</view>
+			</xpopup>
+
+			<!-- <view class="xy-card">
+				<view class="title">
+					在线情况
+				</view>
+				<scroll-view style="height: 410rpx;" scroll-y>
+					<view class="l-content" v-if="netRecords&&netRecords.length>0">
+						<view class="l-item" v-for="(item,index) in netRecords" :key="item.id">
+							<view class="l-time">
+								{{item.createTime}}
+							</view>
+							<view class="l-status">
+								{{item.netStatus}}
+							</view>
+						</view>
+					</view>
+					<view class="l-content" v-else>
+						<u-empty text="没有记录~"></u-empty>
+					</view>
+				</scroll-view>
+			</view>
+
+			<view class="xy-card">
+				<view class="title">
+					锁异常记录
+				</view>
+				<scroll-view style="height: 410rpx;" scroll-y>
+					<view class="l-content" v-if="errRecordLock&&errRecordLock.length>0">
+						<view class="l-item" v-for="(item,index) in errRecordLock" :key="item.id">
+							<view class="l-time">
+								{{item.createTime}}
+							</view>
+							<view class="l-status">
+								{{item.errorDescript}}
+							</view>
+						</view>
+					</view>
+					<view class="l-content" v-else>
+						<u-empty text="设备状态很好,没有异常~"></u-empty>
+					</view>
+				</scroll-view>
+			</view>
+
+			<view class="xy-card">
+				<view class="title">
+					摄像头故障记录
+				</view>
+				<scroll-view style="height: 410rpx;" scroll-y>
+					<view class="l-content" v-if="errRecordCamera&&errRecordCamera.length>0">
+						<view class="l-item" v-for="(item,index) in errRecordCamera" :key="item.id">
+							<view class="l-time">
+								{{item.createTime}}
+							</view>
+							<view class="l-status">
+								{{item.errorDescript}}
+							</view>
+						</view>
+					</view>
+					<view class="l-content" v-else>
+						<u-empty text="设备状态很好,没有任何故障~"></u-empty>
+					</view>
+				</scroll-view>
+			</view>
+
+			<view class="xy-card">
+				<view class="title">
+					温度记录
+				</view>
+				<scroll-view style="height: 410rpx;" scroll-y>
+					<view class="l-content" v-if="deviceTempRecords&&deviceTempRecords.length>0">
+						<view class="l-item" v-for="(item,index) in deviceTempRecords" :key="item.id">
+							<view class="l-time">
+								{{item.createTime}}
+							</view>
+							<view class="l-status">
+								温度为:{{item.tempValue}}
+							</view>
+						</view>
+					</view>
+					<view class="l-content" v-else>
+						<u-empty text="没有温度信息~"></u-empty>
+					</view>
+				</scroll-view>
+			</view>
+
+			<view class="xy-card">
+				<view class="title">
+					操作记录
+				</view>
+				<scroll-view style="height: 410rpx;" scroll-y>
+					<view class="l-content" v-if="operaRecords&&operaRecords.length>0">
+						<view class="l-item" v-for="(item,index) in operaRecords" :key="item.id">
+							<view class="l-time">
+								{{item.createTime}}
+							</view>
+							<view class="l-status">
+								{{item.changeBefore}}变更为{{item.changeAfter}}
+							</view>
+						</view>
+					</view>
+					<view class="l-content" v-else>
+						<u-empty text="没有任何记录~"></u-empty>
+					</view>
+				</scroll-view>
+			</view> -->
+
+			<xpopup :show="show" @close="close" @confirm="submit" :showBtn="true" :title="title">
+				<!-- 设备编辑 -->
+				<view class="popup-content" v-if="title=='编辑'">
+					<u--form labelPosition="left" :model="editForm">
+						<u-form-item labelWidth="90" label="名称" prop="name" borderBottom ref="item1">
+							<u--input placeholder="设置机器名称" v-model="editForm.name" border="none"></u--input>
+						</u-form-item>
+						<u-form-item labelWidth="90" label="区域" prop="areaName" borderBottom ref="item1"
+							@click.native="areaChoose">
+							<u--input placeholder="设置机器区域" v-model="editForm.areaName" readonly
+								border="none"></u--input>
+						</u-form-item>
+						<u-form-item labelWidth="90" label="线路" prop="lineName" borderBottom ref="item1"
+							@click.native="lineChoose">
+							<u--input placeholder="设置机器线路" v-model="editForm.lineName" readonly
+								border="none"></u--input>
+						</u-form-item>
+						<u-form-item labelWidth="90" label="点位" prop="placeName" borderBottom ref="item1"
+							@click.native="placeChoose">
+							<u--input placeholder="设置机器点位" v-model="editForm.placeName" readonly
+								border="none"></u--input>
+						</u-form-item>
+						<u-form-item labelWidth="90" label="位置" prop="lon" borderBottom @click.native="getLocation">
+							<u--input placeholder="设置机器位置" readonly v-model="devicePos" border="none"></u--input>
+							<u-icon slot="right" size="26" name="map"></u-icon>
+						</u-form-item>
+					</u--form>
+				</view>
+
+				<!-- 设备重启 -->
+				<view class="popup-content restart" v-if="title=='设备重启'">
+					是否确定重启设备?
+				</view>
+
+				<!-- 暂停营业 -->
+				<view class="popup-content restart" v-if="title=='暂停营业'||title=='开始营业'">
+					是否确定{{btnState}}?
+				</view>
+
+				<!-- 声音设置 -->
+				<view class="popup-content" v-if="title=='声音设置'">
+					<u-slider :showValue="true" min="0" max="25" v-model="voice"></u-slider>
+				</view>
+
+				<!-- 温度设置 -->
+				<view class="popup-content temp" v-if="title=='温度设置'">
+					<view class="flex" style="margin-top: 0;">
+						<view class="lab" style="margin-top: -10rpx;">工作模式:</view>
+						<u-radio-group v-model="tempDetail.selTempWorkModel" placement="row">
+							<u-radio :customStyle="{marginBottom:'16rpx',marginLeft:'16rpx'}"
+								v-for="(item, index) in tempWorkModels" :key="index" :label="item.name" :name="item.id">
+							</u-radio>
+						</u-radio-group>
+					</view>
+					<view class="flex" style="margin-top: 0rpx; margin-bottom: 20rpx;">
+						<view class="lab">工作温度(℃):</view>
+						<view style=" margin-left: 20rpx;">
+							<u-number-box button-size="30" v-model="tempDetail.targetTemp" class='martop'>
+							</u-number-box>
+						</view>
+
+					</view>
+
+					<view class="info">时段设置为X点至Y点,结束时间需大于起始时间</view>
+					<view class="flex align-center">
+						<view class="lab">时段1:</view>
+						<view class="flex align-center  date-container marleft"
+							style="padding-left: 10rpx; padding-right: 10rpx;" @tap="pickerTimes('temp',0)">
+							<view class="time-box">
+								{{tempDetail.start1}}
+							</view>
+							<view class="lab to">至</view>
+							<view class="time-box">{{tempDetail.end1}}</view>
+						</view>
+					</view>
+					<view class="flex align-center">
+						<view class="lab">时段2:</view>
+						<view class="flex align-center  date-container marleft"
+							style="padding-left: 10rpx; padding-right: 10rpx;" @tap="pickerTimes('temp',1)">
+							<view class="time-box">
+								{{tempDetail.start2}}
+							</view>
+							<view class="lab to">至</view>
+							<view class="time-box">{{tempDetail.end2}}</view>
+						</view>
+					</view>
+					<view class="flex align-center">
+						<view class="lab">时段3:</view>
+						<view class="flex align-center  date-container marleft"
+							style="padding-left: 10rpx; padding-right: 10rpx;" @tap="pickerTimes('temp',2)">
+							<view class="time-box">
+								{{tempDetail.start3}}
+							</view>
+							<view class="lab to">至</view>
+							<view class="time-box">{{tempDetail.end3}}</view>
+						</view>
+					</view>
+					<view style="height: 20rpx;"></view>
+				</view>
+
+				<!-- 灯光设置 -->
+				<view class="popup-content temp" v-if="title=='灯光设置'">
+					<!-- 					<view class="flex" style="margin-top: 0;">
+						<view class="lab" style="margin-top: -10rpx;">是否打开:</view>
+						<u-switch v-model="lightDetail.open"></u-switch>
+					</view> -->
+
+					<view class="info">时段设置为X点至Y点,结束时间需大于起始时间</view>
+					<view class="flex align-center">
+						<view class="lab">时段1:</view>
+						<view class="flex align-center  date-container marleft"
+							style="padding-left: 10rpx; padding-right: 10rpx;" @tap="pickerTimes('light',0)">
+							<view class="time-box">
+								{{lightDetail.start1}}
+							</view>
+							<view class="lab to">至</view>
+							<view class="time-box">{{lightDetail.end1}}</view>
+						</view>
+					</view>
+					<view class="flex align-center">
+						<view class="lab">时段2:</view>
+						<view class="flex align-center  date-container marleft"
+							style="padding-left: 10rpx; padding-right: 10rpx;" @tap="pickerTimes('light',1)">
+							<view class="time-box">
+								{{lightDetail.start2}}
+							</view>
+							<view class="lab to">至</view>
+							<view class="time-box">{{lightDetail.end2}}</view>
+						</view>
+					</view>
+					<view class="flex align-center">
+						<view class="lab">时段3:</view>
+						<view class="flex align-center  date-container marleft"
+							style="padding-left: 10rpx; padding-right: 10rpx;" @tap="pickerTimes('light',2)">
+							<view class="time-box">
+								{{lightDetail.start3}}
+							</view>
+							<view class="lab to">至</view>
+							<view class="time-box">{{lightDetail.end3}}</view>
+						</view>
+					</view>
+					<view style="height: 20rpx;"></view>
+				</view>
+
+				<!-- 清除故障 -->
+				<view class="popup-content restart" v-if="title=='清除故障'">
+					是否确定清除故障?
+				</view>
+			</xpopup>
+
+
+			<!-- 区域选择弹框 -->
+			<xpopup :show="areaShow" @close="areaClose" :showBtn="false" title="选择区域">
+				<!-- 类目选择 -->
+				<scroll-view style="height: 600rpx;" scroll-y scroll-with-animation>
+					<view class="popup-content">
+						<tki-tree style="width:100%;" :range="areaList" :foldAll="false" rangeKey="name" idKey="name"
+							buttonName="选中" @btnClick="areaSubmit">
+						</tki-tree>
+					</view>
+				</scroll-view>
+			</xpopup>
+
+			<!-- 线路/点位 -->
+			<u-picker :show="pickerShow" @confirm="pickerConfirm" :closeOnClickOverlay="true" :columns="columns"
+				keyName="label" @close="pickerClose" @cancel="pickerClose"></u-picker>
+			<!-- 	温度时间选择 -->
+			<u-picker :show="showTimeSelPicker" ref="uPicker" :columns="times" @confirm="pickerTimeConfirm"
+				@change="pickerTimeChangeHandler"></u-picker>
+
+		</view>
+
+		<u-back-top :scroll-top="scrollTop" top="400"></u-back-top>
+	</view>
+</template>
+
+<script setup>
+import {getCurrentInstance, onMounted, reactive, ref} from "vue";
+const { proxy } = getCurrentInstance();
+const instance = getCurrentInstance();
+const {
+  device_type,device_online_status,device_busy_status,device_active_status
+} = proxy.$useDict("device_type","device_online_status",'device_busy_status','device_active_status');
+const dicts = reactive({
+  device_type:[],
+  online_status:[],
+  busy_status:[],
+  active_status:[]
+})
+const data = reactive({
+  activityState:'',
+  opts: {
+    padding: [15, 10, 0, 15],
+    dataLabel: false,
+    color: ['#2D54EC', '#F49B37'],
+    // 开启图表可拖拽滚动
+    // enableScroll: true,
+    dataPointShape: false, //是否显示折线上的点
+    xAxis: {
+      // 开启图表可拖拽滚动后 必配置(显示多少个)
+      // itemCount: 8,
+      rotateLabel: true,
+      labelCount: 10,
+      marginTop: 10,
+    },
+    yAxis: {
+      splitNumber: 5,
+      gridType: "dash",
+      dashLength: 8,
+      gridColor: "rgba(0, 0, 0, 0.15)",
+      data: [{
+        axisLine: false, //坐标轴轴线是否显示
+      }]
+    },
+    extra: {
+      column: {
+        width: 15,
+        seriesGap: 2,
+        barBorderRadius: [10, 10, 10, 10],
+      },
+      line: {
+        type: "curve",
+        width: 2,
+        activeType: "hollow",
+        linearType: "custom",
+        onShadow: true,
+        animation: "horizontal"
+      },
+    }
+  },
+  current: '经营数据',
+  chartTab: [{
+    name: '经营数据'
+  },
+    {
+      name: '经营图表'
+    },
+    {
+      name: '温度图表'
+    },
+    {
+      name: '信号图表'
+    }
+  ],
+  chartData: {
+    series: [],
+    categories: []
+  },
+  errorMessage: '无数据',
+  tempStatus:[
+    {value:2,label:'温控仪检测失败'},
+    {value:3,label:'温控仪故障'},
+    {value:120,label:'温控仪检测失败'},
+    {value:170,label:'未读取到温度'},
+    {value:161,label:'温控仪通讯故障'},
+    {value:162,label:'温控仪故障'},
+    {value:255,label:'无温控仪'},
+  ],
+  tempDetail: {
+    targetTemp: 4,
+    selTempWorkModel: 2,
+    start1: 0,
+    end1: 0,
+    start2: 0,
+    end2: 0,
+    start3: 0,
+    end3: 0,
+    curTimes: 0
+  },
+
+  tempWorkModels: [{
+    name: '制热',
+    id: 1
+  }, {
+    name: '制冷',
+    id: 2
+  }, {
+    name: '恒温',
+    id: 3
+  }, {
+    name: '关闭',
+    id: 4
+  }],
+  showTimeSelPicker: false,
+  timeSelPickerType: undefined,
+  times: [
+    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24],
+    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]
+  ],
+  lightDetail: {
+    open: true,
+    start1: 0,
+    end1: 0,
+    start2: 0,
+    end2: 0,
+    start3: 0,
+    end3: 0,
+    curTimes: 0
+  },
+  totalTab: ['今日', '本月'],
+  time: 0,
+  show: false,
+  popupTitle: '编辑商品',
+
+  editForm: {
+    name: null,
+    areaName: null,
+    areaId: null,
+    lineId: null,
+    placeId: null,
+    lineName: null,
+    placeName: null,
+    lon: null,
+    lat: null,
+    pos: null
+  },
+  editRules: {
+    'code': {
+      type: 'string',
+      required: true,
+      message: '必填项',
+      trigger: ['blur', 'change']
+    },
+  },
+
+  title: '', //弹框标题
+  voice: 4, //声音设置
+  light: 50, //灯光设置
+  id: null, //设备id
+  detail: {}, //设备详情
+
+  allCountData: {}, //经营统计数据显示
+
+  scrollTop: 0, //滚动距离顶部
+
+  operaRecordsFlag: false, //操作记录
+  netRecordsFlag: false, //在线情况
+  errRecordCameraFlag: false, //摄像头故障情况
+  errRecordLockFlag: false, //锁故障数据情况
+  tempRecordsFlag: false, //温度数据情况
+
+  operaRecords: [], //设备操作记录
+  netRecords: [], //在线情况记录
+  errRecordLock: [], //锁故障数据
+  errRecordCamera: [], //摄像头故障数据
+  deviceTempRecords: [], //温度记录
+
+  isMore: true,
+
+  // 编辑弹框
+  areaShow: false,
+  pickerShow: false,
+
+  areaList: [], //区域数据
+
+  columns: [], //picker数据
+
+  pickerType: 1, //1线路2点位
+
+  goodsManageData: {
+    categoryNum: 0,
+    stock: 0,
+    afterFillStock: 0
+  },
+
+  countData: {
+    salesMoney: 0,
+    salesCount: 0,
+    refundMoney: 0
+  },
+
+  pointColumns: [],
+  lineColumns: [],
+
+  qrcodeShow: false,
+  qrCodeImg: null,
+  btnState:null,
+  devicePos:null,
+})
+
+</script>
+
+<style lang="scss" scoped>
+	.container {
+		.content {
+			padding: 24rpx;
+
+			.xy-card {
+				margin-bottom: 24rpx;
+			}
+
+			.top {
+
+				>view:nth-child(1) {
+					display: flex;
+					flex-flow: row nowrap;
+					justify-content: space-between;
+
+					.t-left {
+						width: 380rpx;
+
+						>view:nth-child(1) {
+							font-size: 36rpx;
+							font-weight: bold;
+						}
+
+						>view:nth-child(2) {
+							font-size: 26rpx;
+							color: #2C6FF3;
+							border: 1rpx solid #2C6FF3;
+							border-radius: 6rpx;
+							padding: 0 8rpx;
+							background-color: rgb(243, 249, 525);
+							margin-left: 12rpx;
+						}
+					}
+
+					.t-right {
+						font-size: 30rpx;
+						color: #f56c6c;
+						position: relative;
+						margin-left: 24rpx;
+
+						&::before {
+							content: '';
+							display: inline-block;
+							background-color: green;
+							width: 16rpx;
+							height: 16rpx;
+							border-radius: 16rpx;
+							margin-right: 12rpx;
+						}
+
+						&.off {
+							color: #666;
+
+							&::before {
+								background-color: #666;
+							}
+						}
+					}
+				}
+
+				>view:nth-child(2) {
+					display: flex;
+					flex-direction: row;
+					justify-content: space-between;
+					margin-top: 12rpx;
+
+					>.cu-btn {
+						height: 40rpx;
+					}
+
+					.down-qr {
+						font-size: 26rpx;
+						color: #2C6FF3;
+						border: 1rpx solid #2C6FF3;
+						border-radius: 6rpx;
+						padding: 4rpx 8rpx;
+						background-color: rgb(243, 249, 525);
+					}
+
+					.edit {
+						font-size: 26rpx;
+						color: #fff;
+						border: 1rpx solid #2C6FF3;
+						border-radius: 6rpx;
+						padding: 4rpx 8rpx;
+						background-color: rgb(243, 249, 525);
+						margin-left: 12rpx;
+						background-color: #2C6FF3;
+					}
+				}
+			}
+
+			.center {
+				border-radius: 8rpx;
+				background-color: rgb(245, 248, 251);
+				padding: 24rpx 12rpx 42rpx;
+				font-size: 30rpx;
+				margin-top: 24rpx;
+				overflow: hidden;
+				position: relative;
+				height: 560rpx;
+				transition: all 0.5s ease 0s;
+
+				.unfold {
+					position: absolute;
+					bottom: 8rpx;
+					right: 24rpx;
+				}
+
+				&.center-more {
+					height: 170rpx;
+				}
+
+				.d-line {
+					display: flex;
+					flex-flow: row nowrap;
+					justify-content: flex-start;
+
+					>view:nth-child(1) {
+						width: 50%;
+					}
+
+					>view:nth-child(2) {
+						width: 50%;
+					}
+				}
+
+				.c-item {
+					display: flex;
+					flex-flow: row nowrap;
+					justify-content: flex-start;
+					font-size: 28rpx;
+					margin-bottom: 18rpx;
+					align-items: flex-end;
+
+					.name {
+						min-width: 100rpx;
+						color: #999;
+					}
+
+					.val {
+						color: #333;
+						padding-left: 6rpx;
+						overflow: hidden;
+						text-overflow: ellipsis;
+						white-space: nowrap;
+
+						text {
+							margin-left: 12rpx;
+							text-decoration: underline;
+							color: #2C6FF3;
+						}
+
+						&.net {
+							text {
+								text-decoration: none;
+							}
+						}
+					}
+				}
+
+
+			}
+
+			.bot {
+				display: flex;
+				flex-flow: row nowrap;
+				justify-content: space-between;
+				margin-top: 24rpx;
+
+				.cu-btn {
+					padding: 0 12rpx;
+					font-size: 26rpx;
+					height: 60rpx;
+					line-height: 50rpx;
+				}
+
+				.cu-btn1 {
+					background-color: red;
+					color: #fff;
+				}
+
+				.cu-btn2 {
+					background-color: #fff;
+					color: #2C6FF3;
+					border: 1rpx solid #2C6FF3;
+				}
+
+				.cu-btn3 {
+					background-color: #2C6FF3;
+					color: #fff;
+				}
+			}
+
+			.qrcode-content {
+				padding: 24rpx;
+
+				.save-qrcode {
+					margin-top: 24rpx;
+				}
+			}
+
+			.chart-content {
+				width: 100%;
+
+				.total {
+					display: flex;
+					flex-direction: column;
+					align-items: center;
+				}
+
+				.more {
+					background-color: #2C6FF3;
+					padding: 0 24rpx;
+					font-size: 26rpx;
+					height: 50rpx;
+					line-height: 50rpx;
+					margin-top: 24rpx;
+				}
+			}
+
+			.t-content {
+				display: flex;
+				flex-flow: row nowrap;
+				width: 100%;
+				box-sizing: border-box;
+				text-align: center;
+
+				.t-item {
+					width: 50%;
+					margin-top: 24rpx;
+
+					.t-name {
+						font-size: 28rpx;
+						line-height: 40rpx;
+					}
+
+					.t-num {
+						font-size: 28rpx;
+						line-height: 46rpx;
+						padding: 12rpx 0;
+
+						text {
+							font-size: 40rpx;
+							font-weight: bold;
+						}
+					}
+				}
+			}
+
+			.title {
+				font-size: 34rpx;
+				font-weight: bold;
+				color: #333;
+				line-height: 54rpx;
+
+			}
+
+			.bot1 {
+				display: flex;
+				justify-content: flex-end;
+			}
+
+			.l-content {
+				padding-top: 24rpx;
+
+				.l-item {
+					font-size: 28rpx;
+					line-height: 50rpx;
+					display: flex;
+					flex-direction: row;
+
+					.l-status {
+						margin-left: 24rpx;
+					}
+				}
+			}
+
+			.popup-content {
+				padding: 0 24rpx;
+			}
+
+			.restart {
+				padding: 40rpx 0;
+				font-size: 34rpx;
+				text-align: center;
+			}
+		}
+	}
+
+	.temp {
+		.info {
+			color: red;
+			height: 60rpx;
+			line-height: 60rpx;
+			text-align: center;
+		}
+
+		.flex {
+			margin-top: 20rpx;
+			height: 60rpx;
+
+			.lab {
+				line-height: 60rpx;
+				height: 60rpx;
+				text-align: center;
+				font-weight: 600;
+				margin-top: 20rpx;
+			}
+
+			.to {
+				width: 60rpx;
+				margin-left: 20rpx;
+				margin-top: 0;
+				font-weight: 500;
+			}
+
+			.time-box {
+				height: 50rpx;
+				width: 200rpx;
+				line-height: 50rpx;
+				margin-left: 20rpx;
+				text-align: center;
+				border-radius: 10rpx;
+				border-style: solid;
+				border-width: 1px;
+			}
+		}
+	}
+</style>

+ 591 - 0
src/pages/xy_system/devices/deviceEvents.vue

@@ -0,0 +1,591 @@
+<template>
+  <view class="container">
+    <u-navbar title="设备事件"
+              @rightClick="rightClick" titleStyle="color:#fff;fontSize:36rpx;"   bgColor="#2C6FF3"
+              :autoBack="true" leftIconColor="#fff" :placeholder="true">
+    </u-navbar>
+
+    <view class="content">
+
+      <view class="topbar flex flex-wrap justify-between">
+        <view class="data_total flex align-center">
+          共 {{data.total}} 条记录
+        </view>
+        <view class="search flex" @tap="openSearchMenu" style="margin-left: 20rpx;">
+          筛选<u-icon name="arrow-right" style="margin-left: 6rpx;" size="14"></u-icon>
+        </view>
+      </view>
+
+      <scroll-view  class="scrollview" :scroll-with-animation="true" scroll-y lower-threshold="100"
+                    @scrolltolower="scrolltolower" :style="{height:data.fullHeight}">
+        <view v-if="data.dataList.length>0">
+          <view class="eq-item" @click="gotoDeviceDetail(item1.deviceId)"
+                v-for="(item1,index1) in data.dataList" :key="item1.deviceId">
+            <view class="eq-content">
+              <view class="eq-wrap">
+                <view class="eq-name flex justify-between">
+                  <view class="eq-title" v-if="item1.deviceName">{{item1.deviceName}}<text style="color: #666;">({{item1.deviceId}})</text></view>
+                  <view class="eq-title" v-else>{{item1.deviceId}}</view>
+                  <view class="eq-status-box flex align-center">
+<!--                    <view class="eq-status" :class="[item1.netState==1?'online':'']"><text></text>{{item1.netState=='1'?'在线':'离线'}}-->
+<!--                    </view>-->
+                  </view>
+                </view>
+                <view class="eqeq-type">
+                  <view>
+                    事件编码:
+                  </view>
+                  <view>
+                    {{item1.mercName}}({{item1.code}})
+                  </view>
+                </view>
+                <view class="">
+                  <view class="message">
+                    {{item1.msg}}
+                  </view>
+                </view>
+                <view class="eqeq-type">
+                  <view>
+                    更新时间:
+                  </view>
+                  <view>
+                    {{item1.createTime}}
+                  </view>
+                </view>
+              </view>
+            </view>
+          </view>
+          <view class="load-more" style="padding:24rpx;">
+            <u-loadmore v-if="data.dataList.length>0" :status="data.loadmoreStatus" />
+          </view>
+        </view>
+        <view v-else class='empty'>
+          <u-empty mode="data" text="数据为空"></u-empty>
+        </view>
+      </scroll-view>
+    </view>
+
+    <u-popup :show="searchMenu.showPopMenu"  duration="0" :customStyle="searchMenu.style" :round="10" mode="right" :duration="300" :overlay="false"  >
+      <view class="searchMenu popMenu flex" :style="searchMenu.searchMenu_body_style">
+        <scroll-view scroll-y class="searchMenu_body flex-wrap justify-between">
+          <view>
+            <view class="label">
+              机器名称/编号
+            </view>
+            <view>
+              <u--input
+                  placeholder="请输入机器名称,编号"
+                  border="surround"
+                  v-model="searchMenu.params.deviceId"
+              ></u--input>
+            </view>
+          </view>
+          <view>
+            <view class="label">
+              事件
+            </view>
+            <view class="flex flex-wrap justify-between">
+              <view>
+                <u--input
+                    placeholder="事件代码"
+                    border="surround"
+                    v-model="searchMenu.params.code"
+                ></u--input>
+              </view>
+              <view>
+                <u-button size="normal" :plain="true" text="选择" @click="selectCode()"></u-button>
+              </view>
+
+            </view>
+          </view>
+        </scroll-view>
+        <view class="searchMenu_bottom ">
+          <view class="flex flex-wrap justify-between">
+            <view>
+              <u-button size="normal" :plain="true" text="关闭" @click="doSearch(0)"></u-button>
+            </view>
+
+            <view class="flex">
+              <view>
+                <u-button size="normal"  type="primary" :plain="true" text="重置" @click="resetSearchMenu"></u-button>
+              </view>
+              <view style="margin-left: 20rpx">
+                <u-button size="normal"  type="primary"  text="查找" @click="doSearch(1)"></u-button>
+              </view>
+            </view>
+          </view>
+        </view>
+        <ba-tree-picker zindex="999" ref="treePicker" :multiple='false' @select-change="treeNodeSelectChange" title="选择事件" titleColor="#ff0000"
+                        :localdata="data.treeData" valueKey="code" textKey="title" childrenKey="sonVos" />
+      </view>
+    </u-popup>
+
+  </view>
+</template>
+
+<script setup>
+
+import {getCurrentInstance, onMounted, reactive, ref} from "vue";
+const { proxy } = getCurrentInstance();
+const instance = getCurrentInstance();
+const {
+  device_type,device_online_status,device_busy_status,device_active_status
+} = proxy.$useDict("device_type","device_online_status",'device_busy_status','device_active_status');
+const dicts = reactive({
+  device_type:[],
+  online_status:[],
+  busy_status:[],
+  active_status:[]
+})
+
+const searchMenu = reactive({
+  showPopMenu:false,
+  selStatus:0,
+  style:{
+    marginTop :'174rpx'
+  },
+  searchMenu_body_style:{
+    height:'600rpx'
+  },
+  params:{
+    mercName:undefined,
+    deviceId:undefined,
+    deviceType:undefined,
+    busyState:undefined,
+    activeState:undefined,
+    no:undefined,
+  }
+});
+
+function openSearchMenu(){
+  searchMenu.showPopMenu = true;
+  getTreeData();
+}
+function resetSearchMenu(){
+  searchMenu.params.deviceId = undefined;
+  searchMenu.params.deviceType = undefined;
+  searchMenu.params.mercName = undefined;
+  searchMenu.params.no = undefined;
+}
+function selectCode(){
+  proxy.$refs.treePicker._show();
+}
+function doSearch(o){
+  if(o==0)resetSearchMenu();
+
+  setTimeout(function(){
+    searchMenu.showPopMenu = false;
+    data.curPage = 1;
+    getList()
+  }, 500);
+}
+
+function tabClick(t,v){
+
+}
+
+var aaa = ref('');
+
+
+const data = reactive({
+  reqUrls:{
+    list:'/device/device-event-msg/page',
+    tree:'/sys/sys-code-configure/tree'
+  },
+  curPage:1,
+  loadmoreStatus:'nomore',
+  params:{
+    page: {
+      current: 1,
+      size: 10,
+      orders: [
+        {
+          asc: false,
+          column: 'create_time'
+        }]
+    },
+  },
+  fullHeight:0,
+  screenHeight:1920,
+  // screenWidht:1080,
+  dataList:[],
+  total:0,
+
+  treeData1:[
+    {
+      id: 1,
+      name: '公司1',
+      children: [{
+        id: 11,
+        name: '研发部',
+        children: [{
+          id: 111,
+          name: '张三',
+
+        },{
+          id: 112,
+          name: '李四',
+
+        }]
+      },{
+        id: 12,
+        name: '综合部',
+
+      } ]
+    },
+    {
+      id: 2,
+      name: '公司2',
+      children: [{
+        id: 21,
+        name: '研发部',
+
+      },{
+        id: 22,
+        name: '综合部',
+
+      },{
+        id: 23,
+        name: '财务部',
+
+      }, ]
+    },
+    {
+      id: 3,
+      name: '公司3'
+    },
+    {
+      id: 4,
+      name: '公司4',
+      children: [{
+        id: 41,
+        name: '研发部',
+
+      }]
+    }
+  ]
+})
+
+
+onMounted(() => {
+  dicts.device_type = ([{value:null,label:'全部'}].concat(device_type.value));
+  dicts.online_status = ([{value:null,label:'全部'}].concat(device_online_status.value));
+
+  data.screenHeight = uni.getSystemInfoSync().windowHeight;//-uni.upx2px(100);
+  searchMenu.searchMenu_body_style.height = (data.screenHeight-90)+"px";
+
+  getList();
+
+  const query = uni.createSelectorQuery().in(instance);
+  query.select(".scrollview").boundingClientRect((r) => {
+    uni.getSystemInfo({
+      success(res) {
+        // 针对iPhone X等机型底部安全距离做适配
+        const model = res.model;
+        const modelInclude = [
+          "iPhone X",
+          'iPhone XR',
+          "iPhone XS",
+          "iPhone XS MAX",
+          "iPhone 12/13 mini",
+          "iPhone 12/13 (Pro)",
+          "iPhone 12/13 Pro Max",
+          "iPhone 14 Pro Max"
+        ];
+        let safeDistance = modelInclude.includes(model)
+        //动态设置商品区域高度
+        console.log(res.windowHeight, r.top)
+        // data.screenWidht = data1.right;
+
+        let h = 0;
+        if (safeDistance) {
+
+          h = res.windowHeight - r.top;
+        } else {
+          h = res.windowHeight - r.top;
+        }
+        data.fullHeight+=h+"px";
+
+
+        //console.log("--------",searchMenu.searchMenu_body_style);
+      },
+    });
+  }).exec();
+
+});
+function scrolltolower(){
+  if (data.loadmoreStatus == 'nomore') return
+  data.curPage++
+  getList()
+}
+function getList () {
+  return new Promise((resolve, reject) => {
+    data.params.page.current = data.curPage;
+    data.params.deviceId = searchMenu.params.deviceId;
+    data.params.code = searchMenu.params.code;
+    if(data.curPage == 1){
+      data.dataList = [];
+    }
+    proxy.$request({
+      url: data.reqUrls.list,
+      data: data.params,
+      method:'post'
+    }).then(res => {
+      console.log('***',res);
+
+      if (res.records) {
+        data.dataList = data.dataList.concat(res.records)
+      }
+      data.total = res.total;
+      if (res.records.length < 10) {
+        data.loadmoreStatus = "nomore"
+      } else {
+        data.loadmoreStatus = "loadmore"
+      }
+
+      resolve(res)
+    }).catch(err => {
+      reject(err)
+    })
+  })
+}
+
+function getTreeData () {
+  return new Promise((resolve, reject) => {
+
+    proxy.$request({
+      url: data.reqUrls.tree,
+      data: {code:[]},
+      method:'post'
+    }).then(res => {
+      data.treeData = res;
+      console.log('---***',data.treeData,data.treeData[0],data.treeData[1]);
+      resolve(res)
+    }).catch(err => {
+      reject(err)
+    })
+  })
+}
+function treeNodeSelectChange(ids, names){
+    searchMenu.params.code = ids[0]
+}
+function gotoDeviceDetail(o){
+  //proxy.$tab.navigateTo(`/pages/xy_system/devices/deviceDetail?id=${o}`)
+}
+function rightClick () {
+  console.log('点击了右侧')
+}
+</script>
+
+<style lang="scss" scoped>
+.container {
+  .content {
+    font-size: 28rpx;
+    font-weight: 500;
+    text-align: center;
+    //margin-top: 135rpx;
+  }
+
+  .empty {
+    margin-top: 50%;
+  }
+  .search{
+    margin: 20rpx 10rpx;
+  }
+  .popMenu{
+    background-color: white;
+    padding: 20rpx 20rpx 20rpx 20rpx;
+  }
+  .searchMenu{
+    width: 600rpx;
+    font-size: 28rpx;
+    flex-direction: column;
+    position: relative;
+    .searchMenu_body{
+      overflow-y: scroll;
+      margin-bottom: 100rpx;
+    }
+    .label{
+      text-align: left;
+      margin: 20rpx 10rpx 10rpx 10rpx;
+      width: 600rpx;
+    }
+    .searchMenu_bottom{
+      width: 100%;
+      padding: 20rpx;
+      position: absolute;
+      bottom:0rpx;
+      left:0;
+    }
+  }
+  .topbar{
+    background-color: white;
+    padding: 0rpx 20rpx 0rpx 20rpx;
+  }
+  .data_total{
+    padding-left: 20rpx;
+    background-color: white;
+    text-align: center;
+    font-size: 28rpx;
+  }
+  .tab-list {
+    width: 100%;
+    background-color: #fff;
+    padding: 0 26rpx;
+    .tab-item{
+      padding:0 20rpx;
+      //width: 200rpx;
+      height: 62rpx;
+      background: #F7F7F7;
+      border-radius: 10rpx;
+      font-size: 28rpx;
+      font-weight: 500;
+      color: #777777;
+      line-height: 62rpx;
+      margin-bottom: 14rpx;
+      text-align:center;
+      &.tab-show{
+        background: #F4F8FF;
+        color:#2C6FF3;
+      }
+    }
+  }
+
+  .scrollview{
+    padding: 16rpx 16rpx 16rpx 16rpx;
+    padding-bottom:calc(20rpx + env(safe-area-inset-bottom) / 2);
+
+    .xy-card {
+      margin-bottom: 24rpx;
+    }
+
+    .eq-item {
+      position: relative;
+
+      &+.eq-item {
+        padding-top: 12rpx;
+      }
+
+      .eq-content {
+
+        .eq-wrap {
+          border-radius: 8rpx;
+          background-color: white;//rgb(245, 248, 251);
+          box-sizing: border-box;
+          padding: 24rpx 12rpx;
+          font-size: 26rpx;
+
+          .eq-name {
+            text-align: left;
+            font-size: 32rpx;
+            font-weight: bold;
+            margin-bottom: 12rpx;
+            position: relative;
+
+            >.eq-title{
+              width: 420rpx;
+              >text{
+                font-size: 30rpx;
+                color: #666;
+                font-weight: normal;
+              }
+            }
+
+            .eq-status-box{
+              float: right;
+              position: absolute;
+              right:0;
+              top:0;
+            }
+
+            .eq-status {
+              font-size: 28rpx;
+              color: #666;
+              font-weight: normal;
+              margin-left: 12rpx;
+
+              >text {
+                display: inline-block;
+                background-color: #666;
+                width: 16rpx;
+                height: 16rpx;
+                border-radius: 16rpx;
+                margin-right: 12rpx;
+
+              }
+
+              &.online {
+                color: #f56c6c;
+
+                >text {
+                  background-color: green;
+                }
+              }
+            }
+          }
+        }
+
+        .eqeq-type {
+          display: flex;
+          flex-direction: row;
+          align-items: center;
+          font-size: 28rpx;
+
+          >view:nth-child(1) {
+            text-align: left;
+            color: #000;
+            width: 200rpx;
+          }
+
+          >view:nth-child(2) {
+            color: #666;
+            padding-left: 6rpx;
+          }
+        }
+
+        .eqeq-type+.eqeq-type{
+          margin-top: 8rpx;
+        }
+        .message{
+          min-height: 100rpx;
+          border-style: solid;
+          border-width: 2rpx;
+          border-color: #cccccc;
+          border-radius: 10rpx;
+          margin: 20rpx 10rpx;
+          padding: 10rpx 10rpx;
+          background-color:#FFF0F5;
+          text-align: left;
+        }
+        .status {
+          width: 130rpx;
+          height: 120rpx;
+          box-sizing: border-box;
+          border-radius: 120rpx;
+          // border: 6rpx solid #2C6FF3;
+          text-align: center;
+          display: flex;
+          flex-flow: column;
+          justify-content: space-around;
+          align-items: center;
+          position: absolute;
+          right: 12rpx;
+          bottom: 24rpx;
+
+          .s-name {
+            font-size: 28rpx;
+            padding-top: 20rpx;
+            font-weight: bold;
+          }
+
+          .s-num {
+            font-size: 32rpx;
+            padding-bottom: 20rpx;
+          }
+        }
+      }
+    }
+  }
+
+}
+</style>

+ 636 - 5
src/pages/xy_system/devices/deviceList.vue

@@ -1,16 +1,457 @@
 <template>
   <view class="container">
     <u-navbar title="设备列表"
-              @rightClick="rightClick"
-              :autoBack="true">
+              @rightClick="rightClick" titleStyle="color:#fff;fontSize:36rpx;"   bgColor="#2C6FF3"
+              :autoBack="true" leftIconColor="#fff" :placeholder="true">
     </u-navbar>
+
     <view class="content">
-      11111111111112222244444440000
+
+      <view class="topbar flex flex-wrap justify-between">
+        <view class="flex">
+          <view class="flex align-center" @tap="openNetStatusMenu">
+            联网<u-icon name="arrow-right" style="margin-left: 6rpx;" size="14"></u-icon>{{netStatus.selStatus.label}}
+          </view>
+          <view class="flex align-center" @tap="openActiveStatusMenu" style="margin-left: 20rpx;">
+            激活<u-icon name="arrow-right" style="margin-left: 6rpx;" size="14"></u-icon>{{activeStatus.selStatus.label}}
+          </view>
+        </view>
+        <view class="search flex" @tap="openSearchMenu" style="margin-left: 20rpx;">
+          筛选<u-icon name="arrow-right" style="margin-left: 6rpx;" size="14"></u-icon>
+        </view>
+      </view>
+      <!--      <view class="search">-->
+      <!--        <u-search placeholder="机器名称/ID号" v-model="data.keyword" :showAction="false"></u-search>-->
+      <!--      </view>-->
+      <view class="data_total">
+        共 {{data.total}} 台设备
+      </view>
+      <scroll-view  class="scrollview" :scroll-with-animation="true" scroll-y lower-threshold="100"
+                   @scrolltolower="scrolltolower" :style="{height:data.fullHeight}">
+        <view v-if="data.dataList.length>0">
+          <view class="eq-item" @click="gotoDeviceDetail(item1.deviceId)"
+                v-for="(item1,index1) in data.dataList" :key="item1.deviceId">
+            <view class="eq-content">
+              <view class="eq-wrap">
+                <view class="eq-name flex justify-between">
+                  <view class="eq-title" v-if="item1.deviceName">{{item1.deviceName}}<text style="color: #666;">({{item1.deviceId}})</text></view>
+                  <view class="eq-title" v-else>{{item1.deviceId}}</view>
+                  <view class="eq-status-box flex align-center">
+                    <view class="eq-status" :class="[item1.deviceStatus.netState==1?'online':'']"><text></text>{{item1.deviceStatus.netState=='1'?'在线':'离线'}}
+                    </view>
+                    <view class="eq-status" :class="[item1.deviceStatus.lockStateL==1?'online':'']"><text></text>{{item1.deviceStatus.lockStateL=='1'?'未锁机':'已锁机'}}
+                    </view>
+                  </view>
+                </view>
+                <view class="eqeq-type">
+                  <view>
+                    所属商户:
+                  </view>
+                  <view>
+                    {{item1.mercName}}({{item1.mercId}})
+                  </view>
+                </view>
+                <view class="eqeq-type">
+                  <view>
+                    设备类型:
+                  </view>
+                  <view>
+                    {{proxy.$getDictByValue(dicts.device_type,item1.deviceType).label}}
+                  </view>
+                </view>
+                <view class="eqeq-type">
+                  <view>
+                    激活状态:
+                  </view>
+                  <view>
+                    {{item1.activeState==1?'已激活':'未激活'}}
+                  </view>
+                </view>
+                <view class="eqeq-type">
+                  <view>
+                    软件版本:
+                  </view>
+                  <view>
+                    {{item1.deviceSysinfo.appUpmVersion}}
+                  </view>
+                </view>
+                <view class="eqeq-type">
+                  <view>
+                    更新时间:
+                  </view>
+                  <view>
+                    {{item1.deviceStatus.updateTime}}
+                  </view>
+                </view>
+              </view>
+
+<!--              <view class="status">-->
+<!--                <view class="s-name">-->
+<!--                  在售/补货-->
+<!--                </view>-->
+<!--                <view class="s-num">-->
+<!--                  {{item1.onSaleNum||0}}/{{item1.fillNum||0}}-->
+<!--                </view>-->
+<!--              </view>-->
+            </view>
+          </view>
+          <view class="load-more" style="padding:24rpx;">
+            <u-loadmore v-if="data.dataList.length>0" :status="data.loadmoreStatus" />
+          </view>
+        </view>
+        <view v-else class='empty'>
+          <u-empty mode="data" text="数据为空"></u-empty>
+        </view>
+      </scroll-view>
     </view>
+
+    <u-popup :show="netStatus.showPopMenu" duration="0" :customStyle="netStatus.style" :round="10" mode="top"  :overlay="false"  >
+      <view class="popMenu">
+        <u-subsection :list="dicts.online_status" keyName="label" :current="netStatus.selIdx" @change="netStatusChange"></u-subsection>
+      </view>
+    </u-popup>
+    <u-popup :show="activeStatus.showPopMenu" duration="0" :customStyle="activeStatus.style" :round="10" mode="top" :overlay="false"  >
+      <view class="popMenu">
+        <u-subsection :list="dicts.active_status" keyName="label" :current="activeStatus.selIdx" @change="activeStatusChange"></u-subsection>
+      </view>
+    </u-popup>
+    <u-popup :show="searchMenu.showPopMenu"  duration="0" :customStyle="searchMenu.style" :round="10" mode="right" :duration="300" :overlay="false"  >
+      <view class="searchMenu popMenu flex" :style="searchMenu.searchMenu_body_style">
+        <scroll-view scroll-y class="searchMenu_body flex-wrap justify-between">
+          <view>
+            <view class="label">
+              所属商户
+            </view>
+            <view>
+              <u--input size="mini"
+                        placeholder="请输入商户名称"
+                        border="surround"
+                        v-model="searchMenu.params.mercName"
+              ></u--input>
+            </view>
+          </view>
+          <view>
+            <view class="label">
+              机器名称/编号
+            </view>
+            <view>
+              <u--input
+                  placeholder="请输入机器名称,编号"
+                  border="surround"
+                  v-model="searchMenu.params.deviceIdName"
+              ></u--input>
+            </view>
+          </view>
+          <view>
+            <view class="label">
+              SN/资产编号/流量卡号
+            </view>
+            <view>
+              <u--input
+                  placeholder="SN/资产编号/流量卡号"
+                  border="surround"
+                  v-model="searchMenu.params.no"
+              ></u--input>
+            </view>
+          </view>
+
+          <view>
+            <view class="label">
+              设备类型
+            </view>
+            <view>
+              <view class="tab-list flex flex-wrap justify-between">
+                <block v-for="(item,index) in dicts.device_type" :key="item.value">
+                  <view :class="[searchMenu.params.deviceType==item.value?'tab-item tab-show':'tab-item']" @click="searchMenu.params.deviceType = item.value">
+                    {{item.label}}
+                  </view>
+                </block>
+              </view>
+            </view>
+          </view>
+          <view>
+            <view class="label">
+              运营状态
+            </view>
+            <view>
+              <view class="tab-list flex flex-wrap justify-between">
+                <block v-for="(item,index) in dicts.busy_status" :key="item.value">
+                  <view :class="[searchMenu.params.busyState==item.value?'tab-item tab-show':'tab-item']" @click="searchMenu.params.busyState = item.value">
+                    {{item.label}}
+                  </view>
+                </block>
+              </view>
+            </view>
+          </view>
+          <view>
+            <view class="label">
+              激活状态
+            </view>
+            <view>
+              <view class="tab-list flex flex-wrap justify-between">
+                <block v-for="(item,index) in dicts.active_status" :key="item.value">
+                  <view :class="[searchMenu.params.activeState==item.value?'tab-item tab-show':'tab-item']" @click="searchMenu.params.activeState = item.value">
+                    {{item.label}}
+                  </view>
+                </block>
+              </view>
+            </view>
+          </view>
+        </scroll-view>
+        <view class="searchMenu_bottom ">
+          <view class="flex flex-wrap justify-between">
+            <view>
+              <u-button size="normal" :plain="true" text="关闭" @click="doSearch(0)"></u-button>
+            </view>
+
+            <view class="flex">
+              <view>
+                <u-button size="normal"  type="primary" :plain="true" text="重置" @click="resetSearchMenu"></u-button>
+              </view>
+              <view style="margin-left: 20rpx">
+                <u-button size="normal"  type="primary"  text="查找" @click="doSearch(1)"></u-button>
+              </view>
+            </view>
+          </view>
+        </view>
+      </view>
+    </u-popup>
+
   </view>
 </template>
 
 <script setup>
+import {getCurrentInstance, onMounted, reactive, ref} from "vue";
+const { proxy } = getCurrentInstance();
+const instance = getCurrentInstance();
+const {
+  device_type,device_online_status,device_busy_status,device_active_status
+} = proxy.$useDict("device_type","device_online_status",'device_busy_status','device_active_status');
+const dicts = reactive({
+  device_type:[],
+  online_status:[],
+  busy_status:[],
+  active_status:[]
+})
+
+const netStatus = reactive({
+      showPopMenu:false,
+      selIdx:0,
+      selStatus: {},
+      style:{
+         marginTop :'240rpx'
+      }
+});
+
+function openNetStatusMenu(){
+  console.log('-------','openNetStatusMenu')
+  activeStatus.showPopMenu = false;
+  searchMenu.showPopMenu = false;
+  netStatus.showPopMenu = true;
+
+}
+function netStatusChange(index){
+  netStatus.selStatus = dicts.online_status[index];
+  netStatus.selIdx = index;
+  setTimeout(function(){
+    netStatus.showPopMenu = false;
+    data.curPage = 1;
+    getList()
+  }, 500);
+}
+
+
+const activeStatus = reactive({
+  showPopMenu:false,
+  selIdx:0,
+  selStatus: {},
+  style:{
+    marginTop :'240rpx'
+  },
+});
+
+function openActiveStatusMenu(){
+  console.log('-------','openActiveStatusMenu')
+  netStatus.showPopMenu = false;
+  searchMenu.showPopMenu = false;
+  activeStatus.showPopMenu = true;
+}
+function activeStatusChange(index){
+  activeStatus.selStatus = dicts.active_status[index];
+  activeStatus.selIdx = index;
+  setTimeout(function(){
+    activeStatus.showPopMenu = false;
+    data.curPage = 1;
+    getList()
+  }, 500);
+}
+
+const searchMenu = reactive({
+  showPopMenu:false,
+  selStatus:0,
+  style:{
+    marginTop :'174rpx'
+  },
+  searchMenu_body_style:{
+    height:'600rpx'
+  },
+  params:{
+    mercName:undefined,
+    deviceIdName:undefined,
+    deviceType:undefined,
+    busyState:undefined,
+    activeState:undefined,
+    no:undefined,
+  }
+});
+
+function openSearchMenu(){
+  activeStatus.showPopMenu = false;
+  netStatus.showPopMenu = false;
+  searchMenu.showPopMenu = true;
+}
+function resetSearchMenu(){
+  searchMenu.params.deviceIdName = undefined;
+  searchMenu.params.deviceType = undefined;
+  searchMenu.params.mercName = undefined;
+  searchMenu.params.no = undefined;
+}
+function doSearch(o){
+  if(o==0)resetSearchMenu();
+
+  setTimeout(function(){
+    searchMenu.showPopMenu = false;
+    data.curPage = 1;
+    getList()
+  }, 500);
+}
+
+function tabClick(t,v){
+
+}
+
+var aaa = ref('');
+
+
+const data = reactive({
+  reqUrls:{
+    list:'/device/device-info/page'
+  },
+  curPage:1,
+  loadmoreStatus:'nomore',
+  params:{
+    page: {
+      current: 1,
+      size: 10,
+      orders: [
+        {
+          asc: false,
+          column: 'create_time'
+        }]
+    },
+  },
+  fullHeight:0,
+  screenHeight:1920,
+  // screenWidht:1080,
+  dataList:[],
+  total:0,
+  isRemember: []
+})
+
+onMounted(() => {
+  dicts.device_type = ([{value:null,label:'全部'}].concat(device_type.value));
+  dicts.online_status = ([{value:null,label:'全部'}].concat(device_online_status.value));
+  dicts.active_status = ([{value:null,label:'全部'}].concat(device_active_status.value));
+  dicts.busy_status = ([{value:null,label:'全部'}].concat(device_busy_status.value));
+  netStatus.selStatus =dicts.online_status[0];
+  activeStatus.selStatus =dicts.active_status[0];
+  data.screenHeight = uni.getSystemInfoSync().windowHeight;//-uni.upx2px(100);
+  searchMenu.searchMenu_body_style.height = (data.screenHeight-90)+"px";
+
+  getList();
+
+  const query = uni.createSelectorQuery().in(instance);
+  query.select(".scrollview").boundingClientRect((r) => {
+    uni.getSystemInfo({
+      success(res) {
+        // 针对iPhone X等机型底部安全距离做适配
+        const model = res.model;
+        const modelInclude = [
+          "iPhone X",
+          'iPhone XR',
+          "iPhone XS",
+          "iPhone XS MAX",
+          "iPhone 12/13 mini",
+          "iPhone 12/13 (Pro)",
+          "iPhone 12/13 Pro Max",
+          "iPhone 14 Pro Max"
+        ];
+        let safeDistance = modelInclude.includes(model)
+        //动态设置商品区域高度
+        console.log(res.windowHeight, r.top)
+        // data.screenWidht = data1.right;
+
+        let h = 0;
+        if (safeDistance) {
+
+          h = res.windowHeight - r.top;
+        } else {
+          h = res.windowHeight - r.top;
+        }
+        data.fullHeight+=h+"px";
+
+
+        //console.log("--------",searchMenu.searchMenu_body_style);
+      },
+    });
+  }).exec();
+
+});
+function scrolltolower(){
+  if (data.loadmoreStatus == 'nomore') return
+  data.curPage++
+  getList()
+}
+function getList () {
+  return new Promise((resolve, reject) => {
+    data.params.page.current = data.curPage;
+    data.params.netState = netStatus.selStatus.value;
+    data.params.activeState = activeStatus.selStatus.value;
+    // data.params.bu = activeStatus.status[activeStatus.selStatus].value;
+    data.params.deviceIdName = searchMenu.params.deviceIdName;
+    data.params.deviceType = searchMenu.params.deviceType;
+    data.params.merc = searchMenu.params.mercName;
+    data.params.no = searchMenu.params.no;
+    if(data.curPage == 1){
+      data.dataList = [];
+    }
+    proxy.$request({
+      url: data.reqUrls.list,
+      data: data.params,
+      method:'post'
+    }).then(res => {
+      console.log('***',res);
+
+      if (res.records) {
+        data.dataList = data.dataList.concat(res.records)
+      }
+      data.total = res.total;
+      if (res.records.length < 10) {
+        data.loadmoreStatus = "nomore"
+      } else {
+        data.loadmoreStatus = "loadmore"
+      }
+
+      resolve(res)
+    }).catch(err => {
+      reject(err)
+    })
+  })
+}
+
+function gotoDeviceDetail(o){
+  proxy.$tab.navigateTo(`/pages/xy_system/devices/deviceDetail?id=${o}`)
+}
 function rightClick () {
   console.log('点击了右侧')
 }
@@ -19,12 +460,202 @@ function rightClick () {
 <style lang="scss" scoped>
 .container {
   .content {
+    font-size: 28rpx;
+    font-weight: 500;
     text-align: center;
-    margin-top: 300rpx;
+    //margin-top: 135rpx;
   }
 
   .empty {
     margin-top: 50%;
   }
+  .search{
+    margin: 20rpx 10rpx;
+  }
+  .popMenu{
+    background-color: white;
+    padding: 20rpx 20rpx 20rpx 20rpx;
+  }
+  .searchMenu{
+    width: 600rpx;
+    font-size: 28rpx;
+    flex-direction: column;
+    position: relative;
+    .searchMenu_body{
+      overflow-y: scroll;
+      margin-bottom: 100rpx;
+    }
+    .label{
+      text-align: left;
+      margin: 20rpx 10rpx 10rpx 10rpx;
+      width: 600rpx;
+    }
+    .searchMenu_bottom{
+      width: 100%;
+      padding: 20rpx;
+      position: absolute;
+      bottom:0rpx;
+      left:0;
+    }
+  }
+  .topbar{
+    background-color: white;
+    padding: 0rpx 20rpx 0rpx 20rpx;
+  }
+  .data_total{
+    padding: 0 20rpx 20rpx 20rpx;
+    background-color: white;
+    text-align: left;
+    font-size: 28rpx;
+  }
+  .tab-list {
+    width: 100%;
+    background-color: #fff;
+    padding: 0 26rpx;
+    .tab-item{
+      padding:0 20rpx;
+      //width: 200rpx;
+      height: 62rpx;
+      background: #F7F7F7;
+      border-radius: 10rpx;
+      font-size: 28rpx;
+      font-weight: 500;
+      color: #777777;
+      line-height: 62rpx;
+      margin-bottom: 14rpx;
+      text-align:center;
+      &.tab-show{
+        background: #F4F8FF;
+        color:#2C6FF3;
+      }
+    }
+  }
+
+  .scrollview{
+    padding: 16rpx 16rpx 16rpx 16rpx;
+    padding-bottom:calc(20rpx + env(safe-area-inset-bottom) / 2);
+
+    .xy-card {
+      margin-bottom: 24rpx;
+    }
+
+    .eq-item {
+      position: relative;
+
+      &+.eq-item {
+        padding-top: 12rpx;
+      }
+
+      .eq-content {
+
+        .eq-wrap {
+          border-radius: 8rpx;
+          background-color: white;//rgb(245, 248, 251);
+          box-sizing: border-box;
+          padding: 24rpx 12rpx;
+          font-size: 26rpx;
+
+          .eq-name {
+            text-align: left;
+            font-size: 32rpx;
+            font-weight: bold;
+            margin-bottom: 12rpx;
+            position: relative;
+
+            >.eq-title{
+              width: 420rpx;
+              >text{
+                font-size: 30rpx;
+                color: #666;
+                font-weight: normal;
+              }
+            }
+
+            .eq-status-box{
+              float: right;
+              position: absolute;
+              right:0;
+              top:0;
+            }
+
+            .eq-status {
+              font-size: 28rpx;
+              color: #666;
+              font-weight: normal;
+              margin-left: 12rpx;
+
+              >text {
+                display: inline-block;
+                background-color: #666;
+                width: 16rpx;
+                height: 16rpx;
+                border-radius: 16rpx;
+                margin-right: 12rpx;
+
+              }
+
+              &.online {
+                color: #f56c6c;
+
+                >text {
+                  background-color: green;
+                }
+              }
+            }
+          }
+        }
+
+        .eqeq-type {
+          display: flex;
+          flex-direction: row;
+          align-items: center;
+          font-size: 28rpx;
+
+          >view:nth-child(1) {
+            text-align: left;
+            color: #000;
+            width: 200rpx;
+          }
+
+          >view:nth-child(2) {
+            color: #666;
+            padding-left: 6rpx;
+          }
+        }
+
+        .eqeq-type+.eqeq-type{
+          margin-top: 8rpx;
+        }
+
+        .status {
+          width: 130rpx;
+          height: 120rpx;
+          box-sizing: border-box;
+          border-radius: 120rpx;
+          // border: 6rpx solid #2C6FF3;
+          text-align: center;
+          display: flex;
+          flex-flow: column;
+          justify-content: space-around;
+          align-items: center;
+          position: absolute;
+          right: 12rpx;
+          bottom: 24rpx;
+
+          .s-name {
+            font-size: 28rpx;
+            padding-top: 20rpx;
+            font-weight: bold;
+          }
+
+          .s-num {
+            font-size: 32rpx;
+            padding-bottom: 20rpx;
+          }
+        }
+      }
+    }
+  }
+
 }
-</style>
+</style>

+ 471 - 0
src/pages/xy_system/devices/deviceNetHistory.vue

@@ -0,0 +1,471 @@
+<template>
+  <view class="container">
+    <u-navbar title="联网记录查询"
+              @rightClick="rightClick" titleStyle="color:#fff;fontSize:36rpx;"   bgColor="#2C6FF3"
+              :autoBack="true" leftIconColor="#fff" :placeholder="true">
+    </u-navbar>
+
+    <view class="content">
+
+      <view class="topbar flex flex-wrap justify-between">
+        <view class="data_total flex align-center">
+          共 {{data.total}} 条记录
+        </view>
+        <view class="search flex" @tap="openSearchMenu" style="margin-left: 20rpx;">
+          筛选<u-icon name="arrow-right" style="margin-left: 6rpx;" size="14"></u-icon>
+        </view>
+      </view>
+      <!--      <view class="search">-->
+      <!--        <u-search placeholder="机器名称/ID号" v-model="data.keyword" :showAction="false"></u-search>-->
+      <!--      </view>-->
+
+      <scroll-view  class="scrollview" :scroll-with-animation="true" scroll-y lower-threshold="100"
+                    @scrolltolower="scrolltolower" :style="{height:data.fullHeight}">
+        <view v-if="data.dataList.length>0">
+          <view class="eq-item" @click="gotoDeviceDetail(item1.deviceId)"
+                v-for="(item1,index1) in data.dataList" :key="item1.deviceId">
+            <view class="eq-content">
+              <view class="eq-wrap">
+                <view class="eq-name flex justify-between">
+                  <view class="eq-title" v-if="item1.deviceName">{{item1.deviceName}}<text style="color: #666;">({{item1.deviceId}})</text></view>
+                  <view class="eq-title" v-else>{{item1.deviceId}}</view>
+                  <view class="eq-status-box flex align-center">
+                    <view class="eq-status" :class="[item1.netStatus==1?'online':'']"><text></text>{{item1.netStatus=='1'?'在线':'离线'}}
+                    </view>
+                  </view>
+                </view>
+
+                <view class="eqeq-type">
+                  <view>
+                    更新时间:
+                  </view>
+                  <view>
+                    {{item1.createTime}}
+                  </view>
+                </view>
+              </view>
+            </view>
+          </view>
+          <view class="load-more" style="padding:24rpx;">
+            <u-loadmore v-if="data.dataList.length>0" :status="data.loadmoreStatus" />
+          </view>
+        </view>
+        <view v-else class='empty'>
+          <u-empty mode="data" text="数据为空"></u-empty>
+        </view>
+      </scroll-view>
+    </view>
+
+    <u-popup :show="searchMenu.showPopMenu"  duration="0" :customStyle="searchMenu.style" :round="10" mode="right" :duration="300" :overlay="false"  >
+      <view class="searchMenu popMenu flex" :style="searchMenu.searchMenu_body_style">
+        <scroll-view scroll-y class="searchMenu_body flex-wrap justify-between">
+          <view>
+            <view class="label">
+              机器名称/编号
+            </view>
+            <view>
+              <u--input
+                  placeholder="请输入机器名称,编号"
+                  border="surround"
+                  v-model="searchMenu.params.deviceId"
+              ></u--input>
+            </view>
+          </view>
+
+        </scroll-view>
+        <view class="searchMenu_bottom ">
+          <view class="flex flex-wrap justify-between">
+            <view>
+              <u-button size="normal" :plain="true" text="关闭" @click="doSearch(0)"></u-button>
+            </view>
+
+            <view class="flex">
+              <view>
+                <u-button size="normal"  type="primary" :plain="true" text="重置" @click="resetSearchMenu"></u-button>
+              </view>
+              <view style="margin-left: 20rpx">
+                <u-button size="normal"  type="primary"  text="查找" @click="doSearch(1)"></u-button>
+              </view>
+            </view>
+          </view>
+        </view>
+      </view>
+    </u-popup>
+
+  </view>
+</template>
+
+<script setup>
+import {getCurrentInstance, onMounted, reactive, ref} from "vue";
+const { proxy } = getCurrentInstance();
+const instance = getCurrentInstance();
+const {
+  device_type,device_online_status
+} = proxy.$useDict("device_type","device_online_status");
+const dicts = reactive({
+  device_type:[],
+  online_status:[],
+})
+
+const searchMenu = reactive({
+  showPopMenu:false,
+  selStatus:0,
+  style:{
+    marginTop :'174rpx'
+  },
+  searchMenu_body_style:{
+    height:'600rpx'
+  },
+  params:{
+    mercName:undefined,
+    deviceId:undefined,
+    deviceType:undefined,
+    busyState:undefined,
+    activeState:undefined,
+    no:undefined,
+  }
+});
+
+function openSearchMenu(){
+  searchMenu.showPopMenu = true;
+}
+function resetSearchMenu(){
+  searchMenu.params.deviceId = undefined;
+  searchMenu.params.deviceType = undefined;
+  searchMenu.params.mercName = undefined;
+  searchMenu.params.no = undefined;
+}
+function doSearch(o){
+  if(o==0)resetSearchMenu();
+
+  setTimeout(function(){
+    searchMenu.showPopMenu = false;
+    data.curPage = 1;
+    getList()
+  }, 500);
+}
+
+function tabClick(t,v){
+
+}
+
+var aaa = ref('');
+
+
+const data = reactive({
+  reqUrls:{
+    list:'/device/device-net-record/page'
+  },
+  curPage:1,
+  loadmoreStatus:'nomore',
+  params:{
+    page: {
+      current: 1,
+      size: 10,
+      orders: [
+        {
+          asc: false,
+          column: 'create_time'
+        }]
+    },
+  },
+  fullHeight:0,
+  screenHeight:1920,
+  // screenWidht:1080,
+  dataList:[],
+  total:0,
+  isRemember: []
+})
+
+onMounted(() => {
+  dicts.device_type = ([{value:null,label:'全部'}].concat(device_type.value));
+  dicts.online_status = ([{value:null,label:'全部'}].concat(device_online_status.value));
+
+  data.screenHeight = uni.getSystemInfoSync().windowHeight;//-uni.upx2px(100);
+  searchMenu.searchMenu_body_style.height = (data.screenHeight-90)+"px";
+
+  getList();
+
+  const query = uni.createSelectorQuery().in(instance);
+  query.select(".scrollview").boundingClientRect((r) => {
+    uni.getSystemInfo({
+      success(res) {
+        // 针对iPhone X等机型底部安全距离做适配
+        const model = res.model;
+        const modelInclude = [
+          "iPhone X",
+          'iPhone XR',
+          "iPhone XS",
+          "iPhone XS MAX",
+          "iPhone 12/13 mini",
+          "iPhone 12/13 (Pro)",
+          "iPhone 12/13 Pro Max",
+          "iPhone 14 Pro Max"
+        ];
+        let safeDistance = modelInclude.includes(model)
+        //动态设置商品区域高度
+        console.log(res.windowHeight, r.top)
+        // data.screenWidht = data1.right;
+
+        let h = 0;
+        if (safeDistance) {
+
+          h = res.windowHeight - r.top;
+        } else {
+          h = res.windowHeight - r.top;
+        }
+        data.fullHeight+=h+"px";
+
+
+        //console.log("--------",searchMenu.searchMenu_body_style);
+      },
+    });
+  }).exec();
+
+});
+function scrolltolower(){
+  if (data.loadmoreStatus == 'nomore') return
+  data.curPage++
+  getList()
+}
+function getList () {
+  return new Promise((resolve, reject) => {
+    data.params.page.current = data.curPage;
+    data.params.deviceId = searchMenu.params.deviceId;
+    if(data.curPage == 1){
+      data.dataList = [];
+    }
+    proxy.$request({
+      url: data.reqUrls.list,
+      data: data.params,
+      method:'post'
+    }).then(res => {
+      console.log('***',res);
+
+      if (res.records) {
+        data.dataList = data.dataList.concat(res.records)
+      }
+      data.total = res.total;
+      if (res.records.length < 10) {
+        data.loadmoreStatus = "nomore"
+      } else {
+        data.loadmoreStatus = "loadmore"
+      }
+
+      resolve(res)
+    }).catch(err => {
+      reject(err)
+    })
+  })
+}
+
+function gotoDeviceDetail(o){
+  //proxy.$tab.navigateTo(`/pages/xy_system/devices/deviceDetail?id=${o}`)
+}
+function rightClick () {
+  console.log('点击了右侧')
+}
+</script>
+
+<style lang="scss" scoped>
+.container {
+  .content {
+    font-size: 28rpx;
+    font-weight: 500;
+    text-align: center;
+    //margin-top: 135rpx;
+  }
+
+  .empty {
+    margin-top: 50%;
+  }
+  .search{
+    margin: 20rpx 10rpx;
+  }
+  .popMenu{
+    background-color: white;
+    padding: 20rpx 20rpx 20rpx 20rpx;
+  }
+  .searchMenu{
+    width: 600rpx;
+    font-size: 28rpx;
+    flex-direction: column;
+    position: relative;
+    .searchMenu_body{
+      overflow-y: scroll;
+      margin-bottom: 100rpx;
+    }
+    .label{
+      text-align: left;
+      margin: 20rpx 10rpx 10rpx 10rpx;
+      width: 600rpx;
+    }
+    .searchMenu_bottom{
+      width: 100%;
+      padding: 20rpx;
+      position: absolute;
+      bottom:0rpx;
+      left:0;
+    }
+  }
+  .topbar{
+    background-color: white;
+    padding: 0rpx 20rpx 0rpx 20rpx;
+  }
+  .data_total{
+    padding-left: 20rpx;
+    background-color: white;
+    text-align: center;
+    font-size: 28rpx;
+  }
+  .tab-list {
+    width: 100%;
+    background-color: #fff;
+    padding: 0 26rpx;
+    .tab-item{
+      padding:0 20rpx;
+      //width: 200rpx;
+      height: 62rpx;
+      background: #F7F7F7;
+      border-radius: 10rpx;
+      font-size: 28rpx;
+      font-weight: 500;
+      color: #777777;
+      line-height: 62rpx;
+      margin-bottom: 14rpx;
+      text-align:center;
+      &.tab-show{
+        background: #F4F8FF;
+        color:#2C6FF3;
+      }
+    }
+  }
+
+  .scrollview{
+    padding: 16rpx 16rpx 16rpx 16rpx;
+    padding-bottom:calc(20rpx + env(safe-area-inset-bottom) / 2);
+
+    .xy-card {
+      margin-bottom: 24rpx;
+    }
+
+    .eq-item {
+      position: relative;
+
+      &+.eq-item {
+        padding-top: 12rpx;
+      }
+
+      .eq-content {
+
+        .eq-wrap {
+          border-radius: 8rpx;
+          background-color: white;//rgb(245, 248, 251);
+          box-sizing: border-box;
+          padding: 24rpx 12rpx;
+          font-size: 26rpx;
+
+          .eq-name {
+            text-align: left;
+            font-size: 32rpx;
+            font-weight: bold;
+            margin-bottom: 12rpx;
+            position: relative;
+
+            >.eq-title{
+              width: 420rpx;
+              >text{
+                font-size: 30rpx;
+                color: #666;
+                font-weight: normal;
+              }
+            }
+
+            .eq-status-box{
+              float: right;
+              position: absolute;
+              right:0;
+              top:0;
+            }
+
+            .eq-status {
+              font-size: 28rpx;
+              color: #666;
+              font-weight: normal;
+              margin-left: 12rpx;
+
+              >text {
+                display: inline-block;
+                background-color: #666;
+                width: 16rpx;
+                height: 16rpx;
+                border-radius: 16rpx;
+                margin-right: 12rpx;
+
+              }
+
+              &.online {
+                color: #f56c6c;
+
+                >text {
+                  background-color: green;
+                }
+              }
+            }
+          }
+        }
+
+        .eqeq-type {
+          display: flex;
+          flex-direction: row;
+          align-items: center;
+          font-size: 28rpx;
+
+          >view:nth-child(1) {
+            text-align: left;
+            color: #000;
+            width: 200rpx;
+          }
+
+          >view:nth-child(2) {
+            color: #666;
+            padding-left: 6rpx;
+          }
+        }
+
+        .eqeq-type+.eqeq-type{
+          margin-top: 8rpx;
+        }
+
+        .status {
+          width: 130rpx;
+          height: 120rpx;
+          box-sizing: border-box;
+          border-radius: 120rpx;
+          // border: 6rpx solid #2C6FF3;
+          text-align: center;
+          display: flex;
+          flex-flow: column;
+          justify-content: space-around;
+          align-items: center;
+          position: absolute;
+          right: 12rpx;
+          bottom: 24rpx;
+
+          .s-name {
+            font-size: 28rpx;
+            padding-top: 20rpx;
+            font-weight: bold;
+          }
+
+          .s-num {
+            font-size: 32rpx;
+            padding-bottom: 20rpx;
+          }
+        }
+      }
+    }
+  }
+
+}
+</style>

+ 10 - 1
src/plugins/dict.js

@@ -27,6 +27,7 @@ export function useDict(...args) {
 					res.value[dictType] = resp.map((p) => ({
 						label: p.msg,
 						value: p.value,
+						code:p.code,
 						elTagType: p.cssClass,
 						elTagClass: p.cssClass,
 					}))
@@ -37,4 +38,12 @@ export function useDict(...args) {
 		console.log('res.value', res.value)
 		return toRefs(res.value)
 	})()
-}
+}
+export function getDictByValue(dict, value) {
+	let re = dict.find(e=>e.value == value);
+	return re?re:{};
+}
+export function getDictByCode(dict, code) {
+	let re = dict.find(e=>e.code == code);
+	return re?re:{};
+}

+ 3 - 1
src/plugins/index.js

@@ -3,7 +3,7 @@ import auth from './auth'
 import modal from './modal'
 import xy from './xy'
 import request from './request'
-import { useDict } from './dict'
+import { useDict,getDictByValue,getDictByCode } from './dict'
 
 export default function installPlugins(app) {
   // 页签操作
@@ -18,4 +18,6 @@ export default function installPlugins(app) {
   app.config.globalProperties.$request = request
   //数据字典
   app.config.globalProperties.$useDict = useDict
+  app.config.globalProperties.$getDictByValue = getDictByValue
+  app.config.globalProperties.$getDictByCode = getDictByCode
 }

File diff suppressed because it is too large
+ 0 - 0
src/static/icons/svg/arrow_right.svg


Some files were not shown because too many files changed in this diff