gaoluyang
2025-08-11 386375a8e04f72a8bc17553b649a38b5535e21ba
1.添加用水管理页面
已添加9个文件
2917 ■■■■■ 文件已修改
src/api/energyManagement/waterManagement.js 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockWarning.js 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/energyManagement/waterManagement/components/formDia.vue 221 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/energyManagement/waterManagement/components/waterBillForm.vue 210 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/energyManagement/waterManagement/index.vue 312 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/energyManagement/waterManagement/waterBill.vue 181 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/energyManagement/waterManagement/waterTrends.vue 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/gasTank/simple.vue 566 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockWarning/index.vue 1137 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/energyManagement/waterManagement.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,91 @@
// ç”¨æ°´ç®¡ç†
import request from "@/utils/request";
// ç”¨æ°´è®¾å¤‡-分页查询
export function waterEquipmentListPage(query) {
  return request({
    url: '/waterEquipmentConsumption/listPage',
    method: 'get',
    params: query,
  })
}
// ç”¨æ°´è¶‹åŠ¿-分页查询
export function listPageByWaterTrend(query) {
  return request({
    url: '/waterEquipmentConsumption/listPageByTrend',
    method: 'get',
    params: query,
  })
}
// ç”¨æ°´è®¾å¤‡-删除
export function waterEquipmentDelete(query) {
  return request({
    url: '/waterEquipmentConsumption/delete',
    method: 'delete',
    data: query,
  })
}
// ç”¨æ°´è®¾å¤‡-新增
export function waterEquipmentAdd(query) {
  return request({
    url: '/waterEquipmentConsumption/add',
    method: 'post',
    data: query,
  })
}
// ç”¨æ°´è®¾å¤‡-修改
export function waterEquipmentUpdate(query) {
  return request({
    url: '/waterEquipmentConsumption/update',
    method: 'post',
    data: query,
  })
}
// ç”¨æ°´è®¾å¤‡ä¸‹æ‹‰æ¡†æŸ¥è¯¢
export function waterDeviceList(query) {
  return request({
    url: '/waterEquipmentConsumption/deviceList',
    method: 'get',
  })
}
// æ°´è´¹ç®¡ç†-分页查询
export function waterBillListPage(query) {
  return request({
    url: '/waterBill/listPage',
    method: 'get',
    params: query,
  })
}
// æ°´è´¹ç®¡ç†-新增
export function waterBillAdd(query) {
  return request({
    url: '/waterBill/add',
    method: 'post',
    data: query,
  })
}
// æ°´è´¹ç®¡ç†-修改
export function waterBillUpdate(query) {
  return request({
    url: '/waterBill/update',
    method: 'post',
    data: query,
  })
}
// æ°´è´¹ç®¡ç†-删除
export function waterBillDelete(query) {
  return request({
    url: '/waterBill/delete',
    method: 'delete',
    data: query,
  })
}
src/api/inventoryManagement/stockWarning.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,81 @@
import request from "@/utils/request";
// æŸ¥è¯¢å‚¨æ°”罐预警列表
export const getStockWarningPage = (params) => {
    return request({
        url: "/gasTankWarning/listPage",
        method: "get",
        params,
    });
};
// æ–°å¢žå‚¨æ°”罐预警规则
export const addStockWarning = (data) => {
    return request({
        url: "/gasTankWarning/add",
        method: "post",
        data,
    });
};
// ä¿®æ”¹å‚¨æ°”罐预警规则
export const updateStockWarning = (data) => {
    return request({
        url: "/gasTankWarning/update",
        method: "put",
        data,
    });
};
// åˆ é™¤å‚¨æ°”罐预警规则
export const deleteStockWarning = (ids) => {
    return request({
        url: "/gasTankWarning/delete",
        method: "delete",
        data: { ids },
    });
};
// æ‰¹é‡å¤„理储气罐预警
export const batchProcessStockWarning = (data) => {
    return request({
        url: "/gasTankWarning/batchProcess",
        method: "post",
        data,
    });
};
// å¯¼å‡ºå‚¨æ°”罐预警数据
export const exportStockWarning = (params) => {
    return request({
        url: "/gasTankWarning/export",
        method: "get",
        params,
        responseType: "blob",
    });
};
// æ ¹æ®ID获取储气罐预警详情
export const getStockWarningById = (id) => {
    return request({
        url: `/gasTankWarning/${id}`,
        method: "get",
    });
};
// å¯ç”¨/禁用预警规则
export const toggleStockWarningStatus = (data) => {
    return request({
        url: "/gasTankWarning/toggleStatus",
        method: "put",
        data,
    });
};
// èŽ·å–é¢„è­¦ç»Ÿè®¡ä¿¡æ¯
export const getStockWarningStatistics = () => {
    return request({
        url: "/gasTankWarning/statistics",
        method: "get",
    });
};
src/views/energyManagement/waterManagement/components/formDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,221 @@
<template>
  <div>
    <el-dialog
        v-model="dialogFormVisible"
        title="用水设备"
        width="70%"
        @close="closeDia"
    >
            <el-form
                :model="form"
                label-width="140px"
                label-position="top"
                :rules="rules"
                ref="formRef"
            >
                <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="设备:" prop="code">
                            <el-select
                                v-model="form.code"
                                placeholder="请选择"
                                clearable
                                @change="setName"
                                :disabled="operationType !== 'add'"
                            >
                                <el-option
                                    v-for="item in codeList"
                                    :key="item.deviceModel"
                                    :label="item.deviceName"
                                    :value="item.deviceModel"
                                >
                                    {{item.deviceName + '--' + item.deviceModel}}
                                </el-option>
                            </el-select>
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="每日限制水量:" prop="everyNum">
                            <el-input
                                v-model="form.everyNum"
                                placeholder="请输入"
                                clearable
                            />
                        </el-form-item>
                    </el-col>
                </el-row>
                <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="额定流量:" prop="flowRating">
                            <el-input
                                v-model="form.flowRating"
                                placeholder="请输入"
                                clearable
                            />
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="实际流量:" prop="flowActual">
                            <el-input
                                v-model="form.flowActual"
                                placeholder="请输入"
                                clearable
                            />
                        </el-form-item>
                    </el-col>
                </el-row>
                <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="运行时间:" prop="runDate">
                            <el-date-picker
                                style="width: 100%"
                                v-model="form.runDate"
                                value-format="YYYY-MM-DD"
                                format="YYYY-MM-DD"
                                type="date"
                                placeholder="请选择"
                                clearable
                            />
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="当日用水量:" prop="dayNum">
                            <el-input
                                v-model="form.dayNum"
                                placeholder="请输入"
                                clearable
                            />
                        </el-form-item>
                    </el-col>
                </el-row>
                <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="水费单价:" prop="waterPrice">
                            <el-input
                                v-model="form.waterPrice"
                                placeholder="请输入"
                                clearable
                            />
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="用水类型:" prop="waterType">
                            <el-select
                                v-model="form.waterType"
                                placeholder="请选择"
                                clearable
                            >
                                <el-option label="工业用水" value="industrial" />
                                <el-option label="生活用水" value="domestic" />
                                <el-option label="消防用水" value="fire" />
                                <el-option label="绿化用水" value="greening" />
                            </el-select>
                        </el-form-item>
                    </el-col>
                </el-row>
            </el-form>
            <template #footer>
                <div class="dialog-footer">
                    <el-button type="primary" @click="submitForm">确认</el-button>
                    <el-button @click="closeDia">取消</el-button>
                </div>
            </template>
    </el-dialog>
  </div>
</template>
<script setup>
import {ref, reactive, nextTick} from "vue";
import useUserStore from "@/store/modules/user.js";
import {waterDeviceList, waterEquipmentAdd, waterEquipmentUpdate} from "@/api/energyManagement/waterManagement.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
const operationType = ref('')
const userStore = useUserStore();
const data = reactive({
    form: {
        name: "",
        code: "",
        everyNum: "",
        flowRating: "",
        flowActual: "",
        runDate: "",
        dayNum: "",
        waterPrice: "",
        waterType: "",
    },
    rules: {
        code: [{ required: true, message: "请选择", trigger: "change" }],
        runDate: [{ required: true, message: "请选择", trigger: "change" }],
        everyNum: [{ required: true, message: "请输入", trigger: "blur" }],
        flowRating: [{ required: true, message: "请输入", trigger: "blur" }],
        flowActual: [{ required: true, message: "请输入", trigger: "blur" }],
        dayNum: [{ required: true, message: "请输入", trigger: "blur" }],
        waterPrice: [{ required: true, message: "请输入", trigger: "blur" }],
        waterType: [{ required: true, message: "请选择", trigger: "change" }],
    },
})
const { form, rules } = toRefs(data);
const codeList = ref([])
// æ‰“开弹框
const openDialog = (type, row) => {
  operationType.value = type;
  dialogFormVisible.value = true;
    form.value = {}
    proxy.resetForm("formRef");
    waterDeviceList().then((res) => {
        codeList.value = res.data;
    });
    if (type === "edit") {
        form.value = {...row}
    }
}
const setName = (code) => {
    const index = codeList.value.findIndex(item => item.deviceModel === code);
    if (index > -1) {
        console.log(codeList)
        form.value.name = codeList.value[index].deviceName;
    }
}
const submitForm = () => {
    proxy.$refs["formRef"].validate(valid => {
        if (valid) {
            if (operationType.value === "add") {
                waterEquipmentAdd(form.value).then(response => {
                    proxy.$modal.msgSuccess("新增成功")
                    closeDia()
                })
            } else {
                waterEquipmentUpdate(form.value).then(response => {
                    proxy.$modal.msgSuccess("修改成功")
                    closeDia()
                })
            }
        }
    })
}
// å…³é—­å¼¹æ¡†
const closeDia = () => {
    proxy.resetForm("formRef");
  dialogFormVisible.value = false;
  emit('close')
};
// èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
function getCurrentDate() {
    const today = new Date();
    const year = today.getFullYear();
    const month = String(today.getMonth() + 1).padStart(2, "0"); // æœˆä»½ä»Ž0开始
    const day = String(today.getDate()).padStart(2, "0");
    return `${year}-${month}-${day}`;
}
defineExpose({
  openDialog,
});
</script>
<style scoped>
</style>
src/views/energyManagement/waterManagement/components/waterBillForm.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,210 @@
<template>
  <div>
    <el-dialog
        v-model="dialogFormVisible"
        title="水费管理"
        width="70%"
        @close="closeDia"
    >
            <el-form
                :model="form"
                label-width="140px"
                label-position="top"
                :rules="rules"
                ref="formRef"
            >
                <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="设备:" prop="code">
                            <el-select
                                v-model="form.code"
                                placeholder="请选择"
                                clearable
                                @change="setName"
                                :disabled="operationType !== 'add'"
                            >
                                <el-option
                                    v-for="item in codeList"
                                    :key="item.deviceModel"
                                    :label="item.deviceName"
                                    :value="item.deviceModel"
                                >
                                    {{item.deviceName + '--' + item.deviceModel}}
                                </el-option>
                            </el-select>
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="用水量:" prop="waterConsumption">
                            <el-input
                                v-model="form.waterConsumption"
                                placeholder="请输入"
                                clearable
                            />
                        </el-form-item>
                    </el-col>
                </el-row>
                <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="水费单价:" prop="waterPrice">
                            <el-input
                                v-model="form.waterPrice"
                                placeholder="请输入"
                                clearable
                            />
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="水费金额:" prop="waterBill">
                            <el-input
                                v-model="form.waterBill"
                                placeholder="自动计算"
                                clearable
                                disabled
                            />
                        </el-form-item>
                    </el-col>
                </el-row>
                <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="计费日期:" prop="billDate">
                            <el-date-picker
                                style="width: 100%"
                                v-model="form.billDate"
                                value-format="YYYY-MM-DD"
                                format="YYYY-MM-DD"
                                type="date"
                                placeholder="请选择"
                                clearable
                            />
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="用水类型:" prop="waterType">
                            <el-select
                                v-model="form.waterType"
                                placeholder="请选择"
                                clearable
                            >
                                <el-option label="工业用水" value="industrial" />
                                <el-option label="生活用水" value="domestic" />
                                <el-option label="消防用水" value="fire" />
                                <el-option label="绿化用水" value="greening" />
                            </el-select>
                        </el-form-item>
                    </el-col>
                </el-row>
            </el-form>
            <template #footer>
                <div class="dialog-footer">
                    <el-button type="primary" @click="submitForm">确认</el-button>
                    <el-button @click="closeDia">取消</el-button>
                </div>
            </template>
    </el-dialog>
  </div>
</template>
<script setup>
import {ref, reactive, nextTick, watch} from "vue";
import useUserStore from "@/store/modules/user.js";
import {waterDeviceList, waterBillAdd, waterBillUpdate} from "@/api/energyManagement/waterManagement.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
const operationType = ref('')
const userStore = useUserStore();
const data = reactive({
    form: {
        name: "",
        code: "",
        waterConsumption: "",
        waterPrice: "",
        waterBill: "",
        billDate: "",
        waterType: "",
    },
    rules: {
        code: [{ required: true, message: "请选择", trigger: "change" }],
        waterConsumption: [{ required: true, message: "请输入", trigger: "blur" }],
        waterPrice: [{ required: true, message: "请输入", trigger: "blur" }],
        billDate: [{ required: true, message: "请选择", trigger: "change" }],
        waterType: [{ required: true, message: "请选择", trigger: "change" }],
    },
})
const { form, rules } = toRefs(data);
const codeList = ref([])
// æ‰“开弹框
const openDialog = (type, row) => {
  operationType.value = type;
  dialogFormVisible.value = true;
    form.value = {}
    proxy.resetForm("formRef");
    waterDeviceList().then((res) => {
        codeList.value = res.data;
    });
    if (type === "edit") {
        form.value = {...row}
    }
}
const setName = (code) => {
    const index = codeList.value.findIndex(item => item.deviceModel === code);
    if (index > -1) {
        console.log(codeList)
        form.value.name = codeList.value[index].deviceName;
    }
}
// è®¡ç®—水费金额
const calculateWaterBill = () => {
    if (form.value.waterConsumption && form.value.waterPrice) {
        form.value.waterBill = (parseFloat(form.value.waterConsumption) * parseFloat(form.value.waterPrice)).toFixed(2);
    }
}
// ç›‘听用水量和水费单价变化
watch([() => form.value.waterConsumption, () => form.value.waterPrice], () => {
    calculateWaterBill();
});
const submitForm = () => {
    proxy.$refs["formRef"].validate(valid => {
        if (valid) {
            if (operationType.value === "add") {
                waterBillAdd(form.value).then(response => {
                    proxy.$modal.msgSuccess("新增成功")
                    closeDia()
                })
            } else {
                waterBillUpdate(form.value).then(response => {
                    proxy.$modal.msgSuccess("修改成功")
                    closeDia()
                })
            }
        }
    })
}
// å…³é—­å¼¹æ¡†
const closeDia = () => {
    proxy.resetForm("formRef");
  dialogFormVisible.value = false;
  emit('close')
};
// èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
function getCurrentDate() {
    const today = new Date();
    const year = today.getFullYear();
    const month = String(today.getMonth() + 1).padStart(2, "0"); // æœˆä»½ä»Ž0开始
    const day = String(today.getDate()).padStart(2, "0");
    return `${year}-${month}-${day}`;
}
defineExpose({
  openDialog,
});
</script>
<style scoped>
</style>
src/views/energyManagement/waterManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,312 @@
<template>
    <div class="app-container">
        <div class="search_form">
            <div>
                <span class="search_title">设备名称:</span>
                <el-input
                    v-model="searchForm.name"
                    style="width: 240px"
                    placeholder="请输入"
                    @change="handleQuery"
                    clearable
                    :prefix-icon="Search"
                />
                <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
                >搜索</el-button
                >
            </div>
            <div>
                <el-button type="primary" @click="openForm('add')">新增</el-button>
                <el-button type="info" plain icon="Upload" @click="handleImport">导入</el-button>
                <el-button type="danger" plain @click="handleDelete">删除</el-button>
            </div>
        </div>
        <div class="table_list">
            <PIMTable
                rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                :isSelection="true"
                @selection-change="handleSelectionChange"
                :tableLoading="tableLoading"
                @pagination="pagination"
            ></PIMTable>
        </div>
        <form-dia ref="formDia" @close="handleQuery"></form-dia>
        <el-dialog
            :title="upload.title"
            v-model="upload.open"
            width="400px"
            append-to-body
            @close="handleDialogClose"
        >
            <el-upload
                ref="uploadRef"
                :limit="1"
                accept=".xlsx, .xls"
                :headers="upload.headers"
                :action="upload.url"
                :disabled="upload.isUploading"
                :before-upload="upload.beforeUpload"
                :on-progress="upload.onProgress"
                :on-success="upload.onSuccess"
                :on-error="upload.onError"
                :on-change="upload.onChange"
                :auto-upload="false"
                drag
            >
                <el-icon class="el-icon--upload"><upload-filled /></el-icon>
                <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
                <template #tip>
                    <div class="el-upload__tip text-center">
                        <span>仅允许导入xls、xlsx格式文件。</span>
                        <el-link
                            type="primary"
                            :underline="false"
                            style="font-size: 12px; vertical-align: baseline"
                            @click="importTemplate"
                            >下载模板</el-link
                        >
                    </div>
                </template>
            </el-upload>
            <template #footer>
                <div class="dialog-footer">
                    <el-button type="primary" @click="submitFileForm">ç¡® å®š</el-button>
                    <el-button @click="upload.open = false">取 æ¶ˆ</el-button>
                </div>
            </template>
        </el-dialog>
    </div>
</template>
<script setup>
import {Search} from "@element-plus/icons-vue";
import {onMounted, ref, reactive, nextTick} from "vue";
import FormDia from "@/views/energyManagement/waterManagement/components/formDia.vue";
import {ElMessageBox} from "element-plus";
import {getToken} from "@/utils/auth.js";
import {waterEquipmentDelete, waterEquipmentListPage} from "@/api/energyManagement/waterManagement.js";
const { proxy } = getCurrentInstance();
const data = reactive({
    searchForm: {
        name: "",
    },
});
const { searchForm } = toRefs(data);
const selectedRows = ref([]);
const tableColumn = ref([
    {
        label: "设备名称",
        prop: "name",
        width: 200,
    },
    {
        label: "规格型号",
        prop: "code",
        width: 200,
    },
    {
        label: "额定流量",
        prop: "flowRating",
    },
    {
        label: "实际流量",
        prop: "flowActual",
    },
    {
        label: "运行时间",
        prop: "runDate",
        width:150
    },
    {
        label: "当日用水量",
        prop: "dayNum",
        width: 150,
    },
    {
        label: "每日限制水量",
        prop: "everyNum",
        width:220
    },
    {
        label: "水费单价",
        prop: "waterPrice",
        width: 120,
    },
    {
        dataType: "action",
        label: "操作",
        align: "center",
        fixed: 'right',
        operation: [
            {
                name: "编辑",
                type: "text",
                clickFun: (row) => {
                    openForm("edit", row);
                },
            },
        ],
    },
]);
const tableData = ref([]);
const tableLoading = ref(false);
const page = reactive({
    current: 1,
    size: 100,
    total: 0,
});
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
    selectedRows.value = selection;
};
const formDia = ref()
const upload = reactive({
    // æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚(客户导入)
    open: false,
    // å¼¹å‡ºå±‚标题(客户导入)
    title: "",
    // æ˜¯å¦ç¦ç”¨ä¸Šä¼ 
    isUploading: false,
    // è®¾ç½®ä¸Šä¼ çš„请求头部
    headers: { Authorization: "Bearer " + getToken() },
    // ä¸Šä¼ çš„地址
    url: import.meta.env.VITE_APP_BASE_API + "/waterEquipmentConsumption/importData",
    // æ–‡ä»¶ä¸Šä¼ å‰çš„回调
    beforeUpload: (file) => {
        console.log('文件即将上传', file);
        // å¯ä»¥åœ¨æ­¤å¤„做文件类型或大小校验
        const isValid = file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' || file.name.endsWith('.xlsx') || file.name.endsWith('.xls');
        if (!isValid) {
            proxy.$modal.msgError("只能上传 Excel æ–‡ä»¶");
        }
        return isValid;
    },
    // æ–‡ä»¶çŠ¶æ€æ”¹å˜æ—¶çš„å›žè°ƒ
    onChange: (file, fileList) => {
        console.log('文件状态改变', file, fileList);
    },
    // æ–‡ä»¶ä¸Šä¼ æˆåŠŸæ—¶çš„å›žè°ƒ
    onSuccess: (response, file, fileList) => {
        console.log('上传成功', response, file, fileList);
        if(response.code === 200){
            proxy.$modal.msgSuccess("文件上传成功");
        }else if(response.code === 500){
            proxy.$modal.msgError(response.msg);
        }else{
            proxy.$modal.msgError("文件上传失败");
        }
        upload.open = false;
        getList();
    },
    // æ–‡ä»¶ä¸Šä¼ å¤±è´¥æ—¶çš„回调
    onError: (error, file, fileList) => {
        console.log('上传失败', error, file, fileList);
        proxy.$modal.msgError("文件上传失败");
        upload.open = false;
    },
    // æ–‡ä»¶ä¸Šä¼ è¿›åº¦æ”¹å˜æ—¶çš„回调
    onProgress: (event, file, fileList) => {
        console.log('上传进度', event, file, fileList);
        upload.isUploading = true;
    },
});
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    page.current = 1;
    getList();
};
const pagination = (obj) => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
};
const getList = () => {
    tableLoading.value = true;
    waterEquipmentListPage({ ...searchForm.value, ...page }).then((res) => {
        tableLoading.value = false;
        tableData.value = res.data.records;
        page.total = res.data.total;
    }).catch(() => {
        tableLoading.value = false;
    })
};
// æ‰“开弹框
const openForm = (type, row) => {
    nextTick(() => {
        formDia.value?.openDialog(type, row)
    })
};
/** å¯¼å…¥æŒ‰é’®æ“ä½œ */
function handleImport() {
    upload.title = "用水设备";
    upload.open = true;
    // æ¸…空上次上传的文件列表
    nextTick(() => {
        proxy.$refs["uploadRef"]?.clearFiles();
    });
}
function importTemplate() {
    proxy.download(
        "/waterEquipmentConsumption/export",
        {},
        '用水设备导入模版.xlsx'
    );
}
/** æäº¤ä¸Šä¼ æ–‡ä»¶ */
function submitFileForm() {
    proxy.$refs["uploadRef"].submit();
}
/** å¼¹æ¡†å…³é—­æ—¶æ¸…空文件列表 */
function handleDialogClose() {
    nextTick(() => {
        proxy.$refs["uploadRef"]?.clearFiles();
    });
}
const handleDelete = () => {
    let ids = [];
    if (selectedRows.value.length > 0) {
        ids = selectedRows.value.map((item) => item.id);
    } else {
        proxy.$modal.msgWarning("请选择数据");
        return;
    }
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            tableLoading.value = true;
            waterEquipmentDelete(ids)
                .then((res) => {
                    proxy.$modal.msgSuccess("删除成功");
                    getList();
                })
                .finally(() => {
                    tableLoading.value = false;
                });
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
};
onMounted(() => {
    getList();
});
</script>
<style scoped>
</style>
src/views/energyManagement/waterManagement/waterBill.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,181 @@
<template>
    <div class="app-container">
        <div class="search_form">
            <div>
                <span class="search_title">设备名称:</span>
                <el-input
                    v-model="searchForm.name"
                    style="width: 240px"
                    placeholder="请输入"
                    @change="handleQuery"
                    clearable
                    :prefix-icon="Search"
                />
                <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
                >搜索</el-button
                >
            </div>
            <div>
                <el-button type="primary" @click="openForm('add')">新增</el-button>
                <el-button type="danger" plain @click="handleDelete">删除</el-button>
            </div>
        </div>
        <div class="table_list">
            <PIMTable
                rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                :isSelection="true"
                @selection-change="handleSelectionChange"
                :tableLoading="tableLoading"
                @pagination="pagination"
            ></PIMTable>
        </div>
        <form-dia ref="formDia" @close="handleQuery"></form-dia>
    </div>
</template>
<script setup>
import {Search} from "@element-plus/icons-vue";
import {onMounted, ref, reactive, nextTick} from "vue";
import FormDia from "@/views/energyManagement/waterManagement/components/waterBillForm.vue";
import {ElMessageBox} from "element-plus";
import {waterBillDelete, waterBillListPage} from "@/api/energyManagement/waterManagement.js";
const { proxy } = getCurrentInstance();
const data = reactive({
    searchForm: {
        name: "",
    },
});
const { searchForm } = toRefs(data);
const selectedRows = ref([]);
const tableColumn = ref([
    {
        label: "设备名称",
        prop: "name",
        width: 200,
    },
    {
        label: "规格型号",
        prop: "code",
        width: 200,
    },
    {
        label: "用水量",
        prop: "waterConsumption",
    },
    {
        label: "水费单价",
        prop: "waterPrice",
    },
    {
        label: "水费金额",
        prop: "waterBill",
        width:150
    },
    {
        label: "计费日期",
        prop: "billDate",
        width: 150,
    },
    {
        label: "用水类型",
        prop: "waterType",
        width:120
    },
    {
        dataType: "action",
        label: "操作",
        align: "center",
        fixed: 'right',
        operation: [
            {
                name: "编辑",
                type: "text",
                clickFun: (row) => {
                    openForm("edit", row);
                },
            },
        ],
    },
]);
const tableData = ref([]);
const tableLoading = ref(false);
const page = reactive({
    current: 1,
    size: 100,
    total: 0,
});
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
    selectedRows.value = selection;
};
const formDia = ref()
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    page.current = 1;
    getList();
};
const pagination = (obj) => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
};
const getList = () => {
    tableLoading.value = true;
    waterBillListPage({ ...searchForm.value, ...page }).then((res) => {
        tableLoading.value = false;
        tableData.value = res.data.records;
        page.total = res.data.total;
    });
};
// æ‰“开弹框
const openForm = (type, row) => {
    nextTick(() => {
        formDia.value?.openDialog(type, row)
    })
};
const handleDelete = () => {
    let ids = [];
    if (selectedRows.value.length > 0) {
        ids = selectedRows.value.map((item) => item.id);
    } else {
        proxy.$modal.msgWarning("请选择数据");
        return;
    }
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            tableLoading.value = true;
            waterBillDelete(ids)
                .then((res) => {
                    proxy.$modal.msgSuccess("删除成功");
                    getList();
                })
                .finally(() => {
                    tableLoading.value = false;
                });
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
};
onMounted(() => {
    getList();
});
</script>
<style scoped>
</style>
src/views/energyManagement/waterManagement/waterTrends.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,118 @@
<template>
    <div class="app-container">
        <div class="search_form">
            <div>
                <span class="search_title">设备名称:</span>
                <el-input
                    v-model="searchForm.name"
                    style="width: 240px"
                    placeholder="请输入"
                    @change="handleQuery"
                    clearable
                    :prefix-icon="Search"
                />
                <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
                >搜索</el-button
                >
            </div>
        </div>
        <div class="table_list">
            <PIMTable
                rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                :isSelection="true"
                @selection-change="handleSelectionChange"
                :tableLoading="tableLoading"
                @pagination="pagination"
            ></PIMTable>
        </div>
    </div>
</template>
<script setup>
import {Search} from "@element-plus/icons-vue";
import {onMounted, ref, reactive} from "vue";
import {listPageByWaterTrend} from "@/api/energyManagement/waterManagement.js";
const data = reactive({
    searchForm: {
        name: "",
    },
});
const { searchForm } = toRefs(data);
const selectedRows = ref([]);
const tableColumn = ref([
    {
        label: "设备名称",
        prop: "name",
        width: 220,
    },
    {
        label: "规格型号",
        prop: "code",
        width: 220,
    },
    {
        label: "运行时间",
        prop: "runDate",
        width: 250,
    },
    {
        label: "昨日用水量",
        prop: "toDayNum",
    },
    {
        label: "本月平均水量",
        prop: "avgNum",
        width:150
    },
    {
        label: "趋势",
        prop: "trend",
        width: 220,
    },
]);
const tableData = ref([]);
const tableLoading = ref(false);
const page = reactive({
    current: 1,
    size: 100,
    total: 0,
});
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
    selectedRows.value = selection;
};
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    page.current = 1;
    getList();
};
const pagination = (obj) => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
};
const getList = () => {
    tableLoading.value = true;
    listPageByWaterTrend({ ...searchForm.value, ...page }).then((res) => {
        tableLoading.value = false;
        tableData.value = res.data.records;
        page.total = res.data.total;
    });
};
onMounted(() => {
    getList();
});
</script>
<style scoped>
</style>
src/views/equipmentManagement/gasTank/simple.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,566 @@
<template>
  <div class="app-container">
    <!-- é¡µé¢æ ‡é¢˜ -->
    <div class="page-header">
      <h2>重型罐式货车监控</h2>
      <div class="header-actions">
<!--        <el-button type="primary" @click="addTank">新增储罐</el-button>-->
<!--        <el-button @click="exportData">导出数据</el-button>-->
      </div>
    </div>
    <!-- å››ä¸ªä¸»è¦æ¨¡å— -->
    <div class="modules-container">
      <!-- 1. åŸºæœ¬ä¿¡æ¯æ¨¡å— -->
      <el-card class="module-card">
        <template #header>
          <div class="card-header">
            <span>1. åŸºæœ¬ä¿¡æ¯</span>
                         <el-button type="text" @click="handleEditBasicInfo">编辑</el-button>
          </div>
        </template>
        <div class="info-grid">
          <div class="info-item">
            <label>储罐编号:</label>
            <span>{{ basicInfo.tankCode }}</span>
          </div>
          <div class="info-item">
            <label>储罐名称:</label>
            <span>{{ basicInfo.tankName }}</span>
          </div>
          <div class="info-item">
            <label>储罐类型:</label>
            <span>{{ basicInfo.tankType }}</span>
          </div>
          <div class="info-item">
            <label>设计压力:</label>
            <span>{{ basicInfo.designPressure }} MPa</span>
          </div>
          <div class="info-item">
            <label>工作压力:</label>
            <span>{{ basicInfo.workingPressure }} MPa</span>
          </div>
          <div class="info-item">
            <label>容积:</label>
            <span>{{ basicInfo.volume }} m³</span>
          </div>
        </div>
      </el-card>
      <!-- 2. ç›‘测参数模块 -->
      <el-card class="module-card">
        <template #header>
          <div class="card-header">
            <span>2. ç›‘测参数</span>
            <el-button type="text" @click="refreshMonitoring">刷新</el-button>
          </div>
        </template>
        <div class="monitoring-grid">
          <div class="monitor-item">
            <div class="monitor-label">压力</div>
            <div class="monitor-value" :class="getStatusClass(monitoringData.pressureStatus)">
              {{ monitoringData.pressure }} MPa
            </div>
            <div class="monitor-status">{{ monitoringData.pressureStatus === 'normal' ? '正常' : '异常' }}</div>
          </div>
          <div class="monitor-item">
            <div class="monitor-label">温度</div>
            <div class="monitor-value" :class="getStatusClass(monitoringData.temperatureStatus)">
              {{ monitoringData.temperature }} â„ƒ
            </div>
            <div class="monitor-status">{{ monitoringData.temperatureStatus === 'normal' ? '正常' : '异常' }}</div>
          </div>
          <div class="monitor-item">
            <div class="monitor-label">气体浓度</div>
            <div class="monitor-value" :class="getStatusClass(monitoringData.gasStatus)">
              {{ monitoringData.gasConcentration }} ppm
            </div>
            <div class="monitor-status">{{ monitoringData.gasStatus === 'normal' ? '正常' : '异常' }}</div>
          </div>
          <div class="monitor-item">
            <div class="monitor-label">流量</div>
            <div class="monitor-value" :class="getStatusClass(monitoringData.flowStatus)">
              {{ monitoringData.flow }} m³/h
            </div>
            <div class="monitor-status">{{ monitoringData.flowStatus === 'normal' ? '正常' : '异常' }}</div>
          </div>
        </div>
      </el-card>
      <!-- 3. å®‰å…¨è£…置模块 -->
      <el-card class="module-card">
        <template #header>
          <div class="card-header">
            <span>3. å®‰å…¨è£…ç½®</span>
            <el-button type="text" @click="checkSafetyDevices">检查</el-button>
          </div>
        </template>
        <div class="safety-grid">
          <div class="safety-item" v-for="device in safetyDevices" :key="device.name">
            <div class="device-info">
              <div class="device-name">{{ device.name }}</div>
              <div class="device-status" :class="device.status">
                {{ device.status === 'normal' ? '正常' : '异常' }}
              </div>
            </div>
          </div>
        </div>
      </el-card>
      <!-- 4. ç»´æŠ¤è®°å½•模块 -->
      <el-card class="module-card">
        <template #header>
          <div class="card-header">
            <span>4. ç»´æŠ¤è®°å½•</span>
            <el-button type="text" @click="addMaintenanceRecord">添加记录</el-button>
          </div>
        </template>
        <div class="maintenance-list">
          <div class="maintenance-item" v-for="record in maintenanceRecords" :key="record.id">
            <div class="record-header">
              <span class="record-date">{{ record.date }}</span>
              <el-tag :type="record.type === 'inspection' ? 'primary' : 'success'" size="small">
                {{ record.type === 'inspection' ? '检验' : '维护' }}
              </el-tag>
            </div>
            <div class="record-content">
              <div class="record-title">{{ record.title }}</div>
              <div class="record-desc">{{ record.description }}</div>
              <div class="record-operator">操作人:{{ record.operator }}</div>
            </div>
          </div>
        </div>
      </el-card>
    </div>
    <!-- ç¼–辑基本信息弹窗 -->
    <el-dialog v-model="basicInfoDialogVisible" title="编辑基本信息" width="600px">
      <el-form :model="editBasicInfo" label-width="120px">
        <el-form-item label="储罐编号">
          <el-input v-model="editBasicInfo.tankCode" />
        </el-form-item>
        <el-form-item label="储罐名称">
          <el-input v-model="editBasicInfo.tankName" />
        </el-form-item>
        <el-form-item label="储罐类型">
          <el-select v-model="editBasicInfo.tankType" style="width: 100%">
            <el-option label="液化气体储罐" value="液化气体储罐" />
            <el-option label="压力容器" value="压力容器" />
            <el-option label="常压储罐" value="常压储罐" />
          </el-select>
        </el-form-item>
        <el-form-item label="设计压力">
          <el-input-number v-model="editBasicInfo.designPressure" :precision="2" style="width: 100%" />
        </el-form-item>
        <el-form-item label="工作压力">
          <el-input-number v-model="editBasicInfo.workingPressure" :precision="2" style="width: 100%" />
        </el-form-item>
        <el-form-item label="容积">
          <el-input-number v-model="editBasicInfo.volume" :precision="2" style="width: 100%" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="basicInfoDialogVisible = false">取消</el-button>
        <el-button type="primary" @click="saveBasicInfo">保存</el-button>
      </template>
    </el-dialog>
    <!-- æ·»åŠ ç»´æŠ¤è®°å½•å¼¹çª— -->
    <el-dialog v-model="maintenanceDialogVisible" title="添加维护记录" width="600px">
      <el-form :model="newMaintenanceRecord" label-width="120px">
        <el-form-item label="记录类型">
          <el-select v-model="newMaintenanceRecord.type" style="width: 100%">
            <el-option label="检验" value="inspection" />
            <el-option label="维护" value="maintenance" />
          </el-select>
        </el-form-item>
        <el-form-item label="标题">
          <el-input v-model="newMaintenanceRecord.title" />
        </el-form-item>
        <el-form-item label="描述">
          <el-input type="textarea" v-model="newMaintenanceRecord.description" :rows="3" />
        </el-form-item>
        <el-form-item label="操作人">
          <el-input v-model="newMaintenanceRecord.operator" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="maintenanceDialogVisible = false">取消</el-button>
        <el-button type="primary" @click="saveMaintenanceRecord">保存</el-button>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
// åŸºæœ¬ä¿¡æ¯
const basicInfo = reactive({
  tankCode: 'GT001',
  tankName: '液化气储罐A',
  tankType: '液化气体储罐',
  designPressure: 1.6,
  workingPressure: 0.8,
  volume: 100.5
})
// ç›‘测参数
const monitoringData = reactive({
  pressure: 0.8,
  pressureStatus: 'normal',
  temperature: 25.5,
  temperatureStatus: 'normal',
  gasConcentration: 0.1,
  gasStatus: 'normal',
  flow: 15.2,
  flowStatus: 'normal'
})
// å®‰å…¨è£…ç½®
const safetyDevices = ref([
  { name: '安全阀', status: 'normal' },
  { name: '压力传感器', status: 'normal' },
  { name: '温度传感器', status: 'normal' },
  { name: '气体检测器', status: 'normal' },
  { name: '爆破片', status: 'normal' },
  { name: '泄压装置', status: 'normal' }
])
// ç»´æŠ¤è®°å½•
const maintenanceRecords = ref([
  {
    id: 1,
    date: '2024-01-15',
    type: 'inspection',
    title: '年度检验',
    description: '按照TSG 21-2016标准进行年度检验,设备状态良好',
    operator: '张工程师'
  },
  {
    id: 2,
    date: '2024-02-20',
    type: 'maintenance',
    title: '安全阀维护',
    description: '更换安全阀密封圈,校准压力设定值',
    operator: '李技师'
  },
  {
    id: 3,
    date: '2024-03-10',
    type: 'inspection',
    title: '压力测试',
    description: '进行压力容器水压试验,符合设计要求',
    operator: '王检验员'
  }
])
// å¼¹çª—控制
const basicInfoDialogVisible = ref(false)
const maintenanceDialogVisible = ref(false)
// ç¼–辑表单数据
const editBasicInfo = reactive({ ...basicInfo })
const newMaintenanceRecord = reactive({
  type: 'inspection',
  title: '',
  description: '',
  operator: ''
})
// èŽ·å–çŠ¶æ€æ ·å¼ç±»
const getStatusClass = (status) => {
  return status === 'normal' ? 'status-normal' : 'status-warning'
}
// æ–°å¢žå‚¨ç½
const addTank = () => {
  ElMessage.success('新增储罐功能')
}
// å¯¼å‡ºæ•°æ®
const exportData = () => {
  ElMessage.success('导出成功')
}
// ç¼–辑基本信息
const handleEditBasicInfo = () => {
  Object.assign(editBasicInfo, basicInfo)
  basicInfoDialogVisible.value = true
}
// ä¿å­˜åŸºæœ¬ä¿¡æ¯
const saveBasicInfo = () => {
  Object.assign(basicInfo, editBasicInfo)
  basicInfoDialogVisible.value = false
  ElMessage.success('保存成功')
}
// åˆ·æ–°ç›‘测数据
const refreshMonitoring = () => {
  // æ¨¡æ‹Ÿæ•°æ®æ›´æ–°
  monitoringData.pressure = (Math.random() * 0.5 + 0.6).toFixed(2)
  monitoringData.temperature = (Math.random() * 10 + 20).toFixed(1)
  monitoringData.gasConcentration = (Math.random() * 0.2).toFixed(2)
  monitoringData.flow = (Math.random() * 10 + 10).toFixed(1)
  ElMessage.success('数据已刷新')
}
// æ£€æŸ¥å®‰å…¨è£…ç½®
const checkSafetyDevices = () => {
  // æ¨¡æ‹Ÿæ£€æŸ¥è¿‡ç¨‹
  safetyDevices.value.forEach(device => {
    device.status = Math.random() > 0.1 ? 'normal' : 'warning'
  })
  ElMessage.success('安全装置检查完成')
}
// æ·»åŠ ç»´æŠ¤è®°å½•
const addMaintenanceRecord = () => {
  newMaintenanceRecord.type = 'inspection'
  newMaintenanceRecord.title = ''
  newMaintenanceRecord.description = ''
  newMaintenanceRecord.operator = ''
  maintenanceDialogVisible.value = true
}
// ä¿å­˜ç»´æŠ¤è®°å½•
const saveMaintenanceRecord = () => {
  const record = {
    id: Date.now(),
    date: new Date().toISOString().split('T')[0],
    ...newMaintenanceRecord
  }
  maintenanceRecords.value.unshift(record)
  maintenanceDialogVisible.value = false
  ElMessage.success('记录添加成功')
}
// æ¨¡æ‹Ÿå®žæ—¶æ•°æ®æ›´æ–°
onMounted(() => {
  setInterval(() => {
    monitoringData.pressure = (Math.random() * 0.5 + 0.6).toFixed(2)
    monitoringData.temperature = (Math.random() * 10 + 20).toFixed(1)
    monitoringData.gasConcentration = (Math.random() * 0.2).toFixed(2)
    monitoringData.flow = (Math.random() * 10 + 10).toFixed(1)
  }, 5000)
})
</script>
<style lang="scss" scoped>
.app-container {
  padding: 20px;
  background: #f5f5f5;
  min-height: 100vh;
}
.page-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
  padding: 20px;
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  h2 {
    margin: 0;
    color: #303133;
  }
  .header-actions {
    display: flex;
    gap: 10px;
  }
}
.modules-container {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 20px;
}
.module-card {
  .card-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-weight: bold;
    color: #303133;
  }
}
.info-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 15px;
  .info-item {
    display: flex;
    justify-content: space-between;
    padding: 10px;
    background: #f8f9fa;
    border-radius: 4px;
    label {
      font-weight: bold;
      color: #606266;
    }
    span {
      color: #303133;
    }
  }
}
.monitoring-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 15px;
  .monitor-item {
    text-align: center;
    padding: 15px;
    background: #f8f9fa;
    border-radius: 8px;
    border: 2px solid transparent;
    .monitor-label {
      font-size: 14px;
      color: #606266;
      margin-bottom: 8px;
    }
    .monitor-value {
      font-size: 20px;
      font-weight: bold;
      margin-bottom: 5px;
      &.status-normal {
        color: #67c23a;
      }
      &.status-warning {
        color: #e6a23c;
      }
    }
    .monitor-status {
      font-size: 12px;
      color: #909399;
    }
  }
}
.safety-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 15px;
  .safety-item {
    display: flex;
    align-items: center;
    padding: 15px;
    background: #f8f9fa;
    border-radius: 8px;
    border: 2px solid transparent;
    .device-icon {
      margin-right: 15px;
    }
    .device-info {
      flex: 1;
      .device-name {
        font-weight: bold;
        color: #303133;
        margin-bottom: 5px;
      }
      .device-status {
        font-size: 12px;
        padding: 2px 8px;
        border-radius: 10px;
        display: inline-block;
        &.normal {
          background: #f0f9ff;
          color: #409eff;
        }
        &.warning {
          background: #fef7e0;
          color: #e6a23c;
        }
      }
    }
  }
}
.maintenance-list {
  max-height: 300px;
  overflow-y: auto;
  .maintenance-item {
    padding: 15px;
    border-bottom: 1px solid #ebeef5;
    margin-bottom: 10px;
    &:last-child {
      border-bottom: none;
      margin-bottom: 0;
    }
    .record-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 8px;
      .record-date {
        font-size: 14px;
        color: #909399;
      }
    }
    .record-content {
      .record-title {
        font-weight: bold;
        color: #303133;
        margin-bottom: 5px;
      }
      .record-desc {
        font-size: 14px;
        color: #606266;
        margin-bottom: 5px;
        line-height: 1.4;
      }
      .record-operator {
        font-size: 12px;
        color: #909399;
      }
    }
  }
}
// å“åº”式设计
@media (max-width: 1200px) {
  .modules-container {
    grid-template-columns: 1fr;
  }
}
@media (max-width: 768px) {
  .info-grid,
  .monitoring-grid,
  .safety-grid {
    grid-template-columns: 1fr;
  }
}
</style>
src/views/inventoryManagement/stockWarning/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1137 @@
<template>
  <div class="app-container">
    <!-- æœç´¢è¡¨å• -->
    <div class="search_form">
      <el-form :model="searchForm" :inline="true">
        <el-form-item label="储气罐名称:">
          <el-input v-model="searchForm.tankName" placeholder="请输入储气罐名称" clearable style="width: 200px" />
        </el-form-item>
        <el-form-item label="储气罐类型:">
          <el-select v-model="searchForm.tankType" placeholder="请选择储气罐类型" clearable style="width: 200px">
            <el-option label="液化气储罐" value="液化气储罐" />
            <el-option label="压缩气储罐" value="压缩气储罐" />
            <el-option label="天然气储罐" value="天然气储罐" />
            <el-option label="氧气储罐" value="氧气储罐" />
          </el-select>
        </el-form-item>
        <el-form-item label="预警类型:">
          <el-select v-model="searchForm.warningType" placeholder="请选择预警类型" clearable style="width: 200px">
            <el-option label="气体不足" value="气体不足" />
            <el-option label="压力异常" value="压力异常" />
            <el-option label="温度异常" value="温度异常" />
            <el-option label="泄漏预警" value="泄漏预警" />
          </el-select>
        </el-form-item>
        <el-form-item label="预警级别:">
          <el-select v-model="searchForm.warningLevel" placeholder="请选择预警级别" clearable style="width: 200px">
            <el-option label="紧急" value="紧急" />
            <el-option label="重要" value="重要" />
            <el-option label="一般" value="一般" />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleQuery">搜索</el-button>
          <el-button @click="resetQuery">重置</el-button>
        </el-form-item>
      </el-form>
    </div>
    <!-- æ•°æ®è¡¨æ ¼ -->
    <div class="table_list">
      <!-- æ“ä½œæŒ‰é’® -->
      <div class="table-operations">
        <el-button type="primary" @click="handleAdd">新增预警规则</el-button>
        <el-button type="success" @click="handleBatchProcess">批量处理</el-button>
        <el-button @click="handleExport">导出</el-button>
      </div>
      <el-table
        :data="tableData"
        border
        v-loading="tableLoading"
        @selection-change="handleSelectionChange"
        style="width: 100%"
        height="calc(100vh - 280px)"
      >
        <el-table-column align="center" type="selection" width="55" />
        <el-table-column align="center" label="序号" type="index" width="60" />
        <!-- åŸºç¡€ä¿¡æ¯å­—段 -->
        <el-table-column label="储气罐编码" prop="tankCode" width="120" show-overflow-tooltip />
        <el-table-column label="储气罐名称" prop="tankName" width="200" show-overflow-tooltip />
        <el-table-column label="储气罐类型" prop="tankType" width="120" show-overflow-tooltip />
        <el-table-column label="规格型号" prop="specificationModel" width="150" show-overflow-tooltip />
        <el-table-column label="容积(m³)" prop="volume" width="100" show-overflow-tooltip />
        <!-- åº“存相关字段 -->
        <el-table-column label="当前气体量" prop="currentGasLevel" width="120" show-overflow-tooltip>
          <template #default="scope">
            <span :class="getGasLevelClass(scope.row)">{{ scope.row.currentGasLevel }}%</span>
          </template>
        </el-table-column>
        <el-table-column label="安全气体量" prop="safetyGasLevel" width="120" show-overflow-tooltip />
        <el-table-column label="最低气体量" prop="minGasLevel" width="120" show-overflow-tooltip />
        <el-table-column label="最高气体量" prop="maxGasLevel" width="120" show-overflow-tooltip />
        <el-table-column label="当前压力(MPa)" prop="currentPressure" width="140" show-overflow-tooltip />
        <!-- é¢„警规则字段 -->
        <el-table-column label="预警类型" prop="warningType" width="100" show-overflow-tooltip>
          <template #default="scope">
            <el-tag :type="getWarningTypeTag(scope.row.warningType)">
              {{ scope.row.warningType }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="预警级别" prop="warningLevel" width="100" show-overflow-tooltip>
          <template #default="scope">
            <el-tag :type="getWarningLevelTag(scope.row.warningLevel)">
              {{ scope.row.warningLevel }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="预警阈值" prop="warningThreshold" width="100" show-overflow-tooltip />
        <el-table-column label="是否启用" prop="isEnabled" width="100" show-overflow-tooltip>
          <template #default="scope">
            <el-switch v-model="scope.row.isEnabled" @change="handleEnableChange(scope.row)" />
          </template>
        </el-table-column>
        <!-- æ—¶é—´ç›¸å…³å­—段 -->
        <el-table-column label="预警时间" prop="warningTime" width="150" show-overflow-tooltip />
        <el-table-column label="预警持续天数" prop="warningDuration" width="120" show-overflow-tooltip />
        <el-table-column label="最后更新时间" prop="lastUpdateTime" width="150" show-overflow-tooltip />
        <el-table-column label="预计充装时间" prop="expectedRefillTime" width="150" show-overflow-tooltip />
        <el-table-column label="预计缺气时间" prop="expectedShortageTime" width="150" show-overflow-tooltip>
          <template #default="scope">
            <div v-if="scope.row.expectedShortageTime">
              <div v-if="getCountdown(scope.row.expectedShortageTime).isExpired" class="countdown-expired">
                <el-tag type="danger">已缺气</el-tag>
              </div>
              <div v-else class="countdown-timer">
                <span :class="getCountdownClass(scope.row.expectedShortageTime)">
                  {{ getCountdown(scope.row.expectedShortageTime).text }}
                </span>
              </div>
            </div>
            <span v-else>-</span>
          </template>
        </el-table-column>
        <!-- æ“ä½œåˆ— -->
        <el-table-column fixed="right" label="操作" width="200" align="center">
          <template #default="scope">
            <el-button link type="primary" size="small" @click="handleEdit(scope.row)">编辑</el-button>
            <el-button link type="success" size="small" @click="handleProcess(scope.row)">处理</el-button>
            <el-button link type="danger" size="small" @click="handleDelete(scope.row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- åˆ†é¡µ -->
      <pagination
        v-show="total > 0"
        :total="total"
        layout="total, sizes, prev, pager, next, jumper"
        :page="page.current"
        :limit="page.size"
        @pagination="paginationChange"
      />
    </div>
    <!-- æ–°å¢ž/编辑预警规则弹窗 -->
    <el-dialog
      v-model="dialogFormVisible"
      :title="operationType === 'add' ? '新增预警规则' : '编辑预警规则'"
      width="50%"
      @close="closeDialog"
    >
      <el-form :model="form" :rules="rules" ref="formRef" label-width="140px">
        <el-row :gutter="20">
          <!-- åŸºç¡€ä¿¡æ¯ -->
          <el-col :span="12">
            <el-form-item label="储气罐编码:" prop="tankCode">
              <el-input v-model="form.tankCode" placeholder="请输入储气罐编码" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="储气罐名称:" prop="tankName">
              <el-input v-model="form.tankName" placeholder="请输入储气罐名称" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="储气罐类型:" prop="tankType">
              <el-select v-model="form.tankType" placeholder="请选择储气罐类型" style="width: 100%">
                <el-option label="液化气储罐" value="液化气储罐" />
                <el-option label="压缩气储罐" value="压缩气储罐" />
                <el-option label="天然气储罐" value="天然气储罐" />
                <el-option label="氧气储罐" value="氧气储罐" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="规格型号:" prop="specificationModel">
              <el-input v-model="form.specificationModel" placeholder="请输入规格型号" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="容积(m³):" prop="volume">
              <el-input-number v-model="form.volume" :min="0" :precision="2" style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="当前气体量(%):" prop="currentGasLevel">
              <el-input-number v-model="form.currentGasLevel" :min="0" :max="100" :precision="1" style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <!-- åº“存相关 -->
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="安全气体量(%):" prop="safetyGasLevel">
              <el-input-number v-model="form.safetyGasLevel" :min="0" :max="100" :precision="1" style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="最低气体量(%):" prop="minGasLevel">
              <el-input-number v-model="form.minGasLevel" :min="0" :max="100" :precision="1" style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="最高气体量(%):" prop="maxGasLevel">
              <el-input-number v-model="form.maxGasLevel" :min="0" :max="100" :precision="1" style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="当前压力(MPa):" prop="currentPressure">
              <el-input-number v-model="form.currentPressure" :min="0" :precision="2" style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <!-- é¢„警规则 -->
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="预警类型:" prop="warningType">
              <el-select v-model="form.warningType" placeholder="请选择预警类型" style="width: 100%">
                <el-option label="气体不足" value="气体不足" />
                <el-option label="压力异常" value="压力异常" />
                <el-option label="温度异常" value="温度异常" />
                <el-option label="泄漏预警" value="泄漏预警" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="预警级别:" prop="warningLevel">
              <el-select v-model="form.warningLevel" placeholder="请选择预警级别" style="width: 100%">
                <el-option label="紧急" value="紧急" />
                <el-option label="重要" value="重要" />
                <el-option label="一般" value="一般" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="预警阈值:" prop="warningThreshold">
              <el-input-number v-model="form.warningThreshold" :min="0" :precision="2" style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="是否启用:" prop="isEnabled">
              <el-switch v-model="form.isEnabled" />
            </el-form-item>
          </el-col>
        </el-row>
        <!-- æ—¶é—´ç›¸å…³ -->
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="预警时间:" prop="warningTime">
              <el-date-picker
                v-model="form.warningTime"
                type="datetime"
                placeholder="请选择预警时间"
                style="width: 100%"
                value-format="YYYY-MM-DD HH:mm:ss"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="预计充装时间:" prop="expectedRefillTime">
              <el-date-picker
                v-model="form.expectedRefillTime"
                type="datetime"
                placeholder="请选择预计充装时间"
                style="width: 100%"
                value-format="YYYY-MM-DD HH:mm:ss"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="预计缺气时间:" prop="expectedShortageTime">
              <el-date-picker
                v-model="form.expectedShortageTime"
                type="datetime"
                placeholder="请选择预计缺气时间"
                style="width: 100%"
                value-format="YYYY-MM-DD HH:mm:ss"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="预警规则描述:" prop="warningRule">
              <el-input
                v-model="form.warningRule"
                type="textarea"
                :rows="3"
                placeholder="请输入预警规则描述"
              />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="closeDialog">取消</el-button>
          <el-button type="primary" @click="submitForm">确认</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- ç¼ºæ°”预警弹框 -->
    <el-dialog
      v-model="shortageWarningVisible"
      title="⚠️ ç¼ºæ°”预警"
      width="400px"
      :close-on-click-modal="false"
      :close-on-press-escape="false"
      :show-close="false"
    >
      <div class="shortage-warning-content">
        <div class="warning-icon">
          <el-icon size="48" color="#f56c6c"><WarningFilled /></el-icon>
        </div>
        <div class="warning-message">
          <h3>{{ currentWarningTank.tankName }}</h3>
          <p>储气罐已缺气,请及时处理!</p>
          <p class="warning-details">
            å‚¨æ°”罐编码:{{ currentWarningTank.tankCode }}<br>
            å‚¨æ°”罐类型:{{ currentWarningTank.tankType }}<br>
            å½“前气体量:{{ currentWarningTank.currentGasLevel }}%
          </p>
        </div>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="handleShortageWarning">立即处理</el-button>
          <el-button @click="closeShortageWarning">稍后处理</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- ç¼ºæ°”预警弹框 -->
    <el-dialog
      v-model="shortageWarningVisible"
      title="⚠️ ç¼ºæ°”预警"
      width="400px"
      :close-on-click-modal="false"
      :close-on-press-escape="false"
      :show-close="false"
    >
      <div class="shortage-warning-content">
        <div class="warning-icon">
          <el-icon size="48" color="#f56c6c"><WarningFilled /></el-icon>
        </div>
        <div class="warning-message">
          <h3>{{ currentWarningTank.tankName }}</h3>
          <p>储气罐已缺气,请及时处理!</p>
          <p class="warning-details">
            å‚¨æ°”罐编码:{{ currentWarningTank.tankCode }}<br>
            å‚¨æ°”罐类型:{{ currentWarningTank.tankType }}<br>
            å½“前气体量:{{ currentWarningTank.currentGasLevel }}%
          </p>
        </div>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="handleShortageWarning">立即处理</el-button>
          <el-button @click="closeShortageWarning">稍后处理</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, onUnmounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { WarningFilled } from '@element-plus/icons-vue'
import pagination from '@/components/PIMTable/Pagination.vue'
// æ³¨é‡ŠæŽ‰API导入,使用假数据
// import {
//   getStockWarningPage,
//   addStockWarning,
//   updateStockWarning,
//   deleteStockWarning,
//   batchProcessStockWarning,
//   exportStockWarning,
//   toggleStockWarningStatus
// } from '@/api/inventoryManagement/stockWarning.js'
const { proxy } = getCurrentInstance()
// å“åº”式数据
const tableData = ref([])
const tableLoading = ref(false)
const selectedRows = ref([])
const dialogFormVisible = ref(false)
const operationType = ref('add')
const total = ref(0)
// ç¼ºæ°”预警相关
const shortageWarningVisible = ref(false)
const currentWarningTank = ref({})
const countdownTimer = ref(null)
// åˆ†é¡µå‚æ•°
const page = reactive({
  current: 1,
  size: 10
})
// æœç´¢è¡¨å•
const searchForm = reactive({
  tankName: '',
  tankType: '',
  warningType: '',
  warningLevel: ''
})
// è¡¨å•数据
const form = reactive({
  id: null,
  tankCode: '',
  tankName: '',
  tankType: '',
  specificationModel: '',
  volume: 0,
  currentGasLevel: 0,
  safetyGasLevel: 0,
  minGasLevel: 0,
  maxGasLevel: 0,
  currentPressure: 0,
  warningType: '',
  warningLevel: '',
  warningThreshold: 0,
  isEnabled: true,
  warningTime: '',
  warningDuration: 0,
  lastUpdateTime: '',
  expectedRefillTime: '',
  expectedShortageTime: '',
  warningRule: ''
})
// è¡¨å•验证规则
const rules = {
  tankCode: [{ required: true, message: '请输入储气罐编码', trigger: 'blur' }],
  tankName: [{ required: true, message: '请输入储气罐名称', trigger: 'blur' }],
  tankType: [{ required: true, message: '请选择储气罐类型', trigger: 'change' }],
  warningType: [{ required: true, message: '请选择预警类型', trigger: 'change' }],
  warningLevel: [{ required: true, message: '请选择预警级别', trigger: 'change' }],
  warningThreshold: [{ required: true, message: '请输入预警阈值', trigger: 'blur' }]
}
// èŽ·å–å€’è®¡æ—¶ä¿¡æ¯
const getCountdown = (expectedTime) => {
  if (!expectedTime) return { text: '-', isExpired: false }
  const now = new Date().getTime()
  const expected = new Date(expectedTime).getTime()
  const diff = expected - now
  if (diff <= 0) {
    return { text: '已缺气', isExpired: true }
  }
  const days = Math.floor(diff / (1000 * 60 * 60 * 24))
  const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
  const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60))
  if (days > 0) {
    return { text: `${days}天${hours}小时`, isExpired: false }
  } else if (hours > 0) {
    return { text: `${hours}小时${minutes}分钟`, isExpired: false }
  } else {
    return { text: `${minutes}分钟`, isExpired: false }
  }
}
// èŽ·å–å€’è®¡æ—¶æ ·å¼ç±»
const getCountdownClass = (expectedTime) => {
  if (!expectedTime) return ''
  const now = new Date().getTime()
  const expected = new Date(expectedTime).getTime()
  const diff = expected - now
  if (diff <= 0) {
    return 'countdown-expired'
  } else if (diff <= 24 * 60 * 60 * 1000) { // 24小时内
    return 'countdown-urgent'
  } else if (diff <= 7 * 24 * 60 * 60 * 1000) { // 7天内
    return 'countdown-warning'
  } else {
    return 'countdown-normal'
  }
}
// æ£€æŸ¥ç¼ºæ°”预警
const checkShortageWarnings = () => {
  tableData.value.forEach(tank => {
    if (tank.expectedShortageTime) {
      const countdown = getCountdown(tank.expectedShortageTime)
      if (countdown.isExpired && !tank.warningShown) {
        // æ ‡è®°å·²æ˜¾ç¤ºé¢„警,避免重复弹框
        tank.warningShown = true
        showShortageWarning(tank)
      }
    }
  })
}
// æ˜¾ç¤ºç¼ºæ°”预警弹框
const showShortageWarning = (tank) => {
  currentWarningTank.value = tank
  shortageWarningVisible.value = true
  // æ’­æ”¾æç¤ºéŸ³ï¼ˆå¯é€‰ï¼‰
  // const audio = new Audio('/path/to/warning-sound.mp3')
  // audio.play()
}
// å¤„理缺气预警
const handleShortageWarning = () => {
  ElMessage.success(`正在处理储气罐 ${currentWarningTank.value.tankName} çš„缺气问题`)
  shortageWarningVisible.value = false
  // è¿™é‡Œå¯ä»¥è°ƒç”¨å¤„理API
}
// å¤„理缺气预警
const closeShortageWarning = () => {
  // ElMessage.success(`正在处理储气罐 ${currentWarningTank.value.tankName} çš„缺气问题`)
  shortageWarningVisible.value = false
  // è¿™é‡Œå¯ä»¥è°ƒç”¨å¤„理API
}
// ç”Ÿæˆå‡æ•°æ®
const generateMockData = () => {
  const mockData = [
    {
      id: 1,
      tankCode: 'TANK001',
      tankName: '液化气储罐A',
      tankType: '液化气储罐',
      specificationModel: 'LPG-5000L',
      volume: 5000,
      currentGasLevel: 15,
      safetyGasLevel: 30,
      minGasLevel: 10,
      maxGasLevel: 95,
      currentPressure: 2.5,
      warningType: '气体不足',
      warningLevel: '紧急',
      warningThreshold: 20,
      isEnabled: true,
      warningTime: '2024-01-15 08:30:00',
      warningDuration: 3,
      lastUpdateTime: '2024-01-15 10:00:00',
      expectedRefillTime: '2024-01-16 14:00:00',
      expectedShortageTime: '2024-01-15 18:30:00', // ä»Šå¤©ä¸‹åˆ6:30缺气
      warningRule: '当气体量低于20%时触发预警'
    },
    {
      id: 2,
      tankCode: 'TANK002',
      tankName: '压缩气储罐B',
      tankType: '压缩气储罐',
      specificationModel: 'COMP-3000L',
      volume: 3000,
      currentGasLevel: 45,
      safetyGasLevel: 25,
      minGasLevel: 15,
      maxGasLevel: 90,
      currentPressure: 8.2,
      warningType: '压力异常',
      warningLevel: '重要',
      warningThreshold: 10,
      isEnabled: true,
      warningTime: '2024-01-14 16:20:00',
      warningDuration: 2,
      lastUpdateTime: '2024-01-15 09:15:00',
      expectedRefillTime: '2024-01-17 09:00:00',
      expectedShortageTime: '2024-01-18 12:00:00', // 3天后缺气
      warningRule: '当压力超过8MPa时触发预警'
    },
    {
      id: 3,
      tankCode: 'TANK003',
      tankName: '天然气储罐C',
      tankType: '天然气储罐',
      specificationModel: 'NG-8000L',
      volume: 8000,
      currentGasLevel: 75,
      safetyGasLevel: 20,
      minGasLevel: 10,
      maxGasLevel: 95,
      currentPressure: 4.8,
      warningType: '温度异常',
      warningLevel: '一般',
      warningThreshold: 5,
      isEnabled: true,
      warningTime: '2024-01-13 11:45:00',
      warningDuration: 1,
      lastUpdateTime: '2024-01-15 08:45:00',
      expectedRefillTime: '2024-01-20 10:00:00',
      expectedShortageTime: '2024-01-22 15:30:00', // 7天后缺气
      warningRule: '当温度超过60°C时触发预警'
    },
    {
      id: 4,
      tankCode: 'TANK004',
      tankName: '氧气储罐D',
      tankType: '氧气储罐',
      specificationModel: 'O2-2000L',
      volume: 2000,
      currentGasLevel: 8,
      safetyGasLevel: 25,
      minGasLevel: 5,
      maxGasLevel: 90,
      currentPressure: 6.5,
      warningType: '泄漏预警',
      warningLevel: '紧急',
      warningThreshold: 15,
      isEnabled: true,
      warningTime: '2024-01-15 07:15:00',
      warningDuration: 4,
      lastUpdateTime: '2024-01-15 11:30:00',
      expectedRefillTime: '2024-01-15 16:00:00',
      expectedShortageTime: '2024-01-15 14:00:00', // ä»Šå¤©ä¸‹åˆ2点缺气
      warningRule: '当检测到气体泄漏时触发预警'
    },
    {
      id: 5,
      tankCode: 'TANK005',
      tankName: '液化气储罐E',
      tankType: '液化气储罐',
      specificationModel: 'LPG-6000L',
      volume: 6000,
      currentGasLevel: 35,
      safetyGasLevel: 30,
      minGasLevel: 15,
      maxGasLevel: 95,
      currentPressure: 3.2,
      warningType: '气体不足',
      warningLevel: '重要',
      warningThreshold: 20,
      isEnabled: false,
      warningTime: '2024-01-14 14:30:00',
      warningDuration: 2,
      lastUpdateTime: '2024-01-15 09:00:00',
      expectedRefillTime: '2024-01-19 08:00:00',
      expectedShortageTime: '2024-01-21 10:00:00', // 6天后缺气
      warningRule: '当气体量低于20%时触发预警'
    },
    {
      id: 6,
      tankCode: 'TANK006',
      tankName: '压缩气储罐F',
      tankType: '压缩气储罐',
      specificationModel: 'COMP-4000L',
      volume: 4000,
      currentGasLevel: 85,
      safetyGasLevel: 20,
      minGasLevel: 10,
      maxGasLevel: 90,
      currentPressure: 7.8,
      warningType: '压力异常',
      warningLevel: '一般',
      warningThreshold: 8,
      isEnabled: true,
      warningTime: '2024-01-12 09:20:00',
      warningDuration: 1,
      lastUpdateTime: '2024-01-15 08:30:00',
      expectedRefillTime: '2024-01-25 14:00:00',
      expectedShortageTime: '2024-01-28 16:00:00', // 13天后缺气
      warningRule: '当压力超过8MPa时触发预警'
    },
    {
      id: 7,
      tankCode: 'TANK007',
      tankName: '天然气储罐G',
      tankType: '天然气储罐',
      specificationModel: 'NG-10000L',
      volume: 10000,
      currentGasLevel: 92,
      safetyGasLevel: 15,
      minGasLevel: 8,
      maxGasLevel: 95,
      currentPressure: 5.2,
      warningType: '温度异常',
      warningLevel: '重要',
      warningThreshold: 6,
      isEnabled: true,
      warningTime: '2024-01-11 16:45:00',
      warningDuration: 1,
      lastUpdateTime: '2024-01-15 07:45:00',
      expectedRefillTime: '2024-01-30 09:00:00',
      expectedShortageTime: '2024-02-05 12:00:00', // 21天后缺气
      warningRule: '当温度超过60°C时触发预警'
    },
    {
      id: 8,
      tankCode: 'TANK008',
      tankName: '氧气储罐H',
      tankType: '氧气储罐',
      specificationModel: 'O2-1500L',
      volume: 1500,
      currentGasLevel: 12,
      safetyGasLevel: 30,
      minGasLevel: 8,
      maxGasLevel: 90,
      currentPressure: 4.5,
      warningType: '泄漏预警',
      warningLevel: '紧急',
      warningThreshold: 12,
      isEnabled: true,
      warningTime: '2024-01-15 06:30:00',
      warningDuration: 5,
      lastUpdateTime: '2024-01-15 12:15:00',
      expectedRefillTime: '2024-01-15 20:00:00',
      expectedShortageTime: '2024-01-15 17:30:00', // ä»Šå¤©ä¸‹åˆ5:30缺气
      warningRule: '当检测到气体泄漏时触发预警'
    }
  ]
  // æ ¹æ®æœç´¢æ¡ä»¶è¿‡æ»¤æ•°æ®
  let filteredData = mockData.filter(item => {
    if (searchForm.tankName && !item.tankName.includes(searchForm.tankName)) return false
    if (searchForm.tankType && item.tankType !== searchForm.tankType) return false
    if (searchForm.warningType && item.warningType !== searchForm.warningType) return false
    if (searchForm.warningLevel && item.warningLevel !== searchForm.warningLevel) return false
    return true
  })
  // åˆ†é¡µå¤„理
  const start = (page.current - 1) * page.size
  const end = start + page.size
  const paginatedData = filteredData.slice(start, end)
  return {
    records: paginatedData,
    total: filteredData.length
  }
}
// èŽ·å–åˆ—è¡¨æ•°æ®
const getList = async () => {
  tableLoading.value = true
  try {
    // æ¨¡æ‹Ÿç½‘络延迟
    await new Promise(resolve => setTimeout(resolve, 500))
    const result = generateMockData()
    tableData.value = result.records
    total.value = result.total
    // æ£€æŸ¥ç¼ºæ°”预警
    checkShortageWarnings()
  } catch (error) {
    console.error('获取列表失败:', error)
    ElMessage.error('获取列表失败')
  } finally {
    tableLoading.value = false
  }
}
// æœç´¢
const handleQuery = () => {
  page.current = 1
  getList()
}
// é‡ç½®æœç´¢
const resetQuery = () => {
  Object.keys(searchForm).forEach(key => {
    searchForm[key] = ''
  })
  handleQuery()
}
// åˆ†é¡µå˜åŒ–
const paginationChange = (obj) => {
  page.current = obj.page
  page.size = obj.limit
  getList()
}
// è¡¨æ ¼é€‰æ‹©å˜åŒ–
const handleSelectionChange = (selection) => {
  selectedRows.value = selection
}
// æ–°å¢ž
const handleAdd = () => {
  operationType.value = 'add'
  resetForm()
  dialogFormVisible.value = true
}
// ç¼–辑
const handleEdit = (row) => {
  operationType.value = 'edit'
  Object.assign(form, row)
  dialogFormVisible.value = true
}
// å¤„理预警
const handleProcess = async (row) => {
  try {
    // æ¨¡æ‹ŸAPI调用延迟
    await new Promise(resolve => setTimeout(resolve, 300))
    ElMessage.success(`正在处理预警:${row.tankName}`)
    getList()
  } catch (error) {
    ElMessage.error('处理预警失败')
  }
}
// åˆ é™¤
const handleDelete = async (row) => {
  try {
    await ElMessageBox.confirm(`确定要删除预警规则:${row.tankName}吗?`, '提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    })
    // æ¨¡æ‹ŸAPI调用延迟
    await new Promise(resolve => setTimeout(resolve, 300))
    ElMessage.success('删除成功')
    getList()
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error('删除失败')
    }
  }
}
// æ‰¹é‡å¤„理
const handleBatchProcess = async () => {
  if (selectedRows.value.length === 0) {
    ElMessage.warning('请选择要处理的预警')
    return
  }
  try {
    // æ¨¡æ‹ŸAPI调用延迟
    await new Promise(resolve => setTimeout(resolve, 500))
    ElMessage.success(`批量处理了 ${selectedRows.value.length} æ¡é¢„è­¦`)
    getList()
  } catch (error) {
    ElMessage.error('批量处理失败')
  }
}
// å¯¼å‡º
const handleExport = async () => {
  try {
    // æ¨¡æ‹ŸAPI调用延迟
    await new Promise(resolve => setTimeout(resolve, 800))
    // ç”Ÿæˆå¯¼å‡ºæ•°æ®
    const exportData = generateMockData().records
    const csvContent = generateCSV(exportData)
    // åˆ›å»ºä¸‹è½½é“¾æŽ¥
    const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' })
    const url = window.URL.createObjectURL(blob)
    const link = document.createElement('a')
    link.href = url
    link.download = `储气罐预警数据_${new Date().getTime()}.csv`
    link.click()
    window.URL.revokeObjectURL(url)
    ElMessage.success('导出成功')
  } catch (error) {
    ElMessage.error('导出失败')
  }
}
// ç”ŸæˆCSV内容
const generateCSV = (data) => {
  const headers = [
    '储气罐编码', '储气罐名称', '储气罐类型', '规格型号', '容积(m³)',
    '当前气体量(%)', '安全气体量(%)', '最低气体量(%)', '最高气体量(%)',
    '当前压力(MPa)', '预警类型', '预警级别', '预警阈值', '是否启用',
    '预警时间', '预警持续天数', '最后更新时间', '预计充装时间', '预计缺气时间', '预警规则描述'
  ]
  const csvRows = [headers.join(',')]
  data.forEach(item => {
    const row = [
      item.tankCode,
      item.tankName,
      item.tankType,
      item.specificationModel,
      item.volume,
      item.currentGasLevel,
      item.safetyGasLevel,
      item.minGasLevel,
      item.maxGasLevel,
      item.currentPressure,
      item.warningType,
      item.warningLevel,
      item.warningThreshold,
      item.isEnabled ? '是' : '否',
      item.warningTime,
      item.warningDuration,
      item.lastUpdateTime,
      item.expectedRefillTime,
      item.expectedShortageTime,
      item.warningRule
    ]
    csvRows.push(row.join(','))
  })
  return csvRows.join('\n')
}
// å¯ç”¨çŠ¶æ€å˜åŒ–
const handleEnableChange = async (row) => {
  try {
    // æ¨¡æ‹ŸAPI调用延迟
    await new Promise(resolve => setTimeout(resolve, 200))
    ElMessage.success(`${row.tankName} çš„启用状态已更新`)
  } catch (error) {
    ElMessage.error('状态更新失败')
    // æ¢å¤åŽŸçŠ¶æ€
    row.isEnabled = !row.isEnabled
  }
}
// æäº¤è¡¨å•
const submitForm = async () => {
  try {
    await proxy.$refs.formRef.validate()
    // æ¨¡æ‹ŸAPI调用延迟
    await new Promise(resolve => setTimeout(resolve, 500))
    if (operationType.value === 'add') {
      ElMessage.success('新增成功')
    } else {
      ElMessage.success('编辑成功')
    }
    closeDialog()
    getList()
  } catch (error) {
    if (!error.errors) {
      ElMessage.error(operationType.value === 'add' ? '新增失败' : '编辑失败')
    }
  }
}
// å…³é—­å¼¹çª—
const closeDialog = () => {
  dialogFormVisible.value = false
  resetForm()
}
// é‡ç½®è¡¨å•
const resetForm = () => {
  Object.keys(form).forEach(key => {
    if (key === 'isEnabled') {
      form[key] = true
    } else if (typeof form[key] === 'number') {
      form[key] = 0
    } else {
      form[key] = ''
    }
  })
  proxy.$refs.formRef?.resetFields()
}
// èŽ·å–æ°”ä½“é‡æ ·å¼ç±»
const getGasLevelClass = (row) => {
  if (row.currentGasLevel < row.minGasLevel) {
    return 'text-danger'
  } else if (row.currentGasLevel > row.maxGasLevel) {
    return 'text-warning'
  }
  return 'text-success'
}
// èŽ·å–é¢„è­¦ç±»åž‹æ ‡ç­¾æ ·å¼
const getWarningTypeTag = (type) => {
  const typeMap = {
    '气体不足': 'danger',
    '压力异常': 'warning',
    '温度异常': 'info',
    '泄漏预警': 'danger'
  }
  return typeMap[type] || 'info'
}
// èŽ·å–é¢„è­¦çº§åˆ«æ ‡ç­¾æ ·å¼
const getWarningLevelTag = (level) => {
  const levelMap = {
    '紧急': 'danger',
    '重要': 'warning',
    '一般': 'info'
  }
  return levelMap[level] || 'info'
}
// å¯åŠ¨å€’è®¡æ—¶å®šæ—¶å™¨
const startCountdownTimer = () => {
  countdownTimer.value = setInterval(() => {
    checkShortageWarnings()
  }, 60000) // æ¯åˆ†é’Ÿæ£€æŸ¥ä¸€æ¬¡
}
// åœæ­¢å€’计时定时器
const stopCountdownTimer = () => {
  if (countdownTimer.value) {
    clearInterval(countdownTimer.value)
    countdownTimer.value = null
  }
}
// é¡µé¢åŠ è½½
onMounted(() => {
  getList()
  startCountdownTimer()
})
// é¡µé¢å¸è½½
onUnmounted(() => {
  stopCountdownTimer()
})
</script>
<style scoped lang="scss">
.app-container {
  padding: 20px;
  .table-operations {
    text-align: right;
    margin-bottom: 20px;
    .el-button {
      margin-right: 10px;
    }
  }
  .table_list {
    background: #fff;
    border-radius: 4px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  }
  .text-danger {
    color: #f56c6c;
    font-weight: bold;
  }
  .text-warning {
    color: #e6a23c;
    font-weight: bold;
  }
  .text-success {
    color: #67c23a;
    font-weight: bold;
  }
  .dialog-footer {
    text-align: right;
  }
  // å€’计时样式
  .countdown-timer {
    font-weight: bold;
  }
  .countdown-normal {
    color: #67c23a;
  }
  .countdown-warning {
    color: #e6a23c;
  }
  .countdown-urgent {
    color: #f56c6c;
    animation: blink 1s infinite;
  }
  .countdown-expired {
    color: #f56c6c;
    font-weight: bold;
  }
  @keyframes blink {
    0%, 50% { opacity: 1; }
    51%, 100% { opacity: 0.5; }
  }
  // ç¼ºæ°”预警弹框样式
  .shortage-warning-content {
    text-align: center;
    padding: 20px 0;
    .warning-icon {
      margin-bottom: 20px;
    }
    .warning-message {
      h3 {
        color: #f56c6c;
        margin-bottom: 10px;
      }
      p {
        margin-bottom: 10px;
        color: #606266;
      }
      .warning-details {
        background: #f5f7fa;
        padding: 15px;
        border-radius: 4px;
        text-align: left;
        font-size: 14px;
        line-height: 1.6;
      }
    }
  }
}
</style>