fix: 修复 Handsontable 滚动对齐与主题加载,并补齐轨迹订单表头文案
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
e201b2f458
commit
75019157b3
@ -16,6 +16,7 @@
|
||||
"McpCapabilityGuide": true,
|
||||
"McpCapabilityId": true,
|
||||
"McpFieldHint": true,
|
||||
"NanaBatchOpenOptions": true,
|
||||
"PriceAskContext": true,
|
||||
"PriceAskIntent": true,
|
||||
"PropType": true,
|
||||
@ -228,6 +229,7 @@
|
||||
"useId": true,
|
||||
"useIdle": true,
|
||||
"useImage": true,
|
||||
"useInboxUnread": true,
|
||||
"useInfiniteScroll": true,
|
||||
"useIntersectionObserver": true,
|
||||
"useInterval": true,
|
||||
@ -249,6 +251,7 @@
|
||||
"useMouseInElement": true,
|
||||
"useMousePressed": true,
|
||||
"useMutationObserver": true,
|
||||
"useNanaBatchPanel": true,
|
||||
"useNavigatorLanguage": true,
|
||||
"useNetwork": true,
|
||||
"useNow": true,
|
||||
|
||||
7
src/auto-imports.d.ts
vendored
7
src/auto-imports.d.ts
vendored
@ -208,6 +208,7 @@ declare global {
|
||||
const useId: typeof import('vue')['useId']
|
||||
const useIdle: typeof import('@vueuse/core')['useIdle']
|
||||
const useImage: typeof import('@vueuse/core')['useImage']
|
||||
const useInboxUnread: typeof import('./composables/useInboxUnread')['useInboxUnread']
|
||||
const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll']
|
||||
const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver']
|
||||
const useInterval: typeof import('@vueuse/core')['useInterval']
|
||||
@ -229,6 +230,7 @@ declare global {
|
||||
const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement']
|
||||
const useMousePressed: typeof import('@vueuse/core')['useMousePressed']
|
||||
const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver']
|
||||
const useNanaBatchPanel: typeof import('./composables/useNanaBatchPanel')['useNanaBatchPanel']
|
||||
const useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage']
|
||||
const useNetwork: typeof import('@vueuse/core')['useNetwork']
|
||||
const useNow: typeof import('@vueuse/core')['useNow']
|
||||
@ -343,6 +345,9 @@ declare global {
|
||||
export type { WaybillSource, WaybillContext } from './composables/useAskAdvisor'
|
||||
import('./composables/useAskAdvisor')
|
||||
// @ts-ignore
|
||||
export type { NanaBatchOpenOptions } from './composables/useNanaBatchPanel'
|
||||
import('./composables/useNanaBatchPanel')
|
||||
// @ts-ignore
|
||||
export type { ScanFeedbackType } from './composables/useOperationScanFeedback'
|
||||
import('./composables/useOperationScanFeedback')
|
||||
// @ts-ignore
|
||||
@ -560,6 +565,7 @@ declare module 'vue' {
|
||||
readonly useId: UnwrapRef<typeof import('vue')['useId']>
|
||||
readonly useIdle: UnwrapRef<typeof import('@vueuse/core')['useIdle']>
|
||||
readonly useImage: UnwrapRef<typeof import('@vueuse/core')['useImage']>
|
||||
readonly useInboxUnread: UnwrapRef<typeof import('./composables/useInboxUnread')['useInboxUnread']>
|
||||
readonly useInfiniteScroll: UnwrapRef<typeof import('@vueuse/core')['useInfiniteScroll']>
|
||||
readonly useIntersectionObserver: UnwrapRef<typeof import('@vueuse/core')['useIntersectionObserver']>
|
||||
readonly useInterval: UnwrapRef<typeof import('@vueuse/core')['useInterval']>
|
||||
@ -581,6 +587,7 @@ declare module 'vue' {
|
||||
readonly useMouseInElement: UnwrapRef<typeof import('@vueuse/core')['useMouseInElement']>
|
||||
readonly useMousePressed: UnwrapRef<typeof import('@vueuse/core')['useMousePressed']>
|
||||
readonly useMutationObserver: UnwrapRef<typeof import('@vueuse/core')['useMutationObserver']>
|
||||
readonly useNanaBatchPanel: UnwrapRef<typeof import('./composables/useNanaBatchPanel')['useNanaBatchPanel']>
|
||||
readonly useNavigatorLanguage: UnwrapRef<typeof import('@vueuse/core')['useNavigatorLanguage']>
|
||||
readonly useNetwork: UnwrapRef<typeof import('@vueuse/core')['useNetwork']>
|
||||
readonly useNow: UnwrapRef<typeof import('@vueuse/core')['useNow']>
|
||||
|
||||
@ -23,6 +23,36 @@
|
||||
appStore.theme === 'dark' ? 'ht-theme-classic-dark' : 'ht-theme-classic',
|
||||
);
|
||||
|
||||
/** Vite 异步 CSS 分包时,等 --ht-line-height 就绪再 new Handsontable,避免主题告警 */
|
||||
function waitForHotThemeStyles(themeName: string, timeoutMs = 3000): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const started = Date.now();
|
||||
const probe = document.createElement('div');
|
||||
probe.className = themeName;
|
||||
probe.style.cssText = 'position:absolute;left:-99999px;top:0;visibility:hidden;pointer-events:none;';
|
||||
document.body.appendChild(probe);
|
||||
|
||||
const finish = (ok: boolean) => {
|
||||
probe.remove();
|
||||
resolve(ok);
|
||||
};
|
||||
|
||||
const tick = () => {
|
||||
const lineHeight = getComputedStyle(probe).getPropertyValue('--ht-line-height').trim();
|
||||
if (lineHeight) {
|
||||
finish(true);
|
||||
return;
|
||||
}
|
||||
if (Date.now() - started >= timeoutMs) {
|
||||
finish(false);
|
||||
return;
|
||||
}
|
||||
requestAnimationFrame(tick);
|
||||
};
|
||||
tick();
|
||||
});
|
||||
}
|
||||
|
||||
// 注册所有 Handsontable 模块
|
||||
registerAllModules();
|
||||
|
||||
@ -749,12 +779,11 @@
|
||||
}
|
||||
|
||||
function applyLiquidFieldStyle(el: HTMLElement, focused = false) {
|
||||
const dark = appStore.theme === 'dark';
|
||||
// 内联 !important 压过浏览器 UA / Handsontable 默认边框(截图里那圈硬黑框)
|
||||
// 与工作台卡片同款磨砂玻璃 token(内联 !important 压过 UA / HOT 默认边框)
|
||||
el.style.setProperty('width', '100%', 'important');
|
||||
el.style.setProperty('min-width', '0', 'important');
|
||||
el.style.setProperty('max-width', '100%', 'important');
|
||||
el.style.setProperty('height', '24px', 'important');
|
||||
el.style.setProperty('height', '26px', 'important');
|
||||
el.style.setProperty('padding', '0 10px', 'important');
|
||||
el.style.setProperty('margin', '0', 'important');
|
||||
el.style.setProperty('border-width', '1px', 'important');
|
||||
@ -762,37 +791,35 @@
|
||||
el.style.setProperty(
|
||||
'border-color',
|
||||
focused
|
||||
? 'color-mix(in srgb, var(--app-primary-color) 36%, transparent)'
|
||||
: dark
|
||||
? 'var(--color-border-2)'
|
||||
: 'rgba(255, 255, 255, 0.85)',
|
||||
? 'color-mix(in srgb, var(--app-primary-color) 22%, var(--app-acrylic-border-strong, rgba(255,255,255,0.92)))'
|
||||
: 'var(--app-acrylic-border, rgba(255, 255, 255, 0.72))',
|
||||
'important'
|
||||
);
|
||||
el.style.setProperty('border-radius', '999px', 'important');
|
||||
el.style.setProperty('border-radius', '12px', 'important');
|
||||
el.style.setProperty('outline', 'none', 'important');
|
||||
el.style.setProperty('box-sizing', 'border-box', 'important');
|
||||
el.style.setProperty(
|
||||
'background',
|
||||
dark
|
||||
? focused
|
||||
? 'var(--color-bg-3)'
|
||||
: 'var(--color-fill-2)'
|
||||
: focused
|
||||
? 'linear-gradient(165deg, rgba(255,255,255,0.98) 0%, rgba(255,255,255,0.72) 55%, rgba(240,248,248,0.65) 100%)'
|
||||
: 'linear-gradient(165deg, rgba(255,255,255,0.88) 0%, rgba(255,255,255,0.5) 55%, rgba(245,247,248,0.42) 100%)',
|
||||
focused
|
||||
? 'var(--app-acrylic-hover-bg, rgba(255, 255, 255, 0.44))'
|
||||
: 'var(--app-acrylic-bg, rgba(255, 255, 255, 0.32))',
|
||||
'important'
|
||||
);
|
||||
el.style.setProperty(
|
||||
'backdrop-filter',
|
||||
'var(--app-acrylic-blur, blur(28px) saturate(160%))',
|
||||
'important'
|
||||
);
|
||||
el.style.setProperty(
|
||||
'-webkit-backdrop-filter',
|
||||
'var(--app-acrylic-blur, blur(28px) saturate(160%))',
|
||||
'important'
|
||||
);
|
||||
el.style.setProperty('backdrop-filter', dark ? 'none' : 'blur(12px) saturate(1.2)', 'important');
|
||||
el.style.setProperty('-webkit-backdrop-filter', dark ? 'none' : 'blur(12px) saturate(1.2)', 'important');
|
||||
el.style.setProperty(
|
||||
'box-shadow',
|
||||
dark
|
||||
? focused
|
||||
? '0 0 0 2px color-mix(in srgb, var(--app-primary-color) 28%, transparent)'
|
||||
: 'none'
|
||||
: focused
|
||||
? 'inset 0 1px 0 rgba(255,255,255,1), inset 0 -1px 0 rgba(0,121,121,0.06), 0 0 0 3px rgba(0,121,121,0.12), 0 3px 10px rgba(15,23,42,0.07)'
|
||||
: 'inset 0 1px 0 rgba(255,255,255,0.98), inset 0 -1px 1px rgba(15,23,42,0.03), 0 1px 2px rgba(15,23,42,0.04), 0 2px 6px rgba(15,23,42,0.03)',
|
||||
focused
|
||||
? '0 0 0 2px color-mix(in srgb, var(--app-primary-color) 16%, transparent)'
|
||||
: 'none',
|
||||
'important'
|
||||
);
|
||||
el.style.setProperty(
|
||||
@ -802,11 +829,12 @@
|
||||
);
|
||||
el.style.setProperty('font-size', '12px', 'important');
|
||||
el.style.setProperty('font-weight', '500', 'important');
|
||||
el.style.setProperty('line-height', '22px', 'important');
|
||||
el.style.setProperty('line-height', '24px', 'important');
|
||||
el.style.setProperty('font-family', 'inherit', 'important');
|
||||
el.style.setProperty('caret-color', 'var(--app-primary-color, #054f74)', 'important');
|
||||
el.style.setProperty('-webkit-appearance', 'none', 'important');
|
||||
el.style.setProperty('appearance', 'none', 'important');
|
||||
el.style.setProperty('transition', 'border-color 0.2s ease, background 0.2s ease, color 0.2s ease, box-shadow 0.2s ease', 'important');
|
||||
if (el.tagName === 'BUTTON') {
|
||||
el.style.setProperty('cursor', 'pointer', 'important');
|
||||
el.style.setProperty('text-align', 'left', 'important');
|
||||
@ -858,6 +886,15 @@
|
||||
input.value = raw;
|
||||
input.title = '双击打开筛选 · 回车搜索';
|
||||
});
|
||||
input.addEventListener('mouseenter', () => {
|
||||
if (document.activeElement === input) return;
|
||||
input.style.setProperty('background', 'var(--app-acrylic-hover-bg, rgba(255, 255, 255, 0.44))', 'important');
|
||||
input.style.setProperty('border-color', 'var(--app-acrylic-border-strong, rgba(255, 255, 255, 0.92))', 'important');
|
||||
});
|
||||
input.addEventListener('mouseleave', () => {
|
||||
if (document.activeElement === input) return;
|
||||
applyLiquidFieldStyle(input, false);
|
||||
});
|
||||
input.addEventListener('paste', (e: ClipboardEvent) => {
|
||||
e.stopPropagation();
|
||||
const clipboardData = e.clipboardData;
|
||||
@ -1311,7 +1348,8 @@
|
||||
width: '100%',
|
||||
height: tableHeight,
|
||||
licenseKey: 'non-commercial-and-evaluation',
|
||||
className: hotThemeClass.value,
|
||||
// HOT 16:主题必须用 themeName(勿塞进 className),否则会报 stylesheets missing
|
||||
themeName: hotThemeClass.value,
|
||||
language: 'zh-CN',
|
||||
readOnly: props.readOnly === true,
|
||||
// 可编辑:回车/选中后输入即可开始编辑
|
||||
@ -1806,6 +1844,8 @@
|
||||
removeFilterIconFromColumns();
|
||||
syncHybridHeaders();
|
||||
remasureActionColumnFromDom();
|
||||
// remasure/updateSettings 后 HOT 会按原生滚动条再次缩短 clone,需再对齐一次
|
||||
scheduleCloneOverlaySync();
|
||||
}
|
||||
};
|
||||
|
||||
@ -1830,24 +1870,92 @@
|
||||
holder.style.setProperty('-ms-overflow-style', 'none', 'important');
|
||||
|
||||
const hider = holder.querySelector(':scope > .wtHider') as HTMLElement | null;
|
||||
const spreader = holder.querySelector(':scope > .wtHider > .wtSpreader') as HTMLElement | null;
|
||||
// 切勿给 wtSpreader 设 min-width:它带横向虚拟滚动 left 偏移,强行拉宽会在滚到最右出现空白并导致表头/内容错位
|
||||
if (spreader) {
|
||||
spreader.style.removeProperty('min-width');
|
||||
}
|
||||
if (hider) {
|
||||
hider.style.removeProperty('min-width');
|
||||
// 先去掉强制宽度再测:仅在 stretch 铺满、无真实横向溢出时补 1px,稳住自绘横条
|
||||
const needsFakeOverflow = props.stretchColumns && holder.scrollWidth <= holder.clientWidth;
|
||||
if (needsFakeOverflow) {
|
||||
hider.style.setProperty('min-width', 'calc(100% + 1px)', 'important');
|
||||
}
|
||||
const spreader = holder.querySelector(':scope > .wtHider > .wtSpreader') as HTMLElement | null;
|
||||
if (spreader) {
|
||||
spreader.style.setProperty('min-width', 'calc(100% + 1px)', 'important');
|
||||
}
|
||||
|
||||
bindHorizontalScrollbar(holder);
|
||||
scheduleCloneOverlaySync(holder);
|
||||
syncHorizontalScrollbarMetrics(holder);
|
||||
};
|
||||
|
||||
const syncCloneOverlaysFromMaster = (holder?: HTMLElement | null) => {
|
||||
const currentHolder = holder || masterHolderEl;
|
||||
if (!currentHolder || !hotContainer.value) return;
|
||||
const top = hotContainer.value.querySelector('.ht_clone_top .wtHolder') as HTMLElement | null;
|
||||
const bottom = hotContainer.value.querySelector('.ht_clone_bottom .wtHolder') as HTMLElement | null;
|
||||
const left = hotContainer.value.querySelector('.ht_clone_left .wtHolder') as HTMLElement | null;
|
||||
const corner = hotContainer.value.querySelector('.ht_clone_top_left_corner .wtHolder') as HTMLElement | null;
|
||||
if (top && top.scrollLeft !== currentHolder.scrollLeft) top.scrollLeft = currentHolder.scrollLeft;
|
||||
if (bottom && bottom.scrollLeft !== currentHolder.scrollLeft) bottom.scrollLeft = currentHolder.scrollLeft;
|
||||
if (left && left.scrollTop !== currentHolder.scrollTop) left.scrollTop = currentHolder.scrollTop;
|
||||
if (corner) {
|
||||
if (corner.scrollLeft !== currentHolder.scrollLeft) corner.scrollLeft = currentHolder.scrollLeft;
|
||||
if (corner.scrollTop !== currentHolder.scrollTop) corner.scrollTop = currentHolder.scrollTop;
|
||||
}
|
||||
};
|
||||
|
||||
/** 隐藏原生纵条后 HOT 仍按滚动条宽度缩短 clone_top,导致表头比内容窄一截 */
|
||||
const syncCloneOverlayWidths = (holder?: HTMLElement | null) => {
|
||||
const currentHolder = holder || masterHolderEl;
|
||||
if (!currentHolder || !hotContainer.value) return;
|
||||
const w = currentHolder.clientWidth;
|
||||
if (w <= 0) return;
|
||||
const widthPx = `${w}px`;
|
||||
// 只校准顶部冻结表头;切勿拉宽 ht_clone_bottom,否则会盖住主表导致滚轮/拖动失效
|
||||
const cloneTop = hotContainer.value.querySelector('.ht_clone_top') as HTMLElement | null;
|
||||
const cloneTopHolder = hotContainer.value.querySelector('.ht_clone_top .wtHolder') as HTMLElement | null;
|
||||
if (cloneTop) {
|
||||
cloneTop.style.setProperty('width', widthPx, 'important');
|
||||
cloneTop.style.setProperty('right', '0px', 'important');
|
||||
cloneTop.style.setProperty('z-index', '180', 'important');
|
||||
}
|
||||
if (cloneTopHolder) cloneTopHolder.style.setProperty('width', widthPx, 'important');
|
||||
|
||||
// 无底部冻结行时,底 clone 应为不可点的空层
|
||||
const cloneBottom = hotContainer.value.querySelector('.ht_clone_bottom') as HTMLElement | null;
|
||||
if (cloneBottom) {
|
||||
cloneBottom.style.removeProperty('width');
|
||||
cloneBottom.style.setProperty('pointer-events', 'none', 'important');
|
||||
cloneBottom.style.setProperty('height', '0px', 'important');
|
||||
const bottomHolder = cloneBottom.querySelector('.wtHolder') as HTMLElement | null;
|
||||
if (bottomHolder) {
|
||||
bottomHolder.style.removeProperty('width');
|
||||
bottomHolder.style.setProperty('pointer-events', 'none', 'important');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** HOT 布局可能在 afterRender 之后才写回 clone 宽度,延迟再对齐一次 */
|
||||
const scheduleCloneOverlaySync = (holder?: HTMLElement | null) => {
|
||||
syncCloneOverlayWidths(holder);
|
||||
syncCloneOverlaysFromMaster(holder);
|
||||
requestAnimationFrame(() => {
|
||||
syncCloneOverlayWidths(holder);
|
||||
syncCloneOverlaysFromMaster(holder);
|
||||
});
|
||||
};
|
||||
|
||||
const onMasterHolderScroll = () => {
|
||||
// 滚动时 HOT 会把 clone_top 宽度改回“预留原生纵条”的窄值,需持续拉齐,否则右侧纵条会顶到表头空隙里
|
||||
syncCloneOverlaysFromMaster();
|
||||
syncCloneOverlayWidths();
|
||||
if (scrollSyncRaf != null) return;
|
||||
scrollSyncRaf = window.requestAnimationFrame(() => {
|
||||
scrollSyncRaf = null;
|
||||
syncCloneOverlayWidths();
|
||||
syncHorizontalThumbFromScroll();
|
||||
syncVerticalThumbFromScroll();
|
||||
syncVerticalScrollbarMetrics();
|
||||
});
|
||||
};
|
||||
|
||||
@ -1885,28 +1993,38 @@
|
||||
const trackEl = verticalScrollbarTrackRef.value;
|
||||
if (!currentHolder || !trackEl) return;
|
||||
|
||||
// 纵向滚动条从“主数据区(wtHolder)顶部”开始,避免任何表头样式差异带来的重叠
|
||||
const holderHost = hotContainer.value?.querySelector('.ht_master .wtHolder') as HTMLElement | null;
|
||||
// 纵条从冻结表头下沿开始,避免盖到表头;高度对齐主数据可视区剩余高度
|
||||
const containerEl = containerRef.value;
|
||||
let nextTop = 40;
|
||||
if (holderHost && containerEl) {
|
||||
const holderRect = holderHost.getBoundingClientRect();
|
||||
const cloneTop = hotContainer.value?.querySelector('.ht_clone_top') as HTMLElement | null;
|
||||
const hScroll = horizontalScrollbarTrackRef.value;
|
||||
let nextTop = 0;
|
||||
let trackHeight = Math.max(currentHolder.clientHeight, 0);
|
||||
|
||||
if (containerEl) {
|
||||
const containerRect = containerEl.getBoundingClientRect();
|
||||
// +1 作为边界安全余量,避免 1px 级别重叠
|
||||
nextTop = Math.max(Math.ceil(holderRect.top - containerRect.top) + 1, 0);
|
||||
} else if (holderHost) {
|
||||
nextTop = Math.max(Math.ceil(holderHost.offsetTop) + 1, 0);
|
||||
const holderRect = currentHolder.getBoundingClientRect();
|
||||
if (cloneTop) {
|
||||
const cloneRect = cloneTop.getBoundingClientRect();
|
||||
// +2:与冻结表头留出缝,避免贴边看起来像纵条顶进表头
|
||||
nextTop = Math.max(Math.ceil(cloneRect.bottom - containerRect.top) + 2, 0);
|
||||
} else {
|
||||
nextTop = Math.max(Math.ceil(holderRect.top - containerRect.top), 0);
|
||||
}
|
||||
const bottomLimit = hScroll && customScrollbar.visible
|
||||
? Math.floor(hScroll.getBoundingClientRect().top - containerRect.top)
|
||||
: Math.floor(holderRect.bottom - containerRect.top);
|
||||
trackHeight = Math.max(bottomLimit - nextTop, 0);
|
||||
}
|
||||
|
||||
if (customVerticalScrollbar.trackTop !== nextTop) {
|
||||
customVerticalScrollbar.trackTop = nextTop;
|
||||
}
|
||||
|
||||
const trackHeight = Math.max(currentHolder.clientHeight, 0);
|
||||
customVerticalScrollbar.trackVisibleHeight = trackHeight;
|
||||
const scrollHeight = Math.max(currentHolder.scrollHeight, currentHolder.clientHeight + 1);
|
||||
const maxScrollTop = Math.max(scrollHeight - currentHolder.clientHeight, 0);
|
||||
|
||||
customVerticalScrollbar.visible = maxScrollTop > 0;
|
||||
customVerticalScrollbar.visible = maxScrollTop > 0 && trackHeight > 0;
|
||||
customVerticalScrollbar.trackHeight = trackHeight;
|
||||
|
||||
const thumbHeight = maxScrollTop > 0
|
||||
@ -2121,18 +2239,15 @@
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
// 日夜切换:同步 HOT 主题类并刷新表头液态输入样式
|
||||
// 日夜切换:走 HOT 官方 useTheme,确保 CSS 变量与内部主题状态一致
|
||||
watch(hotThemeClass, (cls) => {
|
||||
const root = hotContainer.value;
|
||||
if (root) {
|
||||
root.classList.remove('ht-theme-classic', 'ht-theme-classic-dark');
|
||||
root.classList.add(cls);
|
||||
}
|
||||
// Handsontable 会把 className 写到表格根节点
|
||||
const hotRoot = hotInstance?.rootElement as HTMLElement | undefined;
|
||||
if (hotRoot) {
|
||||
hotRoot.classList.remove('ht-theme-classic', 'ht-theme-classic-dark');
|
||||
hotRoot.classList.add(cls);
|
||||
if (hotInstance) {
|
||||
hotInstance.useTheme(cls);
|
||||
}
|
||||
nextTick(() => {
|
||||
syncHybridHeaders();
|
||||
@ -2151,6 +2266,21 @@
|
||||
}
|
||||
);
|
||||
|
||||
/** 主题 CSS 就绪后再创建实例(首屏 / 数据晚到补初始化共用) */
|
||||
async function initHandsontableWhenReady(options?: { finalize?: boolean }) {
|
||||
if (hotInstance) return;
|
||||
const themeReady = await waitForHotThemeStyles(hotThemeClass.value);
|
||||
if (!themeReady) {
|
||||
console.warn('[HandsontablePro] theme CSS not ready, init anyway:', hotThemeClass.value);
|
||||
}
|
||||
if (hotInstance) return;
|
||||
initHandsontable();
|
||||
if (options?.finalize !== false) {
|
||||
finalizeInit();
|
||||
setupResizeObserver();
|
||||
}
|
||||
}
|
||||
|
||||
// 监听数据变化
|
||||
watch(() => props.data, () => {
|
||||
if (ignoreDataWatch) return;
|
||||
@ -2158,9 +2288,8 @@
|
||||
updateData();
|
||||
} else {
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
initHandsontable();
|
||||
}, 200);
|
||||
// 仅补创建;finalize/resize 留给 onMounted,避免重复观察
|
||||
void initHandsontableWhenReady({ finalize: false });
|
||||
});
|
||||
}
|
||||
}, { deep: true });
|
||||
@ -2245,6 +2374,10 @@
|
||||
let resizeObserver: ResizeObserver | null = null;
|
||||
const setupResizeObserver = () => {
|
||||
if (!containerRef.value) return;
|
||||
if (resizeObserver) {
|
||||
resizeObserver.disconnect();
|
||||
resizeObserver = null;
|
||||
}
|
||||
resizeObserver = new ResizeObserver((entries) => {
|
||||
const entry = entries[0];
|
||||
if (!entry || !hotInstance) return;
|
||||
@ -2280,12 +2413,7 @@
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
// 确保样式已加载后再初始化
|
||||
setTimeout(() => {
|
||||
initHandsontable();
|
||||
finalizeInit();
|
||||
setupResizeObserver();
|
||||
}, 200);
|
||||
void initHandsontableWhenReady();
|
||||
});
|
||||
|
||||
// 添加窗口大小变化监听器
|
||||
@ -2444,6 +2572,41 @@
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
// 隐藏原生纵条后 HOT 仍按 ~15px 缩短冻结表头;拉满宽度,避免右侧纵条从 freeze 表头旁“冒出来”
|
||||
.ht_clone_top {
|
||||
width: 100% !important;
|
||||
right: 0 !important;
|
||||
left: 0 !important;
|
||||
z-index: 180 !important; // 高于自绘纵条,右上角由表头盖住
|
||||
background: var(--color-fill-2) !important;
|
||||
box-sizing: border-box !important;
|
||||
|
||||
.wtHolder {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
// 表头底边压住纵条顶,视觉上纵条绝不进冻结区
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 1px;
|
||||
background: var(--color-border-2);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 无底部冻结行时,底 clone 常被撑成整表透明层,挡住滚轮与纵条拖动
|
||||
.ht_clone_bottom {
|
||||
pointer-events: none !important;
|
||||
height: 0 !important;
|
||||
max-height: 0 !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.wtHolder {
|
||||
border: none !important;
|
||||
}
|
||||
@ -2463,10 +2626,8 @@
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
|
||||
// 主表 wtHider:stretchH 拉满后 HOT 判定无横向滚动,刷新后横条会消失;略宽于视口以稳定显示横条
|
||||
> .wtHider {
|
||||
min-width: calc(100% + 1px) !important;
|
||||
}
|
||||
// wtHider 的 1px 溢出由 ensureHorizontalScrollbarVisible 按需内联设置;
|
||||
// 勿写死 min-width,否则宽表滚到最右会出现空白列、表头与内容错位
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0 !important;
|
||||
@ -2742,19 +2903,20 @@
|
||||
width: 100% !important;
|
||||
min-width: 0 !important;
|
||||
max-width: 100%;
|
||||
height: 24px !important;
|
||||
height: 26px !important;
|
||||
padding: 0 10px !important;
|
||||
border-radius: 999px !important;
|
||||
/* 工作台卡片同款:磨砂玻璃 + 亮边 */
|
||||
border-radius: 12px !important;
|
||||
border-width: 1px !important;
|
||||
border-style: solid !important;
|
||||
border-color: var(--color-border-2) !important;
|
||||
background: var(--color-bg-2) !important;
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
border-color: var(--app-acrylic-border, rgba(255, 255, 255, 0.72)) !important;
|
||||
background: var(--app-acrylic-bg, rgba(255, 255, 255, 0.32)) !important;
|
||||
backdrop-filter: var(--app-acrylic-blur, blur(28px) saturate(160%));
|
||||
-webkit-backdrop-filter: var(--app-acrylic-blur, blur(28px) saturate(160%));
|
||||
box-shadow: none !important;
|
||||
font-size: 12px !important;
|
||||
font-weight: 500 !important;
|
||||
line-height: 22px !important;
|
||||
line-height: 24px !important;
|
||||
color: var(--color-text-2) !important;
|
||||
outline: none !important;
|
||||
box-sizing: border-box !important;
|
||||
@ -2762,20 +2924,30 @@
|
||||
caret-color: var(--app-primary-color, #054f74);
|
||||
-webkit-appearance: none !important;
|
||||
appearance: none !important;
|
||||
transition:
|
||||
border-color 0.2s ease,
|
||||
background 0.2s ease,
|
||||
color 0.2s ease,
|
||||
box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
:deep(.ht-header-hybrid-input) {
|
||||
&:hover {
|
||||
background: var(--color-fill-2) !important;
|
||||
border-color: var(--app-acrylic-border-strong, rgba(255, 255, 255, 0.92)) !important;
|
||||
background: var(--app-acrylic-hover-bg, rgba(255, 255, 255, 0.44)) !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:focus-visible {
|
||||
color: var(--color-text-1) !important;
|
||||
border-color: color-mix(in srgb, var(--app-primary-color) 36%, transparent) !important;
|
||||
background: var(--color-bg-2) !important;
|
||||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--app-primary-color) 22%, transparent) !important;
|
||||
border-color: color-mix(
|
||||
in srgb,
|
||||
var(--app-primary-color) 22%,
|
||||
var(--app-acrylic-border-strong, rgba(255, 255, 255, 0.92))
|
||||
) !important;
|
||||
background: var(--app-acrylic-hover-bg, rgba(255, 255, 255, 0.44)) !important;
|
||||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--app-primary-color) 16%, transparent) !important;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
@ -2798,6 +2970,8 @@
|
||||
|
||||
&:hover {
|
||||
color: var(--color-text-2) !important;
|
||||
border-color: var(--app-acrylic-border-strong, rgba(255, 255, 255, 0.92)) !important;
|
||||
background: var(--app-acrylic-hover-bg, rgba(255, 255, 255, 0.44)) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2938,7 +3112,8 @@
|
||||
border-left: 0.5px solid #f0f0f0;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
z-index: 3;
|
||||
// 高于 ht_clone_bottom(130),低于冻结表头(180),避免滚到表头右上角时纵条压住表头
|
||||
z-index: 150;
|
||||
opacity: 0.92;
|
||||
}
|
||||
|
||||
|
||||
@ -5046,6 +5046,7 @@ export default {
|
||||
'track.order.drawer.tabs.small': 'Small package',
|
||||
'track.order.drawer.tabs.main': 'Main order',
|
||||
'track.order.table.orderType': 'Order type',
|
||||
'track.order.table.orderNo': 'Order number',
|
||||
'track.order.table.customsOrderNo': 'Customer order number',
|
||||
'track.order.table.customerShortName': 'Customer short name',
|
||||
'track.order.table.thirdPartyOrderNo': 'Last mile order number',
|
||||
@ -5065,6 +5066,7 @@ export default {
|
||||
'track.order.table.product': 'Sales product',
|
||||
'track.order.table.channel': 'Service channel',
|
||||
'track.order.table.apiCode': 'API service provider',
|
||||
'track.order.table.countryCode': 'Country',
|
||||
'track.order.table.destCountryCode': 'Country',
|
||||
'track.order.table.province': 'Province/State',
|
||||
'track.order.table.city': 'City',
|
||||
|
||||
@ -5116,6 +5116,7 @@ export default {
|
||||
'track.order.drawer.tabs.package': '大包',
|
||||
'track.order.drawer.tabs.main': '主单',
|
||||
'track.order.table.orderType': '订单类型',
|
||||
'track.order.table.orderNo': '订单号',
|
||||
'track.order.table.customsOrderNo': '客户单号',
|
||||
'track.order.table.customerShortName': '客户简称',
|
||||
'track.order.table.thirdPartyOrderNo': '尾程单号',
|
||||
@ -5135,6 +5136,7 @@ export default {
|
||||
'track.order.table.product': '销售产品',
|
||||
'track.order.table.channel': '服务渠道',
|
||||
'track.order.table.apiCode': 'API服务商',
|
||||
'track.order.table.countryCode': '国家',
|
||||
'track.order.table.destCountryCode': '国家',
|
||||
'track.order.table.province': '州省',
|
||||
'track.order.table.city': '城市',
|
||||
|
||||
@ -51,8 +51,7 @@ function isH5LikePath(path: string) {
|
||||
normalized === '/h5' ||
|
||||
normalized.startsWith('/h5/') ||
|
||||
normalized.startsWith('/wechat/') ||
|
||||
normalized === '/track/query' ||
|
||||
normalized.startsWith('/track/')
|
||||
normalized === '/track/query'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user