Compare commits

..

No commits in common. "3105c590bbb5b1238ff6ea8874d9ded8b10e61d2" and "20b808adc78f955ecdac721e0493bf2cca4e1ce3" have entirely different histories.

2 changed files with 196 additions and 245 deletions

View File

@ -8,39 +8,48 @@
</view> </view>
</uni-section> </uni-section>
<uni-section title="实时温湿度" titleFontSize="16px" type="line" v-if="value!== 1"> <uni-section title="实时温湿度" titleFontSize="16px" type="line" v-if="value!== 1">
<template v-slot:right > <template v-slot:right >
{{ temp }} {{ temp }}
</template> </template>
<view> <view>
<!-- 优化温度卡片循环渲染 -->
<view class="uni-flex_control uni-row" > <view class="uni-flex_control uni-row" >
<view <view class="text uni-flex_control_one uni-view">
class="text uni-flex_control_one uni-view" <text class="data" :style="fontStyle">{{ liveData.temp1 }}<p v-if=(testNumber(liveData.temp1)) class="tempStyle"></p></text>
v-for="item in sensorCards.temp" <text class="data">温度1</text>
:key="item.key" </view>
> <view class="text uni-flex_control_one uni-view">
<text class="data" :style="fontStyle"> <text class="data" :style="fontStyle">{{ liveData.temp2 }}<p v-if=(testNumber(liveData.temp2)) class="tempStyle"></p></text>
{{ liveData[item.key] }} <text class="data">温度2</text>
<p v-if="isEffectiveValue(liveData[item.key])" class="tempStyle"></p> </view>
</text> <view class="text uni-flex_control_one uni-view">
<text class="data">{{ item.label }}</text> <text class="data" :style="fontStyle">{{ liveData.temp3 }}<p v-if=(testNumber(liveData.temp3)) class="tempStyle"></p></text>
<text class="data">温度3</text>
</view>
<view class="text uni-flex_control_one uni-view">
<text class="data" :style="fontStyle">{{ liveData.temp4 }}<p v-if=(testNumber(liveData.temp4)) class="tempStyle"></p></text>
<text class="data">温度4</text>
</view> </view>
</view> </view>
<!-- 优化湿度卡片循环渲染 -->
<view class="uni-flex_control uni-row" > <view class="uni-flex_control uni-row" >
<view <view class="text uni-flex_control_two uni-view">
class="text uni-flex_control_two uni-view" <text class="data" :style="fontStyle">{{ liveData.humi1 }}<p v-if=(testNumber(liveData.humi1)) class="humiStyle"> %RH</p></text>
v-for="item in sensorCards.humi" <text class="data">湿度1</text>
:key="item.key" </view>
> <view class="text uni-flex_control_two uni-view">
<text class="data" :style="fontStyle"> <text class="data" :style="fontStyle">{{ liveData.humi2 }}<p v-if=(testNumber(liveData.humi2)) class="humiStyle"> %RH</p></text>
{{ liveData[item.key] }} <text class="data">湿度2</text>
<p v-if="isEffectiveValue(liveData[item.key])" class="humiStyle"> %RH</p> </view>
</text> <view class="text uni-flex_control_two uni-view">
<text class="data">{{ item.label }}</text> <text class="data" :style="fontStyle">{{ liveData.humi3 }}<p v-if=(testNumber(liveData.humi3)) class="humiStyle"> %RH</p></text>
<text class="data">湿度3</text>
</view>
<view class="text uni-flex_control_two uni-view">
<text class="data" :style="fontStyle">{{ liveData.humi4 }}<p v-if=(testNumber(liveData.humi4)) class="humiStyle"> %RH</p></text>
<text class="data">湿度4</text>
</view> </view>
</view> </view>
</view> </view>
@ -50,99 +59,147 @@
<template v-slot:right > <template v-slot:right >
{{ control }} {{ control }}
</template> </template>
<!-- 优化设备卡片循环渲染 --> <!-- 卷膜/卷被卡片容器2列栅格布局 -->
<view class="card-grid"> <view class="card-grid">
<view <!-- 卷被开卡片 -->
class="control-card" <view class="control-card" @click="handleCardClick(1-status.jbk, 'jbk')">
v-for="card in deviceCards"
:key="card.type"
@click="openTimeModal(card)"
>
<view class="card-text"> <view class="card-text">
<text class="card-main">{{ card.name }}</text> <text class="card-main">卷被开</text>
<!-- 核心修改添加限位时间与暂停/运行同行靠右 -->
<view class="card-sub-wrapper"> <view class="card-sub-wrapper">
<text class="card-sub" v-if="showStatusText">{{ show[card.type] || '' }}</text> <text class="card-sub" v-if="hide">{{ show.jbk }}</text>
<text class="limit-time">运行时间{{ limitTimes[card.type] || 0 }} s</text> <text class="limit-time">运行时间{{ limitTimes.jbk }} s</text>
</view> </view>
</view> </view>
<view class="card-icon" :class="{ active: status[card.type] === 1 }" @click.stop="handleCardClick(1 - status[card.type], card.type)"> <view class="card-icon" :class="{ active: status.jbk === 1 }">
<!-- @click.stop防止冒泡触发卡片点击的弹窗事件 --> <uni-icons :type=" (status.jbk === 1)?'circle':'circle-filled'" size="24" color="#fff"/>
<uni-icons </view>
:type="status[card.type] === 1 ? 'circle' : 'circle-filled'" </view>
size="24"
color="#fff" <!-- 卷被关卡片 -->
/> <view class="control-card" @click="handleCardClick(1-status.jbg,'jbg')">
<view class="card-text">
<text class="card-main">卷被关</text>
<view class="card-sub-wrapper">
<text class="card-sub" v-if="hide">{{ show.jbg }}</text>
<text class="limit-time">运行时间{{ limitTimes.jbg }} s</text>
</view>
</view>
<view class="card-icon" :class="{ active: status.jbg === 1 }">
<uni-icons :type=" (status.jbg === 1)?'circle':'circle-filled'" size="24" color="#fff"/>
</view>
</view>
<!-- 卷膜1开卡片 -->
<view class="control-card" @click="handleCardClick(1-status.jm1k, 'jm1k')">
<view class="card-text">
<text class="card-main">卷膜1开</text>
<view class="card-sub-wrapper">
<text class="card-sub" v-if="hide">{{ show.jm1k }}</text>
<text class="limit-time">运行时间{{ limitTimes.jm1k }} s</text>
</view>
</view>
<view class="card-icon" :class="{ active: status.jm1k === 1 }">
<uni-icons :type="(status.jm1k === 1)?'circle':'circle-filled'" size="24" color="#fff"/>
</view>
</view>
<!-- 卷膜1关卡片 -->
<view class="control-card" @click="handleCardClick(1-status.jm1g, 'jm1g')">
<view class="card-text">
<text class="card-main">卷膜1关</text>
<view class="card-sub-wrapper">
<text class="card-sub" v-if="hide">{{ show.jm1g }}</text>
<text class="limit-time">运行时间{{ limitTimes.jm1g }} s</text>
</view>
</view>
<view class="card-icon" :class="{ active: status.jm1g === 1 }">
<uni-icons :type="(status.jm1g === 1)?'circle':'circle-filled'" size="24" color="#fff"/>
</view>
</view>
<!-- 卷膜2卡片 -->
<view class="control-card" @click="handleCardClick(1-status.jm2k, 'jm2k')">
<view class="card-text">
<text class="card-main">卷膜2开</text>
<view class="card-sub-wrapper">
<text class="card-sub" v-if="hide">{{ show.jm2k }}</text>
<text class="limit-time">运行时间{{ limitTimes.jm2k }} s</text>
</view>
</view>
<view class="card-icon" :class="{ active: status.jm2k === 1 }">
<uni-icons :type="(status.jm2k === 1)?'circle':'circle-filled'" size="24" color="#fff"/>
</view>
</view>
<!-- 卷膜2关卡片 -->
<view class="control-card" @click="handleCardClick(1-status.jm2g, 'jm2g')">
<view class="card-text">
<text class="card-main">卷膜2关</text>
<view class="card-sub-wrapper">
<text class="card-sub" v-if="hide">{{ show.jm2g }}</text>
<text class="limit-time">运行时间{{ limitTimes.jm2g }} s</text>
</view>
</view>
<view class="card-icon" :class="{ active: status.jm2g === 1 }">
<uni-icons :type="(status.jm2g === 1)?'circle':'circle-filled'" size="24" color="#fff"/>
</view>
</view>
<!-- 卷膜3开卡片 -->
<view class="control-card" @click="handleCardClick(1-status.jm3k, 'jm3k')">
<view class="card-text">
<text class="card-main">卷膜3开</text>
<view class="card-sub-wrapper">
<text class="card-sub" v-if="hide">{{ show.jm3k }}</text>
<text class="limit-time">运行时间{{ limitTimes.jm3k }} s</text>
</view>
</view>
<view class="card-icon" :class="{ active: status.jm3k === 1 }">
<uni-icons :type="(status.jm3k === 1)?'circle':'circle-filled'" size="24" color="#fff"/>
</view>
</view>
<!-- 卷膜3关卡片 -->
<view class="control-card" @click="handleCardClick(1-status.jm3g, 'jm3g')">
<view class="card-text">
<text class="card-main">卷膜3关</text>
<view class="card-sub-wrapper">
<text class="card-sub" v-if="hide">{{ show.jm3g }}</text>
<text class="limit-time">运行时间{{ limitTimes.jm3g }} s</text>
</view>
</view>
<view class="card-icon" :class="{ active: status.jm3g === 1 }">
<uni-icons :type="(status.jm3g === 1)?'circle':'circle-filled'" size="24" color="#fff"/>
</view> </view>
</view> </view>
</view> </view>
</uni-section> </uni-section>
<!-- 输入框示例 -->
<!-- <uni-popup ref="inputDialog" type="dialog">-->
<!-- <uni-popup-dialog ref="inputClose" mode="input" title="输入内容" value="对话框预置提示内容!"-->
<!-- placeholder="请输入内容" @confirm="confirmModifyTime"></uni-popup-dialog>-->
<!-- </uni-popup>-->
<uni-popup v-model="timeModalVisible" ref="inputDialog" mode="center" :z-index="99999">
<!-- 新增修改运行时间的弹窗 -->
<!-- <uni-popup v-model="timeModalVisible" mode="center">-->
<view class="modal-container">
<view class="modal-title">修改{{ currentCard.name }}运行时间</view>
<view class="modal-input-wrap">
<text class="modal-label">当前时间</text>
<text class="modal-current">{{ currentCardTime }} s</text>
</view>
<view class="modal-input-wrap">
<text class="modal-label">新时间</text>
<input
class="modal-input"
type="number"
v-model.number="newTime"
placeholder="请输入1-60的数字"
min="1"
max="60"
/>
<text class="modal-unit">s</text>
</view>
<view class="modal-btn-wrap">
<button class="modal-btn cancel" @click="">取消</button>
<button class="modal-btn confirm" @click="confirmModifyTime"></button>
</view>
</view>
</uni-popup>
</view> </view>
</template> </template>
<script> <script>
// // ========== mqtt ==========
const SENSOR_MAP = { // import mqtt from 'mqtt' // mqtt
temp1: "201", temp2: "202", temp3: "203", temp4: "204",
humi1: "101", humi2: "102", humi3: "103", humi4: "104"
};
const MQTT_TOPIC_SUFFIX = { UP: "/up", DOWN: "/down" };
import UniDatetimePicker from "../../uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue"; import UniDatetimePicker from "../../uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue";
import UniPopup from "../../uni_modules/uni-popup/components/uni-popup/uni-popup.vue"; //
import { findDtuDataByInfo } from "@/api/system/data"; import { findDtuDataByInfo } from "@/api/system/data";
import mqttUtil from '@/utils/mqtt'; import mqttUtil from '@/utils/mqtt'; // MQTT
export default { export default {
dicts: ['sys_data_map'], dicts: ['sys_data_map'],
components: { components: {
UniDatetimePicker, UniDatetimePicker
UniPopup //
}, },
data() { data() {
return { return {
// ========== mqttClient ==========
// mqttClient: null,
temp: "", temp: "",
// ========== mqttConfig/ ==========
mqttConfig: { mqttConfig: {
subscribeTopic:'/up', subscribeTopic:'/up',
}, },
value: 1, value: 1,
// hide: false hide: false,
showStatusText: false,
control: '正在加载中...', control: '正在加载中...',
range: [{ range: [{
"value": '864865085016294', "value": '864865085016294',
@ -158,8 +215,8 @@ export default {
publishTopic: '/down', publishTopic: '/down',
title:'', title:'',
message: {}, message: {},
// connected // ========== ==========
connected: false, // connected:false,
liveData: { liveData: {
temp1: '数据加载中...', temp1: '数据加载中...',
temp2: '数据加载中...', temp2: '数据加载中...',
@ -170,33 +227,7 @@ export default {
humi3: '数据加载中...', humi3: '数据加载中...',
humi4: '数据加载中...' humi4: '数据加载中...'
}, },
// 湿1234湿1234 //
sensorCards: {
temp: [
{ label: '温度1', key: 'temp1' },
{ label: '温度2', key: 'temp2' },
{ label: '温度3', key: 'temp3' },
{ label: '温度4', key: 'temp4' }
],
humi: [
{ label: '湿度1', key: 'humi1' },
{ label: '湿度2', key: 'humi2' },
{ label: '湿度3', key: 'humi3' },
{ label: '湿度4', key: 'humi4' }
]
},
//
deviceCards: [
{ type: 'jbk', name: '卷被开' },
{ type: 'jbg', name: '卷被关' },
{ type: 'jm1k', name: '卷膜1开' },
{ type: 'jm1g', name: '卷膜1关' },
{ type: 'jm2k', name: '卷膜2开' },
{ type: 'jm2g', name: '卷膜2关' },
{ type: 'jm3k', name: '卷膜3开' },
{ type: 'jm3g', name: '卷膜3关' }
],
//
show: { show: {
jbk: "暂停", jbk: "暂停",
jbg: "暂停", jbg: "暂停",
@ -207,7 +238,7 @@ export default {
jm3k: "暂停", jm3k: "暂停",
jm3g: "暂停" jm3g: "暂停"
}, },
// //
limitTimes: { limitTimes: {
jbk: 30, jbk: 30,
jbg: 25, jbg: 25,
@ -229,12 +260,7 @@ export default {
jm3k: 0, jm3k: 0,
jm3g: 0 jm3g: 0
}, },
fontStyle: '', fontStyle: ''
//
timeModalVisible: false, //
currentCard: {}, //
currentCardTime: 0, //
newTime: 0 //
}; };
}, },
onLoad() { onLoad() {
@ -254,21 +280,24 @@ export default {
}); });
}, },
onShow() { onShow() {
// ========== connectMqtt ==========
// this.connectMqtt();
// MQTT // MQTT
mqttUtil.setOnMessageCallback(this.ackMessage); mqttUtil.setOnMessageCallback(this.ackMessage);
// //
this.connected = mqttUtil.getMqttState().isConnected; this.connected = mqttUtil.getMqttState().isConnected;
}, },
onUnload() { onUnload() {
// ========== disconnectMqtt ==========
// this.disconnectMqtt()
// MQTT // MQTT
mqttUtil.removeOnMessageCallback(); mqttUtil.removeOnMessageCallback();
}, },
methods: { methods: {
change(e) { change(e) {
this.imei = e; this.imei = e;
// 使MQTT this.publishTopic = "dtu/"+this.imei+"/down";
this.publishTopic = `dtu/${this.imei}${MQTT_TOPIC_SUFFIX.DOWN}`; this.mqttConfig.subscribeTopic = "dtu/"+this.imei+"/up";
this.mqttConfig.subscribeTopic = `dtu/${this.imei}${MQTT_TOPIC_SUFFIX.UP}`;
const selectedItem = this.range.find(item => item.value === e); const selectedItem = this.range.find(item => item.value === e);
if (selectedItem) { if (selectedItem) {
this.selectedText = selectedItem.text; // this.selectedText = selectedItem.text; //
@ -303,6 +332,7 @@ export default {
this.liveData[key] = '数据加载中...'; this.liveData[key] = '数据加载中...';
}); });
this.deviceType = ''; this.deviceType = '';
this.control = '正在加载中...'; this.control = '正在加载中...';
this.message = {}; this.message = {};
this.temp = ''; this.temp = '';
@ -353,19 +383,25 @@ export default {
// //
this.message = JSON.stringify({[type]: status}) this.message = JSON.stringify({[type]: status})
// //
// this.publishMessage(); this.publishMessage();
// //
this.deviceType = type; this.deviceType = type;
//todo //todo
this.$set(this.status, type, this.status[type] === 0 ? 1 : 0); // this.status[type] = this.status[type] === 0 ? 1 : 0;
this.$set(this.show, type, this.status[type] === 0 ? "暂停" : "运行"); // this.show[type] = this.status[type] === 0 ? "" : "";
} }
} }
}) })
}, },
// ========== connectMqtt ==========
// connectMqtt() { ... },
// ========== disconnectMqtt ==========
// disconnectMqtt() { ... },
// ========== publishMessage ==========
publishMessage() { publishMessage() {
if (!this.connected || !this.publishTopic || !this.message) { if (!this.connected || !this.publishTopic || !this.message) {
uni.showToast({ uni.showToast({
@ -382,6 +418,7 @@ export default {
} else { } else {
this.addMessage(`发布失败:设备:[${this.publishTopic}]`) this.addMessage(`发布失败:设备:[${this.publishTopic}]`)
} }
this.message = {}; this.message = {};
}, },
@ -389,12 +426,14 @@ export default {
ackMessage(topic, payload) { ackMessage(topic, payload) {
// 1. dtu/xxx/up // 1. dtu/xxx/up
if (topic !== this.mqttConfig.subscribeTopic) return; if (topic !== this.mqttConfig.subscribeTopic) return;
// console.log(`topic=${topic}message=${payload}`)
// 2.
let msgData = {}; let msgData = {};
// JSON
try { try {
msgData = JSON.parse(payload); msgData = JSON.parse(payload);
} catch (e) { } catch (e) {
console.error("MQTT消息解析失败:", e, payload); console.error("消息解析失败:", e);
return; return;
} }
@ -405,7 +444,6 @@ export default {
this.handleOtherContent(msgData,payload) this.handleOtherContent(msgData,payload)
} }
}, },
addMessage(content) { addMessage(content) {
console.info("提示消息:" + content) console.info("提示消息:" + content)
}, },
@ -418,17 +456,20 @@ export default {
const isSuccess = ackData.suc; // true const isSuccess = ackData.suc; // true
if (isSuccess) { if (isSuccess) {
// 使$set this.status[type] = this.status[type] === 0 ? 1 : 0;
this.$set(this.status, type, this.status[type] === 0 ? 1 : 0); this.show[type] = this.status[type] === 0 ? "暂停" : "运行";
this.$set(this.show, type, this.status[type] === 0 ? "暂停" : "运行");
} }
this.deviceType = ''; this.deviceType = '';
this.$modal.msgSuccess("设备操作成功!") this.$modal.msgSuccess("设备操作成功!")
console.log(`指令[${commandField}=${commandValue}]执行${isSuccess ? "成功" : "失败"}`); console.log(`指令[${commandField}=${commandValue}]执行${isSuccess ? "成功" : "失败"}`);
}, },
//
handleOtherContent(msgData,payload) { handleOtherContent(msgData,payload) {
// //
// console.log("", msgData);
// 湿线
// //
if (this.value !== 1) { if (this.value !== 1) {
var arr = ['jbk', "jbg", "jm1k", "jm1g", "jm2k", "jm2g", "jm3k", "jm3g"] var arr = ['jbk', "jbg", "jm1k", "jm1g", "jm2k", "jm2g", "jm3k", "jm3g"]
@ -444,17 +485,17 @@ export default {
const allKeysNumeric2 = Object.keys(msgData).every(key => /^\d+$/.test(key)); const allKeysNumeric2 = Object.keys(msgData).every(key => /^\d+$/.test(key));
if (Object.keys(msgData).length > 0 && allKeysNumeric2) { if (Object.keys(msgData).length > 0 && allKeysNumeric2) {
const div10 = (v) => (v == null ? null : Math.round((Number(v)/10)*10)/10) const div10 = (v) => (v == null ? null : Math.round((Number(v)/10)*10)/10)
// 使
this.liveData = { this.liveData = {
temp1: div10(msgData[SENSOR_MAP.temp1]) || "已离线...", temp1: div10(msgData["201"]) || "已离线...",
humi1: div10(msgData[SENSOR_MAP.humi1]) || "已离线...", humi1: div10(msgData["101"]) || "已离线...",
temp2: div10(msgData[SENSOR_MAP.temp2]) || "已离线...", temp2: div10(msgData["202"]) || "已离线...",
humi2: div10(msgData[SENSOR_MAP.humi2]) || "已离线...", humi2: div10(msgData["102"]) || "已离线...",
temp3: div10(msgData[SENSOR_MAP.temp3]) || "已离线...", temp3: div10(msgData["203"]) || "已离线...",
humi3: div10(msgData[SENSOR_MAP.humi3]) || "已离线...", humi3: div10(msgData["103"]) || "已离线...",
temp4: div10(msgData[SENSOR_MAP.temp4]) || "已离线...", temp4: div10(msgData["204"]) || "已离线...",
humi4: div10(msgData[SENSOR_MAP.humi4]) || "已离线...", humi4: div10(msgData["104"]) || "已离线...",
}; }
this.temp = "最后更新时间:" + this.getCurrentTime(); this.temp = "最后更新时间:" + this.getCurrentTime();
this.fontStyle = 'font-size:16px;' this.fontStyle = 'font-size:16px;'
} }
@ -485,50 +526,19 @@ export default {
testNumber(data) { testNumber(data) {
const reg = /^-?\d+(\.\d+)?$/; const reg = /^-?\d+(\.\d+)?$/;
return reg.test(String(data).trim()); return reg.test(String(data).trim());
},
// 湿
isEffectiveValue(value) {
return this.testNumber(value);
},
//
openTimeModal(card) {
this.currentCard = card; //
this.currentCardTime = this.limitTimes[card.type]; //
this.newTime = this.currentCardTime; //
this.timeModalVisible = true; //
console.info(`this.timeModalVisible:${this.timeModalVisible}`);
this.$refs.inputDialog.open()
},
//
confirmModifyTime() {
// 1-60
if (!this.newTime || this.newTime < 1 || this.newTime > 60) {
uni.showToast({
title: '请输入1-60的有效数字',
icon: 'none'
});
return;
}
// limitTimes$set
this.$set(this.limitTimes, this.currentCard.type, this.newTime);
//
uni.showToast({
title: '运行时间修改成功',
icon: 'success'
});
//
this.$refs.inputDialog.close()
//
// this.saveLimitTimeToServer(this.currentCard.type, this.newTime);
} }
}, },
onHide() { onHide() {
// ========== App.vue ==========
// this.disconnectMqtt();
//
mqttUtil.removeOnMessageCallback(); mqttUtil.removeOnMessageCallback();
}, },
beforeDestroy() { beforeDestroy() {
// ========== client ==========
// if (this.client) {
// this.client.end()
// }
mqttUtil.removeOnMessageCallback(); mqttUtil.removeOnMessageCallback();
}, },
}; };
@ -671,63 +681,4 @@ export default {
/deep/ .uni-section-header__slot-right { /deep/ .uni-section-header__slot-right {
color: green; color: green;
} }
/* 新增:弹窗样式 */
.modal-container {
width: 600rpx;
background: #fff;
border-radius: 16rpx;
padding: 30rpx 20rpx;
}
.modal-title {
font-size: 30rpx;
font-weight: 500;
text-align: center;
margin-bottom: 30rpx;
color: #333;
}
.modal-input-wrap {
display: flex;
align-items: center;
margin-bottom: 25rpx;
font-size: 26rpx;
}
.modal-label {
width: 120rpx;
color: #666;
}
.modal-current {
color: #333;
}
.modal-input {
flex: 1;
height: 60rpx;
border: 1px solid #eee;
border-radius: 8rpx;
padding: 0 15rpx;
font-size: 26rpx;
}
.modal-unit {
margin-left: 10rpx;
color: #666;
}
.modal-btn-wrap {
display: flex;
gap: 20rpx;
margin-top: 40rpx;
}
.modal-btn {
flex: 1;
height: 70rpx;
border-radius: 8rpx;
font-size: 26rpx;
}
.modal-btn.cancel {
background: #f5f5f5;
color: #666;
}
.modal-btn.confirm {
background: #007aff;
color: #fff;
}
</style> </style>

View File

@ -143,7 +143,7 @@
.uni-margin-wrap { .uni-margin-wrap {
width: 690rpx; width: 690rpx;
width: 100%; width: 100%;
;
} }
.swiper { .swiper {