2025-05-22 16:24:05 +08:00

830 lines
20 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<page-meta>
<navigation-bar :title="$tt('navBar.physicalModels')" title-align="center" background-color="#007AFF" />
</page-meta>
<view class="scene-model-wrap">
<view class="container-wrap">
<view class="group-wrap">
<u-cell-group :border="false">
<view class="cell-wrap" v-for="(item, index) in list" :key="index">
<u-cell :border="false" isLink
:arrow-direction="item.datatype.type === 'object' || item.datatype.type === 'array' ? 'left' : 'down'"
@click="handleModel(item)">
<view slot="title"> {{item.name}}</view>
</u-cell>
</view>
</u-cell-group>
</view>
</view>
<view class="other">
<u-empty mode="data" :show="list.length === 0" marginTop="60" :text="$tt('scene.emptyData')"></u-empty>
<!-- integer、decimal 类型 -->
<u-popup :show="isInteger" :round="5" mode="bottom" bgColor="#eef3f7" :closeOnClickOverlay="false"
@close="isInteger = false">
<view class="integer-popup-wrap">
<view class="nav">
<text @click="isInteger = false">{{$tt('common.cancel')}}</text>
<text @click="handleConfirmInteger">{{$tt('common.confirm')}}</text>
</view>
<view class="select-wrap">
<view class="form-wrap" v-if="type === 'trigger'">
<u--form labelPosition="left" labelWidth="80">
<view class="form-item-wrap">
<u-form-item :label="$tt('product.operator')" @click="isOperator = true">
<u--input v-model="operators.name" border="none" inputAlign="right"
:placeholder="$tt('product.selectOperator')" disabledColor="#fff" disabled
:customStyle="{ marginRight: '20rpx' }" />
<u-icon slot="right" name="arrow-right"></u-icon>
</u-form-item>
</view>
</u--form>
</view>
<template v-if="operators.value !== 'between' && operators.value !== 'notBetween' ">
<view class="num-wrap">
<text class="num">{{value}}</text>
<text class="unit">{{model.datatype.unit}}</text>
</view>
<view class="slider-wrap">
<u-slider :min="model.datatype.min === undefined ? 0 : model.datatype.min"
:max="model.datatype.max === undefined ? 100 : model.datatype.max"
:step="model.datatype.step === undefined ? 1 : model.datatype.step"
@input="handleSliderChange"></u-slider>
<view class="limit">
<text>
{{`${model.datatype.min === undefined ? 0 : model.datatype.min} ${model.datatype.unit || ''}`}}
</text>
<text>
{{`${model.datatype.max === undefined ? 0 : model.datatype.max} ${model.datatype.unit || ''}`}}
</text>
</view>
</view>
</template>
<view v-else class="range-wrap">
<u--input v-model="valueA" type="number" :placeholder="$tt('product.input')"
border="surround"></u--input>
<text style="padding: 0 20rpx;">-</text>
<u--input v-model="valueB" type="number" :placeholder="$tt('product.input')"
border="surround"></u--input>
<text v-if="model.datatype.unit" style="margin-left: 20rpx;">{{model.datatype.unit}}</text>
</view>
</view>
</view>
</u-popup>
<!-- bool 类型 -->
<u-popup :show="isBool" :round="5" mode="bottom" bgColor="#eef3f7" :closeOnClickOverlay="false"
@close="isBool = false">
<view class="bool-popup-wrap">
<view class="nav">
<text @click="isBool = false">{{$tt('common.cancel')}}</text>
<text @click="handleConfirmBool">{{$tt('common.confirm')}}</text>
</view>
<view class="radio-group-wrap">
<u-radio-group v-model="value" :borderBottom="false" placement="column" iconPlacement="right">
<view class="radio-wrap">
<u-radio :label="$tt('product.on')" :name="1" iconSize="16"></u-radio>
</view>
<view class="radio-wrap">
<u-radio :label="$tt('product.off')" :name="0" iconSize="16"></u-radio>
</view>
</u-radio-group>
</view>
</view>
</u-popup>
<!-- enum 类型 -->
<u-popup :show="isEnum" :round="5" mode="bottom" bgColor="#eef3f7" :closeOnClickOverlay="false"
@close="isEnum = false">
<view class="enum-popup-wrap">
<view class="nav">
<text @click="isEnum = false">{{$tt('common.cancel')}}</text>
<text @click="handleConfirmEnum">{{$tt('common.confirm')}}</text>
</view>
<view class="radio-group-wrap">
<u-radio-group v-model="value" :borderBottom="false" placement="column" iconPlacement="right">
<view class="radio-wrap" v-for="(item, index) in model.datatype.enumList" :key="index">
<u-radio :label="item.text" :name="item.value" iconSize="16"></u-radio>
</view>
</u-radio-group>
</view>
</view>
</u-popup>
<!-- string 类型 -->
<u-popup :show="isString" :round="5" mode="bottom" bgColor="#eef3f7" :closeOnClickOverlay="false"
@close="isString = false">
<view class="string-popup-wrap">
<view class="nav">
<text @click="isString = false">{{$tt('common.cancel')}}</text>
<text @click="handleConfirmString">{{$tt('common.confirm')}}</text>
</view>
<view class="select-wrap">
<view class="form-wrap" v-if="type === 'trigger'">
<u--form labelPosition="left" labelWidth="80">
<view class="form-item-wrap">
<u-form-item :label="$tt('product.operator')" @click="isOperator = true">
<u--input v-model="operators.name" border="none" inputAlign="right"
:placeholder="$tt('product.selectOperator')" disabledColor="#fff" disabled
:customStyle="{ marginRight: '20rpx' }" />
<u-icon slot="right" name="arrow-right"></u-icon>
</u-form-item>
</view>
</u--form>
</view>
<view class="input-wrap">
<u--input v-model="value" :customStyle="{ padding: '24rpx' }"
:placeholder="$tt('deviceDetail.inputMsg')" border="surround" fontSize="16"
clearable></u--input>
</view>
</view>
</view>
</u-popup>
<!-- 操作符 -->
<u-popup :show="isOperator" :round="5" mode="bottom" :closeOnClickOverlay="true" zIndex="10076"
@close="isOperator = false">
<view class="operator-popup-wrap">
<view class="cell-group-wrap">
<u-cell-group :border="false">
<view class="cell-wrap" v-for="(item,index) in oprGroup" :key="index"
v-if="item.types.length === 0 || item.types.includes(model.datatype.type)">
<u-cell :title="item.name" :name="item.value" :border="false"
@click="handleConfirmOperator(item)"></u-cell>
</view>
<view class="cell-wrap">
<u-cell :title="$tt('common.cancel')" name="cancel" :border="false"
@click="handleConfirmOperator({ value: 'cancel' })"></u-cell>
</view>
</u-cell-group>
</view>
</view>
</u-popup>
</view>
</view>
</template>
<script>
import {
getCacheThingsModel
} from '@/apis/modules/device.js';
import {
navigateBackTo
} from '@/utils/common.js';
export default {
data () {
return {
type: 'trigger',
editIndex: null, // null 代表新增
parentId: null,
list: [], // 属性
isInteger: false, // 整型
oprGroup: [{
types: [],
name: '等于(=',
value: '=',
},
{
types: [],
name: '不等于(!=',
value: '!=',
},
{
types: ['integer', 'decimal'],
name: '大于(>',
value: '>',
},
{
types: ['integer', 'decimal'],
name: '小于(<',
value: '<',
},
{
types: ['integer', 'decimal'],
name: '大于等于(>=',
value: '>=',
},
{
types: ['integer', 'decimal'],
name: '小于等于(<=',
value: '<=',
},
{
types: ['integer', 'decimal'],
name: '在...之间between',
value: 'between',
},
{
types: ['integer', 'decimal'],
name: '不在...之间notBetween',
value: 'notBetween',
},
{
types: ['string'],
name: '包含contain',
value: 'contain',
},
{
types: ['string'],
name: '不包含notContain',
value: 'notContain',
}
],
operators: {}, // 对象
isOperator: false, // 操作符
isBool: false,
isEnum: false, // 枚举
isString: false,
model: {
datatype: {},
},
value: null, // 属性值
valueA: null,
valueB: null,
trigger: {},
action: {},
};
},
onLoad (option) {
const {
type,
data,
editIndex
} = option;
this.type = type;
this.editIndex = Number(editIndex);
if (data) {
this.list = JSON.parse(data);
} else {
if (type === 'trigger') {
this.trigger = uni.getStorageSync('trigger');
const {
productId
} = this.trigger;
this.productId = productId;
this.$nextTick(() => {
this.getModelDatas();
})
} else {
this.action = uni.getStorageSync('action');
const {
productId
} = this.action;
this.productId = productId;
this.$nextTick(() => {
this.getModelDatas();
})
}
};
},
methods: {
// 获取区设备列表
getModelDatas () {
getCacheThingsModel(this.productId)
.then(res => {
if (res.code === 200) {
let data = JSON.parse(res.data);
this.list = this.formatArrayIndex(data);
} else {
uni.showToast({
icon: 'none',
title: res.msg
});
}
})
.catch(err => {
console.log(err);
});
},
// 物模型格式化处理
formatArrayIndex (data) {
let obj = {
...data
};
for (let o in obj) {
obj[o] = obj[o].map((item) => {
item = {
...item,
level: 'parent'
};
if (item.datatype.type === 'array') {
let arrayModel = [];
for (let k = 0; k < item.datatype.arrayCount; k++) {
let index = k > 9 ? String(k) : '0' + k;
if (item.datatype.arrayType === 'object') {
arrayModel.push({
id: index,
name: item.name + ' ' + (k + 1),
level: 'index',
datatype: {
type: item.datatype.arrayType,
params: item.datatype.params
},
});
} else {
arrayModel.push({
id: index,
name: item.name + ' ' + (k + 1),
level: 'index',
datatype: {
type: item.datatype.arrayType,
},
});
}
}
item.datatype.arrayModel = arrayModel;
}
return item;
});
}
const ap = this.type;
let list = [];
if (this[ap].type == 1) {
list = obj.properties || [];
} else if (this[ap].type == 2) {
list = obj.functions || [];
} else if (this[ap].type == 3) {
list = obj.events || [];
}
if (ap === 'action') {
list = list.filter((item) => {
if (item.datatype.params && item.datatype.params.length !== 0) {
item.datatype.params = item.datatype.params.filter((item) => item.isMonitor == 0 &&
item
.isReadonly == 0);
}
return item.isMonitor == 0 && item.isReadonly == 0;
});
}
return list;
},
// 选中选择
handleModel (item) {
this.model = item;
// 存储 parentId 和 arrayIndex
let action = uni.getStorageSync(this.type);
if (item.level === 'parent') {
action = {
...action,
parentId: item.id,
parentName: item.name,
arrayIndex: '',
arrayIndexName: ''
};
}
if (item.level === 'index') {
action = {
...action,
arrayIndex: item.id,
arrayIndexName: item.name
};
}
uni.setStorageSync(this.type, action);
// 根据类型弹出对应框
this.operators = {};
const {
datatype
} = item;
const {
type,
params,
arrayModel
} = datatype;
this.value = null;
if (type === 'integer' || type === 'decimal') {
this.isInteger = true;
this.value = datatype.min ? datatype.min : 0;
this.valueA = null;
this.valueB = null;
} else if (type === 'bool') {
this.isBool = true;
} else if (type === 'enum') {
this.isEnum = true;
} else if (type === 'string') {
this.isString = true
} else if (type === 'object') {
uni.$u.route('/pagesA/scene/product/model', {
type: this.type,
data: JSON.stringify(params),
editIndex: this.editIndex
});
} else if (type === "array") {
uni.$u.route('/pagesA/scene/product/model', {
type: this.type,
data: JSON.stringify(arrayModel),
editIndex: this.editIndex
});
}
},
// 操作符选择{{$tt('common.confirm')}}
handleConfirmOperator (item) {
if (item.value !== 'cancel') {
this.operators = item;
}
this.isOperator = false;
},
// slider 负数会有问题,所以采用这方式
handleSliderChange (value) {
if (this.model.datatype.step) {
const str = this.model.datatype.step.toString();
const places = str.match(/\.(\d+)/);
const num = places ? places[1].length : 0;
this.value = value.toFixed(num);
} else {
this.value = value;
}
},
// 整型、浮点型
handleConfirmInteger () {
const {
id,
name
} = this.model;
if (this.type === 'trigger' && Object.keys(this.operators).length === 0) {
uni.showToast({
icon: 'none',
title: this.$tt('product.selectOperator')
});
return;
};
let value = '';
if (this.operators.value !== 'between' && this.operators.value !== 'notBetween') {
if (this.value === this.model.datatype.min || !this.value) {
uni.showToast({
icon: 'none',
title: this.$tt('product.inputValue')
});
return;
};
value = this.value;
} else {
if (!this.valueA || !this.valueB) {
uni.showToast({
icon: 'none',
title: this.$tt('product.inputValue')
});
return;
}
value = this.valueA + '-' + this.valueB;
}
const {
tempId,
tempName
} = this.getModelIdAndName(id, name);
const model = {
id: tempId,
name: tempName,
value: value,
};
this.setModelData(model);
this.isInteger = false;
uni.setStorageSync('callback', true);
navigateBackTo('/pagesA/scene/detail');
},
// bool 类型
handleConfirmBool () {
const {
id,
name
} = this.model;
if (this.value === null) {
uni.showToast({
icon: 'none',
title: this.$tt('product.selectValue')
});
return;
};
const {
tempId,
tempName
} = this.getModelIdAndName(id, name);
const model = {
id: tempId,
name: tempName,
value: this.value,
};
this.setModelData(model);
this.isBool = false;
uni.setStorageSync('callback', true);
navigateBackTo('/pagesA/scene/detail');
},
// enum 类型
handleConfirmEnum () {
const {
id,
name
} = this.model;
if (!this.value) {
uni.showToast({
icon: 'none',
title: this.$tt('product.selectValue')
});
return;
};
const {
tempId,
tempName
} = this.getModelIdAndName(id, name);
const model = {
id: tempId,
name: tempName,
value: this.value,
};
this.setModelData(model);
this.isEnum = false;
uni.setStorageSync('callback', true);
navigateBackTo('/pagesA/scene/detail');
},
// string 类型
handleConfirmString () {
const {
id,
name
} = this.model;
if (this.type === 'trigger' && Object.keys(this.operators).length === 0) {
uni.showToast({
icon: 'none',
title: this.$tt('product.selectOperator')
});
return;
};
if (!this.value) {
uni.showToast({
icon: 'none',
title: this.$tt('product.inputValue')
});
return;
};
const {
tempId,
tempName
} = this.getModelIdAndName(id, name);
const model = {
id: tempId,
name: tempName,
value: this.value,
};
this.setModelData(model);
this.isString = false;
uni.setStorageSync('callback', true);
navigateBackTo('/pagesA/scene/detail');
},
// 获取model的id和name
getModelIdAndName (id, name) {
let tempId = id;
let tempName = name;
const {
parentId,
parentName,
arrayIndex,
arrayIndexName
} = uni.getStorageSync(this.type);
if (arrayIndex) {
if (arrayIndex === id) { // 数组
tempId = `array_${arrayIndex}_${parentId}`;
tempName = parentName;
} else { // 数组对象
tempId = `array_${arrayIndex}_${id}`;
}
} else {
tempId = id;
}
return {
tempId,
tempName
}
},
// 存储数据
setModelData (data) {
let action = uni.getStorageSync(this.type);
const {
id,
name,
value
} = data;
action = {
...action,
id,
name,
value
};
if (this.type === 'trigger') {
action = {
...action,
operator: this.operators.value || ''
};
}
uni.setStorageSync(this.type, action);
// 更新或者插入新的触发或者执行
let {
triggers,
actions,
...res
} = uni.getStorageSync('sceneData');
if (Number.isNaN(this.editIndex) || this.editIndex === null) {
if (this.type === 'trigger') {
triggers.push(action);
} else {
actions.push(action);
}
uni.setStorageSync('sceneData', {
triggers,
actions,
...res
});
} else {
if (this.type === 'trigger') {
let list = triggers.map((item, i) => {
if (i == this.editIndex) {
return action
} else {
return item
}
});
uni.setStorageSync('sceneData', {
triggers: [...list],
actions,
...res
});
} else {
let list = actions.map((item, i) => {
if (i == this.editIndex) {
return action
} else {
return item
}
});
uni.setStorageSync('sceneData', {
triggers,
actions: [...list],
...res
});
}
}
}
}
};
</script>
<style>
page {
height: 100%;
background: #eef3f7;
}
</style>
<style lang="scss" scoped>
.scene-model-wrap {
.container-wrap {
.group-wrap {
margin: 30rpx;
background: #fff;
border-radius: 12rpx;
.cell-wrap {
padding: 7rpx 0;
border-bottom: 1rpx solid #F1F2F5;
}
}
}
.other {
.integer-popup-wrap {
padding: 30rpx;
.nav {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
font-size: 32rpx;
margin-bottom: 34rpx;
}
.select-wrap {
background-color: #fff;
border-radius: 10rpx;
padding: 40rpx 20rpx;
.form-wrap {
.form-item-wrap {
padding: 4rpx 20rpx;
}
}
.num-wrap {
position: relative;
margin-top: 60rpx;
text-align: center;
.num {
font-size: 80rpx;
}
.unit {
position: absolute;
top: 50%;
margin-top: -23rpx;
font-size: 32rpx;
color: #868686;
margin-left: 60rpx;
}
}
.slider-wrap {
.limit {
font-size: 28rpx;
color: #868686;
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 0 30rpx;
}
}
.range-wrap {
display: flex;
flex-direction: row;
align-items: center;
margin-top: 30rpx;
}
}
}
.operator-popup-wrap {
padding: 10rpx 0 20rpx;
.cell-group-wrap {
background: #eef3f7;
.cell-wrap {
text-align: center;
background: #fff;
padding: 10rpx;
border-bottom: 1rpx solid #F1F2F5;
&:last-child {
margin-top: 15rpx;
}
}
}
}
.bool-popup-wrap,
.enum-popup-wrap {
padding: 30rpx;
.nav {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
font-size: 32rpx;
margin-bottom: 34rpx;
}
.radio-group-wrap {
background-color: #fff;
border-radius: 10rpx;
.radio-wrap {
padding: 40rpx;
&:not(:last-child) {
border-bottom: 1rpx solid #F1F2F5;
}
}
}
}
.string-popup-wrap {
padding: 30rpx;
.nav {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
font-size: 32rpx;
margin-bottom: 34rpx;
}
.select-wrap {
background-color: #fff;
border-radius: 10rpx;
padding: 40rpx 20rpx;
.form-wrap {
.form-item-wrap {
padding: 4rpx 20rpx;
}
}
.input-wrap {
margin: 32rpx 12rpx 20rpx;
}
}
}
}
}
</style>