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