diff --git a/src/api/bst/device.js b/src/api/bst/device.js
new file mode 100644
index 0000000..9322dc6
--- /dev/null
+++ b/src/api/bst/device.js
@@ -0,0 +1,80 @@
+import request from '@/utils/request'
+
+// 查询设备列表
+export function listDevice(query) {
+  return request({
+    url: '/bst/device/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询设备详细
+export function getDevice(id) {
+  return request({
+    url: '/bst/device/' + id,
+    method: 'get'
+  })
+}
+
+// 新增设备
+export function addDevice(data) {
+  return request({
+    url: '/bst/device',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改设备
+export function updateDevice(data) {
+  return request({
+    url: '/bst/device',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除设备
+export function delDevice(id) {
+  return request({
+    url: '/bst/device/' + id,
+    method: 'delete'
+  })
+}
+
+// 设备入仓
+export function inDevice(ids) {
+  return request({
+    url: '/bst/device/in',
+    method: 'put',
+    data: ids
+  })
+}
+
+// 设备出仓
+export function outDevice(ids) {
+  return request({
+    url: '/bst/device/out',
+    method: 'put',
+    data: ids
+  })
+}
+
+// 设备禁用
+export function disableDevice(ids) {
+  return request({
+    url: '/bst/device/disable',
+    method: 'put',
+    data: ids
+  })
+}
+
+// 设备启用
+export function enableDevice(ids) {
+  return request({
+    url: '/bst/device/enable',
+    method: 'put',
+    data: ids
+  })
+}
diff --git a/src/api/bst/deviceIot.js b/src/api/bst/deviceIot.js
new file mode 100644
index 0000000..a6d9eac
--- /dev/null
+++ b/src/api/bst/deviceIot.js
@@ -0,0 +1,55 @@
+import request from '@/utils/request'
+
+// 管理员开锁
+export function unlockDevice(data) {
+  return request({
+    url: '/bst/device/iot/unlock',
+    method: 'put',
+    params: data
+  })
+}
+
+// 管理员锁车
+export function lockDevice(data) {
+  return request({
+    url: '/bst/device/iot/lock',
+    method: 'put',
+    params: data
+  })
+}
+
+// 管理员响铃寻车
+export function ringDevice(data) {
+  return request({
+    url: '/bst/device/iot/ring',
+    method: 'put',
+    params: data
+  })
+}
+
+// 管理员重启
+export function rebootDevice(data) {
+  return request({
+    url: '/bst/device/iot/reboot',
+    method: 'put',
+    params: data
+  })
+}
+
+// 管理员开坐垫锁
+export function unlockSeatDevice(data) {
+  return request({
+    url: '/bst/device/iot/unlockSeat',
+    method: 'put',
+    params: data
+  })
+}
+
+// 管理员刷新
+export function refreshDevice(data) {
+  return request({
+    url: '/bst/device/iot/refresh',
+    method: 'put',
+    params: data
+  })
+}
diff --git a/src/api/bst/suit.js b/src/api/bst/suit.js
index 2303b02..50ed796 100644
--- a/src/api/bst/suit.js
+++ b/src/api/bst/suit.js
@@ -9,6 +9,18 @@ export function listSuit(query) {
   })
 }
 
+// 查询套餐列表ByIds
+export function listSuitByIds(ids) {
+  return request({
+    url: '/bst/suit/listByIds',
+    headers: {
+      repeatSubmit: false,
+    },
+    method: 'post',
+    data: ids
+  })
+}
+
 // 查询套餐详细
 export function getSuit(id) {
   return request({
diff --git a/src/components/Business/Area/AreaRemoteSelect.vue b/src/components/Business/Area/AreaRemoteSelect.vue
index 4dc4b5e..8b4a3de 100644
--- a/src/components/Business/Area/AreaRemoteSelect.vue
+++ b/src/components/Business/Area/AreaRemoteSelect.vue
@@ -6,9 +6,12 @@
     :multiple="multiple"
     :loading="loading"
     @change="handleChange"
+    @visible-change="handleVisibleChange"
+    remote
+    :remote-method="remoteMethod"
   >
     <div class="select-footer">
-      <div style="text-align: center; color: #8492a6; font-size: 13px; line-height: 28px; ">
+      <div style="text-align: center; color: #8492a6; font-size: 13px;  ">
         共{{ total }}条数据
       </div>
       <el-button v-if="multiple && !isEmpty(options)" style="margin-left: 10px;" size="mini" type="text" @click.stop="handleSelectAll">
@@ -83,20 +86,24 @@ export default {
       return this.multiple && this.options.length > 0 && Array.isArray(this.value) && this.value.length === this.options.length;
     }
   },
-  watch: {
-    query: {
-      handler(nv) {
-        this.queryParams = {
-          ...this.queryParams,
-          ...nv
-        }
-        this.getOptions();
-      },
-      immediate: true
+  created() {
+    if (!isEmpty(this.initOptions)) {
+      this.options = this.initOptions;
     }
   },
   methods: {
     isEmpty,
+    // 远程搜索
+    remoteMethod(val) {
+      this.queryParams.keyword = val;
+      this.getOptions();
+    },
+    // 下拉框可见性变化
+    handleVisibleChange(visible) {
+      if (visible) {
+        this.getOptions();
+      }
+    },
     // 全选
     handleSelectAll() {
       if (this.isAllSelected) {
@@ -107,9 +114,15 @@ export default {
       }
     },
     handleChange(value) {
-      let list = this.options.filter(item => value.includes(item.id));
-      this.$emit('change', list);
+      if (this.multiple) {
+        let list = this.options.filter(item => value.includes(item.id));
+        this.$emit('change', list);
+      } else {
+        let item = this.options.find(item => value.includes(item.id));
+        this.$emit('change', item);
+      }
     },
+    // 获取选项
     getOptions() {
       this.loading = true;
       this.queryParams = {
@@ -129,13 +142,11 @@ export default {
 
 <style lang="scss" scoped>
 .select-footer {
-  padding: 5px 12px;
+  padding: 2px 12px;
   border-bottom: 1px solid #EBEEF5;
   display: flex;
   justify-content: flex-end;
   align-items: center;
-}
-.user-name {
-  padding-left: 6px;
+  line-height: 1em;
 }
 </style>
\ No newline at end of file
diff --git a/src/components/Business/Model/ModelRemoteSelect.vue b/src/components/Business/Model/ModelRemoteSelect.vue
new file mode 100644
index 0000000..42cfb4b
--- /dev/null
+++ b/src/components/Business/Model/ModelRemoteSelect.vue
@@ -0,0 +1,152 @@
+<template>
+  <el-select 
+    v-model="selectedValue" 
+    placeholder="请选择" 
+    filterable
+    :multiple="multiple"
+    :loading="loading"
+    @change="handleChange"
+    @visible-change="handleVisibleChange"
+    remote
+    :remote-method="remoteMethod"
+  >
+    <div class="select-footer">
+      <div style="text-align: center; color: #8492a6; font-size: 13px; ">
+        共{{ total }}条数据
+      </div>
+      <el-button v-if="multiple && !isEmpty(options)" style="margin-left: 10px;" size="mini" type="text" @click.stop="handleSelectAll">
+        {{ isAllSelected ? '取消全选' : '全选' }}
+      </el-button>
+    </div>
+    <el-option 
+      v-for="item in options" 
+      :key="item.id" 
+      :value="item.id"
+      :label="item.name"
+    />
+    <el-option v-if="isEmpty(value) && isEmpty(options)" style="display:none" disabled :value="null"></el-option>
+  </el-select>
+</template>
+
+<script>
+import { listModel } from '@/api/bst/model';
+import Avatar from '@/components/Avatar';
+import {isEmpty} from '@/utils'
+
+export default {
+  name: 'AreaRemoteSelect',
+  components: {
+    Avatar
+  },
+  props: {
+    // 区域id
+    value: {
+      type: [String, Array],
+      default: null
+    },
+    // 自定义查询参数
+    query: {
+      type: Object,
+      default: () => ({})
+    },
+    // 是否多选
+    multiple: {
+      type: Boolean,
+      default: false
+    },
+    // 初始化选项
+    initOptions: {
+      type: Array,
+      default: () => []
+    }
+  },
+  data() {
+    return {
+      options: [],
+      total: 0,
+      loading: false,
+      queryParams: {
+        pageNum: 1,
+        pageSize: 100,
+        keyword: null
+      }
+    }
+  },
+  computed: {
+    selectedValue: {
+      get() {
+        return this.value
+      },
+      set(value) {
+        this.$emit('input', value)
+      }
+    },
+    // 是否全选
+    isAllSelected() {
+      return this.multiple && this.options.length > 0 && Array.isArray(this.value) && this.value.length === this.options.length;
+    }
+  },
+  created() {
+    if (!isEmpty(this.initOptions)) {
+      this.options = this.initOptions;
+    }
+  },
+  methods: {
+    isEmpty,
+    // 远程搜索
+    remoteMethod(val) {
+      this.queryParams.keyword = val;
+      this.getOptions();
+    },
+    // 下拉框可见性变化
+    handleVisibleChange(visible) {
+      if (visible) {
+        this.getOptions();
+      }
+    },
+    // 全选
+    handleSelectAll() {
+      if (this.isAllSelected) {
+        this.handleChange([]);
+      } else {
+        const allids = this.options.map(item => item.id);
+        this.handleChange(allids);
+      }
+    },
+    handleChange(value) {
+      if (this.multiple) {
+        let list = this.options.filter(item => value.includes(item.id));
+        this.$emit('change', list);
+      } else {
+        let item = this.options.find(item => value.includes(item.id));
+        this.$emit('change', item);
+      }
+    },
+    // 获取选项
+    getOptions() {
+      this.loading = true;
+      this.queryParams = {
+        ...this.queryParams,
+        ...this.query
+      }
+      listModel(this.queryParams).then(res => {
+        this.options = res.rows;
+        this.total = res.total;
+      }).finally(() => {
+        this.loading = false;
+      });
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.select-footer {
+  padding: 2px 12px;
+  border-bottom: 1px solid #EBEEF5;
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
+  line-height: 1em;
+}
+</style>
\ No newline at end of file
diff --git a/src/components/Business/Suit/SuitDialog.vue b/src/components/Business/Suit/SuitDialog.vue
new file mode 100644
index 0000000..4cc0011
--- /dev/null
+++ b/src/components/Business/Suit/SuitDialog.vue
@@ -0,0 +1,175 @@
+<template>
+  <check-dialog
+    :show.sync="dialogVisible"
+    :title="title"
+    :columns="columns"
+    :selected-ids="selectedIds"
+    :list-api="listApi"
+    :load-api="loadApi"
+    :query="queryParams"
+    :custom-query="query"
+    prop="id"
+    @confirm="handleConfirm"
+    :multiple="multiple"
+    ref="checkDialog"
+  >
+    <template #search-form>
+      <el-form-item label="运营商" prop="userName">
+        <el-input
+          v-model="queryParams.userName"
+          placeholder="请输入运营商名称"
+          clearable
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="套餐名称" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          placeholder="请输入套餐名称"
+          clearable
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-select
+          v-model="queryParams.status"
+          placeholder="请选择状态"
+          clearable
+          @change="handleQuery"
+        >
+          <el-option
+            v-for="dict in dict.type.suit_status"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="租赁单位" prop="rentalUnit">
+        <el-select
+          v-model="queryParams.rentalUnit"
+          placeholder="请选择租赁单位"
+          clearable
+          @change="handleQuery"
+        >
+          <el-option
+            v-for="dict in dict.type.suit_rental_unit"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+    </template>
+
+    <template #status="{row}">
+      <dict-tag :options="dict.type.suit_status" :value="row.status" />
+    </template>
+    <template #rentalUnit="{row}">
+      <dict-tag :options="dict.type.suit_rental_unit" :value="row.rentalUnit" />
+    </template>
+    <template #depositAmount="{row}">
+      {{ row.depositAmount | fix2 | dv }} 元
+    </template>
+    <template #freeRideTime="{row}">
+      {{ row.freeRideTime | dv }} 分钟
+    </template>
+
+  </check-dialog>
+</template>
+
+<script>
+import { listSuit, listSuitByIds } from "@/api/bst/suit";
+import CheckDialog from "@/components/CheckDialog/index.vue";
+import { isEmpty } from '@/utils/index'
+
+export default {
+  name: "SuitDialog",
+  dicts: ['suit_status', 'suit_rental_unit', 'suit_riding_rule'],
+  components: {
+    CheckDialog
+  },
+  props: {
+    // 弹窗标题
+    title: {
+      type: String,
+      default: "选择套餐"
+    },
+    // 是否显示弹窗
+    show: {
+      type: Boolean,
+      default: false
+    },
+    // 默认选中的ID列表
+    selectedIds: {
+      type: [String, Array],
+      default: null,
+    },
+    // 列表API
+    listApi: {  
+      type: Function,
+      default: listSuit
+    },
+    // 加载API
+    loadApi: {
+      type: Function,
+      default: listSuitByIds
+    },
+    // 是否多选
+    multiple: {
+      type: Boolean,
+      default: false
+    },
+    // 查询参数
+    query: {
+      type: Object,
+      default: () => ({})
+    },
+  },
+  data() {
+    return {
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        name: null,
+        userName: null,
+        status: null,
+        rentalUnit: null
+      },
+      // 列信息
+      columns: [
+        {key: 'id', visible: false, label: 'ID', minWidth: null, sortable: true, overflow: false, align: 'center', width: "80"},
+        {key: 'name', visible: true, label: '名称', minWidth: "200", sortable: true, overflow: false, align: 'left', width: null},
+        {key: 'userName', visible: true, label: '运营商', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
+        {key: 'depositAmount', visible: true, label: '预存', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
+        {key: 'rentalUnit', visible: true, label: '租赁单位', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
+        {key: 'freeRideTime', visible: true, label: '免费时长', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
+        {key: 'status', visible: true, label: '状态', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
+        {key: 'createTime', visible: true, label: '创建时间', minWidth: null, sortable: true, overflow: false, align: 'center', width: null}
+      ],
+    }
+  },
+  computed: {
+    dialogVisible: {
+      get() {
+        return this.show;
+      },
+      set(val) {
+        this.$emit("update:show", val);
+      }
+    }
+  },
+  methods: {
+    isEmpty,
+    // 确认选择
+    handleConfirm(selection) {
+      this.$emit('confirm', selection);
+    },
+    // 处理查询
+    handleQuery() {
+      this.$refs.checkDialog.handleQuery();
+    }
+  }
+}
+</script>
diff --git a/src/components/Business/Suit/SuitInput.vue b/src/components/Business/Suit/SuitInput.vue
new file mode 100644
index 0000000..40b6e74
--- /dev/null
+++ b/src/components/Business/Suit/SuitInput.vue
@@ -0,0 +1,84 @@
+<template>
+  <div>
+    <el-input 
+      :value="text"
+      @focus="onOpen"
+      placeholder="点击选择套餐"
+      :disabled="disabled"
+    />
+
+    <suit-dialog 
+      :show.sync="showDialog" 
+      @confirm="handleConfirm" 
+      :selected-ids="value"
+      :multiple="multiple"
+      :query="query"
+    />
+  </div>
+</template>
+
+<script>
+import SuitDialog from '@/components/Business/Suit/SuitDialog.vue';
+
+export default {
+  name: 'SuitInput',
+  components: { SuitDialog },
+  props: {
+    // 选中数据
+    value: {
+      type: [String, Array],
+      default: null
+    },
+    // 文本
+    text: {
+      type: String,
+      default: null,
+    },
+    // 是否多选
+    multiple: {
+      type: Boolean,
+      default: false
+    },
+    // 是否禁用
+    disabled: {
+      type: Boolean,
+      default: false,
+    },
+    // 查询参数
+    query: {
+      type: Object,
+      default: () => ({})
+    },
+    // 打开弹窗前回调
+    beforeOpen: {
+      type: Function,
+      default: () => {
+        return true;
+      }
+    }
+  },
+  data() {
+    return {
+      showDialog: false,
+    }
+  },
+  methods: {
+    onOpen() {
+      if (!this.beforeOpen()) {
+        return;
+      }
+      this.showDialog = true;
+    },
+    handleConfirm(selection) {
+      if (this.multiple) {
+        this.$emit('input', selection.map(item => item.id));
+        this.$emit('update:text', selection.map(item => item.name).join(','));
+      } else {
+        this.$emit('input', selection?.id);
+        this.$emit('update:text', selection?.name);
+      }
+      this.$emit('confirm', selection);
+    }
+  }
+}
+</script>
\ No newline at end of file
diff --git a/src/components/Business/User/UserInput.vue b/src/components/Business/User/UserInput.vue
index 2808e2a..3fb7f41 100644
--- a/src/components/Business/User/UserInput.vue
+++ b/src/components/Business/User/UserInput.vue
@@ -21,6 +21,7 @@
 <script>
 import UserDialog from '@/components/Business/User/UserDialog.vue';
 import { UserType } from '@/utils/enums';
+import { isDeepEqual } from '@/utils';
 export default {
   components: { UserDialog },
   props: {
@@ -62,14 +63,24 @@ export default {
   },
   methods: {
     handleConfirm(selection) {
+      let value = null;
+      let text = null;
       if (this.multiple) {
-        this.$emit('input', selection.map(item => item.userId));
-        this.$emit('update:text', selection.map(item => item.nickName).join(','));
+        value = selection.map(item => item.userId);
+        text = selection.map(item => item.nickName).join(',');
       } else {
-        this.$emit('input', selection?.userId);
-        this.$emit('update:text', selection?.nickName);
+        value = selection?.userId;
+        text = selection?.nickName;
       }
+      // 如果值发生变化,则触发 change 事件
+      if (!isDeepEqual(this.value, value)) {
+        this.$emit('change', value);
+      }
+      
+      this.$emit('input', value);
+      this.$emit('update:text', text);
       this.$emit('confirm', selection);
+      
     }
   }
 }
diff --git a/src/components/CheckDialog/index.vue b/src/components/CheckDialog/index.vue
index 43e15ba..51ebfc5 100644
--- a/src/components/CheckDialog/index.vue
+++ b/src/components/CheckDialog/index.vue
@@ -122,7 +122,7 @@ export default {
     customQuery: {
       type: Object,
       default: () => ({})
-    }
+    },
   },
   data() {
     return {
@@ -155,7 +155,7 @@ export default {
       // 合并自定义查询条件
       Object.assign(this.query, this.customQuery);
 
-      if (this.multiple) {
+      if (this.multiple && this.selectedIds != null) {
         this.selection = [...this.selectedIds];
       } else {
         this.selection = [this.selectedIds];
diff --git a/src/utils/enums.js b/src/utils/enums.js
index c813481..b59e43e 100644
--- a/src/utils/enums.js
+++ b/src/utils/enums.js
@@ -74,4 +74,45 @@ export const SuitRentalUnit = {
 export const SuitRidingRule = {
   START: "1", // 起步价
   INTERVAL: "2", // 区间计费
-}
\ No newline at end of file
+}
+
+// 设备状态
+export const DeviceStatus = {
+  STORAGE: "0", // 仓库中
+  AVAILABLE: "1", // 待骑行
+  RESERVED: "2", // 预约中
+  IN_USE: "3", // 骑行中
+  TEMP_LOCKED: "4", // 临时锁车
+  DISPATCHING: "6", // 调度中
+  DISABLED: "8", // 禁用
+
+
+  // 允许入仓的设备状态
+  canIn() {
+    return [this.AVAILABLE, this.DISPATCHING, this.DISABLED];
+  },
+  // 允许出仓的设备状态
+  canOut() {
+    return [this.STORAGE];
+  },
+  // 允许禁用的设备状态
+  canDisable() {
+    return [this.AVAILABLE, this.DISPATCHING, this.STORAGE];
+  },
+  // 允许启用的设备状态
+  canEnable() {
+    return [this.DISABLED];
+  },
+  // 允许管理员开锁的设备状态
+  canAdminUnlock() {
+    return [this.DISPATCHING, this.STORAGE, this.AVAILABLE, this.TEMP_LOCKED];
+  },
+  // 允许用户开锁的设备状态
+  canUserUnlock() {
+    return [this.IN_USE, this.AVAILABLE, this.TEMP_LOCKED];
+  },
+  // 允许锁车的设备状态
+  canLock() {
+    return [this.AVAILABLE, this.TEMP_LOCKED, this.DISPATCHING, this.IN_USE];
+  },
+}
diff --git a/src/views/bst/device/components/DeviceEditDialog.vue b/src/views/bst/device/components/DeviceEditDialog.vue
new file mode 100644
index 0000000..0019997
--- /dev/null
+++ b/src/views/bst/device/components/DeviceEditDialog.vue
@@ -0,0 +1,179 @@
+<template>
+  <el-dialog 
+    :title="title" 
+    :visible.sync="dialogVisible" 
+    width="500px" 
+    append-to-body 
+    :close-on-click-modal="false"
+    @open="handleOpen"
+  >
+    <el-form ref="form" :model="form" :rules="rules" label-width="80px" v-loading="loading">
+      <el-row>
+        <form-col :span="span" label="MAC" prop="mac">
+          <el-input v-model="form.mac" placeholder="请输入设备Mac号" :disabled="form.id != null"/>
+        </form-col>
+        <form-col :span="span" label="SN" prop="sn">
+          <el-input v-model="form.sn" placeholder="请输入设备SN号" :disabled="form.id != null" />
+        </form-col>
+        <form-col :span="span" label="车牌号" prop="vehicleNum">
+          <el-input v-model="form.vehicleNum" placeholder="请输入车牌号" />
+        </form-col>
+        <form-col :span="span" label="车型" prop="modelId">
+          <model-remote-select v-model="form.modelId" style="width: 100%;" v-if="!loading" :init-options="initModelOptions" />
+        </form-col>
+        <form-col :span="span" label="所属用户" prop="mchId">
+          <user-input v-model="form.mchId" :text.sync="form.mchName" :disabled="!checkPermi(['system:user:list'])"/>
+        </form-col>
+        <form-col :span="span" label="运营区" prop="areaId">
+          <area-remote-select v-model="form.areaId" style="width: 100%;" :init-options="initAreaOptions" v-if="!loading"/>
+        </form-col>
+      </el-row>
+    </el-form>
+    <div slot="footer" class="dialog-footer">
+      <el-button type="primary" @click="submitForm">确 定</el-button>
+      <el-button @click="cancel">取 消</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { getDevice, addDevice, updateDevice } from "@/api/bst/device";
+import FormCol from "@/components/FormCol/index.vue";
+import UserInput from '@/components/Business/User/UserInput.vue';
+import AreaRemoteSelect from '@/components/Business/Area/AreaRemoteSelect.vue';
+import {RoleKeys} from '@/utils/enums';
+import { mapGetters } from 'vuex';
+import ModelRemoteSelect from '@/components/Business/Model/ModelRemoteSelect.vue';
+
+export default {
+  name: 'DeviceEditDialog',
+  components: { FormCol, UserInput, AreaRemoteSelect, ModelRemoteSelect },
+  dicts: ['device_status', 'device_lock_status', 'device_iot_status', 'device_online_status'],
+  props: {
+    visible: {
+      type: Boolean,
+      default: false
+    },
+    id: {
+      type: [String, Number],
+      default: null
+    },
+    initData: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  data() {
+    return {
+      RoleKeys,
+      loading: false,
+      span: 24,
+      title: '',
+      form: {},
+      rules: {
+        mac: [
+          { required: true, message: "设备Mac号不能为空", trigger: "blur" }
+        ],
+        sn: [
+          { required: true, message: "设备SN号不能为空", trigger: "blur" }
+        ],
+      }
+    }
+  },
+  computed: {
+    ...mapGetters(['userId', 'nickName']),
+    dialogVisible: {
+      get() {
+        return this.visible;
+      },
+      set(val) {
+        this.$emit('update:visible', val);
+      }
+    },
+    initAreaOptions() {
+      return [{id: this.form.areaId, name: this.form.areaName}]
+    },
+    initModelOptions() {
+      return [{id: this.form.modelId, name: this.form.modelName}]
+    }
+  },
+  methods: {
+    handleOpen() {
+      if (this.id == null) {
+        this.reset();
+        this.title = "添加设备";
+      } else {
+        this.getDetail();
+        this.title = "修改设备";
+      }
+    },
+    reset() {
+      this.form = {
+        id: null,
+        modelId: null,
+        picture: null,
+        deviceName: null,
+        mac: null,
+        sn: null,
+        vehicleNum: null,
+        areaId: null,
+        activationTime: null,
+        onlineStatus: null,
+        createTime: null,
+        updateTime: null,
+        remark: null,
+        status: null,
+        lockStatus: null,
+        location: null,
+        remainingPower: null,
+        voltage: null,
+        longitude: null,
+        latitude: null,
+        lastTime: null,
+        version: null,
+        signalStrength: null,
+        quality: null,
+        satellites: null,
+        gps: null,
+        lastLocationTime: null,
+        hardwareVersionId: null,
+        mchId: null,
+        iotStatus: null,
+        isSound: null,
+        ...this.initData
+      };
+      this.$nextTick(() => {
+        this.$refs.form && this.$refs.form.clearValidate();
+      });
+    },
+    getDetail() {
+      this.loading = true;
+      getDevice(this.id).then(response => {
+        this.form = response.data;
+      }).finally(() => {
+        this.loading = false;
+      });
+    },
+    submitForm() {
+      this.$refs.form.validate(valid => {
+        if (valid) {
+          const promise = this.form.id != null ? updateDevice(this.form) : addDevice(this.form);
+          promise.then(response => {
+            this.$modal.msgSuccess(this.form.id != null ? "修改成功" : "新增成功");
+            this.dialogVisible = false;
+            this.$emit('success');
+          });
+        }
+      });
+    },
+    cancel() {
+      this.dialogVisible = false;
+      this.reset();
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style> 
\ No newline at end of file
diff --git a/src/views/bst/device/index.vue b/src/views/bst/device/index.vue
new file mode 100644
index 0000000..fa28a2a
--- /dev/null
+++ b/src/views/bst/device/index.vue
@@ -0,0 +1,696 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="SN" prop="sn">
+        <el-input
+          v-model="queryParams.sn"
+          placeholder="请输入设备SN号"
+          clearable
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="MAC" prop="mac">
+        <el-input
+          v-model="queryParams.mac"
+          placeholder="请输入MAC号"
+          clearable
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="设备名称" prop="deviceName">
+        <el-input
+          v-model="queryParams.deviceName"
+          placeholder="请输入设备名称"
+          clearable
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="型号" prop="modelName">
+        <el-input
+          v-model="queryParams.modelName"
+          placeholder="请输入型号名称"
+          clearable
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="车牌号" prop="vehicleNum">
+        <el-input
+          v-model="queryParams.vehicleNum"
+          placeholder="请输入车牌号"
+          clearable
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="运营区" prop="areaId">
+        <el-input
+          v-model="queryParams.areaName"
+          placeholder="请输入运营区名称"
+          clearable
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="在线状态" prop="onlineStatus">
+        <el-select v-model="queryParams.onlineStatus" placeholder="请选择在线状态" clearable @change="handleQuery">
+          <el-option
+            v-for="dict in dict.type.device_online_status"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="车辆状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择车辆状态" clearable @change="handleQuery">
+          <el-option
+            v-for="dict in dict.type.device_status"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="锁状态" prop="lockStatus">
+        <el-select v-model="queryParams.lockStatus" placeholder="请选择锁状态" clearable @change="handleQuery">
+          <el-option
+            v-for="dict in dict.type.device_lock_status"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="运营商" prop="mchName">
+        <el-input
+          v-model="queryParams.mchName"
+          placeholder="请输入运营商"
+          clearable
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="物联网状态" prop="iotStatus" label-width="6em">
+        <el-select v-model="queryParams.iotStatus" placeholder="请选择物联网状态" clearable @change="handleQuery">
+          <el-option
+            v-for="dict in dict.type.device_iot_status"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+          v-has-permi="['bst:device:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="el-icon-delete"
+          size="mini"
+          :disabled="multiple"
+          @click="handleDelete"
+          v-has-permi="['bst:device:remove']"
+        >删除</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          @click="handleExport"
+          v-has-permi="['bst:device:export']"
+        >导出</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="el-icon-upload"
+          size="mini"
+          @click="handleOut"
+          :disabled="multiple"
+          v-has-permi="['bst:device:out']"
+        >一键出仓</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          @click="handleIn"
+          :disabled="multiple"
+          v-has-permi="['bst:device:in']"
+        >一键入仓</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="el-icon-close"
+          size="mini"
+          @click="handleDisable"
+          :disabled="multiple"
+          v-has-permi="['bst:device:disable']"
+        >一键禁用</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-check"
+          size="mini"
+          @click="handleEnable"
+          :disabled="multiple"
+          v-has-permi="['bst:device:enable']"
+        >一键启用</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList(true)" :columns="columns"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" :data="deviceList" @selection-change="handleSelectionChange" :default-sort="defaultSort"  @sort-change="onSortChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <template v-for="column of showColumns">
+        <el-table-column
+            :key="column.key"
+            :label="column.label"
+            :prop="column.key"
+            :align="column.align"
+            :min-width="column.minWidth"
+            :sort-orders="orderSorts"
+            :sortable="column.sortable"
+            :show-overflow-tooltip="column.overflow"
+            :width="column.width"
+        >
+          <template slot-scope="d">
+            <template v-if="column.key === 'id'">
+              {{d.row[column.key]}}
+            </template>
+            <template v-else-if="column.key === 'sn'">
+              {{d.row.sn | dv}}
+              <dict-tag :options="dict.type.device_online_status" :value="d.row.onlineStatus" size="mini"/>
+              <dict-tag :options="dict.type.device_status" :value="d.row.status" size="mini" style="margin-left: 4px;"/>
+            </template>
+            <template v-else-if="column.key === 'lockStatus'">
+              <dict-tag :options="dict.type.device_lock_status" :value="d.row[column.key]" size="mini"/>
+            </template>
+            <template v-else-if="column.key === 'iotStatus'">
+              <dict-tag :options="dict.type.device_iot_status" :value="d.row[column.key]" size="mini"/>
+            </template>
+            <template v-else-if="column.key === 'quality'">
+              <dict-tag :options="dict.type.device_quality" :value="d.row[column.key]" size="mini"/>
+            </template>
+            <template v-else-if="column.key === 'remainingPower'">
+              {{d.row.remainingPower | fix2 | dv}} %
+            </template>
+            <template v-else-if="column.key === 'voltage'">
+              {{d.row.voltage | fix2 | dv}} V
+            </template>
+            <template v-else-if="column.key === 'isSound'">
+              <boolean-tag :value="d.row[column.key]" size="mini" true-text="有声" false-text="静音"/>
+            </template>
+            <template v-else>
+              {{d.row[column.key]}}
+            </template>
+          </template>
+        </el-table-column>
+      </template>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="300">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+            v-has-permi="['bst:device:edit']"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-upload"
+            @click="handleOut(scope.row)"
+            v-has-permi="['bst:device:out']"
+            v-show="DeviceStatus.canOut().includes(scope.row.status)"
+          >出仓</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-download" 
+            @click="handleIn(scope.row)"
+            v-has-permi="['bst:device:in']"
+            v-show="DeviceStatus.canIn().includes(scope.row.status)"
+          >入仓</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-close"
+            @click="handleDisable(scope.row)"
+            v-has-permi="['bst:device:disable']"
+            v-show="DeviceStatus.canDisable().includes(scope.row.status)"
+          >禁用</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-check"
+            @click="handleEnable(scope.row)"
+            v-has-permi="['bst:device:enable']"
+            v-show="DeviceStatus.canEnable().includes(scope.row.status)"
+          >启用</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-unlock"
+            @click="handleUnlock(scope.row)"
+            v-has-permi="['bst:device:unlock']"
+            v-show="DeviceStatus.canAdminUnlock().includes(scope.row.status)"
+          >开锁</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-lock"
+            @click="handleLock(scope.row)"
+            v-has-permi="['bst:device:lock']"
+            v-show="DeviceStatus.canLock().includes(scope.row.status)"
+          >锁车</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-bell"
+            @click="handleRing(scope.row)"
+            v-has-permi="['bst:device:ring']"
+          >响铃</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-refresh"
+            @click="handleReboot(scope.row)"
+            v-has-permi="['bst:device:reboot']"
+          >重启</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-unlock"
+            @click="handleUnlockSeat(scope.row)"
+            v-has-permi="['bst:device:unlockSeat']"
+          >开坐垫锁</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-refresh"
+            @click="handleRefresh(scope.row)"
+            v-has-permi="['bst:device:refresh']"
+          >刷新</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-has-permi="['bst:device:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList(true)"
+    />
+
+    <!-- 添加或修改设备对话框 -->
+    <device-edit-dialog
+      :visible.sync="open"
+      :id="editId"
+      @success="getList(true)"
+    />
+  </div>
+</template>
+
+<script>
+import { listDevice, delDevice, inDevice, outDevice, disableDevice, enableDevice} from "@/api/bst/device";
+import { unlockDevice, lockDevice, ringDevice, rebootDevice, unlockSeatDevice, refreshDevice} from "@/api/bst/deviceIot";
+import { $showColumns } from '@/utils/mixins';
+import FormCol from "@/components/FormCol/index.vue";
+import DeviceEditDialog from './components/DeviceEditDialog.vue';
+import BooleanTag from '@/components/BooleanTag/index.vue';
+import { DeviceStatus } from '@/utils/enums';
+
+  // 默认排序字段
+const defaultSort = {
+  prop: "createTime",
+  order: "descending"
+}
+
+export default {
+  name: "Device",
+  mixins: [$showColumns],
+  dicts: ['device_status', 'device_lock_status', 'device_iot_status', 'device_online_status', 'device_quality'],
+  components: {FormCol, DeviceEditDialog, BooleanTag},
+  data() {
+    return {
+      DeviceStatus,
+      span: 24,
+      // 字段列表
+      columns: [
+        {key: 'id', visible: false, label: 'ID', minWidth: null, sortable: true, overflow: false, align: 'center', width: "80"},
+        {key: 'sn', visible: true, label: 'SN', minWidth: null, sortable: true, overflow: false, align: 'left', width: "180"},
+        {key: 'mac', visible: true, label: 'MAC', minWidth: "100", sortable: true, overflow: false, align: 'center', width: null},
+        {key: 'vehicleNum', visible: true, label: '车牌', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
+        {key: 'mchName', visible: true, label: '用户', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
+        {key: 'areaName', visible: true, label: '运营区', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
+        {key: 'modelName', visible: true, label: '型号', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
+        {key: 'signalStrength', visible: true, label: '信号', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
+        {key: 'satellites', visible: true, label: '卫星', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
+        {key: 'quality', visible: true, label: '电门', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
+        {key: 'lockStatus', visible: true, label: '锁', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
+        {key: 'voltage', visible: true, label: '电压', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
+        {key: 'remainingPower', visible: true, label: '电量', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
+        {key: 'isSound', visible: true, label: '声音', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
+        {key: 'remark', visible: true, label: '备注', minWidth: null, sortable: true, overflow: true, align: 'center', width: null},
+      ],
+      // 排序方式
+      orderSorts: ['ascending', 'descending', null],
+      // 遮罩层
+      loading: true,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 设备表格数据
+      deviceList: [],
+      // 是否显示弹出层
+      open: false,
+      // 编辑ID
+      editId: null,
+      defaultSort,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 20,
+        orderByColumn: defaultSort.prop,
+        isAsc: defaultSort.order,
+        refresh: true,
+        id: null,
+        modelId: null,
+        deviceName: null,
+        mac: null,
+        sn: null,
+        vehicleNum: null,
+        areaId: null,
+        onlineStatus: null,
+        remark: null,
+        status: null,
+        lockStatus: null,
+        location: null,
+        gps: null,
+        lastLocationTime: null,
+        hardwareVersionId: null,
+        mchId: null,
+        iotStatus: null,
+        isSound: null
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        modelId: [
+          { required: true, message: "型号id不能为空", trigger: "blur" }
+        ],
+        mac: [
+          { required: true, message: "设备Mac号不能为空", trigger: "blur" }
+        ],
+        sn: [
+          { required: true, message: "设备SN号不能为空", trigger: "blur" }
+        ],
+        createTime: [
+          { required: true, message: "创建时间不能为空", trigger: "blur" }
+        ],
+      }
+    };
+  },
+  created() {
+    this.getList(true);
+  },
+  methods: {
+    // 管理员开锁
+    handleUnlock(row) {
+      this.$confirm('是否确认操作设备【' + row.sn + '】开锁?', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        unlockDevice({ id: row.id }).then((res) => {
+          if (res.code === 200) {
+            this.$message.success("操作成功,设备已开锁");
+            this.getList(true);
+          }
+        })
+      })
+    },
+    // 管理员锁车
+    handleLock(row) {
+      this.$confirm('是否确认操作设备【' + row.sn + '】锁车?', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        lockDevice({ id: row.id }).then((res) => {
+          if (res.code === 200) {
+            this.$message.success("操作成功,设备已锁车");
+            this.getList(true);
+          }
+        })
+      })
+    },
+    // 管理员响铃寻车
+    handleRing(row) {
+      this.$confirm('是否确认操作设备【' + row.sn + '】响铃寻车?', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        ringDevice({ id: row.id }).then((res) => {
+          if (res.code === 200) {
+            this.$message.success("操作成功,设备已响铃");
+            this.getList(true);
+          }
+        })
+      })
+    },
+    // 管理员重启
+    handleReboot(row) {
+      this.$confirm('是否确认操作设备【' + row.sn + '】重启?', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        rebootDevice({ id: row.id }).then((res) => {
+          if (res.code === 200) {
+            this.$message.success("操作成功,设备已重启");
+            this.getList(true);
+          }
+        })
+      })
+    },
+    // 管理员开坐垫锁
+    handleUnlockSeat(row) {
+      this.$confirm('是否确认操作设备【' + row.sn + '】开坐垫锁?', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        unlockSeatDevice({ id: row.id }).then((res) => {
+          if (res.code === 200) {
+            this.$message.success("操作成功,坐垫锁已开");
+            this.getList(true);
+          }
+        })
+      })
+    },
+    // 管理员刷新
+    handleRefresh(row) {
+      this.$confirm('是否确认刷新设备【' + row.sn + '】?', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        refreshDevice({ id: row.id }).then((res) => {
+          if (res.code === 200) {
+            this.$message.success("操作成功,设备已刷新");
+            this.getList(false);
+          }
+        })
+      })
+    },
+    // 一键入仓
+    handleOut(row) {
+      let msg = '是否确认一键出仓?';
+      let ids = this.ids;
+      if (row != null) {
+        ids = [row.id];
+        msg = '是否确认出仓设备【' + row.sn + '】?';
+      }
+      this.$confirm(msg, {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        outDevice(ids).then((res) => {
+          if (res.code === 200) {
+            this.$message.success("操作成功,共出仓" + res.data + "台设备");
+            this.getList(true);
+          } 
+        })
+      })
+    },
+    // 一键出仓
+    handleIn(row) {
+      let msg = '是否确认一键入仓?';
+      let ids = this.ids;
+      if (row != null) {
+        ids = [row.id];
+        msg = '是否确认入仓设备【' + row.sn + '】?';
+      }
+      this.$confirm(msg, {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        inDevice(ids).then((res) => {
+          if (res.code === 200) {
+            this.$message.success("操作成功,共入仓" + res.data + "台设备");
+            this.getList(true);
+          }
+        })
+      })
+    },
+    // 一键禁用
+    handleDisable(row) {
+      let msg = '是否确认一键禁用?';
+      let ids = this.ids;
+      if (row != null) {
+        ids = [row.id];
+        msg = '是否确认禁用设备【' + row.sn + '】?';
+      }
+      this.$confirm(msg, { 
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        disableDevice(ids).then((res) => {
+          if (res.code === 200) {
+            this.$message.success("操作成功,共禁用" + res.data + "台设备");
+            this.getList(true);
+          }
+        })
+      })
+    },
+    // 一键启用
+    handleEnable(row) {
+      let msg = '是否确认一键启用?';
+      let ids = this.ids;
+      if (row != null) {
+        ids = [row.id];
+        msg = '是否确认启用设备【' + row.sn + '】?';
+      }
+      this.$confirm(msg, { 
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        enableDevice(ids).then((res) => {
+          if (res.code === 200) {
+            this.$message.success("操作成功,共启用" + res.data + "台设备");
+            this.getList();
+          }
+        })
+      })
+    },
+    /** 当排序按钮被点击时触发 **/
+    onSortChange(column) {
+      if (column.order == null) {
+        this.queryParams.orderByColumn = defaultSort.prop;
+        this.queryParams.isAsc = defaultSort.order;
+      } else {
+        this.queryParams.orderByColumn = column.prop;
+        this.queryParams.isAsc = column.order;
+      }
+      this.getList();
+    },
+    /** 查询设备列表 */
+    getList(refresh = false) {
+      this.loading = true;
+      this.queryParams.refresh = refresh;
+      listDevice(this.queryParams).then(response => {
+        this.deviceList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList(true);
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.editId = null;
+      this.open = true;
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.editId = row.id || this.ids;
+      this.open = true;
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      this.$modal.confirm('是否确认删除设备编号为"' + ids + '"的数据项?').then(function() {
+        return delDevice(ids);
+      }).then(() => {
+        this.getList(true);
+        this.$modal.msgSuccess("删除成功");
+      }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      this.download('bst/device/export', {
+        ...this.queryParams
+      }, `device_${new Date().getTime()}.xlsx`)
+    }
+  }
+};
+</script>
diff --git a/src/views/bst/model/components/ModelEditDialog.vue b/src/views/bst/model/components/ModelEditDialog.vue
new file mode 100644
index 0000000..ff94822
--- /dev/null
+++ b/src/views/bst/model/components/ModelEditDialog.vue
@@ -0,0 +1,228 @@
+<template>
+  <el-dialog 
+    :title="title" 
+    :visible.sync="dialogVisible" 
+    width="500px" 
+    append-to-body 
+    :close-on-click-modal="false"
+    @open="handleOpen"
+  >
+    <el-form ref="form" :model="form" :rules="rules" label-width="80px" size="small">
+      <el-row>
+        <form-col :span="span" label="车型名称" prop="name">
+          <el-input v-model="form.name" placeholder="请输入车型名称" />
+        </form-col>
+        <form-col :span="span" label="所属用户" prop="userId">
+          <user-input 
+            v-model="form.userId" 
+            :text.sync="form.userName" 
+            :disabled="!checkRole([RoleKeys.ADMIN])"
+            @change="handleChangeUser"
+          />
+        </form-col>
+        <form-col :span="span" label="满电电压" prop="fullVoltage">
+          <el-input v-model="form.fullVoltage" placeholder="请输入满电电压" type="number">
+            <template slot="append">V</template>
+          </el-input>
+        </form-col>
+        <form-col :span="span" label="亏电电压" prop="lowVoltage">
+          <el-input v-model="form.lowVoltage" placeholder="请输入亏电电压" type="number">
+            <template slot="append">V</template>
+          </el-input>
+        </form-col>
+        <form-col :span="span" label="满电续航" prop="fullEndurance">
+          <el-input v-model="form.fullEndurance" placeholder="请输入满电续航" type="number">
+            <template slot="append">KM</template>
+          </el-input>
+        </form-col>
+        <form-col :span="span" label="备注" prop="remark">
+          <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
+        </form-col>
+        <form-col :span="span" label="低电量提醒开关" prop="lowBatteryReminderSwitch" label-width="9em">
+          <el-switch v-model="form.lowBatteryReminderSwitch" />
+        </form-col>
+        <form-col :span="span" label="骑行低电量提醒" prop="lowBatteryReminder" label-width="9em" v-if="form.lowBatteryReminderSwitch">
+          <el-input v-model="form.lowBatteryReminder" placeholder="请输入骑行低电量提醒" type="number">
+            <template slot="append">%</template>
+          </el-input>
+        </form-col>
+        <form-col :span="span" label="套餐" prop="suitIds">
+          <suit-input 
+            v-model="form.suitIds"
+            :text="suitNames" 
+            multiple
+            :query="suitQuery"
+            :before-open="beforeOpenSuit"
+          />
+        </form-col>
+      </el-row>
+    </el-form>
+    <div slot="footer" class="dialog-footer">
+      <el-button type="primary" @click="submitForm">确 定</el-button>
+      <el-button @click="cancel">取 消</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { getModel, addModel, updateModel } from "@/api/bst/model";
+import FormCol from "@/components/FormCol/index.vue";
+import UserInput from '@/components/Business/User/UserInput.vue';
+import AreaRemoteSelect from '@/components/Business/Area/AreaRemoteSelect.vue';
+import { RoleKeys } from '@/utils/enums';
+import SuitInput from '@/components/Business/Suit/SuitInput.vue';
+import { mapGetters } from 'vuex';
+
+export default {
+  name: "ModelEditDialog",
+  components: {
+    FormCol,
+    UserInput,
+    AreaRemoteSelect,
+    SuitInput
+  },
+  props: {
+    visible: {
+      type: Boolean,
+      default: false
+    },
+    id: {
+      type: [String, Number],
+      default: null
+    },
+    initData: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  data() {
+    return {
+      RoleKeys,
+      span: 24,
+      title: '',
+      form: {},
+      rules: {
+        areaId: [
+          { required: true, message: "运营区不能为空", trigger: "change" }
+        ],
+        userId: [
+          { required: true, message: "运营商不能为空", trigger: "change" }
+        ],
+        name: [
+          { required: true, message: "车型名称不能为空", trigger: "blur" }
+        ],
+        fullVoltage: [
+          { required: true, message: "满电电压不能为空", trigger: "blur" }
+        ],
+        lowVoltage: [
+          { required: true, message: "亏电电压不能为空", trigger: "blur" }
+        ],
+        fullEndurance: [
+          { required: true, message: "满电续航不能为空", trigger: "blur" }
+        ],
+        lowBatteryReminderSwitch: [
+          { required: true, message: "低电量提醒开关不能为空", trigger: "blur" }
+        ],
+        lowBatteryReminder: [
+          { required: true, message: "骑行低电量提醒值不能为空", trigger: "blur" }
+        ]
+      }
+    }
+  },
+  computed: {
+    ...mapGetters(['userId']),
+    dialogVisible: {
+      get() {
+        return this.visible;
+      },
+      set(val) {
+        this.$emit('update:visible', val);
+      }
+    },
+    // 套餐查询条件
+    suitQuery() {
+      return {
+        userId: this.form.userId
+      }
+    },
+    suitNames() {
+      return 
+    }
+  },
+  methods: {
+    handleChangeUser() {
+      if (this.form.suitIds != null && this.form.suitIds.length > 0) {
+        this.form.suitIds = [];
+        this.form.suitNames = null;
+        this.$message.warning("由于更换了所属用户,套餐数据已清空");
+      }
+    },
+    beforeOpenSuit() {
+      if (this.form.userId == null) {
+        this.$modal.msgError("请先选择所属用户");
+        return false;
+      }
+      return true;
+    },
+    handleOpen() {
+      if (this.id == null) {
+        this.reset();
+        this.title = "添加车辆型号";
+      } else {
+        this.getDetail();
+        this.title = "修改车辆型号";
+      }
+    },
+    reset() {
+      this.form = {
+        id: null,
+        areaId: null,
+        userId: this.userId,
+        name: null,
+        fullVoltage: null,
+        lowVoltage: null,
+        fullEndurance: null,
+        createTime: null,
+        remark: null,
+        deleted: null,
+        lowBatteryReminderSwitch: false,
+        lowBatteryReminder: null,
+        // dto
+        suitIds: [],
+        // vo
+        suitNames: null,
+        // 初始化数据
+        ...this.initData
+      };
+      this.$nextTick(() => {
+        this.$refs.form && this.$refs.form.clearValidate();
+      });
+    },
+    getDetail() {
+      getModel(this.id).then(response => {
+        this.form = response.data;
+      });
+    },
+    submitForm() {
+      this.$refs.form.validate(valid => {
+        if (valid) {
+          const promise = this.form.id != null ? updateModel(this.form) : addModel(this.form);
+          promise.then(response => {
+            this.$modal.msgSuccess(this.form.id != null ? "修改成功" : "新增成功");
+            this.dialogVisible = false;
+            this.$emit('success');
+          });
+        }
+      });
+    },
+    cancel() {
+      this.dialogVisible = false;
+      this.reset();
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style> 
\ No newline at end of file
diff --git a/src/views/bst/model/index.vue b/src/views/bst/model/index.vue
index a6b3ff2..c0a49da 100644
--- a/src/views/bst/model/index.vue
+++ b/src/views/bst/model/index.vue
@@ -17,14 +17,6 @@
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="运营区" prop="areaName">
-        <el-input
-          v-model="queryParams.areaName"
-          placeholder="请输入运营区名称"
-          clearable
-          @keyup.enter.native="handleQuery"
-        />
-      </el-form-item>
       <el-form-item>
         <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
         <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
@@ -130,65 +122,21 @@
       @pagination="getList"
     />
 
-    <!-- 添加或修改车辆型号对话框 -->
-    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body :close-on-click-modal="false">
-      <el-form ref="form" :model="form" :rules="rules" label-width="80px" size="small">
-        <el-row>
-          <form-col :span="span"  label="所属用户" prop="userId">
-            <user-input v-model="form.userId" :text.sync="form.userName" :disabled="!checkRole([RoleKeys.ADMIN])"/>
-          </form-col>
-          <form-col :span="span"  label="运营区" prop="areaId">
-            <area-remote-select v-model="form.areaId" style="width: 100%;"/>
-          </form-col>
-          <form-col :span="span"  label="车型名称" prop="name">
-            <el-input v-model="form.name" placeholder="请输入车型名称" />
-          </form-col>
-          <form-col :span="span"  label="满电电压" prop="fullVoltage">
-            <el-input v-model="form.fullVoltage" placeholder="请输入满电电压" type="number">
-              <template slot="append">V</template>
-            </el-input>
-          </form-col>
-          <form-col :span="span"  label="亏电电压" prop="lowVoltage">
-            <el-input v-model="form.lowVoltage" placeholder="请输入亏电电压" type="number">
-              <template slot="append">V</template>
-            </el-input>
-          </form-col>
-          <form-col :span="span"  label="满电续航" prop="fullEndurance">
-            <el-input v-model="form.fullEndurance" placeholder="请输入满电续航" type="number">
-              <template slot="append">KM</template>
-            </el-input>
-          </form-col>
-          <form-col :span="span"  label="备注" prop="remark">
-            <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
-          </form-col>
-          <form-col :span="span"  label="低电量提醒开关" prop="lowBatteryReminderSwitch" label-width="9em">
-            <el-switch v-model="form.lowBatteryReminderSwitch" />
-          </form-col>
-          <form-col :span="span"  label="骑行低电量提醒" prop="lowBatteryReminder" label-width="9em" v-if="form.lowBatteryReminderSwitch">
-            <el-input v-model="form.lowBatteryReminder" placeholder="请输入骑行低电量提醒" type="number">
-              <template slot="append">%</template>
-            </el-input>
-          </form-col>
-        </el-row>
-      </el-form>
-      <div slot="footer" class="dialog-footer">
-        <el-button type="primary" @click="submitForm">确 定</el-button>
-        <el-button @click="cancel">取 消</el-button>
-      </div>
-    </el-dialog>
+    <model-edit-dialog
+      :visible.sync="dialogVisible"
+      :id="selectedId"
+      @success="getList"
+    />
   </div>
 </template>
 
 <script>
-import { listModel, getModel, delModel, addModel, updateModel } from "@/api/bst/model";
+import { listModel, delModel } from "@/api/bst/model";
 import { $showColumns } from '@/utils/mixins';
-import FormCol from "@/components/FormCol/index.vue";
-import UserInput from '@/components/Business/User/UserInput.vue';
 import BooleanTag from '@/components/BooleanTag/index.vue';
-import AreaRemoteSelect from '@/components/Business/Area/AreaRemoteSelect.vue';
-import { RoleKeys } from '@/utils/enums';
+import ModelEditDialog from '@/views/bst/model/components/ModelEditDialog.vue';
 
-  // 默认排序字段
+// 默认排序字段
 const defaultSort = {
   prop: "createTime",
   order: "descending"
@@ -197,17 +145,18 @@ const defaultSort = {
 export default {
   name: "Model",
   mixins: [$showColumns],
-  components: {FormCol, UserInput, BooleanTag, AreaRemoteSelect},
+  components: {
+    BooleanTag,
+    ModelEditDialog
+  },
   data() {
     return {
-      RoleKeys,
-      span: 24,
       // 字段列表
       columns: [
         {key: 'id', visible: false, label: 'ID', minWidth: null, sortable: true, overflow: false, align: 'center', width: "80"},
         {key: 'name', visible: true, label: '名称', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
-        {key: 'areaName', visible: true, label: '运营区', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
-        {key: 'userName', visible: true, label: '运营商', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
+        // {key: 'areaName', visible: true, label: '运营区', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
+        {key: 'userName', visible: true, label: '所属用户', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
         {key: 'fullVoltage', visible: true, label: '满电电压', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
         {key: 'lowVoltage', visible: true, label: '亏电电压', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
         {key: 'fullEndurance', visible: true, label: '满电续航', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
@@ -231,10 +180,6 @@ export default {
       total: 0,
       // 车辆型号表格数据
       modelList: [],
-      // 弹出层标题
-      title: "",
-      // 是否显示弹出层
-      open: false,
       defaultSort,
       // 查询参数
       queryParams: {
@@ -249,38 +194,9 @@ export default {
         deleted: null,
         lowBatteryReminderSwitch: null,
       },
-      // 表单参数
-      form: {},
-      // 表单校验
-      rules: {
-        areaId: [
-          { required: true, message: "运营区不能为空", trigger: "change" }
-        ],
-        userId: [
-          { required: true, message: "运营商不能为空", trigger: "change" }
-        ],
-        name: [
-          { required: true, message: "车型名称不能为空", trigger: "blur" }
-        ],
-        fullVoltage: [
-          { required: true, message: "满电电压不能为空", trigger: "blur" }
-        ],
-        lowVoltage: [
-          { required: true, message: "亏电电压不能为空", trigger: "blur" }
-        ],
-        fullEndurance: [
-          { required: true, message: "满电续航不能为空", trigger: "blur" }
-        ],
-        createTime: [
-          { required: true, message: "创建时间不能为空", trigger: "blur" }
-        ],
-        lowBatteryReminderSwitch: [
-          { required: true, message: "低电量提醒开关不能为空", trigger: "blur" }
-        ],
-        lowBatteryReminder: [
-          { required: true, message: "骑行低电量提醒值不能为空", trigger: "blur" }
-        ],
-      }
+      // 对话框相关
+      dialogVisible: false,
+      selectedId: null
     };
   },
   created() {
@@ -307,29 +223,6 @@ export default {
         this.loading = false;
       });
     },
-    // 取消按钮
-    cancel() {
-      this.open = false;
-      this.reset();
-    },
-    // 表单重置
-    reset() {
-      this.form = {
-        id: null,
-        areaId: null,
-        userId: null,
-        name: null,
-        fullVoltage: null,
-        lowVoltage: null,
-        fullEndurance: null,
-        createTime: null,
-        remark: null,
-        deleted: null,
-        lowBatteryReminderSwitch: false,
-        lowBatteryReminder: null
-      };
-      this.resetForm("form");
-    },
     /** 搜索按钮操作 */
     handleQuery() {
       this.queryParams.pageNum = 1;
@@ -348,39 +241,13 @@ export default {
     },
     /** 新增按钮操作 */
     handleAdd() {
-      this.reset();
-      this.open = true;
-      this.title = "添加车辆型号";
+      this.selectedId = null;
+      this.dialogVisible = true;
     },
     /** 修改按钮操作 */
     handleUpdate(row) {
-      this.reset();
-      const id = row.id || this.ids
-      getModel(id).then(response => {
-        this.form = response.data;
-        this.open = true;
-        this.title = "修改车辆型号";
-      });
-    },
-    /** 提交按钮 */
-    submitForm() {
-      this.$refs["form"].validate(valid => {
-        if (valid) {
-          if (this.form.id != null) {
-            updateModel(this.form).then(response => {
-              this.$modal.msgSuccess("修改成功");
-              this.open = false;
-              this.getList();
-            });
-          } else {
-            addModel(this.form).then(response => {
-              this.$modal.msgSuccess("新增成功");
-              this.open = false;
-              this.getList();
-            });
-          }
-        }
-      });
+      this.selectedId = row.id || this.ids;
+      this.dialogVisible = true;
     },
     /** 删除按钮操作 */
     handleDelete(row) {