nw-cqdevicemgt-ui/src/views/base/terminal/index.vue

874 lines
26 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

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

<template>
<div class="app-container">
<el-row :gutter="10" class="mb12">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-edit"
size="mini"
:disabled="single"
@click="handleUpdate"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-row :gutter="10" class="mb12">
<el-col :span="24">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>终端统计</span>
</div>
<el-row :gutter="20">
<el-col :span="8">
<el-statistic title="终端总数" :value="statistics.total || 0" />
</el-col>
<el-col :span="8">
<el-statistic title="在线终端" :value="statistics.onlineCount || 0">
<template slot="suffix">
<el-tag size="mini" type="success">在线</el-tag>
</template>
</el-statistic>
</el-col>
<el-col :span="8">
<el-statistic title="离线终端" :value="statistics.offlineCount || 0">
<template slot="suffix">
<el-tag size="mini" type="danger">离线</el-tag>
</template>
</el-statistic>
</el-col>
</el-row>
</el-card>
</el-col>
</el-row>
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px" class="search-form">
<el-form-item label="手机号" prop="phoneNumber">
<el-input
v-model="queryParams.phoneNumber"
placeholder="请输入手机号"
clearable
size="small"
style="width: 140px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="IMEI号" prop="imei">
<el-input
v-model="queryParams.imei"
placeholder="请输入IMEI号"
clearable
size="small"
style="width: 140px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="车牌号" prop="plateNumber">
<el-input
v-model="queryParams.plateNumber"
placeholder="请输入车牌号"
clearable
size="small"
style="width: 140px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="省份" prop="provinceId">
<el-select
v-model="queryParams.provinceId"
placeholder="请选择省份"
clearable
size="small"
@change="handleQueryProvinceChange"
style="width: 120px"
>
<el-option
v-for="item in provinceOptions"
:key="item.areaCode"
:label="item.name"
:value="item.areaCode"
/>
</el-select>
</el-form-item>
<el-form-item label="城市" prop="cityId">
<el-select
v-model="queryParams.cityId"
placeholder="请选择城市"
clearable
size="small"
:disabled="!queryParams.provinceId"
style="width: 120px"
>
<el-option
v-for="item in queryCityOptions"
:key="item.areaCode"
:label="item.name"
:value="item.areaCode"
/>
</el-select>
</el-form-item>
<el-form-item label="在线状态" prop="onlineStatus">
<el-select v-model="queryParams.onlineStatus" placeholder="在线状态" clearable size="small" style="width: 100px">
<el-option label="在线" :value="1" />
<el-option label="离线" :value="0" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="cyan" 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-table v-loading="loading" :data="terminalList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="终端ID" align="center" prop="terminalId" width="80" />
<el-table-column label="手机号" align="center" prop="phoneNumber" width="120" />
<el-table-column label="IMEI号" align="center" prop="imei" width="150" />
<el-table-column label="省份" align="center" prop="provinceName" width="100" />
<el-table-column label="城市" align="center" prop="cityName" width="100" />
<el-table-column label="终端型号" align="center" prop="terminalModel" />
<el-table-column label="车牌号" align="center" prop="plateNumber" width="120">
<template slot-scope="scope">
<span>{{ scope.row.plateNumber }}</span>
<el-tag v-if="scope.row.plateColor === 1" size="mini" type="primary">蓝</el-tag>
<el-tag v-else-if="scope.row.plateColor === 2" size="mini" type="warning">黄</el-tag>
<el-tag v-else-if="scope.row.plateColor === 3" size="mini" type="info">黑</el-tag>
<el-tag v-else-if="scope.row.plateColor === 4" size="mini">白</el-tag>
<el-tag v-else-if="scope.row.plateColor === 9" size="mini" type="success">新能源</el-tag>
</template>
</el-table-column>
<el-table-column label="在线状态" align="center" prop="onlineStatus" width="80">
<template slot-scope="scope">
<el-tag v-if="scope.row.onlineStatus === 1" type="success">在线</el-tag>
<el-tag v-else type="danger">离线</el-tag>
</template>
</el-table-column>
<el-table-column label="最后心跳" align="center" prop="lastHeartbeatTime" width="150">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.lastHeartbeatTime, '{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
<el-table-column label="客户端IP" align="center" prop="clientIp" width="120" />
<el-table-column label="注册时间" align="center" prop="registerTime" width="150">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.registerTime, '{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
>删除</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"
/>
<!-- 添加或修改终端设备对话框 -->
<el-dialog :title="title" :visible.sync="open" width="700px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
<el-row>
<el-col :span="12">
<el-form-item label="手机号" prop="phoneNumber">
<el-input v-model="form.phoneNumber" placeholder="请输入终端手机号" maxlength="20" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="IMEI号" prop="imei">
<el-input v-model="form.imei" placeholder="请输入设备IMEI号(15位)" maxlength="15" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="省份" prop="provinceId">
<el-select
v-model="form.provinceId"
placeholder="请选择省份"
clearable
@change="handleFormProvinceChange"
style="width: 100%"
>
<el-option
v-for="item in provinceOptions"
:key="item.areaCode"
:label="item.name"
:value="item.areaCode"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="城市" prop="cityId">
<el-select
v-model="form.cityId"
placeholder="请选择城市"
clearable
:disabled="!form.provinceId"
style="width: 100%"
>
<el-option
v-for="item in formCityOptions"
:key="item.areaCode"
:label="item.name"
:value="item.areaCode"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="制造商ID" prop="manufacturerId">
<el-input v-model="form.manufacturerId" placeholder="请输入制造商ID" maxlength="20" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="终端型号" prop="terminalModel">
<el-input v-model="form.terminalModel" placeholder="请输入终端型号" maxlength="30" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="设备ID" prop="terminalDeviceId">
<el-input v-model="form.terminalDeviceId" placeholder="请输入终端设备ID" maxlength="30" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="车牌颜色" prop="plateColor">
<el-select v-model="form.plateColor" placeholder="请选择车牌颜色" style="width: 100%">
<el-option label="其他" :value="0" />
<el-option label="蓝色" :value="1" />
<el-option label="黄色" :value="2" />
<el-option label="黑色" :value="3" />
<el-option label="白色" :value="4" />
<el-option label="新能源" :value="9" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="车牌号" prop="plateNumber">
<el-input v-model="form.plateNumber" placeholder="请输入车牌号" maxlength="20" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="鉴权码" prop="authCode">
<el-input v-model="form.authCode" placeholder="请输入鉴权码" maxlength="50" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="在线状态" prop="onlineStatus">
<el-radio-group v-model="form.onlineStatus">
<el-radio :label="1">在线</el-radio>
<el-radio :label="0">离线</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="客户端IP" prop="clientIp">
<el-input v-model="form.clientIp" placeholder="请输入客户端IP地址" maxlength="50" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="注册时间" prop="registerTime">
<el-date-picker
v-model="form.registerTime"
type="datetime"
placeholder="选择注册时间"
value-format="yyyy-MM-dd HH:mm:ss"
style="width: 100%"
>
</el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="最后鉴权时间" prop="lastAuthTime">
<el-date-picker
v-model="form.lastAuthTime"
type="datetime"
placeholder="选择最后鉴权时间"
value-format="yyyy-MM-dd HH:mm:ss"
style="width: 100%"
>
</el-date-picker>
</el-form-item>
</el-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>
</div>
</template>
<script>
import { listTerminal, getTerminal, delTerminal, addTerminal, updateTerminal, exportTerminal, getTerminalStatistics } from "@/api/base/terminal";
import { listProvinces, listCities } from "@/api/base/area";
export default {
name: "Terminal",
data() {
return {
// 遮罩层
loading: true,
// 选中数组
ids: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 终端设备表格数据
terminalList: [],
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
phoneNumber: null,
imei: null,
provinceId: null,
cityId: null,
plateNumber: null,
onlineStatus: null,
},
// 表单参数
form: {
terminalId: null,
phoneNumber: null,
imei: null,
provinceId: null,
cityId: null,
manufacturerId: null,
terminalModel: null,
terminalDeviceId: null,
plateColor: 0,
plateNumber: null,
authCode: null,
registerTime: null,
lastAuthTime: null,
onlineStatus: 0,
lastHeartbeatTime: null,
clientIp: null
},
// 表单校验
rules: {
phoneNumber: [
{ required: true, message: "手机号不能为空", trigger: "blur" },
{ pattern: /^[0-9]{11,20}$/, message: "手机号格式不正确", trigger: "blur" }
],
imei: [
{ pattern: /^[0-9]{15}$/, message: "IMEI号必须为15位数字", trigger: "blur" }
]
},
// 省份选项
provinceOptions: [],
// 查询表单城市选项
queryCityOptions: [],
// 编辑表单城市选项
formCityOptions: [],
// 统计信息
statistics: {}
};
},
created() {
this.getList();
this.getStatistics();
this.getProvinceList();
},
methods: {
/** 查询终端设备列表 */
getList() {
this.loading = true;
listTerminal(this.queryParams).then(response => {
this.terminalList = response.rows || [];
this.total = response.total || 0;
this.loading = false;
}).catch(error => {
console.error("获取终端列表失败:", error);
this.loading = false;
this.terminalList = [];
this.total = 0;
});
},
/** 获取终端统计信息 */
getStatistics() {
getTerminalStatistics().then(response => {
this.statistics = response.data || {};
}).catch(error => {
console.error("获取统计信息失败:", error);
this.statistics = {};
});
},
/** 查询省份列表 */
getProvinceList() {
listProvinces().then(response => {
console.log("省份列表响应:", response);
// 处理不同的响应结构
let provinceData = [];
if (response && response.data) {
provinceData = Array.isArray(response.data) ? response.data : [];
} else if (Array.isArray(response)) {
provinceData = response;
} else if (response && response.rows) {
provinceData = response.rows;
}
this.provinceOptions = provinceData.map(item => ({
areaCode: item.areaCode || item.area_code || item.id,
name: item.name || item.areaName || item.area_name
}));
console.log("处理后的省份列表:", this.provinceOptions);
}).catch(error => {
console.error("获取省份列表失败:", error);
this.provinceOptions = [];
this.$modal.msgError("获取省份列表失败");
});
},
/** 查询表单省份变化 */
handleQueryProvinceChange(value) {
console.log("查询表单选择的省份ID:", value);
// 清空城市选择
this.queryParams.cityId = null;
this.queryCityOptions = [];
if (value) {
// 直接使用 value不进行 Number 转换,保持原始类型
listCities(value).then(response => {
console.log("查询表单城市列表响应:", response);
// 处理不同的响应结构
let cityData = [];
if (response && response.data) {
cityData = Array.isArray(response.data) ? response.data : [];
} else if (Array.isArray(response)) {
cityData = response;
} else if (response && response.rows) {
cityData = response.rows;
}
this.queryCityOptions = cityData.map(item => ({
areaCode: item.areaCode || item.area_code || item.id,
name: item.name || item.areaName || item.area_name
}));
console.log("处理后的城市列表:", this.queryCityOptions);
}).catch(error => {
console.error("获取查询表单城市列表失败:", error);
this.queryCityOptions = [];
this.$modal.msgError("获取城市列表失败");
});
}
},
/** 编辑表单省份变化 */
handleFormProvinceChange(value) {
console.log("编辑表单选择的省份ID:", value);
// 清空城市选择
this.form.cityId = null;
this.formCityOptions = [];
if (value) {
// 直接使用 value不进行 Number 转换,保持原始类型
listCities(value).then(response => {
console.log("编辑表单城市列表响应:", response);
// 处理不同的响应结构
let cityData = [];
if (response && response.data) {
cityData = Array.isArray(response.data) ? response.data : [];
} else if (Array.isArray(response)) {
cityData = response;
} else if (response && response.rows) {
cityData = response.rows;
}
this.formCityOptions = cityData.map(item => ({
areaCode: item.areaCode || item.area_code || item.id,
name: item.name || item.areaName || item.area_name
}));
console.log("处理后的城市列表:", this.formCityOptions);
}).catch(error => {
console.error("获取编辑表单城市列表失败:", error);
this.formCityOptions = [];
this.$modal.msgError("获取城市列表失败");
});
}
},
// 取消按钮
cancel() {
this.open = false;
this.reset();
},
// 表单重置
reset() {
this.form = {
terminalId: null,
phoneNumber: null,
imei: null,
provinceId: null,
cityId: null,
manufacturerId: null,
terminalModel: null,
terminalDeviceId: null,
plateColor: 0,
plateNumber: null,
authCode: null,
registerTime: null,
lastAuthTime: null,
onlineStatus: 0,
lastHeartbeatTime: null,
clientIp: null
};
this.formCityOptions = []; // 清空编辑表单的城市选项
// 清空表单验证
if (this.$refs.form) {
this.$refs.form.clearValidate();
}
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
// 使用$nextTick确保DOM更新后再重置
this.$nextTick(() => {
this.queryParams = {
pageNum: 1,
pageSize: 10,
phoneNumber: null,
imei: null,
provinceId: null,
cityId: null,
plateNumber: null,
onlineStatus: null,
};
this.queryCityOptions = []; // 清空查询表单的城市选项
this.getList();
});
},
// 多选框选中数据
handleSelectionChange(selection) {
this.ids = selection.map(item => item.terminalId);
this.single = selection.length !== 1;
this.multiple = !selection.length;
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加终端设备";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const terminalId = row.terminalId || this.ids[0];
getTerminal(terminalId).then(response => {
this.form = { ...response.data };
console.log("编辑表单数据:", this.form);
// 如果省份ID存在加载对应的城市列表到编辑表单
if (this.form.provinceId) {
listCities(this.form.provinceId).then(cityResponse => {
console.log("编辑时城市列表响应:", cityResponse);
// 处理不同的响应结构
let cityData = [];
if (cityResponse && cityResponse.data) {
cityData = Array.isArray(cityResponse.data) ? cityResponse.data : [];
} else if (Array.isArray(cityResponse)) {
cityData = cityResponse;
} else if (cityResponse && cityResponse.rows) {
cityData = cityResponse.rows;
}
this.formCityOptions = cityData.map(item => ({
areaCode: item.areaCode || item.area_code || item.id,
name: item.name || item.areaName || item.area_name
}));
this.open = true;
this.title = "修改终端设备";
}).catch(error => {
console.error("加载城市列表失败:", error);
this.formCityOptions = [];
this.open = true;
this.title = "修改终端设备";
});
} else {
this.open = true;
this.title = "修改终端设备";
}
}).catch(error => {
console.error("获取终端信息失败:", error);
this.$modal.msgError("获取终端信息失败");
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.terminalId != null) {
// 修改操作
updateTerminal(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
this.getStatistics();
}).catch(error => {
this.$modal.msgError(error.message || "修改失败");
});
} else {
// 新增操作
addTerminal(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
this.getStatistics();
}).catch(error => {
this.$modal.msgError(error.message || "新增失败");
});
}
}
});
},
/** 删除按钮操作 */
handleDelete(row) {
const terminalIds = row.terminalId || this.ids;
this.$modal.confirm('是否确认删除终端设备编号为"' + terminalIds + '"的数据项?').then(() => {
return delTerminal(terminalIds);
}).then(() => {
this.getList();
this.getStatistics();
this.$modal.msgSuccess("删除成功");
}).catch(() => {
// 用户取消操作
});
},
/** 导出按钮操作 */
handleExport() {
const queryParams = { ...this.queryParams };
this.$modal.confirm('是否确认导出所有终端设备数据项?').then(() => {
return exportTerminal(queryParams);
}).then(response => {
this.$download.name(response, '终端设备.xlsx');
}).catch(() => {
// 用户取消操作
});
}
}
};
</script>
<style scoped>
.app-container {
padding: 20px;
}
.mb12 {
margin-bottom: 12px;
}
.mb20 {
margin-bottom: 20px;
}
.box-card {
margin-bottom: 20px;
}
.text-center {
text-align: center;
}
.clearfix:before,
.clearfix:after {
display: table;
content: "";
}
.clearfix:after {
clear: both;
}
.statistic-item {
text-align: center;
padding: 10px 0;
}
.statistic-title {
font-size: 14px;
color: #666;
margin-bottom: 5px;
}
.statistic-value {
font-size: 24px;
font-weight: bold;
color: #333;
}
.text-success {
color: #67c23a;
}
.text-danger {
color: #f56c6c;
}
/* 表单样式优化 */
.el-form-item {
margin-bottom: 18px;
}
.el-select {
width: 100%;
}
.el-input {
width: 100%;
}
.el-date-picker {
width: 100%;
}
/* 表格样式优化 */
.el-table {
margin-top: 20px;
}
.el-table__header {
background-color: #f5f7fa;
}
.el-table th {
background-color: #f5f7fa;
color: #333;
font-weight: bold;
}
.el-table td {
padding: 8px 0;
}
/* 操作按钮样式 */
.small-padding .cell {
padding-left: 5px;
padding-right: 5px;
}
/* 分页样式 */
.pagination-container {
margin-top: 20px;
text-align: center;
}
/* 对话框样式 */
.el-dialog__body {
max-height: 70vh;
overflow-y: auto;
}
.el-dialog__footer {
padding: 10px 20px;
border-top: 1px solid #ebeef5;
}
/* 搜索表单样式优化 - 确保一行显示 */
.search-form {
white-space: nowrap;
}
.search-form .el-form-item {
margin-bottom: 10px;
margin-right: 10px;
display: inline-block;
vertical-align: top;
}
.search-form .el-form-item__label {
padding-right: 8px;
}
.search-form .el-form-item__content {
display: inline-block;
}
</style>