Files
sunProduct/src/views/system/Sdb/SdbWaterDevice/index.vue
2026-04-27 11:20:48 +08:00

567 lines
20 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="h-full flex flex-col p-2">
<pageheader></pageheader>
<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" label-width="100px" :model="queryParams" :inline="true">
<el-form-item label="设备状态" prop="eleStatus">
<el-select v-model="queryParams.eleStatus">
<el-option v-for="(item, index) in com_waterdevice_on_status" :key="index" :label="item.label" :value="item.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="分合状态" prop="status">
<el-select v-model="queryParams.status">
<el-option v-for="(item, index) in com_water_readrecord_status" :key="index" :label="item.label" :value="item.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="房间号" prop="houseName">
<el-input v-model="queryParams.houseName" placeholder="请输入房间号" clearable @keyup.enter="handleQuery" />
</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 class="flex-1" shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleMarkqrcode" v-hasPermi="['waterRecord:waterRecord:add']"
>批量生成条形码</el-button
>
</el-col>
<el-col :span="1.5">
<!-- <el-button type="success" plain icon="Upload">批量更新固件</el-button> -->
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="waterDeviceList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="水表设备编码" width="250" align="center" prop="deviceCode" />
<el-table-column label="设备状态" width="100" align="center" prop="eleStatus">
<template #default="scope">
<dict-tag v-if="scope.row.waterRecord" :options="com_waterdevice_on_status" :value="scope.row.waterRecord.eleStatus"></dict-tag>
<span v-else>-------</span>
</template>
</el-table-column>
<el-table-column label="分合状态" width="100" align="center">
<template #default="scope">
<dict-tag v-if="scope.row.status" :options="com_water_readrecord_status" :value="scope.row.status"></dict-tag>
<span v-else>-------</span>
</template>
</el-table-column>
<el-table-column label="户号" align="center" prop="houseName" />
<el-table-column label="余额" width="120" align="center" prop="deviceCode">
<template #default="scope">
<span v-if="scope.row.waterRecord">{{ scope.row.waterRecord.balance }}</span>
<span v-else>------</span>
</template>
</el-table-column>
<el-table-column label="条形码" width="250" align="center">
<template #default="scope">
<el-image
v-if="scope.row.url && scope.row.url.includes('http')"
:src="scope.row.url"
:preview-src-list="[scope.row.url]"
style="width: 100%; height: 100%; border-radius: 4px"
fit="cover"
preview-teleported
>
<template #toolbar>
<el-icon @click="download(scope.row.url)"><Download /></el-icon>
</template>
</el-image>
<span v-else>暂未生成</span>
</template>
</el-table-column>
<el-table-column label="抄表时间" width="200" align="center">
<template #default="scope">
<span>{{ scope.row.waterRecord ? scope.row.waterRecord.readTime : '--' }}</span>
</template>
</el-table-column>
<!-- <el-table-column width="150" label="启停" align="center" prop="deleted">
<template #default="scope">
<el-switch
@change="(val: string) => handleChangeWater(val, scope.row)"
v-model="scope.row.deleted"
active-text="启用"
inactive-text="停止"
active-value="0"
inactive-value="1"
></el-switch>
</template>
</el-table-column> -->
<el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
<template #default="scope">
<el-row>
<el-col :span="24" class="flex flex-items-center">
<el-button link type="primary" @click="handleRecode(scope.row)">充值记录</el-button>
<el-button link type="primary" @click="handleQrCode(scope.row)">生成条码</el-button>
<el-dropdown style="vertical-align: text-top">
<el-button link type="primary">
<div>
更多<el-icon><arrow-down /></el-icon>
</div>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>
<div @click="handleOpenDialog('usedWater', scope.row)">用水量</div>
</el-dropdown-item>
<el-dropdown-item>
<div @click="handleOpenDialog('Upload', scope.row)">更新固件</div>
</el-dropdown-item>
<el-dropdown-item>
<div @click="handleOpenDialog('UpdateHouse', scope.row)" v-hasPermi="['system:waterDevice:edit']">修改</div>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-col>
</el-row>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 添加或修改水 设备对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<!-- water -->
<el-form v-if="dialogType === 'usedWater'" ref="UsedWaterformRef" :model="UsedWaterform" label-width="100px">
<el-form-item label="设备编号" prop="deviceCode"> {{ UsedWaterform.deviceCode }} </el-form-item>
<el-form-item label="开始时间" prop="startTime">
<el-date-picker
format="YYYY-MM-DD HH:MM:ss"
value-format="YYYY-MM-DD HH:MM:ss"
v-model="UsedWaterform.startTime"
type="datetime"
placeholder="开始时间"
/>
</el-form-item>
<el-form-item label="结束时间" prop="endTime">
<el-date-picker
format="YYYY-MM-DD HH:MM:ss"
value-format="YYYY-MM-DD HH:MM:ss"
v-model="UsedWaterform.endTime"
type="datetime"
placeholder="结束时间"
/>
</el-form-item>
<el-form-item>
<el-button :loading="buttonLoading" type="primary" @click="submitForm">搜索</el-button>
</el-form-item>
</el-form>
<!-- upload file -->
<el-form v-else-if="dialogType === 'Upload'" ref="UploadFileformRef" :model="UploadFileform" :rules="UploadFileformRules" label-width="120px">
<el-form-item label="上传更新文件" prop="file">
<el-upload
ref="uploadRef"
accept=".bin"
:action="uploadUrl"
name="file"
:headers="headerToken()"
:show-file-list="true"
class="upload-demo"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload"
>
<div class="uploadFileBox flex flex-items-center">
<el-icon>
<upload></upload>
</el-icon>
<div style="margin-left: 10px">上传文件</div>
</div>
<template #tip>
<div class="el-upload__tip">文件格式必须是 bin 格式</div>
</template>
</el-upload>
</el-form-item>
<el-form-item>
<el-button @click="unSubmit">取消</el-button>
<el-button :loading="buttonLoading" type="primary" @click="submitForm">更新到设备</el-button>
</el-form-item>
</el-form>
<!-- house -->
<el-form v-else-if="dialogType === 'UpdateHouse'" ref="EditHouseformRef" :model="EditHouseform" :rules="EditHouseformRules" label-width="100px">
<el-form-item label="更新户号" prop="houseId">
<el-cascader v-model="userHousepath" :options="treedata" @change="handleChange" />
</el-form-item>
<el-form-item>
<el-button @click="unSubmit">取消</el-button>
<el-button :loading="buttonLoading" type="primary" @click="submitForm">更新到设备</el-button>
</el-form-item>
</el-form>
<div v-show="!usedWaterInfo" class="usedWaterbox">
<div class="useditem">查询用水量开始时间</div>
<div class="useditem value">{{ UsedWaterInfo.startTime }}</div>
<div class="useditem">查询用水量结束时间</div>
<div class="useditem value">{{ UsedWaterInfo.endTime }}</div>
<div class="useditem">用水量</div>
<div class="useditem value">{{ UsedWaterInfo.count }}</div>
</div>
</el-dialog>
</div>
</template>
<script setup name="WaterDevice" lang="ts">
import 'element-plus/theme-chalk/display.css';
import { listWaterDevice, getWaterDeviceTimeRangeUsedWater, WaterDeviceMakerQrcode, updateWaterDevice } from '@/api/system/SdbWaterDevice/index';
import { WaterDeviceVO, WaterDeviceQuery, WaterDeviceForm } from '@/api/system/SdbWaterDevice/type';
import { useHouseStore } from '@/store/modules/house';
import { useUserStore } from '@/store/modules/user';
import { getHousePathById } from '@/utils/house';
import { Upload } from '@element-plus/icons-vue';
import { CascaderValue, tourEmits, UploadProps, UploadRawFile } from 'element-plus';
const huhao = 3096371295;
const uploadUrl = import.meta.env.VITE_APP_BASE_API + '/waterDevice/upload';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { com_waterdevice_on_status } = toRefs<any>(proxy?.useDict('com_waterdevice_on_status'));
const { com_water_readrecord_status } = toRefs<any>(proxy?.useDict('com_water_readrecord_status'));
const waterDeviceList = ref<WaterDeviceVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<{ id: string; deviceCode: string }[]>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const data = reactive({
queryParams: {
pageNum: 1,
pageSize: 10,
eleStatus: '',
houseName: '',
status: ''
}
});
const { queryParams } = toRefs(data);
/** 查询水 设备列表 */
const getList = async () => {
loading.value = true;
const res = await listWaterDevice(queryParams.value);
waterDeviceList.value = res.rows;
total.value = res.total;
loading.value = false;
};
// ! 用水量 =============================================================================
const usedWaterInfo = ref(true);
const UsedWaterformRef = ref(null);
const UsedWaterform = ref({
deviceCode: '',
startTime: '',
endTime: ''
});
const UsedWaterInfo = ref({
count: '',
startTime: '',
endTime: ''
});
// ! =======================================================================================
// ? 上传固件 ==============================================================================================
const UploadFileformRef = ref(null);
const uploadRef = ref();
const UploadFileform = ref({
id: '',
deviceCode: '',
file: ''
});
const UploadFileformRules = {
file: [{ required: true, message: '更新文件不能为空', trigger: 'blur' }]
};
function headerToken() {
const useStore = useUserStore();
return {
Authorization: 'Bearer ' + useStore.token
};
}
const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => {
// 1. 校验后缀
console.log(rawFile);
if (!rawFile.name.endsWith('.bin')) {
ElMessage.warning('请上传 .bin 后缀的文件');
return false;
}
// 2. 校验文件类型(bin 文件类型)
if (rawFile.type !== 'application/octet-stream' && rawFile.type !== '') {
ElMessage.warning('文件类型不正确,请上传标准 bin 文件');
return false;
}
// 3. 校验文件大小(不能空)
if (rawFile.size === 0) {
ElMessage.warning('上传 bin 文件不能为空');
return false;
}
return true;
};
const handleAvatarSuccess: UploadProps['onSuccess'] = (response: any) => {
UploadFileform.value.file = response.data.url;
};
// ? 上传固件 ==============================================================================================
// * 编辑房屋 ==============================================================================================
// 编辑房屋
const EditHouseformRef = ref(null);
const EditHouseformRules = {
file: [{ required: true, message: '更新文件不能为空', trigger: 'blur' }]
};
const EditHouseform = ref({
id: '',
deviceCode: '',
houseId: ''
});
// * 编辑房屋 ==============================================================================================
/** 批量生成 条形码 */
function handleMarkqrcode() {
if (ids.value.length > 0) {
WaterDeviceMakerQrcode(ids.value).then((res) => {
ElMessage.success('批量生成成功');
getList();
});
} else {
ElMessage.warning('请选择设备');
}
}
/** 多选框选中数据 */
const handleSelectionChange = (selection: WaterDeviceVO[]) => {
ids.value = selection.map((item) => {
return {
id: item.id,
deviceCode: item.deviceCode
};
});
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 生成二维码 */
const handleQrCode = (row: WaterDeviceVO) => {
// 1.电表 2.水表
WaterDeviceMakerQrcode([
{
id: row.id,
deviceCode: row.deviceCode
}
]).then(() => {
ElMessage.success('生成成功');
getList();
});
};
// * house
const treedata = ref([]);
const houseStore = useHouseStore();
const userHousepath = ref<CascaderValue>([]);
function handleChange(val: CascaderValue) {
userHousepath.value = val;
EditHouseform.value.houseId = val[2];
}
// * house
const dialogType = ref<'usedWater' | 'Upload' | 'UpdateHouse'>('usedWater');
/** 用水量 更新固件 编辑房屋*/
const handleOpenDialog = (type: 'usedWater' | 'Upload' | 'UpdateHouse', row: WaterDeviceVO) => {
reset();
if (type === 'usedWater') {
UsedWaterform.value.deviceCode = row.deviceCode;
UsedWaterform.value.startTime = new Date().toLocaleString();
UsedWaterform.value.endTime = new Date().toLocaleString();
usedWaterInfo.value = true;
} else if (type === 'Upload') {
UploadFileform.value.file = '';
UploadFileform.value.deviceCode = row.deviceCode;
UploadFileform.value.id = row.id;
} else if (type === 'UpdateHouse') {
EditHouseform.value.id = row.id;
EditHouseform.value.deviceCode = row.deviceCode;
EditHouseform.value.houseId = '';
getTree();
}
dialog.visible = true;
dialogType.value = type;
dialog.title = type === 'usedWater' ? '用水量' : type === 'UpdateHouse' ? '编辑户号' : '更新固件';
};
function getTree() {
treedata.value = [];
houseStore.getCurrentVilageTreeHouse().then((res) => {
treedata.value = res;
userHousepath.value = [];
});
}
/** 水表 启停*/
const handleChangeWater = (val: string, row: WaterDeviceVO) => {};
/** 提交按钮 */
const submitForm = () => {
if (dialogType.value === 'usedWater') {
UsedWaterformRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
await getWaterDeviceTimeRangeUsedWater(UsedWaterform.value)
.then((res) => {
UsedWaterInfo.value.endTime = res.data.endTime;
UsedWaterInfo.value.startTime = res.data.startTime;
UsedWaterInfo.value.count = res.data.count;
})
.finally(() => (buttonLoading.value = false));
proxy?.$modal.msgSuccess('操作成功');
usedWaterInfo.value = false;
}
});
} else if (dialogType.value === 'UpdateHouse') {
EditHouseformRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
await updateWaterDevice(EditHouseform.value).finally(() => (buttonLoading.value = false));
proxy?.$modal.msgSuccess('操作成功');
getList();
unSubmit();
}
});
}
};
const unSubmit = () => {
reset();
dialog.visible = false;
dialog.title = '';
};
/** 表单重置 */
const reset = () => {
usedWaterInfo.value = true;
UsedWaterform.value = {
deviceCode: '',
startTime: '',
endTime: ''
};
UsedWaterInfo.value = {
count: '',
startTime: '',
endTime: ''
};
EditHouseform.value = {
id: '',
deviceCode: '',
houseId: ''
};
// 上传 清空
UploadFileformRef.value?.resetFields();
// 使用水量 清空
EditHouseformRef.value?.resetFields();
// 使用水量 清空
UsedWaterformRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
const router = useRouter();
/** 充值记录 */
const handleRecode = (row: WaterDeviceVO) => {
// 1.电表 2.水表
router.push({
path: '/equipment/TopupRecord',
query: {
type: '2',
id: row.id,
houseName: row.houseName,
deviceCode: row.deviceCode
}
});
};
onMounted(() => {
getList();
});
const download = (url: string) => {
console.log(url);
if (!url) return;
if (!url.includes('http')) return;
// 创建一个隐藏的 a 标签,直接跳转下载
const link = document.createElement('a');
link.href = url;
link.download = ''; // 自动获取文件名
link.target = '_blank';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
</script>
<style lang="scss" scoped>
.example-showcase .el-dropdown + .el-dropdown {
margin-left: 15px;
}
.example-showcase .el-dropdown-link {
cursor: pointer;
color: var(--el-color-primary);
display: flex;
align-items: center;
}
.el-table {
height: calc(100% - 80px);
}
.usedWaterbox {
width: 100%;
border: 1px solid #eef0ff;
.useditem {
height: 42px;
line-height: 42px;
background-color: rgba(26, 117, 255, 0.1);
color: rgba(16, 16, 16, 1);
font-size: 14px;
padding: 0 10px;
&.value {
background-color: #fff;
}
}
}
.uploadFileBox {
width: 276px;
height: 35px;
line-height: 20px;
border-radius: 4px 4px 4px 4px;
background-color: rgba(255, 255, 255, 1);
color: rgba(16, 16, 16, 1);
border: 1px solid #ccc;
padding-left: 20px;
font-size: 14px;
}
</style>