Explorar el Código

[26110601] 优化设备管理页面: 数据源由实时功率表切至设备配置/config/device(listDevice, 服务端分页), 查询区场站改单选+设备名称关键字并新增重置按钮, 表格上方新增带icon的新增/删除/导出按钮及对应事件(弹窗新增/勾选批量删除/设备配置.xlsx导出), 列改为ID/场站名称/设备名称/通道/备注, 去掉原无数据列与无效新增按钮

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
taosj hace 5 horas
padre
commit
4f0f5f9471
Se han modificado 1 ficheros con 136 adiciones y 109 borrados
  1. 136 109
      forecast-backend-vue/src/views/ufo/electricity/device/TurbineInfo.vue

+ 136 - 109
forecast-backend-vue/src/views/ufo/electricity/device/TurbineInfo.vue

@@ -3,167 +3,194 @@
     <a-card :bordered="false">
       <a-row :gutter="[12, 12]" align="middle">
         <a-col :xs="24" :md="8">
-          <StationSelector v-model:value="query.stationIds" :options="stationOptions" />
+          <StationSelector v-model:value="queryParams.configStationId" :options="stationOptions" :multiple="false" />
         </a-col>
         <a-col :xs="24" :md="6">
-          <a-select v-model:value="query.deviceType" :options="deviceTypeOptions" class="full-control" />
-        </a-col>
-        <a-col :xs="24" :md="6">
-          <a-input v-model:value="query.keyword" placeholder="请输入设备名称" />
+          <a-input v-model:value="queryParams.name" placeholder="请输入设备名称" allow-clear @press-enter="handleQuery" />
         </a-col>
         <a-col :xs="24" :md="4" class="action-bar">
           <a-space>
-            <a-button type="primary" :loading="loading" @click="handleQuery">查询</a-button>
-            <a-button>新增</a-button>
+            <a-button type="primary" :loading="loading" @click="handleQuery"><BearJiaIcon icon="SearchOutlined" />查询</a-button>
+            <a-button @click="resetQuery">重置</a-button>
           </a-space>
         </a-col>
       </a-row>
     </a-card>
 
-    <a-card title="光伏设备信息" :bordered="false" class="section" :loading="loading">
+    <a-card title="光伏设备信息" :bordered="false" class="section">
+      <a-space class="table-actions">
+        <a-button type="primary" @click="openForm"><BearJiaIcon icon="plus-outlined" />新增</a-button>
+        <a-button danger type="primary" :disabled="selectedRowKeys.length <= 0" @click="handleBatchDelete"><BearJiaIcon icon="delete-outlined" />删除</a-button>
+        <a-button @click="handleExport"><BearJiaIcon icon="export-outlined" />导出</a-button>
+      </a-space>
       <a-table
         :columns="columns"
         :data-source="rows"
-        :pagination="{pageSize: 10}"
+        :loading="loading"
+        :pagination="pagination"
         :locale="{emptyText: '暂无数据'}"
         row-key="id"
+        :row-selection="rowSelection"
+        @change="handleTableChange"
       />
     </a-card>
+
+    <a-modal v-model:open="formOpen" title="新增设备" width="640px" @ok="submitForm">
+      <a-form ref="formRef" :model="form" :rules="rules" :label-col="{span: 5}" :wrapper-col="{span: 17}">
+        <a-form-item label="设备名称" name="name">
+          <a-input v-model:value="form.name" allow-clear placeholder="请输入设备名称" />
+        </a-form-item>
+        <a-form-item label="所属场站" name="configStationId">
+          <a-select v-model:value="form.configStationId" allow-clear :options="stationOptions" placeholder="请选择所属场站" />
+        </a-form-item>
+        <a-form-item label="通道" name="configChannelId">
+          <a-select v-model:value="form.configChannelId" allow-clear :options="channelOptions" placeholder="请选择通道" />
+        </a-form-item>
+        <a-form-item label="备注" name="remark">
+          <a-textarea v-model:value="form.remark" :rows="3" placeholder="请输入备注" />
+        </a-form-item>
+      </a-form>
+    </a-modal>
   </div>
 </template>
 
 <script setup name="ElectricityTurbineInfo">
-import {onMounted, reactive, ref} from 'vue';
+import {computed, onMounted, reactive, ref} from 'vue';
+import {message, Modal} from 'ant-design-vue';
+import BearJiaUtil from '@/utils/BearJiaUtil';
 import {listStation} from '@/api/config/station';
-import {listDevicePowerReal} from '@/api/realtime/devicePowerReal';
+import {listChannel} from '@/api/config/channel';
+import {addDevice, delDevice, listDevice} from '@/api/config/device';
 import StationSelector from '../components/StationSelector.vue';
-import {normalizeRows, pick, toNumber} from '../components/dataAdapter';
-import {buildStationNameMap, resolveStationName, toStationOptions} from '../components/stationAdapter';
+import {normalizeRows} from '../components/dataAdapter';
+import {toStationOptions} from '../components/stationAdapter';
+import {BearJiaIcon} from '@/utils/BearJiaIcon.js';
 
 const loading = ref(false);
-const stationOptions = ref([]);
-const stationNameMap = ref(new Map());
 const rows = ref([]);
-
-const query = reactive({
-  stationIds: [],
-  deviceType: 'inverter',
-  keyword: ''
+const stationOptions = ref([]);
+const channelOptions = ref([]);
+const selectedRowKeys = ref([]);
+const formOpen = ref(false);
+const formRef = ref();
+
+const queryParams = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  configStationId: undefined,
+  name: undefined
 });
 
-const deviceTypeOptions = [
-  {label: '逆变器', value: 'inverter'},
-  {label: '箱变', value: 'box_transformer'},
-  {label: '汇流箱', value: 'combiner_box'}
-];
-
-const columns = [
-  {title: '序号', dataIndex: 'id', width: 80},
-  {title: '设备名称', dataIndex: 'deviceName'},
-  {title: '设备类型', dataIndex: 'deviceType'},
-  {title: '所属场站', dataIndex: 'stationName'},
-  {title: '额定容量', dataIndex: 'capacity'},
-  {title: '型号', dataIndex: 'model'},
-  {title: '状态', dataIndex: 'status'}
-];
+const pagination = reactive({
+  current: 1,
+  pageSize: 10,
+  total: 0,
+  showSizeChanger: true
+});
 
-const deviceTypeMap = {
-  inverter: '逆变器',
-  box_transformer: '箱变',
-  combiner_box: '汇流箱'
-};
+const form = reactive({});
 
-const toStatus = (value) => {
-  const text = String(value ?? '').trim().toLowerCase();
-  if (text.includes('offline') || text.includes('离线') || text === '0' || text === 'false') return '离线';
-  if (text.includes('abnormal') || text.includes('fault') || text.includes('异常') || text.includes('告警')) return '检修';
-  return '在线';
-};
+const rowSelection = computed(() => ({
+  selectedRowKeys: selectedRowKeys.value,
+  onChange: (keys) => {
+    selectedRowKeys.value = keys;
+  }
+}));
 
-const inferDeviceType = (item) => {
-  const raw = String(pick(item, ['deviceType', 'type', 'equipmentType'], '')).toLowerCase();
-  const name = String(pick(item, ['deviceName', 'name', 'deviceId'], '')).toLowerCase();
-  const text = `${raw} ${name}`;
-  if (text.includes('combiner') || text.includes('汇流')) return 'combiner_box';
-  if (text.includes('box') || text.includes('transform') || text.includes('箱变') || text.includes('升压')) return 'box_transformer';
-  if (text.includes('invert') || text.includes('inv') || text.includes('逆变')) return 'inverter';
-  return 'inverter';
+const rules = {
+  name: [{required: true, message: '请输入设备名称', trigger: 'blur'}]
 };
 
-const formatCapacity = (item) => {
-  const raw = pick(item, ['capacity', 'ratedCapacity', 'installCapacity'], '');
-  const num = toNumber(raw, NaN);
-  if (Number.isFinite(num) && num !== 0) return `${Number(num.toFixed(2))}MW`;
-  return raw || '-';
-};
+const columns = [
+  {title: 'ID', dataIndex: 'id', width: 90},
+  {title: '场站名称', dataIndex: 'stationName', width: 180},
+  {title: '设备名称', dataIndex: 'name'},
+  {title: '通道', dataIndex: 'channelName', width: 180},
+  {title: '备注', dataIndex: 'remark'}
+];
 
-const toPvDeviceName = (name, typeKey, index) => {
-  const text = String(name || '').trim();
-  if (text) {
-    return text.replace(/风机/g, '逆变器');
-  }
-  return `${deviceTypeMap[typeKey] || '逆变器'} #${index + 1}`;
+const resetObject = (target, source = {}) => {
+  Object.keys(target).forEach((key) => delete target[key]);
+  Object.assign(target, source);
 };
 
 const loadStationOptions = async () => {
   const res = await listStation({pageNum: 1, pageSize: 500});
-  const stationRows = normalizeRows(res);
-  stationOptions.value = toStationOptions(stationRows);
-  stationNameMap.value = buildStationNameMap(stationRows);
-  if (!query.stationIds.length && stationOptions.value.length) {
-    query.stationIds = [stationOptions.value[0].value];
+  stationOptions.value = toStationOptions(normalizeRows(res));
+  if (!queryParams.configStationId && stationOptions.value.length) {
+    queryParams.configStationId = stationOptions.value[0].value;
   }
 };
 
-const filterRowsBySelectedStations = (sourceRows) => {
-  const stationIds = Array.isArray(query.stationIds) ? query.stationIds : [query.stationIds].filter(Boolean);
-  if (!stationIds.length) {
-    return sourceRows;
-  }
-  const selectedKeys = new Set(stationIds);
-  return sourceRows.filter((item) =>
-    [item.stationId, item.entityId, item.id, item.Id].some((key) => selectedKeys.has(key))
-  );
+const loadChannelOptions = async () => {
+  const res = await listChannel({pageNum: 1, pageSize: 500});
+  const channelRows = res?.rows || res?.data || [];
+  channelOptions.value = channelRows.map((item) => ({label: item.name || item.id, value: item.id}));
 };
 
 const handleQuery = async () => {
   loading.value = true;
   try {
-    const stationIds = Array.isArray(query.stationIds) ? query.stationIds : [query.stationIds].filter(Boolean);
-    const params = {
-      pageNum: 1,
-      pageSize: 500,
-      entityId: stationIds.length === 1 ? stationIds[0] : undefined,
-      stationIds: stationIds.join(',')
-    };
-    const res = await listDevicePowerReal(params);
-    const sourceRows = filterRowsBySelectedStations(normalizeRows(res)).map((item, index) => {
-      const typeKey = inferDeviceType(item);
-      return {
-        id: item.id || item.Id || index + 1,
-        deviceName: toPvDeviceName(pick(item, ['deviceName', 'name', 'deviceId', 'sensorName']), typeKey, index),
-        deviceTypeKey: typeKey,
-        deviceType: deviceTypeMap[typeKey] || '逆变器',
-        stationName: resolveStationName(item, stationNameMap.value) || '江门站',
-        capacity: formatCapacity(item),
-        model: pick(item, ['model', 'deviceModel', 'specification', 'version'], '-'),
-        status: toStatus(pick(item, ['status', 'state', 'onlineStatus'], 'online'))
-      };
-    });
-
-    rows.value = sourceRows.filter((item) => {
-      const typeMatched = query.deviceType ? item.deviceTypeKey === query.deviceType : true;
-      const keyword = query.keyword?.trim();
-      const keywordMatched = keyword ? String(item.deviceName || '').includes(keyword) : true;
-      return typeMatched && keywordMatched;
-    });
+    const res = await listDevice(queryParams);
+    rows.value = res?.rows || res?.data || [];
+    pagination.total = res?.total || rows.value.length;
+    pagination.current = queryParams.pageNum;
+    pagination.pageSize = queryParams.pageSize;
   } finally {
     loading.value = false;
   }
 };
 
+const resetQuery = () => {
+  queryParams.pageNum = 1;
+  queryParams.configStationId = stationOptions.value[0]?.value;
+  queryParams.name = undefined;
+  handleQuery();
+};
+
+const handleTableChange = (page) => {
+  queryParams.pageNum = page.current;
+  queryParams.pageSize = page.pageSize;
+  handleQuery();
+};
+
+const openForm = () => {
+  resetObject(form, {configStationId: queryParams.configStationId});
+  formOpen.value = true;
+};
+
+const submitForm = async () => {
+  await formRef.value?.validate();
+  await addDevice(form);
+  message.success('保存成功');
+  formOpen.value = false;
+  handleQuery();
+};
+
+const handleBatchDelete = () => {
+  if (selectedRowKeys.value.length <= 0) {
+    message.warning('请至少选择一条数据');
+    return;
+  }
+  Modal.confirm({
+    title: '确认删除',
+    content: `确认删除选中的 ${selectedRowKeys.value.length} 条数据?`,
+    onOk: async () => {
+      await delDevice(selectedRowKeys.value);
+      message.success('删除成功');
+      selectedRowKeys.value = [];
+      handleQuery();
+    }
+  });
+};
+
+const handleExport = () => {
+  BearJiaUtil.download('config/device/export', queryParams, '设备配置.xlsx');
+};
+
 onMounted(async () => {
   await loadStationOptions();
+  await loadChannelOptions();
   await handleQuery();
 });
 </script>
@@ -172,5 +199,5 @@ onMounted(async () => {
 .device-page { padding: 16px; }
 .section { margin-top: 16px; }
 .action-bar { display: flex; justify-content: flex-end; }
-.full-control { width: 100%; }
+.table-actions { display: flex; margin-bottom: 12px; }
 </style>