| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350 |
- <template>
- <div class="station-config-page">
- <a-card :bordered="false">
- <a-form :model="queryParams" layout="inline">
- <a-form-item v-if="hasStation" label="场站">
- <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" />
- <a-input v-else v-model:value="queryParams[item.name]" allow-clear :placeholder="'请输入' + item.label" />
- </a-form-item>
- <a-form-item>
- <a-space>
- <a-button type="primary" :loading="loading" @click="handleQuery"><BearJiaIcon icon="SearchOutlined"/>查询</a-button>
- <a-button @click="resetQuery">重置</a-button>
- </a-space>
- </a-form-item>
- </a-form>
- </a-card>
- <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 === 'stateColumn'">
- <a-tag :color="stateColor(record.state)">{{ stateText(record.state) }}</a-tag>
- </template>
- <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>
- <template v-if="stateActionOf(record)">
- <a-divider type="vertical"/>
- <a v-hasPermi="[moduleConfig.perm + ':edit']" class="action-btn" @click="handleRowStateAction(record)"><BearJiaIcon :icon="stateActionOf(record).icon"/>{{ stateActionOf(record).text }}</a>
- </template>
- </TableActionBar>
- </template>
- </template>
- </a-table>
- </a-card>
- <a-modal v-model:open="formOpen" :title="form.id ? '编辑' + moduleConfig.title : '新增' + moduleConfig.title" width="860px" @ok="submitForm">
- <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="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-select v-else-if="item[2] === 'transmit'" v-model:value="form[item[0]]" allow-clear show-search :options="transmitOptions" :filter-option="filterOption" />
- <a-select v-else-if="item[2] === 'protocol'" v-model:value="form[item[0]]" allow-clear show-search :options="protocolOptions" :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 />
- </a-form-item>
- </a-col>
- </a-row>
- </a-form>
- </a-modal>
- </div>
- </template>
- <script setup>
- import { computed, reactive, ref } from '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 { listProtocol } from '@/api/config/protocol';
- import { listTransmit } from '@/api/config/transmit';
- 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 } });
- const loading = ref(false);
- const rows = ref([]);
- const stationOptions = ref([]);
- const allDevices = ref([]);
- const allChannels = ref([]);
- const allTransmits = ref([]);
- const allProtocols = ref([]);
- const formOpen = ref(false);
- const formRef = ref();
- 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 transmitFieldName = computed(() => props.moduleConfig.fields.find((item) => item[2] === 'transmit')?.[0]);
- const protocolFieldName = computed(() => props.moduleConfig.fields.find((item) => item[2] === 'protocol')?.[0]);
- const fieldMap = computed(() => new Map(props.moduleConfig.fields.map((item) => [item[0], item])));
- 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 '通道';
- if (name === 'configTransmitName') return '通信模块';
- if (name === 'configProtocalName') return '协议模块';
- return fieldMap.value.get(name)?.[1] || name;
- };
- const columns = computed(() => [
- { title: 'ID', dataIndex: 'id', width: 80 },
- ...props.moduleConfig.columns.map((name) => ({ title: columnTitle(name), dataIndex: name, width: 140, ellipsis: true })),
- ...(props.moduleConfig.stateColumn ? [{ title: props.moduleConfig.stateColumn.title || '状态', dataIndex: 'state', key: 'stateColumn', width: 110 }] : []),
- { title: '操作', key: 'operation', dataIndex: 'operation', width: props.moduleConfig.stateColumn ? 260 : 200, fixed: 'right' }
- ]);
- 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 (props.moduleConfig.stateColumn && !valid.includes('state')) valid.push('state');
- 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 ? { [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[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 transmitOptions = computed(() => allTransmits.value.map((item) => ({ label: item.name || item.id, value: item.id })));
- const protocolOptions = computed(() => allProtocols.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); };
- const loadStations = async () => {
- if (!hasStation.value) return;
- const res = await listStation({ pageNum: 1, pageSize: 500 });
- const list = res?.rows || res?.data || [];
- stationOptions.value = list.map((item) => ({ label: item.stationName || item.name || item.id, value: item.id }));
- };
- const loadDevices = async () => {
- if (!deviceFieldName.value) return;
- 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 loadTransmits = async () => {
- if (!transmitFieldName.value) return;
- const res = await listTransmit({ pageNum: 1, pageSize: 500 });
- allTransmits.value = res?.rows || res?.data || [];
- };
- const loadProtocols = async () => {
- if (!protocolFieldName.value) return;
- const res = await listProtocol({ pageNum: 1, pageSize: 500 });
- allProtocols.value = res?.rows || res?.data || [];
- };
- const handleQuery = async () => {
- loading.value = true;
- try {
- const res = await props.api.list(queryParams);
- rows.value = res?.rows || res?.data || [];
- pagination.total = res?.total || rows.value.length;
- pagination.current = queryParams.pageNum;
- pagination.pageSize = queryParams.pageSize;
- if (props.moduleConfig.stateColumn) {
- const list = rows.value;
- list.forEach((row) => { row.state = -2; });
- refreshRowStates(list);
- }
- } finally {
- loading.value = false;
- }
- };
- const stateText = (state) => props.moduleConfig.stateColumn?.textMap?.[String(state)] ?? '';
- const stateColor = (state) => props.moduleConfig.stateColumn?.colorMap?.[String(state)] ?? (Number(state) === 1 ? 'success' : 'default');
- const stateActionOf = (record) => {
- const actions = props.moduleConfig.stateColumn?.actions;
- return (actions && actions[String(record.state)]) || null;
- };
- const refreshRowStates = async (list) => {
- if (!list.length || typeof props.api.queryState !== 'function') return;
- try {
- const res = await props.api.queryState({ ids: list.map((row) => row.id) });
- const states = res?.data || [];
- const stateMap = new Map(states.map((item) => [String(item.channelId), item.state]));
- list.forEach((row) => { row.state = stateMap.has(String(row.id)) ? Number(stateMap.get(String(row.id))) : 1; });
- } catch (e) {
- list.forEach((row) => { row.state = -1; });
- }
- };
- const handleRowStateAction = async (record) => {
- const action = stateActionOf(record);
- if (!action || typeof props.api.operate !== 'function') return;
- await props.api.operate({ id: record.id, command: action.command });
- message.success('操作成功!');
- refreshRowStates(rows.value);
- };
- const resetQuery = () => {
- Object.keys(queryParams).forEach((key) => { if (!['pageNum', 'pageSize'].includes(key)) queryParams[key] = undefined; });
- queryParams.pageNum = 1;
- handleQuery();
- };
- const handleTableChange = (page) => {
- queryParams.pageNum = page.current;
- queryParams.pageSize = page.pageSize;
- handleQuery();
- };
- const openForm = async (record) => {
- await loadDevices();
- await loadChannels();
- await loadTransmits();
- await loadProtocols();
- if (record?.id) {
- const res = await props.api.get(record.id);
- resetObject(form, { ...res.data });
- } else {
- 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 = () => {
- if (deviceFieldName.value) {
- form[deviceFieldName.value] = undefined;
- form.deviceName = undefined;
- }
- };
- const handleDeviceChange = (value) => {
- const device = allDevices.value.find((item) => String(item.id) === String(value));
- form.deviceName = device?.deviceName;
- };
- const submitForm = async () => {
- await formRef.value?.validate();
- if (form.id) await props.api.update(form); else await props.api.add(form);
- message.success('保存成功');
- formOpen.value = false;
- 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();
- loadTransmits();
- loadProtocols();
- handleQuery();
- </script>
- <style lang="less" scoped>
- .station-config-page {
- .section { margin-top: 12px; }
- .query-select { width: 180px; }
- .full-input { width: 100%; }
- }
- </style>
|