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

816 lines
25 KiB
Vue
Raw Permalink 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('deviceAdd.addDevice')" title-align="center"
background-color="#007AFF" /></page-meta>
<view class="container">
<view class="card">
<u--form labelPosition="left" :model="form" :rules="rules" ref="form" labelWidth="80" labelAlign="center">
<view style="padding:10px 0;;border-bottom:1px solid #EFEFEF;display:flex;">
<u--text prefixIcon="grid-fill" iconStyle="font-size: 16px;color:#606266" :text="$tt('deviceAdd.step1')" bold
color="#606266"></u--text>
</view>
<u-form-item :label="$tt('deviceAdd.wifi')" prop="SSID"><u-input v-model="form.SSID"
:placeholder="$tt('deviceAdd.inputWifiName')"></u-input></u-form-item>
<u-form-item :label="$tt('deviceAdd.passsword')" prop="password">
<u-input v-model="form.password" :placeholder="$tt('deviceAdd.inputWifiPassword')"
:type="passwordShow ? 'text' : 'password'">
<template slot="suffix">
<view @click="passwordShow = !passwordShow"><u-icon name="eye-off" size="24"
v-if="!passwordShow" color="#666"></u-icon></view>
<view @click="passwordShow = !passwordShow"><u-icon name="eye-fill" size="24"
v-if="passwordShow" color="#666"></u-icon></view>
</template>
</u-input>
</u-form-item>
<u-form-item label=" ">
<u-checkbox-group v-model="checkboxConfigs">
<u-checkbox :customStyle="{ marginRight: '15px' }" :label="$tt('deviceAdd.remember')"
name="remeber"></u-checkbox>
<u-checkbox :customStyle="{}" :label="$tt('deviceAdd.senior')" name="advance"></u-checkbox>
</u-checkbox-group>
</u-form-item>
</u--form>
<view v-if="checkboxConfigs.indexOf('advance') != -1">
<u--form labelPosition="left" :model="form" :rules="rules" ref="form2" labelWidth="80"
labelAlign="center" :labelStyle="{ color: '#3fd1ad', fontSize: '14px' }">
<u-form-item :label="$tt('deviceAdd.userId')" prop="userId">
<u-input v-model="form.userId" :placeholder="$tt('deviceAdd.inputUserId')"
:disabled="userIdDisabled">
<template slot="suffix">
<view @click="userIdDisabled = !userIdDisabled"><u-icon name="lock-fill" size="24"
v-if="userIdDisabled" color="#666"></u-icon></view>
<view @click="userIdDisabled = !userIdDisabled"><u-icon name="lock-opened-fill"
size="24" v-if="!userIdDisabled" color="#666"></u-icon></view>
</template>
</u-input>
</u-form-item>
<u-form-item :label="$tt('deviceAdd.deviceNum')" prop="deviceNum">
<u-input v-model="form.deviceNum" :placeholder="$tt('deviceAdd.inputDeviceNum')">
<template slot="suffix">
<u-icon name="scan" color="#666" size="24" @click="openScan"></u-icon>
</template>
</u-input>
</u-form-item>
<u-form-item :label="$tt('deviceAdd.authorization')" prop="authCode"><u-input
v-model="form.authCode" :placeholder="$tt('deviceAdd.inputAuthor')"></u-input></u-form-item>
<u-form-item :label="$tt('deviceAdd.supplementary')" prop="extra">
<u-textarea v-model="form.extra" height="40" fontSize="14"
:placeholder="$tt('deviceAdd.inputSupplementary')" confirmType="done"></u-textarea>
</u-form-item>
</u--form>
</view>
</view>
<view class="card">
<view style="padding:10px 0;">
<u--text prefixIcon="grid-fill" iconStyle="font-size: 16px;color:#606266" :text="$tt('deviceAdd.step2')"
bold color="#606266"></u--text>
</view>
</view>
<view class="card">
<view style="padding:0;border-bottom:1px solid #EFEFEF;display:flex;">
<u--text prefixIcon="grid-fill" iconStyle="font-size: 16px;color:#606266" :text="$tt('deviceAdd.step3')"
bold color="#606266"></u--text>
<view>
<u-tabs :list="[{ name: $tt('deviceAdd.single') }, { name: $tt('deviceAdd.multiple') }]"
:current="tabIndex" :scrollable="false" lineWidth="50" lineHeight="2" :duration="100"
@click="tabClick"></u-tabs>
</view>
</view>
<view v-if="tabIndex == 0">
<view style="margin:30px 0 0 0;display:flex;">
<u-steps :current="step" direction="column">
<u-steps-item :title="$tt('deviceAdd.networkMode')"
:desc="$tt('deviceAdd.wifiHotspot')"></u-steps-item>
<u-steps-item :title="$tt('deviceAdd.manually')" :desc="$tt('deviceAdd.mobile')"></u-steps-item>
<u-steps-item :title="$tt('deviceAdd.detection')"
:desc="$tt('deviceAdd.system')"></u-steps-item>
<u-steps-item :title="$tt('deviceAdd.end')" :desc="$tt('deviceAdd.reconnected')"></u-steps-item>
</u-steps>
<view style="margin:0 auto;">
<circleProgress :percent="progress" borderWidth="12" width="260">
<view>
<text style="font-size:24px;display:block;">{{ progress }}%</text>
</view>
</circleProgress>
<view style="display:flex;">
<u-loading-icon customStyle="width:30px;margin-left:10px;" size="12"
v-if="step < 2"></u-loading-icon>
<u--text :text="count.text" :type="count.type" size="12"
:customStyle="step < 2 ? '' : 'margin:0 auto;'"></u--text>
</view>
</view>
</view>
<view style="padding:30px 10px;" v-if="step < 3"><u-button :text="$tt('deviceAdd.startWork')"
type="primary" @click="beginConfig" :disabled="step < 2"></u-button></view>
<view style="padding:30px 10px;" v-if="step == 4"><u-button
:text="$tt('deviceAdd.redistributingNetwork')" type="warning" @click="restartConfig"></u-button>
</view>
<view style="padding:30px 10px;" v-if="step == 3"><u-button :text="$tt('deviceAdd.after')"
type="primary" @click="goBack"></u-button></view>
</view>
<view v-if="tabIndex == 1">
<view style="margin:20px 0 0 10px;"><u--text type="warning" :text="$tt('deviceAdd.tip')"
size="12"></u--text></view>
<uni-table ref="table" :loading="loading" :emptyText="$tt('deviceAdd.noData')" style="margin-top:10px;">
<uni-tr>
<uni-th>
<view style="display:flex;width:100%;">
<view style="flex:1;"><u--text size="15" color="#606266" lines="1"
:text="$tt('deviceAdd.select')"></u--text></view>
<view style="width:40px;">
<u-button :text="$tt('deviceAdd.refresh')" :disabled="!isWeChat || updateDisable"
size="mini" type="primary" @click="updateWfifiListInWeChat"></u-button>
</view>
</view>
</uni-th>
</uni-tr>
<uni-tr v-for="(wifi, index) in wifiList" :key="index">
<uni-td v-if="wifi.signalStrength > 0">
<view @click="selectChange(wifi)">
<view style="display:flex;margin:-10px;padding:10px 0;">
<view style="width:35px;align-self:center;">
<checkbox :checked="wifi.checked" style="transform:scale(0.8)"
color="#486FF2" />
</view>
<view style="flex:1">
<view style="display:flex;line-height:30px;align-items: center;">
<view style="flex:1;">
<u--text size="14" :color="wifi.secure ? '#999' : '#486FF2'"
:text="wifi.SSID"></u--text>
</view>
<view style="margin-right: 10px;">
<u-icon v-if="wifi.signalStrength > 0 && wifi.signalStrength < 25"
name="/static/wifi_1.png" size="14"></u-icon>
<u-icon v-if="wifi.signalStrength >= 25 && wifi.signalStrength < 50"
name="/static/wifi_2.png" size="14"></u-icon>
<u-icon v-if="wifi.signalStrength >= 50 && wifi.signalStrength < 75"
name="/static/wifi_3.png" size="14"></u-icon>
<u-icon v-if="wifi.signalStrength >= 75 && wifi.signalStrength <= 100"
name="/static/wifi_4.png" size="14"></u-icon>
</view>
</view>
<view style="display:flex;" v-if="wifi.checked == true">
<view style="width:100%;margin-top:6px;"><u-line-progress
:percentage="wifi.process" activeColor="#486FF2"></u-line-progress>
</view>
<view style="width:70px;margin-left:6px;"><u--text size="12"
:text="wifi.text" :type="wifi.type"></u--text></view>
</view>
</view>
</view>
</view>
</uni-td>
</uni-tr>
</uni-table>
<view style="padding:20px 10px;" v-if="mstep == 0"><u-button :text="$tt('deviceAdd.start')"
type="primary" @click="beginConfigInWeChart" :disabled="!isWeChat"></u-button></view>
<view style="padding:20px 10px;" v-if="mstep == 1"><u-button :text="$tt('deviceAdd.distribution')"
type="primary" :loading="true"></u-button></view>
<view style="padding:20px 10px;" v-if="mstep == 2"><u-button :text="$tt('deviceAdd.connect')"
type="primary" :loading="true"></u-button></view>
<view style="padding:20px 10px;" v-if="mstep == 3"><u-button :text="$tt('deviceAdd.return')"
type="primary" @click="goBack"></u-button></view>
</view>
</view>
</view>
</template>
<script>
import {
getProfile
} from '@/apis/modules/common';
export default {
data() {
return {
// 是否为微信小程序
isWeChat: false,
// 复选框配置remeber、advance
checkboxConfigs: ['remeber'],
// 配网模式选项卡索引
tabIndex: 0,
// wifi密码可见性
passwordShow: true,
// 用户编号是否可编辑
userIdDisabled: true,
// wifi信息
form: {
type: 0, // 类型0=设备配网1=关联设备
SSID: '',
password: '',
userId: 0,
deviceNum: '',
authCode: '',
extra: ''
},
// 计数
count: {
// 进度定时器
timer: {},
// 显示文本
text: this.$tt('deviceAdd.noDevice'),
// 文字类型
type: 'warning'
},
// 发现设备计时器
discoverTimer: {},
// 单设备配网进度
progress: 0,
// 单设备配网步骤
step: 1,
// wifi列表加载
loading: false,
// wifi列表
wifiList: [
// {
// SSID: 'wumei-device1',
// secure: false,
// signalStrength: 30,
// checked: false,
// process: 0,
// text: '准备配网',
// type: 'primary'
// },
// {
// SSID: 'wumei-device2',
// secure: true,
// signalStrength: 90,
// checked: false,
// process: 0,
// text: '准备配网',
// type: 'primary'
// }
],
// 选中的wifi列表
selectedWifiList: [],
// Wifi表格选中的索引
// index: [],
// 多设备配网步骤,0=未开始配网1=配网中2=配网结束
mstep: 0,
// 更新WIFI按钮
updateDisable: false,
rules: {
SSID: [{
required: true,
message: this.$tt('deviceAdd.wifiName'),
trigger: ['blur', 'change']
}],
password: [{
required: true,
message: this.$tt('deviceAdd.wifiPassword'),
trigger: ['blur', 'change']
}],
userId: [{
required: true,
message: this.$tt('deviceAdd.userNum'),
trigger: ['blur', 'change']
}]
}
};
},
created() {
// #ifdef MP-WEIXIN
this.initInWeChat();
this.tabIndex = 1;
this.isWeChat = true;
// #endif
// #ifndef MP-WEIXIN
this.tabIndex = 0;
// #endif
//获取登录用户信息
if (this.profile == null) {
this.getProfile();
} else {
this.form.userId = this.profile.userId;
}
// 断开Mqtt连接
this.endMqtt();
// 获取WIFI信息
this.getWifi();
if (this.tabIndex == 0) {
// 发现设备
this.discoverDevice();
}
},
onUnload() {
clearInterval(this.discoverTimer);
// 页面卸载时连接mqtt
this.connectMqtt();
},
methods: {
/* 断开Mqtt消息服务器 */
async endMqtt() {
await this.$mqttTool.end();
},
/* 连接Mqtt消息服务器 */
async connectMqtt() {
if (this.$mqttTool.client == null) {
console.log('连接mqtt...');
await this.$mqttTool.connect(this.vuex_token);
}
},
// 获取登录用户信息
getProfile() {
// 调用用户信息接口
getProfile().then(res => {
//存储用户信息,TODO 需要调用一次,不然其他页面调用返回空
uni.$u.vuex('profile', res.data);
this.profile;
this.form.userId = this.profile.userId;
}).catch(err => {
this.$u.toast(err.msg);
});
},
// 单击选显卡
tabClick(item) {
this.tabIndex = item.index;
if (this.tabIndex == 0) {
// 发现设备
this.discoverDevice();
} else {
// 清除发现设备定时器
clearInterval(this.discoverTimer);
}
},
// 返回
goBack() {
getApp().globalData.operate = 'operate';
uni.navigateBack({
delta: 1
});
},
// 扫码
openScan() {
// #ifndef MP-WEIXIN || APP-PLUS
uni.showToast({
icon: 'none',
title: this.$tt('user.scanning')
});
return;
// #endif
// 允许从相机和相册扫码
uni.scanCode({
success: res => {
console.log('条码类型:' + res.scanType);
console.log('条码内容:' + res.result);
if (res.result.substr(0, 1) != '{') {
console.log('坑点:解析二维码后第一个位置包含一个特殊字符,大部分编译器和调试工具识别不了这个特殊字符');
res.result = res.result.substring(1);
}
// 解析JSON
try {
let json = JSON.parse(res.result);
// type=1 代表扫码关联设备
if (json.type == 1) {
this.form.deviceNum = json.deviceNumber;
return;
}
uni.showToast({
icon: 'none',
title: this.$tt('linkDevice.format')
});
} catch (error) {
uni.showToast({
icon: 'none',
title: this.$tt('linkDevice.format')
});
}
}
});
},
// 保存WIFI
saveWifi() {
if (this.form.SSID != '' && this.form.password != '') {
// 本地缓存存储
uni.setStorageSync('WIFI_SSID', this.form.SSID);
uni.setStorageSync('WIFI_PASSWORD', this.form.password);
}
},
// 获取WIFI
getWifi() {
// 本地缓存获取
let wifi_ssid = uni.getStorageSync('WIFI_SSID');
let wifi_password = uni.getStorageSync('WIFI_PASSWORD');
if (wifi_ssid && wifi_ssid != '') {
this.form.SSID = wifi_ssid;
}
if (wifi_password && wifi_password != '') {
this.form.password = wifi_password;
}
},
// 获取配网接口地址
getParamString() {
console.log('form', this.form);
let ip = 'http://192.168.4.1/config';
let params = '?SSID=' + this.form.SSID + '&password=' + this.form.password + '&userId=' + this.form.userId;
if (this.form.deviceNum && this.form.deviceNum != '') {
params = params + '&deviceNum=' + this.form.deviceNum;
}
if (this.form.deviceName && this.form.deviceName != '') {
params = params + '&deviceName=' + this.form.deviceName;
}
if (this.form.authCode && this.form.authCode != '') {
params = params + '&authCode=' + this.form.authCode;
}
if (this.form.extra && this.form.extra != '') {
params = params + '&extra=' + this.form.extra;
}
return ip + params;
},
// 设备AP配网
apConfig() {
return new Promise((resolve, reject) => {
uni.request({
url: this.getParamString(),
method: 'POST',
timeout: 10000,
success: res => {
resolve(true);
},
fail: res => {
reject(false);
}
});
});
},
/****************************************** 单设备 *********************************************/
// 检查设备是否配网准备就绪
discoverDevice() {
this.discoverTimer = setInterval(() => {
if (this.tabIndex == 0) {
uni.request({
url: 'http://192.168.4.1/status',
method: 'GET',
timeout: 5000,
success: res => {
clearInterval(this.discoverTimer);
this.step = 2;
this.progress == 0;
this.count = {
text: this.$tt('deviceAdd.deviceDetected'),
type: 'success',
process: 0
};
}
});
}
}, 5000);
},
// 配网进度显示
showConfigProgress() {
this.count.timer = setInterval(() => {
if (this.progress == 96) {
clearInterval(this.count.timer);
} else {
this.progress = this.progress + 1;
}
}, 100);
},
// 重新开始配网
restartConfig() {
this.progress = 0;
this.step = 1;
this.count = {
// 进度定时器
timer: {},
// 显示文本
text: this.$tt('deviceAdd.noDevice'),
// 文字类型
type: 'warning'
};
this.discoverDevice();
},
// 开始配网
async beginConfig() {
// 验证用户编号和WIFI信息
if (!this.$refs.form.validate()) {
uni.$u.toast(this.$tt('deviceAdd.userIdAndWifiAccount'));
return;
}
//保存WIFI信息
if (this.checkboxConfigs.indexOf('remeber') != -1) {
this.saveWifi();
}
// 设置进度
this.progress = 0;
this.showConfigProgress();
this.count.text = this.$tt('deviceAdd.sendInformation');
this.count.type = 'info';
// 配网
try {
let result = await this.apConfig();
console.log('ap config result:', result);
if (result) {
// 清除计数器
clearInterval(this.count.timer);
this.progress = 100;
this.count.text = this.$tt('deviceAdd.successNetwork');
this.count.type = 'success';
this.step = 3;
} else {
// 清除计数器
clearInterval(this.count.timer);
this.count.text = this.$tt('deviceAdd.successNetwork');
this.count.type = 'error';
this.step = 4;
}
} catch (e) {
// 清除计数器
clearInterval(this.count.timer);
this.count.text = this.$tt('deviceAdd.fail');
this.count.type = 'error';
this.step = 4;
}
},
/****************************************** 多设备,小程序可用 *********************************************/
// 表格中的Wifi选择改变
selectChange(wifi) {
if (wifi.checked == true) {
wifi.checked = false;
for (let i = 0; i < this.selectedWifiList.length; i++) {
if (wifi.SSID == this.selectedWifiList[i].SSID) {
this.selectedWifiList.splice(i, 1);
}
}
} else {
wifi.checked = true;
this.selectedWifiList.push(wifi);
}
console.log(this.selectedWifiList);
},
// 获取选中Wifi在所有wifi列表中的索引
getWifiIndex(SSID) {
for (let i = 0; i < this.wifiList.length; i++) {
if (SSID == this.wifiList[i].SSID) {
return i;
}
}
return -1;
},
// 小程序多设备配网
async beginConfigInWeChart() {
// 验证用户编号和WIFI信息
if (!this.$refs.form.validate()) {
uni.$u.toast(this.$tt('deviceAdd.userIdAndWifiAccount'));
return;
}
// 判断是否选择了设备热点
if (this.selectedWifiList.length == 0) {
uni.$u.toast(this.$tt('deviceAdd.selectNetwork'));
return;
}
//保存WIFI信息
if (this.checkboxConfigs.indexOf('remeber') != -1) {
this.saveWifi();
}
this.mstep = 1;
let that = this;
for (let i = 0; i < that.selectedWifiList.length; i++) {
let index = this.getWifiIndex(that.selectedWifiList[i].SSID);
console.log('wifi在列表中的索引', index);
// 配网进度显示
that.wifiList[index].type = 'primary';
that.wifiList[index].text = '配网中...';
let processTimer = setInterval(() => {
if (that.wifiList[index].process == 90) {
clearInterval(processTimer);
} else {
that.wifiList[index].process = that.wifiList[index].process + 1;
}
}, 350);
// 微信连接设备热点
try {
let result = await that.connectWifiInWeChat(that.wifiList[index].SSID, '');
console.log('连接设备热点: ' + that.wifiList[index].SSID + ' result:', result);
if (result == true) {
// 配网
let apResult = await that.apConfig();
console.log('AP配网result:', apResult);
if (apResult == true) {
// 配网成功
clearInterval(processTimer);
that.wifiList[index].process = 100;
that.wifiList[index].type = 'success';
that.wifiList[index].text = that.$tt('deviceAdd.successDistribution');
} else {
// 配网失败
that.failConfigInWeChat(that.wifiList[index], processTimer);
}
}
} catch (e) {
console.log('连接WIFI异常catch: ', e);
// 配网失败
that.failConfigInWeChat(that.wifiList[index], processTimer);
}
}
//配网成功后停止wifi然后启动、手机自动连接wifi
that.mstep = 2;
try {
console.log('停止Wifi...');
await that.stopWifiInWeChat();
} catch (e) {
console.log('停止Wifi异常', e);
uni.showToast({
icon: 'none',
title: that.$tt('deviceAdd.afterNetwork')
});
}
try {
console.log('启动Wifi...');
await that.startWifiInWeChat();
} catch (e) {
console.log('启动Wifi异常', e);
uni.showToast({
icon: 'none',
title: that.$tt('deviceAdd.afterNetwork')
});
}
that.mstep = 3;
},
// 多设备配网失败
failConfigInWeChat(wifi, processTimer) {
clearInterval(processTimer);
wifi.type = 'error';
wifi.text = this.$tt('deviceAdd.failNetwork');
},
// 微信小程序连接wifi
connectWifiInWeChat(ssid, password) {
return new Promise((resolve, reject) => {
wx.connectWifi({
SSID: ssid,
password: password,
success: res => {
resolve(true);
},
fail: res => {
reject(res);
}
});
});
},
// 微信小程序停止wifi
stopWifiInWeChat() {
return new Promise((resolve, reject) => {
wx.stopWifi({
success: res => {
resolve(true);
},
fail: res => {
reject(res);
}
});
});
},
// 微信小程序启动wifi
startWifiInWeChat() {
return new Promise((resolve, reject) => {
wx.startWifi({
success: res => {
resolve(true);
},
fail: res => {
reject(res);
}
});
});
},
// 获取连接的wifi信息
getConnectedWifiInWeChart() {
let that = this;
wx.getConnectedWifi({
success: res => {
console.log('success wifi info ', res);
if (that.form.SSID == '') {
that.form.SSID = res.wifi.SSID;
}
},
fail: res => {
console.log(res);
uni.showToast({
icon: 'none',
title: that.$tt('deviceAdd.phoneTurnOn')
});
}
});
},
// 获取wifi列表
getWifiListInWeChart() {
let that = this;
that.wifiList = [];
wx.getWifiList({
// 请求获取wifi列表
success: function(e) {
wx.onGetWifiList(function(res) {
console.log('wifi列表');
console.log(res.wifiList);
let platform = uni.getSystemInfoSync().platform;
let tmpList = [];
res.wifiList.map(item => {
if (item.SSID.length > 0) {
// 设置热点的配网进度默认为0,未选中状态,进度条默认蓝色
item.process = 0;
item.checked = false;
item.type = 'primary';
item.text = this.$tt('deviceAdd.prepare');
item.signalStrength = platform === 'ios' ? Math.round(item
.signalStrength * 100) : item
.signalStrength //ios signalStrength 值是0-1, android 值是1-100
tmpList.push(item);
}
});
that.wifiList = tmpList; // 存放wifi列表
console.log('列表数据', that.wifiList);
that.loading = false;
// 配网未开始
that.mstep = 0;
});
},
fail: res => {
console.log(res);
uni.showToast({
icon: 'none',
title: that.$tt('deviceAdd.phoneTurnOn')
});
}
});
},
// 更新WIFI列表
updateWfifiListInWeChat() {
// 清空选中的wifi
this.selectedWifiList = [];
this.updateDisable = true;
this.initInWeChat();
setTimeout(res => {
this.updateDisable = false;
}, 6000);
},
// 小程序初始化Wifi模块
initInWeChat() {
let that = this;
that.loading = true;
uni.getLocation({
//授权定位后才能获取wifi
type: 'wgs84',
success: function(res) {
that.wifiList = [];
wx.startWifi({
success(res) {
console.log('启动Wifi模块成功' + res.errMsg);
// 获取连接的wifi信息
that.getConnectedWifiInWeChart();
// 获取wifi列表
that.getWifiListInWeChart();
},
fail: res => {
that.loading = false;
console.log(res,'res');
console.log('启动Wifi模块失败' + res);
uni.showToast({
icon: 'none',
title: that.$tt('deviceAdd.miniProgrem')
});
}
});
},
fail: function(error) {
console.log(error, 'xxx');
that.loading = false;
uni.showToast({
title: that.$tt('deviceAdd.miniProgrem'),
icon: 'none'
});
}
});
}
}
};
</script>
<style lang="scss">
page {
// background: #eef3f7;
background: linear-gradient(30deg, #46e9a9 30%, #007aff 70%);
background-size: 100% 100%;
background-attachment: fixed;
}
.container {
padding-bottom: 50px;
}
.card {
box-shadow: 0 1px 0px 0 rgba(0, 0, 0, 0.1);
border-radius: 6px;
background-color: #fff;
margin: 10px;
margin-bottom: 15px;
padding: 15px 10px;
}
</style>