Jelajahi Sumber

[0806] 场站管理、光伏组件、限电配置、设备配置、通道配置、测点配置|按钮等布局样式调整

taosj 2 bulan lalu
induk
melakukan
7b867dd2f3

+ 1 - 0
forecast-backend-vue/src/components/TableActionBar/index.vue

@@ -94,6 +94,7 @@ const props = defineProps({
   font-size: 13px;
   border-radius: 4px;
   cursor: pointer;
+  white-space: nowrap;
   transition: background-color 0.2s;
 
   &.view-btn {

+ 102 - 20
forecast-backend-vue/src/views/ufo/config/station/StationConfigCrud.vue

@@ -11,26 +11,44 @@
         </a-form-item>
         <a-form-item>
           <a-space>
-            <a-button type="primary" :loading="loading" @click="handleQuery">查询</a-button>
-            <a-button @click="resetQuery">重置</a-button>
-            <a-button v-hasPermi="[moduleConfig.perm + ':add']" type="primary" @click="openForm()">新增</a-button>
-            <a-button v-hasPermi="[moduleConfig.perm + ':export']" @click="handleExport">导出</a-button>
+            <a-button type="primary" :loading="loading" @click="handleQuery"><BearJiaIcon icon="SearchOutlined"/>查询</a-button>
+            <a-button @click="resetQuery"><BearJiaIcon icon="LoadingOutlined"/>重置</a-button>
           </a-space>
         </a-form-item>
       </a-form>
     </a-card>
 
-    <a-card :bordered="false" class="section">
-      <a-table :columns="columns" :data-source="rows" :loading="loading" :pagination="pagination" row-key="id" :scroll="tableScroll" @change="handleTableChange">
+    <a-card ref="tableCardRef" :bordered="false" class="section">
+      <TableToolbar
+        :columns="columns"
+        :selected-columns="selectedColumnKeys"
+        :loading="loading"
+        :export-config="exportConfig"
+        @columnSettingsChange="handleColumnSettingsChange"
+        @refresh="handleQuery"
+        @fullscreen="handleFullscreen"
+        @export="handleExport"
+        @reset="handleTableReset"
+      >
+        <template #left>
+          <a-button v-hasPermi="[moduleConfig.perm + ':add']" type="primary" @click="openForm()"><BearJiaIcon icon="plus-outlined"/>新增</a-button>
+          <a-button v-hasPermi="[moduleConfig.perm + ':remove']" danger type="primary" :disabled="selectedRowKeys.length <= 0" @click="handleBatchDelete"><BearJiaIcon icon="delete-outlined"/>删除</a-button>
+          <a-button v-hasPermi="[moduleConfig.perm + ':export']" @click="handleExport"><BearJiaIcon icon="export-outlined"/>导出</a-button>
+        </template>
+      </TableToolbar>
+      <a-table :columns="displayColumns" :data-source="rows" :loading="loading" :pagination="pagination" row-key="id" :row-selection="rowSelection" :size="tableConfigStore.size" :scroll="tableScroll" @change="handleTableChange">
         <template #bodyCell="{ column, record }">
           <a-switch v-if="isSwitchColumn(column)" :checked="record[column.dataIndex] === switchCheckedValue(column)" disabled size="small" />
           <template v-else-if="column.key === 'operation'">
-            <a-space>
-              <a v-hasPermi="[moduleConfig.perm + ':edit']" @click="openForm(record)">编辑</a>
-              <a-popconfirm :title="'确认删除该' + moduleConfig.title + '?'" @confirm="removeRecord(record)">
-                <a v-hasPermi="[moduleConfig.perm + ':remove']" class="danger-link">删除</a>
-              </a-popconfirm>
-            </a-space>
+            <TableActionBar :record="record" :has-view="false" :has-edit="true" :has-delete="true">
+              <template #edit>
+                <a v-hasPermi="[moduleConfig.perm + ':edit']" class="action-btn edit-btn" @click="openForm(record)"><BearJiaIcon icon="edit-outlined"/>修改</a>
+              </template>
+              <template #delete>
+                <a-divider type="vertical"/>
+                <a v-hasPermi="[moduleConfig.perm + ':remove']" class="action-btn delete-btn" @click="handleDeleteRecord(record)"><BearJiaIcon icon="delete-outlined"/>删除</a>
+              </template>
+            </TableActionBar>
           </template>
         </template>
       </a-table>
@@ -62,10 +80,14 @@
 
 <script setup>
 import { computed, reactive, ref } from 'vue';
-import { message } from 'ant-design-vue';
+import { message, Modal } from 'ant-design-vue';
 import BearJiaUtil from '@/utils/BearJiaUtil';
 import { listStation } from '@/api/config/station';
 import { listStationDevice } from '@/api/config/stationDevice';
+import TableActionBar from '@/components/TableActionBar/index.vue';
+import TableToolbar from '@/components/BearJiaProTable/TableToolbar.vue';
+import { useTableConfigStore } from '@/stores/tableConfig';
+import { BearJiaIcon } from '@/utils/BearJiaIcon.js';
 
 const props = defineProps({ moduleConfig: { type: Object, required: true }, api: { type: Object, required: true } });
 
@@ -75,6 +97,10 @@ const stationOptions = ref([]);
 const allDevices = ref([]);
 const formOpen = ref(false);
 const formRef = ref();
+const selectedRowKeys = ref([]);
+const tableCardRef = ref();
+const selectedColumnKeys = ref([]);
+const tableConfigStore = useTableConfigStore();
 const queryParams = reactive({ pageNum: 1, pageSize: 10, stationId: undefined });
 const pagination = reactive({ current: 1, pageSize: 10, total: 0, showSizeChanger: true });
 const form = reactive({});
@@ -86,9 +112,23 @@ const searchFields = computed(() => props.moduleConfig.search.filter((name) => n
 const columns = computed(() => [
   { title: 'ID', dataIndex: 'id', width: 80 },
   ...props.moduleConfig.columns.map((name) => ({ title: name === 'stationName' ? '场站' : fieldMap.value.get(name)?.[1] || name, dataIndex: name, width: 140, ellipsis: true })),
-  { title: '操作', key: 'operation', width: 130, fixed: 'right' }
+  { title: '操作', key: 'operation', dataIndex: 'operation', width: 200, fixed: 'right' }
 ]);
-const tableScroll = computed(() => ({ x: columns.value.reduce((sum, col) => sum + (col.width || 140), 0) }));
+const storageKey = 'station-config-columns-' + props.moduleConfig.api;
+const readSavedColumns = () => {
+  try {
+    const saved = JSON.parse(localStorage.getItem(storageKey) || 'null');
+    if (Array.isArray(saved) && saved.length > 0) {
+      const valid = saved.filter((k) => columns.value.some((c) => c.dataIndex === k));
+      if (valid.length > 0) return valid;
+    }
+  } catch (e) { /* 忽略存储解析错误 */ }
+  return columns.value.map((c) => c.dataIndex);
+};
+selectedColumnKeys.value = readSavedColumns();
+const displayColumns = computed(() => columns.value.filter((col) => selectedColumnKeys.value.includes(col.dataIndex)));
+const tableScroll = computed(() => ({ x: displayColumns.value.reduce((sum, col) => sum + (col.width || 140), 0) + 40 }));
+const exportConfig = { enabled: true };
 const rules = computed(() => ({
   ...(hasStation.value ? { stationId: [{ required: true, message: '请选择所属场站', trigger: 'change' }] } : {}),
   ...Object.fromEntries(props.moduleConfig.fields.filter((item) => item[3]).map((item) => [item[0], [{ required: true, message: '请输入' + item[1], trigger: item[2] === 'select' || item[2] === 'device' || item[2] === 'switch' ? 'change' : 'blur' }]]))
@@ -96,6 +136,10 @@ const rules = computed(() => ({
 const deviceOptions = computed(() => allDevices.value
   .filter((item) => !form.stationId || String(item.stationId) === String(form.stationId))
   .map((item) => ({ label: item.deviceName || item.name || item.id, value: item.id, raw: item })));
+const rowSelection = computed(() => ({
+  selectedRowKeys: selectedRowKeys.value,
+  onChange: (keys) => { selectedRowKeys.value = keys; }
+}));
 
 const toOptions = (items = []) => items.map((item) => ({ label: item[0], value: item[1] }));
 const isSwitchColumn = (column) => fieldMap.value.get(column.dataIndex)?.[2] === 'switch';
@@ -164,12 +208,51 @@ const submitForm = async () => {
   formOpen.value = false;
   handleQuery();
 };
-const removeRecord = async (record) => {
-  await props.api.del(record.id);
-  message.success('删除成功');
-  handleQuery();
+const handleDeleteRecord = (record) => {
+  Modal.confirm({
+    title: '确认删除',
+    content: `确认删除该${moduleConfig.title}?`,
+    onOk: async () => {
+      await props.api.del(record.id);
+      message.success('删除成功');
+      handleQuery();
+    }
+  });
+};
+const handleBatchDelete = () => {
+  if (selectedRowKeys.value.length <= 0) {
+    message.warning('请至少选择一条数据');
+    return;
+  }
+  Modal.confirm({
+    title: '确认删除',
+    content: `确认删除选中的 ${selectedRowKeys.value.length} 条数据?`,
+    onOk: async () => {
+      await props.api.del(selectedRowKeys.value);
+      message.success('删除成功');
+      selectedRowKeys.value = [];
+      handleQuery();
+    }
+  });
 };
 const handleExport = () => BearJiaUtil.download('config/' + props.moduleConfig.api + '/export', queryParams, props.moduleConfig.exportName);
+const handleColumnSettingsChange = (keys) => {
+  selectedColumnKeys.value = keys;
+  localStorage.setItem(storageKey, JSON.stringify(keys));
+};
+const handleFullscreen = (isFullscreen) => {
+  const el = tableCardRef.value?.$el || tableCardRef.value;
+  if (isFullscreen) {
+    el?.requestFullscreen?.();
+  } else {
+    document.exitFullscreen?.();
+  }
+};
+const handleTableReset = () => {
+  selectedColumnKeys.value = columns.value.map((c) => c.dataIndex);
+  localStorage.removeItem(storageKey);
+  handleQuery();
+};
 
 loadStations();
 loadDevices();
@@ -181,6 +264,5 @@ handleQuery();
   .section { margin-top: 12px; }
   .query-select { width: 180px; }
   .full-input { width: 100%; }
-  .danger-link { color: #ff4d4f; }
 }
 </style>

+ 105 - 23
forecast-backend-vue/src/views/ufo/config/station/index.vue

@@ -16,22 +16,39 @@
         </a-form-item>
         <a-form-item>
           <a-space>
-            <a-button type="primary" :loading="loading" @click="handleQuery">查询</a-button>
-            <a-button @click="resetQuery">重置</a-button>
-            <a-button v-hasPermi="['config:station:add']" type="primary" @click="openForm()">新增</a-button>
-            <a-button v-hasPermi="['config:station:export']" @click="handleExport">导出</a-button>
+            <a-button type="primary" :loading="loading" @click="handleQuery"><BearJiaIcon icon="SearchOutlined"/>查询</a-button>
+            <a-button @click="resetQuery"><BearJiaIcon icon="LoadingOutlined"/>重置</a-button>
           </a-space>
         </a-form-item>
       </a-form>
     </a-card>
 
-    <a-card :bordered="false" class="section">
-      <a-table
+    <a-card ref="tableCardRef" :bordered="false" class="section">
+      <TableToolbar
         :columns="columns"
+        :selected-columns="selectedColumnKeys"
+        :loading="loading"
+        :export-config="exportConfig"
+        @columnSettingsChange="handleColumnSettingsChange"
+        @refresh="handleQuery"
+        @fullscreen="handleFullscreen"
+        @export="handleExport"
+        @reset="handleTableReset"
+      >
+        <template #left>
+          <a-button v-hasPermi="['config:station:add']" type="primary" @click="openForm()"><BearJiaIcon icon="plus-outlined"/>新增</a-button>
+          <a-button v-hasPermi="['config:station:remove']" danger type="primary" :disabled="selectedRowKeys.length <= 0" @click="handleBatchDelete"><BearJiaIcon icon="delete-outlined"/>删除</a-button>
+          <a-button v-hasPermi="['config:station:export']" @click="handleExport"><BearJiaIcon icon="export-outlined"/>导出</a-button>
+        </template>
+      </TableToolbar>
+      <a-table
+        :columns="displayColumns"
         :data-source="rows"
         :loading="loading"
         :pagination="pagination"
         row-key="id"
+        :row-selection="rowSelection"
+        :size="tableConfigStore.size"
         @change="handleTableChange"
       >
         <template #bodyCell="{ column, record }">
@@ -42,12 +59,15 @@
             <a-tag :color="record.isEnable === '1' ? 'success' : 'default'">{{ enableText(record.isEnable) }}</a-tag>
           </template>
           <template v-else-if="column.key === 'operation'">
-            <a-space>
-              <a v-hasPermi="['config:station:edit']" @click="openForm(record)">编辑</a>
-              <a-popconfirm title="确认删除该场站?" @confirm="removeRecord(record)">
-                <a v-hasPermi="['config:station:remove']" class="danger-link">删除</a>
-              </a-popconfirm>
-            </a-space>
+            <TableActionBar :record="record" :has-view="false" :has-edit="true" :has-delete="true">
+              <template #edit>
+                <a v-hasPermi="['config:station:edit']" class="action-btn edit-btn" @click="openForm(record)"><BearJiaIcon icon="edit-outlined"/>修改</a>
+              </template>
+              <template #delete>
+                <a-divider type="vertical"/>
+                <a v-hasPermi="['config:station:remove']" class="action-btn delete-btn" @click="handleDeleteRecord(record)"><BearJiaIcon icon="delete-outlined"/>删除</a>
+              </template>
+            </TableActionBar>
           </template>
         </template>
       </a-table>
@@ -123,15 +143,23 @@
 </template>
 
 <script setup name="ConfigStation">
-import { reactive, ref } from 'vue';
-import { message } from 'ant-design-vue';
+import { computed, reactive, ref } from 'vue';
+import { message, Modal } from 'ant-design-vue';
 import BearJiaUtil from '@/utils/BearJiaUtil';
 import { addStation, delStation, getStation, listStation, updateStation } from '@/api/config/station';
+import TableActionBar from '@/components/TableActionBar/index.vue';
+import TableToolbar from '@/components/BearJiaProTable/TableToolbar.vue';
+import { useTableConfigStore } from '@/stores/tableConfig';
+import { BearJiaIcon } from '@/utils/BearJiaIcon.js';
 
 const loading = ref(false);
 const rows = ref([]);
 const formOpen = ref(false);
 const formRef = ref();
+const selectedRowKeys = ref([]);
+const tableCardRef = ref();
+const selectedColumnKeys = ref([]);
+const tableConfigStore = useTableConfigStore();
 
 const queryParams = reactive({
   pageNum: 1,
@@ -151,6 +179,11 @@ const pagination = reactive({
 
 const form = reactive({});
 
+const rowSelection = computed(() => ({
+  selectedRowKeys: selectedRowKeys.value,
+  onChange: (keys) => { selectedRowKeys.value = keys; }
+}));
+
 const stationTypeOptions = [
   { label: '风电', value: 0 },
   { label: '光伏', value: 1 }
@@ -176,8 +209,22 @@ const columns = [
   { title: '负责人', dataIndex: 'responsiblePersonName', width: 120 },
   { title: '电话', dataIndex: 'responsiblePersonPhone', width: 140 },
   { title: '启用', dataIndex: 'isEnable', key: 'isEnable', width: 90 },
-  { title: '操作', key: 'operation', width: 130, fixed: 'right' }
+  { title: '操作', key: 'operation', dataIndex: 'operation', width: 200, fixed: 'right' }
 ];
+const storageKey = 'station-config-columns-station';
+const readSavedColumns = () => {
+  try {
+    const saved = JSON.parse(localStorage.getItem(storageKey) || 'null');
+    if (Array.isArray(saved) && saved.length > 0) {
+      const valid = saved.filter((k) => columns.some((c) => c.dataIndex === k));
+      if (valid.length > 0) return valid;
+    }
+  } catch (e) { /* 忽略存储解析错误 */ }
+  return columns.map((c) => c.dataIndex);
+};
+selectedColumnKeys.value = readSavedColumns();
+const displayColumns = computed(() => columns.filter((col) => selectedColumnKeys.value.includes(col.dataIndex)));
+const exportConfig = { enabled: true };
 
 const stationTypeText = (value) => stationTypeOptions.find((item) => item.value === Number(value))?.label || '-';
 const enableText = (value) => enableOptions.find((item) => item.value === String(value))?.label || '-';
@@ -241,15 +288,54 @@ const submitForm = async () => {
   handleQuery();
 };
 
-const removeRecord = async (record) => {
-  await delStation(record.id);
-  message.success('删除成功');
-  handleQuery();
+const handleDeleteRecord = (record) => {
+  Modal.confirm({
+    title: '确认删除',
+    content: '确认删除该场站?',
+    onOk: async () => {
+      await delStation(record.id);
+      message.success('删除成功');
+      handleQuery();
+    }
+  });
+};
+const handleBatchDelete = () => {
+  if (selectedRowKeys.value.length <= 0) {
+    message.warning('请至少选择一条数据');
+    return;
+  }
+  Modal.confirm({
+    title: '确认删除',
+    content: `确认删除选中的 ${selectedRowKeys.value.length} 条数据?`,
+    onOk: async () => {
+      await delStation(selectedRowKeys.value);
+      message.success('删除成功');
+      selectedRowKeys.value = [];
+      handleQuery();
+    }
+  });
 };
 
 const handleExport = () => {
   BearJiaUtil.download('config/station/export', queryParams, '场站信息.xlsx');
 };
+const handleColumnSettingsChange = (keys) => {
+  selectedColumnKeys.value = keys;
+  localStorage.setItem(storageKey, JSON.stringify(keys));
+};
+const handleFullscreen = (isFullscreen) => {
+  const el = tableCardRef.value?.$el || tableCardRef.value;
+  if (isFullscreen) {
+    el?.requestFullscreen?.();
+  } else {
+    document.exitFullscreen?.();
+  }
+};
+const handleTableReset = () => {
+  selectedColumnKeys.value = columns.map((c) => c.dataIndex);
+  localStorage.removeItem(storageKey);
+  handleQuery();
+};
 
 handleQuery();
 </script>
@@ -267,9 +353,5 @@ handleQuery();
   .full-input {
     width: 100%;
   }
-
-  .danger-link {
-    color: #ff4d4f;
-  }
 }
 </style>