|
|
@@ -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>
|