|
|
@@ -3,7 +3,7 @@
|
|
|
<a-card :bordered="false">
|
|
|
<a-form :model="queryParams" layout="inline">
|
|
|
<a-form-item v-if="hasStation" label="场站">
|
|
|
- <a-select v-model:value="queryParams.stationId" allow-clear show-search class="query-select" :options="stationOptions" :filter-option="filterOption" />
|
|
|
+ <a-select v-model:value="queryParams[stationField]" allow-clear show-search class="query-select" :options="stationOptions" :filter-option="filterOption" />
|
|
|
</a-form-item>
|
|
|
<a-form-item v-for="item in searchFields" :key="item.name" :label="item.label">
|
|
|
<a-select v-if="item.type === 'select'" v-model:value="queryParams[item.name]" allow-clear class="query-select" :options="item.options" />
|
|
|
@@ -11,25 +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 }">
|
|
|
- <template v-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>
|
|
|
+ <a-switch v-if="isSwitchColumn(column)" :checked="record[column.dataIndex] === switchCheckedValue(column)" disabled size="small" />
|
|
|
+ <template v-else-if="column.key === 'operation'">
|
|
|
+ <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>
|
|
|
@@ -39,14 +58,16 @@
|
|
|
<a-form ref="formRef" :model="form" :rules="rules" :label-col="{ span: 7 }" :wrapper-col="{ span: 15 }">
|
|
|
<a-row :gutter="16">
|
|
|
<a-col v-if="hasStation" :span="12">
|
|
|
- <a-form-item label="所属场站" name="stationId">
|
|
|
- <a-select v-model:value="form.stationId" show-search :options="stationOptions" :filter-option="filterOption" @change="handleStationChange" />
|
|
|
+ <a-form-item label="所属场站" :name="stationField">
|
|
|
+ <a-select v-model:value="form[stationField]" show-search :options="stationOptions" :filter-option="filterOption" @change="handleStationChange" />
|
|
|
</a-form-item>
|
|
|
</a-col>
|
|
|
<a-col v-for="item in moduleConfig.fields" :key="item[0]" :span="12">
|
|
|
<a-form-item :label="item[1]" :name="item[0]">
|
|
|
<a-select v-if="item[2] === 'select'" v-model:value="form[item[0]]" allow-clear :options="toOptions(item[4])" />
|
|
|
<a-select v-else-if="item[2] === 'device'" v-model:value="form[deviceFieldName]" allow-clear show-search :options="deviceOptions" :filter-option="filterOption" @change="handleDeviceChange" />
|
|
|
+ <a-select v-else-if="item[2] === 'channel'" v-model:value="form[channelFieldName]" allow-clear show-search :options="channelOptions" :filter-option="filterOption" />
|
|
|
+ <a-switch v-else-if="item[2] === 'switch'" v-model:checked="form[item[0]]" :checked-value="item[4]?.[0]?.[1] ?? true" :un-checked-value="item[4]?.[1]?.[1] ?? false" />
|
|
|
<a-input-number v-else-if="item[2] === 'number'" v-model:value="form[item[0]]" class="full-input" />
|
|
|
<a-date-picker v-else-if="item[2] === 'date'" v-model:value="form[item[0]]" value-format="YYYY-MM-DD" class="full-input" />
|
|
|
<a-input v-else v-model:value="form[item[0]]" allow-clear />
|
|
|
@@ -60,10 +81,15 @@
|
|
|
|
|
|
<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 { listChannel } from '@/api/config/channel';
|
|
|
+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 } });
|
|
|
|
|
|
@@ -71,31 +97,64 @@ const loading = ref(false);
|
|
|
const rows = ref([]);
|
|
|
const stationOptions = ref([]);
|
|
|
const allDevices = ref([]);
|
|
|
+const allChannels = ref([]);
|
|
|
const formOpen = ref(false);
|
|
|
const formRef = ref();
|
|
|
-const queryParams = reactive({ pageNum: 1, pageSize: 10, stationId: undefined });
|
|
|
+const selectedRowKeys = ref([]);
|
|
|
+const tableCardRef = ref();
|
|
|
+const selectedColumnKeys = ref([]);
|
|
|
+const tableConfigStore = useTableConfigStore();
|
|
|
+const stationField = computed(() => props.moduleConfig.stationField || 'stationId');
|
|
|
+const queryParams = reactive({ pageNum: 1, pageSize: 10, [stationField.value]: undefined });
|
|
|
const pagination = reactive({ current: 1, pageSize: 10, total: 0, showSizeChanger: true });
|
|
|
const form = reactive({});
|
|
|
|
|
|
const hasStation = computed(() => props.moduleConfig.hasStation !== false);
|
|
|
const deviceFieldName = computed(() => props.moduleConfig.fields.find((item) => item[2] === 'device')?.[0]);
|
|
|
+const channelFieldName = computed(() => props.moduleConfig.fields.find((item) => item[2] === 'channel')?.[0]);
|
|
|
const fieldMap = computed(() => new Map(props.moduleConfig.fields.map((item) => [item[0], item])));
|
|
|
-const searchFields = computed(() => props.moduleConfig.search.filter((name) => name !== 'stationId').map((name) => ({ name, label: fieldMap.value.get(name)?.[1] || name, type: fieldMap.value.get(name)?.[2], options: toOptions(fieldMap.value.get(name)?.[4]) })));
|
|
|
+const searchFields = computed(() => props.moduleConfig.search.filter((name) => name !== stationField.value).map((name) => ({ name, label: fieldMap.value.get(name)?.[1] || name, type: fieldMap.value.get(name)?.[2], options: toOptions(fieldMap.value.get(name)?.[4]) })));
|
|
|
+const columnTitle = (name) => {
|
|
|
+ if (name === 'stationName') return '场站';
|
|
|
+ if (name === 'channelName') return '通道';
|
|
|
+ return fieldMap.value.get(name)?.[1] || name;
|
|
|
+};
|
|
|
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' }
|
|
|
+ ...props.moduleConfig.columns.map((name) => ({ title: columnTitle(name), dataIndex: name, width: 140, ellipsis: true })),
|
|
|
+ { 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' ? 'change' : 'blur' }]]))
|
|
|
+ ...(hasStation.value ? { [stationField.value]: [{ 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' }]]))
|
|
|
}));
|
|
|
const deviceOptions = computed(() => allDevices.value
|
|
|
- .filter((item) => !form.stationId || String(item.stationId) === String(form.stationId))
|
|
|
+ .filter((item) => !form[stationField.value] || String(item.stationId) === String(form[stationField.value]))
|
|
|
.map((item) => ({ label: item.deviceName || item.name || item.id, value: item.id, raw: item })));
|
|
|
+const channelOptions = computed(() => allChannels.value.map((item) => ({ label: item.name || item.id, value: item.id })));
|
|
|
+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';
|
|
|
+const switchCheckedValue = (column) => fieldMap.value.get(column.dataIndex)?.[4]?.[0]?.[1] ?? true;
|
|
|
const filterOption = (input, option) => String(option?.label || '').toLowerCase().includes(input.toLowerCase());
|
|
|
const resetObject = (target, source = {}) => { Object.keys(target).forEach((key) => delete target[key]); Object.assign(target, source); };
|
|
|
|
|
|
@@ -110,6 +169,11 @@ const loadDevices = async () => {
|
|
|
const res = await listStationDevice({ pageNum: 1, pageSize: 500 });
|
|
|
allDevices.value = res?.rows || res?.data || [];
|
|
|
};
|
|
|
+const loadChannels = async () => {
|
|
|
+ if (!channelFieldName.value) return;
|
|
|
+ const res = await listChannel({ pageNum: 1, pageSize: 500 });
|
|
|
+ allChannels.value = res?.rows || res?.data || [];
|
|
|
+};
|
|
|
const handleQuery = async () => {
|
|
|
loading.value = true;
|
|
|
try {
|
|
|
@@ -134,12 +198,14 @@ const handleTableChange = (page) => {
|
|
|
};
|
|
|
const openForm = async (record) => {
|
|
|
await loadDevices();
|
|
|
+ await loadChannels();
|
|
|
if (record?.id) {
|
|
|
const res = await props.api.get(record.id);
|
|
|
resetObject(form, { ...res.data });
|
|
|
} else {
|
|
|
- resetObject(form, { stationId: queryParams.stationId });
|
|
|
+ resetObject(form, { [stationField.value]: queryParams[stationField.value] });
|
|
|
}
|
|
|
+ props.moduleConfig.fields.filter((item) => item[2] === 'switch').forEach((item) => { if (form[item[0]] === undefined) form[item[0]] = item[4]?.[1]?.[1] ?? false; });
|
|
|
formOpen.value = true;
|
|
|
};
|
|
|
const handleStationChange = () => {
|
|
|
@@ -159,15 +225,55 @@ 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();
|
|
|
+loadChannels();
|
|
|
handleQuery();
|
|
|
</script>
|
|
|
|
|
|
@@ -176,6 +282,5 @@ handleQuery();
|
|
|
.section { margin-top: 12px; }
|
|
|
.query-select { width: 180px; }
|
|
|
.full-input { width: 100%; }
|
|
|
- .danger-link { color: #ff4d4f; }
|
|
|
}
|
|
|
</style>
|