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

874 lines
26 KiB
Vue
Raw Normal View History

2025-12-24 17:54:46 +08:00
<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>