1
0
forked from dyf/dyf-vue-ui
Files
dyf-vue-ui/src/views/equipmentManagement/devices/index.vue
2025-09-09 16:58:00 +08:00

1093 lines
38 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="p-2">
<div :class="Status.Mode == PageMode.device ? '' : 'displayNone'">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="设备名称" prop="deviceName">
<el-input v-model="queryParams.deviceName" placeholder="请输入设备名称" clearable />
</el-form-item>
<el-form-item label="设备MAC" prop="deviceMac">
<el-input v-model="queryParams.deviceMac" placeholder="请输入设备MAC" clearable />
</el-form-item>
<el-form-item label="请输入设备IMEI" prop="deviceImei">
<el-input v-model="queryParams.deviceImei" placeholder="请输入设备IMEI" clearable />
</el-form-item>
<el-form-item label="设备类型" prop="deviceType">
<el-select v-model="queryParams.deviceType" placeholder="设备类型">
<el-option v-for="item in deviceTypeOptions" :key="item.value" :label="item.typeName" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="设备状态" prop="deviceStatus">
<el-select v-model="queryParams.deviceStatus" placeholder="设备状态" style="margin-left: 10px">
<el-option label="正常" value="1" />
<el-option label="失效" value="0" />
</el-select>
</el-form-item>
<el-form-item label="创建时间">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10">
<el-col :span="1.5">
<el-button v-hasPermi="['equipment:devices:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['equipment:devices:edit']" type="success" plain :disabled="single" icon="Edit" @click="handleUpdate()">
修改
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['equipment:devices:export']" type="warning" :disabled="multiple" plain icon="Download" @click="handleExport"
>导出</el-button
>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['equipment:devices:remove']" type="danger" plain :disabled="multiple" @click="handleDelete()">
批量删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['equipment:devices:import']" type="warning" plain @click="handleBatchImport"> 批量导入 </el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['equipment:devices:allocate']" type="warning" plain :disabled="multiple" @click="handleBatchAssign">
批量分配客户
</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" :search="true" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="deviceDist" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="50" align="center" />
<el-table-column label="设备名称" align="center" prop="deviceName" />
<el-table-column prop="customerName" label="所属客户" />
<el-table-column prop="devicePic" label="设备图片">
<template #default="scope">
<el-popover placement="right" trigger="click">
<template #reference>
<img
:src="scope.row.devicePic"
style="width: 40px; height: 40px; cursor: pointer; object-fit: contain"
class="hover:opacity-80 transition-opacity"
/>
</template>
<img :src="scope.row.devicePic" style="max-width: 600px; max-height: 600px; object-fit: contain" />
</el-popover>
</template>
</el-table-column>
<el-table-column prop="deviceMac" label="设备MAC" />
<el-table-column prop="bluetoothName" label="蓝牙名称" />
<el-table-column prop="deviceImei" label="设备IMEI" />
<el-table-column prop="typeName" label="设备类型" />
<el-table-column prop="bindingStatus" label="绑定状态">
<template #default="scope">
<el-tag :type="scope.row.bindingStatus === 1 ? 'success' : 'info'">
{{ scope.row.bindingStatus === 1 ? '已绑定' : '未绑定' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="deviceStatus" label="设备状态">
<template #default="scope">
<el-tag :type="scope.row.deviceStatus == 1 ? 'success' : 'danger'">
{{ scope.row.deviceStatus == 1 ? '正常' : '失效' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" />
<el-table-column prop="createTime" label="创建日期" width="160" />
<el-table-column prop="createByName" label="创建人" />
<el-table-column label="操作" fixed="right" width="280" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip v-if="scope.row.id !== 1 && scope.row.deviceStatus == 1" content="修改" placement="top">
<el-button v-hasPermi="['equipment:devices:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip v-if="!scope.row.customerName" content="删除" placement="top">
<el-button v-hasPermi="['equipment:devices:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
<el-tooltip v-if="scope.row.deviceStatus == 1 && !scope.row.customerName" content="分配" placement="top">
<el-button v-hasPermi="['equipment:devices:allocate']" link type="primary" icon="User" @click="handleAssign(scope.row)"></el-button>
</el-tooltip>
<el-tooltip v-if="scope.row.customerName && scope.row.deviceStatus == 1" content="撤回" placement="top">
<el-button
v-hasPermi="['equipment:devices:revoke']"
link
type="primary"
icon="UploadFilled"
@click="handleWithdraw(scope.row)"
></el-button>
</el-tooltip>
<el-tooltip v-if="scope.row.bindingStatus == 1" :disabled="scope.row.deviceStatus === 0" content="解绑" placement="top">
<el-button v-hasPermi="['equipment:devices:unbind']" link type="primary" icon="Refresh" @click="handleUnbind(scope.row)"></el-button>
</el-tooltip>
<el-tooltip v-if="scope.row.deviceImei" content="查看二维码" placement="top">
<el-button link type="primary" icon="Postcard" @click="showQrCode(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="详情" placement="top">
<el-button link type="primary" icon="More" @click="handleDetail(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-card>
</div>
<div :class="Status.Mode == PageMode.detail ? '' : 'displayNone'" class="detailMain">
<div class="tabContent">
<div class="tabHeader">
<div class="indexContent">
<div class="tabIndex" :class="Status.tabActive == 1 ? 'active' : ''" @click="tabIndexChange(1)">设备信息</div>
<div class="tabIndex" :class="Status.tabActive == 2 ? 'active' : ''" @click="tabIndexChange(2)">用户信息</div>
<div class="tabIndex" :class="Status.tabActive == 3 ? 'active' : ''" @click="tabIndexChange(3)">操作记录</div>
<div class="tabIndex" :class="Status.tabActive == 4 ? 'active' : ''" @click="tabIndexChange(4)">报警记录</div>
<div class="tabIndex" :class="Status.tabActive == 5 ? 'active' : ''" @click="tabIndexChange(5)">分享管理</div>
<div class="tabIndex" :class="Status.tabActive == 6 ? 'active' : ''" @click="tabIndexChange(6)">充放电</div>
</div>
<div class="tabClose">
<el-icon @click="closeDetail()" :size="20" :color="'#7787a4'">
<Close />
</el-icon>
</div>
</div>
<div class="tabItem" v-show="Status.tabActive == 1">
<eqDetail :data="detailData" :acIndex="Status.tabActive" data-name="eqDetail"></eqDetail>
</div>
<div class="tabItem" v-show="Status.tabActive == 2">
<Usr :data="detailData" :acIndex="Status.tabActive" data-name="Usr"></Usr>
</div>
<div class="tabItem" v-show="Status.tabActive == 3">
<OpraRecored :data="detailData" :acIndex="Status.tabActive" data-name="OpraRecored"></OpraRecored>
</div>
<div class="tabItem" v-show="Status.tabActive == 4">
<WarnRecord :data="detailData" :acIndex="Status.tabActive" data-name="WarnRecord"></WarnRecord>
</div>
<div class="tabItem" v-show="Status.tabActive == 5">
<shareManage :data="detailData" :acIndex="Status.tabActive" data-name="shareManage"></shareManage>
</div>
<div class="tabItem" v-show="Status.tabActive == 6">
<Charge :data="detailData" :acIndex="Status.tabActive" data-name="Charge"></Charge>
</div>
</div>
</div>
<!-- 添加或修改用户配置对话框 -->
<el-dialog ref="formDialogRef" v-model="dialog.visible" :title="dialog.title" width="30%" append-to-body @close="closeDialog">
<el-form ref="userFormRef" :model="form" :rules="rules" label-width="120px">
<el-row>
<el-col :span="24">
<el-form-item label="设备名称" prop="deviceName">
<el-input v-model="form.deviceName" placeholder="请输入设备名称" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="设备类型" prop="deviceType">
<el-select v-model="form.deviceType" placeholder="设备类型" @change="(id) => handleDeviceTypeChange(id)" :disabled="form.id != ''">
<el-option v-for="item in deviceTypeOptions" :key="item.value" :label="item.typeName" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row v-if="showMacField">
<el-col :span="24">
<el-form-item label="设备MAC" prop="deviceMac" required>
<el-input v-model="form.deviceMac" placeholder="请输入设备MAC" />
</el-form-item>
</el-col>
</el-row>
<el-row v-if="showMacField">
<el-col :span="24">
<el-form-item label="蓝牙名称" prop="bluetoothName" required>
<el-input v-model="form.bluetoothName" placeholder="请输入蓝牙名称" />
</el-form-item>
</el-col>
</el-row>
<el-row v-if="showImeiField">
<el-col :span="24">
<el-form-item label="设备IMEI" prop="deviceImei" required>
<el-input v-model="form.deviceImei" placeholder="请输入设备IMEI" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="设备图片" prop="image">
<el-upload
action="#"
list-type="picture-card"
:before-upload="beforeUpload"
:on-change="fileUploadChange"
:http-request="httpRequestImg"
:file-list="fileList"
:limit="1"
>
<i class="el-icon-plus"></i>
<template v-if="form.image && typeof form.image === 'string'">
<img :src="form.image" class="avatar" style="width: 100px; height: 100px; object-fit: contain" />
</template>
</el-upload>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm" :loading="loadingIng"> </el-button>
<el-button @click="cancel()"> </el-button>
</div>
</template>
</el-dialog>
<!-- 分配客户弹窗 -->
<el-dialog v-model="assignDialogVisible" title="分配客户" width="400px">
<el-form>
<el-form-item label="选择客户">
<el-select v-model="assignCustomerId" placeholder="请选择客户" style="width: 100%">
<el-option v-for="item in customerList" :key="item.customerId" :label="item.nickName" :value="item.customerId" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="assignDialogVisible = false"> </el-button>
<el-button type="primary" @click="handleAssignConfirm" :loading="loadingIng"> </el-button>
</div>
</template>
</el-dialog>
<!-- 导入设备数据弹窗 -->
<el-dialog v-model="importDialogVisible" title="导入设备数据" width="500px">
<div style="margin-bottom: 16px">
<p>请按照模板文件的格式准备需要导入的数据</p>
<p>模板文件中的表头请勿修改数据请从第二行开始填写</p>
<el-button type="primary" icon="el-icon-download" @click="downloadTemplate">下载模板文件</el-button>
</div>
<el-upload
ref="importUpload"
:action="api.devicDeimport()"
:headers="head_upload()"
:show-file-list="false"
:before-upload="beforeImportUpload"
:on-success="handleImportSuccess"
:on-error="handleImportError"
:limit="1"
accept=".xlsx,.xls"
>
<el-button type="success">选择文件开始导入</el-button>
<div v-if="!importResult.isShow" slot="tip" class="el-upload__tip">
<div style="color: #409eff">只能上传模板excel文件</div>
</div>
<div v-if="importResult.isShow" slot="tip" class="el-upload__tip">
<span style="color: #409eff"
>批量导入完成共检测到 <span style="color: #e6a23c">{{ importResult.total }}</span> 条数据导入成功
<span style="color: #67c23a">{{ importResult.succeed }}</span> 失败
<span style="color: red">{{ importResult.errorSun }}</span> </span
>
<p v-if="importResult.errorSun > 0" style="padding: 10px">
<a :href="importResult.link">>>> 上传失败明细下载 <i class="el-icon-download" /></a>
</p>
</div>
</el-upload>
<template #footer>
<div class="dialog-footer">
<el-button @click="importDialogVisible = false"> </el-button>
</div>
</template>
</el-dialog>
<!-- 批量分配客户弹窗 -->
<el-dialog v-model="batchAssignDialogVisible" title="批量分配客户" width="400px">
<el-form>
<el-form-item label="选择客户">
<el-select v-model="batchAssignCustomerId" placeholder="请选择客户" style="width: 100%">
<el-option v-for="item in customerList" :key="item.customerId" :label="item.nickName" :value="item.customerId" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="batchAssignDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleBatchAssignConfirm">确定</el-button>
</div>
</template>
</el-dialog>
<!-- IMEI 二维码弹窗 -->
<el-dialog v-model="qrCodeDialogVisible" title="设备IMEI二维码" width="20%" append-to-body>
<div style="text-align: center">
<!-- 使用 v-if 强制重新渲染 -->
<QRCodeVue3 v-if="qrCodeDialogVisible" :value="qrCodeValue" :size="100" />
<p style="margin-top: 10px">{{ qrCodeValue }}</p>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="qrCodeDialogVisible = false">关闭</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="User" lang="ts">
import QRCodeVue3 from 'qrcode-vue3';
import api from '@/api/equipmentManagement/device/index';
import { deviceForm, deviceQuery, deviceVO, deviceTypeOption } from '@/api/equipmentManagement/device/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const deviceDist = ref<deviceVO[]>();
import { to } from 'await-to-js';
import request from '@/utils/request';
import { getBearerToken } from '@/utils/auth';
import eqDetail from './eqDetail.vue';
import Usr from './Usr.vue';
import OpraRecored from './OpraRecored.vue';
import WarnRecord from './WarnRecord.vue';
import shareManage from './shareManage.vue';
import Charge from './Charge.vue';
import router from '@/router';
const loading = ref(true);
const showSearch = ref(true);
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const ids = ref<deviceVO[]>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const initPassword = ref<string>('');
const queryFormRef = ref<ElFormInstance>();
const userFormRef = ref<ElFormInstance>();
const formDialogRef = ref<ElDialogInstance>();
const deviceTypeOptions = ref([]); //设备类型
const fileList = ref();
const communicationModeInfo = ref<any>(null);
const showMacField = ref(false); //MAC地址
const showImeiField = ref(false); //mei地址
const assignDialogVisible = ref(false); //分配客户
const importDialogVisible = ref(false); //批量导入
const batchAssignDialogVisible = ref(false); //批量分配客户
const loadingIng = ref(false);
const assignCustomerId = ref(); //分配客户id
const batchAssignCustomerId = ref(); //批量分配客户id
const customerList = ref();
const qrCodeDialogVisible = ref(false);
const qrCodeValue = ref('');
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
//页面类型
enum PageMode {
device = 'device', //设备
detail = 'detail' //详情
}
//页面状态控制
var Status = reactive({
Mode: PageMode.device,
tabActive: 0
});
//传给详情的数据
var detailData = ref(null);
//加载详情
function handleDetail(item) {
Status.tabActive = 1;
detailData.value = { data: item };
Status.Mode = PageMode.detail;
}
//关闭详情
function closeDetail() {
Status.Mode = PageMode.device;
Status.tabActive = -1;
}
function tabIndexChange(index) {
if (Status.tabActive == index) {
return;
}
Status.tabActive = index;
}
//
const initFormData: deviceForm = {
deviceName: '',
deviceMac: '',
deviceImei: '',
remark: '',
id: '',
deviceType: '',
image: '',
bluetoothName: '' // 蓝牙名称字段
};
const initData: PageData<deviceForm, deviceQuery> = {
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
deviceName: '',
deviceMac: '',
deviceImei: '',
deviceType: '',
deviceStatus: ''
},
rules: {
deviceName: [{ required: true, message: '请输入设备名称', trigger: 'blur' }],
deviceType: [{ required: true, message: '请选择设备类型', trigger: 'blur' }],
bluetoothName: [{ required: true, message: '请输入蓝牙名称', trigger: 'blur' }],
deviceMac: [{ required: true, message: '请输入设备MAC', trigger: 'blur' }],
deviceImei: [{ required: true, message: '请输入设备IMEI', trigger: 'blur' }]
}
};
const data = reactive<PageData<deviceForm, deviceQuery>>(initData);
const { queryParams, form, rules } = toRefs<PageData<deviceForm, deviceQuery>>(data);
/** 查询设备列表 */
const getList = async () => {
loading.value = true;
const res = await api.deviceList(proxy?.addDateRange(queryParams.value, dateRange.value));
loading.value = false;
deviceDist.value = res.rows;
total.value = res.total;
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
dateRange.value = ['', ''];
handleQuery();
};
/** 删除按钮操作 */
const handleDelete = async (row?: deviceVO) => {
// 批量删除逻辑
let arrey = ids.value.map((item) => item.id);
if (!row) {
const [err] = await to(
proxy?.$modal.confirm(`是否确认删除选中的 ${ids.value.length} 条数据?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
);
if (!err) {
await api.deleteDevice(arrey);
await getList();
proxy?.$modal.msgSuccess('删除成功');
}
return;
}
// 单行删除逻辑
const [err] = await to(
proxy?.$modal.confirm('是否确认删除"' + row.deviceName + '"的数据项?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
);
if (!err) {
await api.deleteDevice([row.id]);
await getList();
proxy?.$modal.msgSuccess('删除成功');
}
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'/api/device/download',
{
...queryParams.value
},
`${new Date().getTime()}.xlsx`,
'get'
);
};
// 解绑
const handleUnbind = (row) => {
proxy?.$modal
.confirm(`确定要解绑设备 ${row.deviceName} 吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
let data = {
id: row.id
};
api.deviceUnbind(data).then((res) => {
if (res.code == 200) {
proxy?.$modal.msgSuccess(res.msg);
getList(); // 初始化列表数据
} else {
proxy?.$modal.msgError(res.msg);
}
});
})
.catch(() => {});
};
// 撤回
const handleWithdraw = (row: any) => {
proxy?.$modal
.confirm(`确定要从客户 ${row.customerName} 撤回设备 ${row.deviceName} 吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
api.withdrawDevice([row.id]).then((res) => {
if (res.code === 200) {
proxy?.$modal.msgSuccess('撤回成功');
getList(); // 初始化列表数据
} else {
proxy?.$modal.msgError(res.msg || '撤回失败');
}
});
})
.catch(() => {
proxy?.$modal.msgError('已取消撤回');
});
};
/** 选择条数 */
const handleSelectionChange = (selection: deviceVO[]) => {
ids.value = selection.map((item) => item);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 重置操作表单 */
const reset = () => {
form.value = { ...initFormData };
userFormRef.value?.resetFields();
fileList.value = [];
};
/** 取消按钮 */
const cancel = () => {
dialog.visible = false;
reset();
};
/** 新增按钮操作 */
const handleAdd = async () => {
reset();
dialog.visible = true;
dialog.title = '新增设备';
form.value.password = initPassword.value.toString();
// 新增时默认不显示
showMacField.value = false;
showImeiField.value = false;
// 每次打开弹框时获取最新的设备类型数据
getDeviceType();
};
/** 修改按钮操作 */
const handleUpdate = async (row?: deviceForm) => {
reset();
dialog.visible = true;
dialog.title = '修改设备';
// 每次打开弹框时获取最新的设备类型数据
getDeviceType();
try {
if (row) {
// 使用 nextTick 确保对话框完全渲染后再设置表单值
await nextTick();
Object.assign(form.value, row);
form.value.image = row.devicePic;
// 编辑时根据已有值显示字段
showMacField.value = !!row.deviceMac;
showImeiField.value = !!row.deviceImei;
} else {
const customerId = ids.value[0];
Object.assign(form.value, customerId);
form.value.image = customerId.devicePic; //图片回显
// 编辑时根据已有值显示字段
showMacField.value = !!customerId.deviceMac;
showImeiField.value = !!customerId.deviceImei;
}
// 加载设备类型对应的通讯方式
if (form.value.deviceType) {
await handleDeviceTypeChange(form.value.deviceType);
}
} catch (error) {
dialog.visible = false;
}
};
// 设备类型触发事件
let isProcessing = false; // 添加处理锁
const handleDeviceTypeChange = async (deviceTypeId: string | number) => {
console.log(deviceTypeId, 'deviceTypeIddeviceTypeId');
// 重置规则和显示状态
rules.value.deviceMac = [];
rules.value.deviceImei = [];
showMacField.value = false;
showImeiField.value = false;
communicationModeInfo.value = null;
// 编辑时如果有值,根据已有值确定显示哪个字段
if (form.value.id) {
console.log('zheshi me1 ');
// 1. 先判断Mac 和 Imei 都有值(新增的关键分支)
const hasMac = typeof form.value.deviceMac === 'string' && form.value.deviceMac.trim() !== '';
const hasImei = typeof form.value.deviceImei === 'string' && form.value.deviceImei.trim() !== '';
if (hasMac && hasImei) {
//两个都有值:显示两个字段 + 都加校验
showMacField.value = true;
showImeiField.value = true;
rules.value.deviceMac = [{ required: true, message: '请输入设备MAC', trigger: 'blur' }];
rules.value.deviceImei = [{ required: true, message: '请输入设备IMEI', trigger: 'blur' }];
console.log('两个字段都有值');
} else if (hasMac) {
showMacField.value = true;
showImeiField.value = false;
rules.value.deviceMac = [{ required: true, message: '请输入设备MAC', trigger: 'blur' }];
rules.value.deviceImei = [];
console.log('只有 Mac 有值');
} else if (hasImei) {
showImeiField.value = true;
showMacField.value = false;
rules.value.deviceImei = [{ required: true, message: '请输入设备IMEI', trigger: 'blur' }];
rules.value.deviceMac = [];
console.log('只有 Imei 有值');
}
return;
}
if (isProcessing) return;
isProcessing = true;
// 新增或编辑时没有值,根据设备类型获取通讯方式
try {
userFormRef.value?.clearValidate(['deviceMac', 'deviceImei', 'bluetoothName']);
if (!deviceTypeId) {
return;
}
const res = await api.getCommunicationMode({ id: deviceTypeId });
if (res.code == 200 && res.data) {
communicationModeInfo.value = res.data;
// 根据通讯方式确定显示哪个字段
if (res.data.communicationMode == '1') {
// 蓝牙设备 - 显示MAC
showMacField.value = true;
showImeiField.value = false;
form.value.deviceImei = ''; // 清空IMEI
} else if (res.data.communicationMode == '0') {
// 4G设备 - 显示IMEI
showMacField.value = false;
showImeiField.value = true;
form.value.deviceMac = ''; // 清空MAC
form.value.bluetoothName = ''; // 清空蓝牙名称
} else if (res.data.communicationMode == '2') {
//既是4G设备又是蓝牙设备
showImeiField.value = true;
showMacField.value = true;
}
}
} catch (error) {
} finally {
isProcessing = false;
}
};
// 覆盖默认的上传行为,可以自定义上传的实现
const httpRequestImg = (parm): Promise<any> => {
return Promise.resolve();
};
const beforeUpload = (file) => {
const isLt2M = file.size / 1024 / 1024 < 2;
const isJPG = file.type === 'image/jpeg' || file.type === 'image/png';
if (!isJPG) {
ElMessage.warning('请上传jpg、png格式大小不超过2M的照片');
return false;
}
if (!isLt2M) {
ElMessage.warning('大小不超过2M的照片片');
return false;
}
return isJPG && isLt2M;
};
// 文件上传状态改变时触发
const fileUploadChange = (files, fileList) => {
console.log(fileList, '5555');
if (fileList.length > 0) {
form.value.image = fileList[0].raw; // 正确获取File对象
// 调试:检查是否是真正的 File 对象
console.log('File对象验证:', form.value.image);
} else {
form.value.image = null;
}
};
/** 提交按钮 */
const submitForm = async () => {
try {
const valid = await userFormRef.value?.validate();
if (!valid) return;
loadingIng.value = true;
const formData = new FormData();
// 处理图片字段
if (form.value.image instanceof File) {
formData.append('file', form.value.image);
} else if (form.value.image && typeof form.value.image === 'string') {
// 如果是URL且需要转换为二进制
const blob = await urlToBlob(form.value.image);
formData.append('file', blob, 'image.jpg'); // 添加文件名
}
// 添加其他必要字段
const fields = ['id', 'deviceName', 'deviceType', 'remark'];
fields.forEach((key) => {
if (form.value[key] !== undefined && form.value[key] !== null) {
formData.append(key, String(form.value[key])); // 确保所有值都转为字符串
}
});
// 根据通讯方式有条件地添加deviceMac或deviceImei
if (form.value.deviceMac) {
formData.append('deviceMac', form.value.deviceMac);
}
if (form.value.deviceImei) {
formData.append('deviceImei', form.value.deviceImei);
}
// 添加蓝牙名称字段(仅蓝牙设备)
if (form.value.bluetoothName) {
formData.append('bluetoothName', form.value.bluetoothName);
}
// 根据操作类型设置URL和方法
const isAdd = !form.value.id; // 注意这里逻辑反了应该是没有id时是新增
const res = await request({
url: isAdd ? '/api/device/add' : '/api/device/update',
method: isAdd ? 'post' : 'put',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
});
if (res.code == 200) {
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
loadingIng.value = false;
await getList();
} else {
proxy?.$modal.msgWarning(res.msg);
loadingIng.value = false;
}
} catch (err) {
console.error(err);
loadingIng.value = false;
}
};
// URL转Blob的方法函数
const urlToBlob = async (url: string): Promise<Blob> => {
const response = await fetch(url);
if (!response.ok) throw new Error('图片加载失败');
return await response.blob();
};
/**
* 关闭用户弹窗
*/
const closeDialog = () => {
dialog.visible = false;
resetForm();
};
const showQrCode = (row: any) => {
if (row.deviceImei) {
qrCodeValue.value = row.deviceImei;
qrCodeDialogVisible.value = true;
}
};
/**
* 重置表单
*/
const resetForm = () => {
userFormRef.value?.resetFields();
userFormRef.value?.clearValidate();
form.value.customerId = undefined;
};
// 设备类型
const getDeviceType = () => {
api
.deviceTypeAll()
.then((res) => {
if (res.code == 200) {
deviceTypeOptions.value = res.data;
}
})
.catch((err) => {});
};
// 客户下拉框
const getAllCustomerAll = () => {
api.userAllCustomerAll().then((res) => {
if (res.code == 200) {
customerList.value = res.data;
}
});
};
// 分配客户
const assignRow = ref();
const handleAssign = (row: any) => {
console.log(row, 'eeeeee');
getAllCustomerAll();
assignDialogVisible.value = true;
assignRow.value = row;
assignCustomerId.value == !row.customerName ? row.customerId : '';
};
const handleAssignConfirm = () => {
if (!assignCustomerId.value) {
return proxy?.$modal.msgError('请选择客户');
}
loadingIng.value = true;
// 这里调用分配API
let data = {
customerId: assignCustomerId.value,
deviceIds: [assignRow.value.id]
};
api
.deviceAssignCustomer(data)
.then((res) => {
if (res.code == 200) {
loadingIng.value = false;
const customer = customerList.value.find((c) => c.id === assignCustomerId.value);
const customerName = customer ? customer.nickName : `ID: ${assignCustomerId.value}`;
getList();
assignDialogVisible.value = false;
return proxy?.$modal.msgSuccess(`设备已分配给客户: ${customerName}`);
} else {
loadingIng.value = false;
}
})
.catch(() => {
loadingIng.value = false;
});
};
const importUpload = ref();
const importResult = ref();
const handleBatchImport = () => {
importDialogVisible.value = true;
importResult.value = {
isShow: false,
total: 0,
succeed: 0,
errorSun: 0,
link: ''
};
nextTick(() => {
if (importUpload.value) {
importUpload.value.clearFiles();
}
});
};
const downloadTemplate = () => {
// 这里可用 window.open 或 a 标签下载模板
const link = document.createElement('a');
link.href = 'https://fuyuanshen.com/fys/Equipmentimporttemplate/EquipmentImportTemplate.xlsx';
link.download = '设备数据导入模板.xlsx'; // 可选:指定下载文件名
link.style.display = 'none'; // 隐藏标签
document.body.appendChild(link);
link.click(); // 触发下载
document.body.removeChild(link); // 移除标签
};
const beforeImportUpload = (file: any) => {
const isLt5M = file.size / 1024 / 1024 < 5;
if (!isLt5M) {
proxy?.$modal.msgError('上传文件大小不能超过 5MB!');
}
return isLt5M;
};
//添加tokenf方法head_upload 直接返回 getBearerToken()
const head_upload = () => getBearerToken();
const handleImportSuccess = (response: any) => {
if (response.code == 200) {
importResult.value.isShow = true;
if (response.data) {
importResult.value.succeed = response.data.successCount || 0;
importResult.value.errorSun = response.data.failureCount || 0;
importResult.value.total = importResult.value.succeed + importResult.value.errorSun;
importResult.value.link = response.data.errorExcelUrl || '';
}
getList(); // 初始化列表数据
} else {
proxy?.$modal.msgError(response.msg || '导入失败');
}
};
const handleImportError = () => {
proxy?.$modal.msgError('导入失败');
};
// 批量分配客户
const handleBatchAssign = () => {
batchAssignDialogVisible.value = true;
getAllCustomerAll();
};
// 批量分配客户确定
const handleBatchAssignConfirm = () => {
if (!batchAssignCustomerId.value) {
return proxy?.$modal.msgError('请选择客户');
}
// 提取选中设备的 ID 数组
const selectedIds = ids.value.map((item: any) => item.id);
// 构造请求数据
const data = {
customerId: batchAssignCustomerId.value, // 目标客户ID
deviceIds: selectedIds // 选中的设备ID数组
};
api
.deviceAssignCustomer(data)
.then((res) => {
if (res.code == 200) {
batchAssignDialogVisible.value = false;
getList();
return proxy?.$modal.msgSuccess(`分配成功`);
}
})
.catch(() => {});
};
watch(
() => form.value.deviceType,
(newVal) => {
if (dialog.title === '新增设备') {
// Only for add form
handleDeviceTypeChange(newVal);
}
}
);
onMounted(() => {
getList(); // 初始化列表数据
getDeviceType();
});
</script>
<style lang="scss" scoped>
:deep .el-upload--picture-card {
width: 100px !important;
height: 100px !important;
border: 1px solid #cccc;
margin-bottom: 10px;
}
:deep .el-upload.el-upload--text {
display: block;
}
:deep .el-upload-list--picture-card .el-upload-list__item {
width: 100px !important;
height: 100px !important;
}
:deep .el-upload-list__item-thumbnail {
width: 100px !important;
height: 100px !important;
object-fit: cover;
}
.displayNone {
display: none !important;
}
.detailMain {
width: 100%;
height: calc(100vh - 115px);
border-radius: 4px;
box-shadow: 0px 0px 6px 0px rgba(0, 34, 96, 0.1);
background: rgba(255, 255, 255, 1);
overflow: hidden;
box-sizing: border-box;
padding: 13px;
}
.detailMain .tabContent {
width: 100%;
height: 100%;
box-sizing: border-box;
}
.detailMain .tabHeader {
width: 100%;
height: 36px;
background-color: #ffffff;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-content: center;
justify-content: space-between;
align-items: flex-start;
border-bottom: 1px solid rgba(235, 238, 248, 1);
box-sizing: border-box;
}
.detailMain .tabHeader .indexContent {
height: 100%;
width: calc(100% - 20px);
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-content: flex-start;
justify-content: space-between;
align-items: flex-start;
}
.detailMain .tabHeader .tabIndex {
color: rgba(56, 64, 79, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
letter-spacing: 0px;
text-align: left;
padding: 0px 30px;
position: relative;
height: 36px;
line-height: 36px;
cursor: pointer;
}
.detailMain .tabHeader .tabIndex.active {
color: rgba(2, 124, 251, 1);
font-weight: 700;
border-bottom: 2px solid rgba(2, 124, 251, 1);
}
.detailMain .tabHeader .tabClose {
width: 20px;
cursor: pointer;
}
.detailMain .tabItem {
height: calc(100% - 36px);
width: 100%;
}
.p-2 {
background: rgba(247, 248, 252, 1);
min-height: calc(100vh - 85px) !important;
}
</style>