diff --git a/src/api/business/loading-manage.js b/src/api/business/loading-manage.js
index e051fa0..6938bc3 100644
--- a/src/api/business/loading-manage.js
+++ b/src/api/business/loading-manage.js
@@ -89,3 +89,10 @@ export const batchComplete = ids =>
ids,
},
});
+
+export const changeRoute = row =>
+ request({
+ url: `${baseUrl}/change-route`,
+ method: 'post',
+ data: row,
+ });
diff --git a/src/config/website.js b/src/config/website.js
index 7a0c20f..5172e82 100644
--- a/src/config/website.js
+++ b/src/config/website.js
@@ -23,7 +23,7 @@ export default {
//配置首页不可关闭
setting: {
sidebar: 'vertical',
- tag: false,
+ tag: true,
debug: false,
collapse: true,
search: false,
diff --git a/src/main.js b/src/main.js
index 195571f..b7979d8 100644
--- a/src/main.js
+++ b/src/main.js
@@ -15,6 +15,7 @@ import crudCommon from '@/mixins/crud.js';
import { getScreen, findColumn } from './utils/util';
import { setupPaginationEnhancer } from './utils/pagination';
import { setupDialogFormLabelAlign } from './utils/dialog-form-label';
+import { setupTableColumnDrag } from './utils/table-column-drag';
import './permission';
import error from './error';
import App from './App.vue';
@@ -107,6 +108,7 @@ app.use(VueClipboard, {
autoSetContainer: true,
appendToBody: true, // 这可以帮助解决一些更复杂的使用场景下的问题
});
+setupTableColumnDrag(app);
app.mount('#app');
setupPaginationEnhancer();
setupDialogFormLabelAlign();
diff --git a/src/option/business/temporary-credit-limit.js b/src/option/business/temporary-credit-limit.js
index 553dad0..0ace898 100644
--- a/src/option/business/temporary-credit-limit.js
+++ b/src/option/business/temporary-credit-limit.js
@@ -32,6 +32,7 @@ export const config = {
temporaryCreditLimitSelectable: true,
},
enableAttachmentTable: true,
+ detailAttachmentUploadTimeWidth: 220,
enableDraftSave: true,
saveAsDraft: true,
defaultForm: {
@@ -271,6 +272,8 @@ export const option = {
},
...auditColumns.map(column => ({ ...column, hide: true })),
]),
- dialogWidth: 1280,
+ dialogWidth: 1400,
+ menuFixed: 'right',
+ menuWidth: 320,
index: false,
};
diff --git a/src/styles/element-ui.scss b/src/styles/element-ui.scss
index 9aba441..cb30892 100644
--- a/src/styles/element-ui.scss
+++ b/src/styles/element-ui.scss
@@ -574,12 +574,10 @@
gap: 8px;
}
-// 全站统一:表格操作列文字链接统一为主色(蓝色)
-// 覆盖 Avue #menu 槽位与手写表格操作列中 el-link 的 warning / success / danger 等类型,
-// 同时统一 hover / active / disabled 态,保证全站操作列风格一致。
+// 全站统一:表格普通操作链接使用主色(蓝色),删除等危险操作保持危险色(红色)。
// 选择器同时覆盖 Avue 行菜单(.avue-crud__menu)与任意含 el-link 的表格体单元格(手写操作列)。
-.avue-crud__menu .el-link,
-.el-table .el-table__body .cell:has(.el-link) .el-link {
+.avue-crud__menu .el-link:not(.el-link--danger),
+.el-table .el-table__body .cell:has(.el-link) .el-link:not(.el-link--danger) {
// 覆盖 Element Plus el-link 默认的 font-weight: var(--el-font-weight-primary)(500 中等粗),
// 操作列文字统一为常规字重,避免「查看/编辑/删除」等链接显得加粗。
font-weight: normal;
@@ -589,6 +587,14 @@
--el-link-disabled-text-color: var(--el-color-primary-light-5);
}
+.avue-crud__menu .el-link.el-link--danger,
+.el-table .el-table__body .cell:has(.el-link) .el-link.el-link--danger {
+ --el-link-text-color: var(--el-color-danger);
+ --el-link-hover-text-color: var(--el-color-danger-light-3);
+ --el-link-active-text-color: var(--el-color-danger-dark-2);
+ --el-link-disabled-text-color: var(--el-color-danger-light-5);
+}
+
// 全站统一:去除操作列 el-link 默认 margin,避免与 8px gap 叠加导致文字间距过宽
.avue-crud .avue-crud__menu .el-link,
.el-table .el-table__body .cell:not(.avue-crud__menu):has(> .el-link) .el-link {
@@ -652,3 +658,13 @@
gap: 6px;
}
}
+
+// 全局表格列拖拽:仅对可移动的普通列显示拖拽反馈。
+.el-table th.tms-table-column-draggable {
+ cursor: grab;
+
+ &.is-column-dragging {
+ cursor: grabbing;
+ opacity: 0.55;
+ }
+}
diff --git a/src/utils/dialog-form-label.js b/src/utils/dialog-form-label.js
index 89739a0..7e06544 100644
--- a/src/utils/dialog-form-label.js
+++ b/src/utils/dialog-form-label.js
@@ -86,7 +86,8 @@ const collectLabelGroups = form => {
};
const alignFormLabelsByColumn = form => {
- const groups = collectLabelGroups(form);
+ if (form.classList.contains('dialog-form-label-fixed')) return;
+ const groups = collectLabelGroups(form);
groups.forEach(group => {
group.labels.forEach(label => {
diff --git a/src/utils/table-column-drag.js b/src/utils/table-column-drag.js
new file mode 100644
index 0000000..0e074e4
--- /dev/null
+++ b/src/utils/table-column-drag.js
@@ -0,0 +1,176 @@
+const STORAGE_PREFIX = 'tms:table-column-order:';
+
+const getStore = instance =>
+ instance?.store ||
+ instance?.exposed?.store ||
+ instance?.proxy?.store ||
+ instance?.$?.exposed?.store ||
+ instance?.$?.ctx?.store ||
+ instance?.$?.store;
+
+const getColumnKey = column =>
+ String(column.property || column.columnKey || column.label || column.id || '');
+
+const getTableKey = (instance, columns) => {
+ const routePath = instance.$route?.path || instance.proxy?.$route?.path || window.location.pathname;
+ const signature = columns
+ .map(getColumnKey)
+ .filter(Boolean)
+ .sort()
+ .join('|');
+ return `${STORAGE_PREFIX}${routePath}:${signature}`;
+};
+
+const canDragColumn = column =>
+ column && !column.fixed && !['selection', 'index', 'expand'].includes(column.type);
+
+const getColumnFromHeader = (header, columns) =>
+ columns.find(column => header.classList.contains(column.id));
+
+const updateColumnOrder = (instance, columns) => {
+ const store = getStore(instance);
+ if (!store?.states?._columns || columns.length !== store.states._columns.value.length) return;
+ columns.forEach((column, index) => {
+ column.no = index;
+ });
+ store.states._columns.value = [...columns];
+ store.updateColumns?.();
+ store.scheduleLayout?.(true, true);
+};
+
+const restoreColumnOrder = instance => {
+ const store = getStore(instance);
+ const columns = store?.states?._columns?.value;
+ if (!Array.isArray(columns) || columns.length < 2) return;
+ const saved = JSON.parse(localStorage.getItem(getTableKey(instance, columns)) || '[]');
+ if (!Array.isArray(saved) || !saved.length) return;
+ const orderMap = new Map(saved.map((key, index) => [key, index]));
+ const ordered = [...columns].sort((a, b) => {
+ const aOrder = orderMap.get(getColumnKey(a));
+ const bOrder = orderMap.get(getColumnKey(b));
+ if (aOrder === undefined && bOrder === undefined) return a.no - b.no;
+ if (aOrder === undefined) return 1;
+ if (bOrder === undefined) return -1;
+ return aOrder - bOrder;
+ });
+ updateColumnOrder(instance, ordered);
+};
+
+const bindHeaderDrag = instance => {
+ const store = getStore(instance);
+ const columns = store?.states?._columns?.value;
+ const headerRow = instance.$el?.querySelector('.el-table__header-wrapper thead tr');
+ if (!headerRow || headerRow.__tmsColumnDragBound || !Array.isArray(columns)) return;
+ headerRow.__tmsColumnDragBound = true;
+ let draggingColumn = null;
+
+ [...headerRow.children].forEach(header => {
+ const column = getColumnFromHeader(header, columns);
+ if (!canDragColumn(column)) return;
+ header.draggable = true;
+ header.classList.add('tms-table-column-draggable');
+ });
+
+ headerRow.addEventListener('dragstart', event => {
+ const header = event.target.closest('th');
+ const currentColumns = store.states._columns.value;
+ const column = header && getColumnFromHeader(header, currentColumns);
+ if (!canDragColumn(column)) {
+ event.preventDefault();
+ return;
+ }
+ draggingColumn = column;
+ event.dataTransfer.effectAllowed = 'move';
+ header.classList.add('is-column-dragging');
+ });
+ headerRow.addEventListener('dragover', event => {
+ const header = event.target.closest('th');
+ const column = header && getColumnFromHeader(header, store.states._columns.value);
+ if (draggingColumn && canDragColumn(column)) event.preventDefault();
+ });
+ headerRow.addEventListener('drop', event => {
+ const header = event.target.closest('th');
+ const targetColumn = header && getColumnFromHeader(header, store.states._columns.value);
+ if (!draggingColumn || !canDragColumn(targetColumn) || draggingColumn === targetColumn) return;
+ event.preventDefault();
+ const ordered = [...store.states._columns.value];
+ const from = ordered.indexOf(draggingColumn);
+ if (from < 0) return;
+ const movingRight = from < ordered.indexOf(targetColumn);
+ ordered.splice(from, 1);
+ const targetIndex = ordered.indexOf(targetColumn);
+ ordered.splice(targetIndex + (movingRight ? 1 : 0), 0, draggingColumn);
+ updateColumnOrder(instance, ordered);
+ localStorage.setItem(getTableKey(instance, ordered), JSON.stringify(ordered.map(getColumnKey)));
+ });
+ headerRow.addEventListener('dragend', () => {
+ instance.$el.querySelectorAll('.is-column-dragging').forEach(item => item.classList.remove('is-column-dragging'));
+ draggingColumn = null;
+ });
+};
+
+export const setupTableColumnDrag = app => {
+ let dragging = null;
+ const restoredTables = new WeakSet();
+ const getTableInstance = element => {
+ let instance = element.closest('.el-table')?.__vueParentComponent;
+ while (instance && instance.type?.name !== 'ElTable') {
+ instance = instance.parent;
+ }
+ return instance;
+ };
+ const enableHeaderDrag = () => {
+ document.querySelectorAll('.el-table__header-wrapper th').forEach(header => {
+ if (header.closest('.el-table__fixed, .el-table__fixed-right')) return;
+ const instance = getTableInstance(header);
+ if (instance && !restoredTables.has(instance)) {
+ restoreColumnOrder(instance);
+ restoredTables.add(instance);
+ }
+ const column = getColumnFromHeader(header, getStore(instance)?.states?._columns?.value || []);
+ if (!canDragColumn(column)) return;
+ header.draggable = true;
+ header.classList.add('tms-table-column-draggable');
+ });
+ };
+ const tableObserver = new MutationObserver(enableHeaderDrag);
+ tableObserver.observe(document.documentElement, { childList: true, subtree: true });
+ setTimeout(enableHeaderDrag, 0);
+
+ document.addEventListener('dragstart', event => {
+ const header = event.target.closest?.('.el-table__header-wrapper th.tms-table-column-draggable');
+ const instance = header && getTableInstance(header);
+ const store = getStore(instance);
+ const column = header && getColumnFromHeader(header, store?.states?._columns?.value || []);
+ if (!header || !canDragColumn(column)) return;
+ dragging = { instance, column, header };
+ event.dataTransfer.effectAllowed = 'move';
+ header.classList.add('is-column-dragging');
+ });
+ document.addEventListener('dragover', event => {
+ const header = event.target.closest?.('.el-table__header-wrapper th.tms-table-column-draggable');
+ const instance = header && getTableInstance(header);
+ if (dragging && instance === dragging.instance) event.preventDefault();
+ });
+ document.addEventListener('drop', event => {
+ const header = event.target.closest?.('.el-table__header-wrapper th.tms-table-column-draggable');
+ const instance = header && getTableInstance(header);
+ const store = getStore(instance);
+ const target = header && getColumnFromHeader(header, store?.states?._columns?.value || []);
+ if (!dragging || instance !== dragging.instance || !canDragColumn(target) || target === dragging.column) return;
+ event.preventDefault();
+ const ordered = [...store.states._columns.value];
+ const from = ordered.indexOf(dragging.column);
+ if (from < 0) return;
+ const movingRight = from < ordered.indexOf(target);
+ ordered.splice(from, 1);
+ const targetIndex = ordered.indexOf(target);
+ ordered.splice(targetIndex + (movingRight ? 1 : 0), 0, dragging.column);
+ updateColumnOrder(instance, ordered);
+ localStorage.setItem(getTableKey(instance, ordered), JSON.stringify(ordered.map(getColumnKey)));
+ });
+ document.addEventListener('dragend', () => {
+ document.querySelectorAll('.is-column-dragging').forEach(item => item.classList.remove('is-column-dragging'));
+ dragging = null;
+ });
+};
diff --git a/src/views/business/components/business-crud-page.vue b/src/views/business/components/business-crud-page.vue
index e1fb1f0..3288965 100644
--- a/src/views/business/components/business-crud-page.vue
+++ b/src/views/business/components/business-crud-page.vue
@@ -2179,7 +2179,12 @@
{{ formatFileSize(row.size) }}