This commit is contained in:
xiaolipro 2026-09-07 10:28:09 +08:00
commit 152e281cdd
58 changed files with 793 additions and 403 deletions

View File

@ -31,7 +31,7 @@ alwaysApply: true
- HandsontablePro 列定义用 `key` + `label`,勿用 `data` + `title`
- HandsontablePro 固定列序:操作(可选,内容自适应宽)→ 序号(50px)→ 复选框(可选,60px)→ 数据;`stretch-columns` 只拉宽数据列,勿再传 `action-column-position` / `action-column-width`
- 列表页高度:`flex: 1; min-height: 0`,`.list-wrap` 勿写死 min-height
- 分页 10/30/50/100,默认 30
- 分页 10/30/50/100,默认 50
- 新页面 path 与 API 前缀冲突时须配置 nginx SPA 回退(见规范 §十五)
### 权限

View File

@ -4,6 +4,13 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>9610跨境电商出口方案选择指南 | IDE国际物流</title>
<meta name="description" content="IDE 9610跨境电商出口指南:报关、退税与专线物流怎么选,一看就懂。">
<meta property="og:title" content="9610跨境电商出口方案选择指南 | IDE国际物流">
<meta property="og:description" content="IDE 9610跨境电商出口指南:报关、退税与专线物流怎么选,一看就懂。">
<meta property="og:image" content="https://ideship.com/site/assets/img/og-cover.png">
<meta property="og:url" content="https://ideship.com/9610.html">
<meta property="og:type" content="website">
<script src="assets/js/og-meta.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -712,6 +712,10 @@ document.addEventListener('DOMContentLoaded', () => {
if (window.IDEAdvisor) {
window.IDEAdvisor.setLanguage(isChinese ? 'zh' : 'en');
}
if (window.IDEOpenGraph && typeof window.IDEOpenGraph.refresh === 'function') {
window.IDEOpenGraph.refresh();
}
}
function resolveSavedLanguage() {

View File

@ -0,0 +1,78 @@
/**
* Open Graph:复用 <title> / meta[name=description],补齐绝对 og:url / og:image。
* 爬虫可读 head 内静态 meta;本脚本在浏览器中按当前域名校正。
*/
(function () {
var PROD_ORIGIN = 'https://ideship.com';
var OG_IMAGE_PATH = '/site/assets/img/og-cover.png';
var OG_IMAGE_REL = 'assets/img/og-cover.png';
function truncate(text, max) {
var value = String(text || '')
.replace(/\s+/g, ' ')
.trim();
if (value.length <= max) return value;
return value.slice(0, max);
}
function ensureMeta(property, content) {
if (!content) return;
var node = document.querySelector('meta[property="' + property + '"]');
if (!node) {
node = document.createElement('meta');
node.setAttribute('property', property);
document.head.appendChild(node);
}
node.setAttribute('content', content);
}
function resolveOrigin() {
if (location.protocol === 'http:' || location.protocol === 'https:') {
return location.origin;
}
return PROD_ORIGIN;
}
function resolvePageUrl(origin) {
if (location.protocol === 'file:') {
return /9610\.html$/i.test(location.pathname || '')
? PROD_ORIGIN + '/9610.html'
: PROD_ORIGIN + '/';
}
var path = location.pathname || '/';
if (/\/index\.html$/i.test(path)) {
path = path.replace(/\/index\.html$/i, '/') || '/';
}
if (path === '/site' || path === '/site/' || path === '/site/index.html') {
path = '/';
}
return origin + path + (location.search || '');
}
function resolveImageUrl(origin) {
if (origin === PROD_ORIGIN || /(^|\.)ideship\.com$/i.test(location.hostname || '')) {
return origin + OG_IMAGE_PATH;
}
try {
return new URL(OG_IMAGE_REL, location.href).href;
} catch (e) {
return origin + '/assets/img/og-cover.png';
}
}
function applyOpenGraph() {
var title = truncate(document.title, 30);
var descNode = document.querySelector('meta[name="description"]');
var description = truncate(descNode && descNode.getAttribute('content'), 60);
var origin = resolveOrigin();
ensureMeta('og:title', title);
if (description) ensureMeta('og:description', description);
ensureMeta('og:image', resolveImageUrl(origin));
ensureMeta('og:url', resolvePageUrl(origin));
ensureMeta('og:type', 'website');
}
applyOpenGraph();
window.IDEOpenGraph = { refresh: applyOpenGraph };
})();

View File

@ -9,6 +9,14 @@
<meta content="IDE 面向中小卖家的跨境物流。全球数字化一站式跨境物流服务商。注册登记后即可询价,一口价不砍,专属顾问跟到签收。" name="description">
<meta content="IDE, 跨境物流, 中小卖家, 9610, 疑难单, 出口退税, 欧美专线, 东南亚专线" name="keywords">
<!-- Open Graph(标题/描述复用上方 title、description;图片为 478×478) -->
<meta property="og:title" content="IDE - 跨境物流,全球即递">
<meta property="og:description" content="IDE 面向中小卖家的跨境物流。全球数字化一站式服务,一口价不砍。">
<meta property="og:image" content="https://ideship.com/site/assets/img/og-cover.png">
<meta property="og:url" content="https://ideship.com/">
<meta property="og:type" content="website">
<script src="assets/js/og-meta.js"></script>
<!-- Favicons(logo1 已入库;favicon.png 由 postbuild 同步) -->
<link href="assets/img/logo1.png" rel="icon" type="image/png">
<link href="assets/img/apple-touch-icon.png" rel="apple-touch-icon">

View File

@ -237,7 +237,7 @@ flowchart TB
| `sku` | `WarehouseSku` | `views/warehouse/sku/index.vue` | `Warehouse.Sku.Query` |
| `inboundNotice` | `WarehouseInboundNotice` | `views/warehouse/inboundNotice/index.vue` | `Warehouse.InboundNotice.Query` |
列表页布局遵循 `docs/前端页面设计规范.md`:`xxx-container`、`table-toolbar`、分页右上角、`PaginationPro` 默认 30 条。
列表页布局遵循 `docs/前端页面设计规范.md`:`xxx-container`、`table-toolbar`、分页右上角、`PaginationPro` 默认 50 条。
### 2.3 RBAC 权限码

View File

@ -302,7 +302,7 @@ itemList: [
- **位置(必选)**:所有分页组件(`PaginationPro`、Arco `a-pagination`、项目内其它分页封装等)须置于**列表的右上角**——即放在**紧邻列表上方**的工具栏**右侧**(`table-toolbar` / `.list-toolbar` 的 `.toolbar-right`,与 `toolbar-left` 同一行),与列表左右对齐、视觉上位于列表区域上方靠右。禁止将分页放在表格**下方**、列表**底部**、或单独居中另起一行(与「二、2.5.3」「2.5.4」工具栏约定一致)。
- 可选 pageSize:`10 / 30 / 50 / 100`(按需裁剪)
- 默认 pageSize:`30`
- 默认 pageSize:`50`
- 接口参数:`pageIndex`、`pageSize`;返回:`totalCount`、`items` 或 `data.list`
### 7.4 操作列
@ -547,7 +547,7 @@ const { t } = useI18n();
- [ ] 表单使用 FormPro 或统一 label/span
- [ ] 表格优先 TablePro / CustomList / HandsontablePro
- [ ] **HandsontablePro** 列定义使用 **`key` + `label`**,勿用 `data` + `title`(**§7.5**)
- [ ] 分页组件置于**列表上方工具栏右侧**(列表右上角),**§7.3**;分页 10/30/50/100,默认 30
- [ ] 分页组件置于**列表上方工具栏右侧**(列表右上角),**§7.3**;分页 10/30/50/100,默认 50
- [ ] 操作按钮按权限使用 `hasPermission`
- [ ] 路由 meta 配置 `permission`
- [ ] **新增前端页面路径**若落在 `/order/`、`/channel/`、`/system/` 等与 Admin API 同源前缀下,须在 **`nginx-default.conf` / `nginx-docker.conf`** 增加 `^~` 的 `try_files /index.html =200` 规则(**§十五**),避免刷新/直链 404

View File

@ -632,3 +632,10 @@ div.main-menu[data-v-eb552580] {
font-size: 12px !important;
font-weight: bold !important;
}
/* 工具栏已选条数:数字默认红色 */
.selected-count .count-number {
color: #f53f3f;
font-weight: 600;
margin: 0 2px;
}

View File

@ -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;
if (hider) {
hider.style.setProperty('min-width', 'calc(100% + 1px)', 'important');
}
const spreader = holder.querySelector(':scope > .wtHider > .wtSpreader') as HTMLElement | null;
// 切勿给 wtSpreader 设 min-width:它带横向虚拟滚动 left 偏移,强行拉宽会在滚到最右出现空白并导致表头/内容错位
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);
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();
});
// 添加窗口大小变化监听器
@ -2443,6 +2571,41 @@
.ht_clone_corner {
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;
}

View File

@ -67,7 +67,7 @@
},
defaultPageSize: {
type: Number,
default: null, // null 表示使用默认值 300
default: null, // null 表示使用统一默认值 50
},
});
const attrs = useAttrs();
@ -129,16 +129,16 @@
return baseSizes;
};
// 确定默认 pageSize(按统一分页规则,默认 30)
// 确定默认 pageSize(按统一分页规则,默认 50)
const getDefaultPageSize = () => {
if (props.defaultPageSize) {
return props.defaultPageSize;
}
if (props.maxPageSize) {
// 有最大限制时,默认取 30,但不超过 maxPageSize
return Math.min(30, props.maxPageSize);
// 有最大限制时,默认取 50,但不超过 maxPageSize
return Math.min(50, props.maxPageSize);
}
return 30;
return 50;
};
const tablePage = reactive<VxeGridPropTypes.PagerConfig>({

View File

@ -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',

View File

@ -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': '城市',

View File

@ -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'
);
}

View File

@ -50,7 +50,7 @@ import { computed } from 'vue';
{
totalCount: 0,
currentPage: 1,
pageSize: 30,
pageSize: 50,
serialNoHeader: '',
actionColumnHeader: '',
customCellRenderer: undefined,

View File

@ -71,7 +71,7 @@
showPagination: true,
totalCount: 0,
currentPage: 1,
pageSize: 30,
pageSize: 50,
pageSizes: () => [10, 30, 50, 100],
}
);

View File

@ -359,7 +359,7 @@ export function useKpiAreaPage(areaType: KpiAreaType) {
const totalCount = ref(0);
const currentPage = ref(1);
const pageSize = ref(30);
const pageSize = ref(50);
const tableLoading = ref(false);
const leftLoading = ref(false);

View File

@ -47,7 +47,7 @@ type IssueTemplateRow = {
const tableData = ref<IssueTemplateRow[]>([])
const currentPage = ref(1)
const pageSize = ref(30)
const pageSize = ref(50)
const totalCount = ref(0)
const listLoading = ref(false)
const organizationOptions = ref<Array<{ label: string; value: string }>>([])
@ -128,7 +128,7 @@ function formatNotifyMethods(methods: string[]) {
const found = NOTIFY_METHOD_OPTIONS.find((item) => item.value === code)
return found ? t(found.labelKey) : code
})
.join('、')
.join('�)
}
function mapRow(r: any): IssueTemplateRow {
@ -443,7 +443,7 @@ const { tableContainerRef, tableHeight } = useTableContainerHeight()
const handsontableHeight = computed(() => Math.max(tableHeight.value || 0, 400))
function customCellRenderer(_row: number, col: number, _value: any, rowData: IssueTemplateRow): string | null {
// 固定列序:操作 + 序号 → 数据从 col 2 起
// 固定列åº<EFBFBD>:æ“<EFBFBD>ä½?+ åº<EFBFBD>å<EFBFBD>· â†?æ•°æ<EFBFBD>®ä»?col 2 èµ?
const dataColIndex = col - 2
if (dataColIndex < 0 || dataColIndex >= hotColumns.value.length) return null
const colKey = hotColumns.value[dataColIndex].key
@ -466,7 +466,7 @@ function customCellRenderer(_row: number, col: number, _value: any, rowData: Iss
? t('channel.issueTemplate.handlerType.customer')
: t('channel.issueTemplate.handlerType.user')
const detail = rowData.handlerType === 'user' && rowData.handlerUserName
? `:${rowData.handlerUserName}`
? `ï¼?{rowData.handlerUserName}`
: ''
return `<span>${typeLabel}${detail}</span>`
}

View File

@ -35,7 +35,7 @@
const tableData = ref<Record<string, unknown>[]>([]);
const pagination = ref({
current: 1,
pageSize: 30,
pageSize: 50,
total: 0,
showTotal: true,
showPageSize: true,

View File

@ -382,7 +382,7 @@
const searchModel = reactive<any>({ keyword: '' });
const totalCount = ref(0);
const currentPage = ref(1);
const pageSize = ref(30);
const pageSize = ref(50);
const tableLoading = ref(false);
const leftLoading = ref(false);

View File

@ -445,7 +445,7 @@
const searchModel = reactive<any>({ keyword: '' });
const totalCount = ref(0);
const currentPage = ref(1);
const pageSize = ref(30);
const pageSize = ref(50);
const tableLoading = ref(false);
const leftLoading = ref(false);
@ -480,7 +480,7 @@
]);
function hotCellRenderer(_row: number, col: number, _value: any, rowData: any): string | null {
// ChannelConfigHotTable:操作 + 序号 → 数据从 col 2 起
// ChannelConfigHotTable:æ“<EFBFBD>ä½?+ åº<EFBFBD>å<EFBFBD>· â†?æ•°æ<EFBFBD>®ä»?col 2 èµ?
const dataColIndex = col - 2;
if (dataColIndex < 0 || dataColIndex >= hotColumns.value.length) return null;
const colKey = hotColumns.value[dataColIndex].key;

View File

@ -206,7 +206,7 @@ const viewMode = ref<ViewMode>('detail')
const dateRange = ref<any[]>([])
const currentPage = ref(1)
const pageSize = ref(30)
const pageSize = ref(50)
const feeItemDictRows = ref<DictDto[]>([])
const columnConfigState = ref<ColumnConfigState>(

View File

@ -271,7 +271,7 @@ const categoryFilter = ref<number | undefined>()
const statusFilter = ref<string | undefined>()
const audienceFilter = ref<string | undefined>()
const currentPage = ref(1)
const pageSize = ref(30)
const pageSize = ref(50)
const modalVisible = ref(false)
const modalLoading = ref(false)

View File

@ -172,7 +172,7 @@ const listLoading = ref(false)
const orderNo = ref('')
const statusFilter = ref<number | undefined>(undefined)
const currentPage = ref(1)
const pageSize = ref(30)
const pageSize = ref(50)
const adjustVisible = ref(false)
const adjustLoading = ref(false)

View File

@ -166,7 +166,7 @@ const customerCode = ref('')
const statusFilter = ref<number | undefined>(1)
const withdrawType = ref<number | undefined>(undefined)
const currentPage = ref(1)
const pageSize = ref(30)
const pageSize = ref(50)
const rejectVisible = ref(false)
const rejectLoading = ref(false)
@ -220,7 +220,7 @@ function statusTone(status: number): StatusTagTone {
}
function customCellRenderer(_row: number, col: number, _value: unknown, rowData: WithdrawRow): string | null {
// 固定列序:操作 + 序号 → 数据从 col 2 起
// 固定列åº<EFBFBD>:æ“<EFBFBD>ä½?+ åº<EFBFBD>å<EFBFBD>· â†?æ•°æ<EFBFBD>®ä»?col 2 èµ?
const dataColIndex = col - 2
if (dataColIndex < 0 || dataColIndex >= hotColumns.value.length) return null
const colKey = hotColumns.value[dataColIndex].key

View File

@ -169,7 +169,7 @@ const listLoading = ref(false)
const customerCode = ref('')
const changeType = ref<number | undefined>(undefined)
const currentPage = ref(1)
const pageSize = ref(30)
const pageSize = ref(50)
const adjustVisible = ref(false)
const adjustLoading = ref(false)

View File

@ -158,7 +158,7 @@ const listLoading = ref(false)
const keyword = ref('')
const statusFilter = ref<number | undefined>(undefined)
const currentPage = ref(1)
const pageSize = ref(30)
const pageSize = ref(50)
const modalVisible = ref(false)
const modalLoading = ref(false)

View File

@ -329,7 +329,7 @@ const totalCount = ref(0)
const listLoading = ref(false)
const keyword = ref('')
const currentPage = ref(1)
const pageSize = ref(30)
const pageSize = ref(50)
const drawerVisible = ref(false)
const drawerLoading = ref(false)

View File

@ -34,7 +34,7 @@ const listLoading = ref(false)
const confirmLoading = ref(false)
const queueList = ref<any[]>([])
const currentPage = ref(1)
const pageSize = ref(30)
const pageSize = ref(50)
const totalCount = ref(0)
const selectedOrderId = ref<number | null>(null)
const selectedQueueRows = ref<any[]>([])

View File

@ -195,7 +195,7 @@ const submitting = ref(false)
const lookingUp = ref(false)
const listLoading = ref(false)
const currentPage = ref(1)
const pageSize = ref(30)
const pageSize = ref(50)
const headerSearchParams = reactive<Record<string, string>>({})
const NUMBER_FILTER_KEYS = ['waybillNumber'] as const
let softPollTimer: ReturnType<typeof setInterval> | null = null

View File

@ -179,7 +179,7 @@ const submitting = ref(false)
const lookingUp = ref(false)
const listLoading = ref(false)
const currentPage = ref(1)
const pageSize = ref(30)
const pageSize = ref(50)
const headerSearchParams = reactive<Record<string, string>>({})
const NUMBER_FILTER_KEYS = ['waybillNumber'] as const

View File

@ -1,7 +1,7 @@
<template>
<div class="pl-page">
<div class="pl-col pl-col--main">
<!-- 卡片1:扫码打印 -->
<!-- å<EFBFBD>¡ç‰‡1:扫ç <EFBFBD>打å<EFBFBD>?-->
<section class="pl-card pl-card--scan">
<div class="pl-card__body pl-scan-bar">
<div class="pl-scan-bar__row pl-scan-bar__row--scan">
@ -62,7 +62,7 @@
</div>
</section>
<!-- 卡片2:核对信息 -->
<!-- å<EFBFBD>¡ç‰‡2:核对信æ<EFBFBD>?-->
<section class="pl-card pl-card--verify">
<div class="pl-card__body pl-body">
<a-spin :loading="lookingUp" class="pl-body-spin">
@ -88,7 +88,7 @@
</div>
</div>
<!-- 已命中 -->
<!-- 已命�-->
<div v-else-if="lookup?.matched" class="pl-panel pl-panel--hit">
<div class="pl-hero">
<div class="pl-hero__nos">
@ -187,7 +187,7 @@
</div>
</div>
<!-- 空态 -->
<!-- 空�-->
<div v-else class="pl-empty">
<p class="pl-empty__title">{{ $t('operation.printLabel.preview.empty') }}</p>
<p class="pl-empty__tip">{{ $t('operation.printLabel.tip') }}</p>
@ -197,7 +197,7 @@
</section>
</div>
<!-- 最近打印 -->
<!-- 最近打å<EFBFBD>?-->
<aside class="pl-card pl-card--recent-wide">
<div class="pl-list-toolbar">
<div />
@ -307,7 +307,7 @@ const lookingUp = ref(false)
const printing = ref(false)
const listLoading = ref(false)
const currentPage = ref(1)
const pageSize = ref(30)
const pageSize = ref(50)
const headerSearchParams = reactive<Record<string, string>>({})
const NUMBER_FILTER_KEYS = ['waybillNumber', 'customerOrderNumber'] as const
const lookup = ref<PrintLabelLookupResult | null>(null)

View File

@ -31,7 +31,7 @@ const { tableContainerRef, tableHeight: handsontableHeight } = useTableContainer
const listLoading = ref(false)
const currentPage = ref(1)
const pageSize = ref(30)
const pageSize = ref(50)
const totalCount = ref(0)
const tableRows = ref<any[]>([])

View File

@ -40,7 +40,7 @@ const keyword = ref('')
const listLoading = ref(false)
const tableRows = ref<any[]>([])
const currentPage = ref(1)
const pageSize = ref(30)
const pageSize = ref(50)
const totalCount = ref(0)
const selectedIds = ref<number[]>([])
const statusCounts = ref({ total: 0, pending: 0, declared: 0 })
@ -597,7 +597,11 @@ onMounted(() => {
</div>
<div class="toolbar-right">
<span class="selected-count">
{{ t('order.myOrders.selectedCount', { count: selectedRowCount }) }}
<i18n-t keypath="order.myOrders.selectedCount" tag="span">
<template #count>
<span class="count-number">{{ selectedRowCount }}</span>
</template>
</i18n-t>
</span>
<div class="pagination-controls">
<PaginationPro

View File

@ -120,7 +120,7 @@
return undefined;
}
/** 列表行与表格列对齐:接口用 serviceNo/clientNo/trackNo,表格用运单号/客户单号/跟踪号 */
/** 列表行与表格列对é½<EFBFBD>:接å<EFBFBD>£ç”?serviceNo/clientNo/trackNo,表格用è¿<C3A8>å<EFBFBD>•å<E280A2>?客户å<C2B7>•å<E280A2>·/跟踪å<C2AA>?*/
function normalizeOrderRow(raw: any) {
const r = raw?.receiver || raw?.Receiver || {};
const wl = raw?.weightList || raw?.WeightList;
@ -212,7 +212,7 @@
const detail = await getOrderDetail(id);
nextRow = normalizeOrderRow({ ...row, ...detail });
} catch {
/* 保留列表行数据 */
/* ä¿<EFBFBD>留列表行数æ<EFBFBD>?*/
}
}
currentOrder.value = nextRow;
@ -237,8 +237,7 @@
}
};
// 当前激活的标签页(我的订单=订单状态;代发订单=代发进度)
const validStatusSet = new Set(ORDER_STATUS_TABS.map((meta) => meta.value));
// 当å‰<EFBFBD>激活的标签页(我的订å<EFBFBD>•=订å<EFBFBD>•çŠæ€<EFBFBD>;代å<EFBFBD>‘订å<EFBFBD>•=代å<EFBFBD>‘进度ï¼? const validStatusSet = new Set(ORDER_STATUS_TABS.map((meta) => meta.value));
function resolveActiveTabFromQuery(): string {
if (isFulfillList.value) {
@ -284,7 +283,7 @@
return isFulfillList.value && keys.includes(activeTab.value);
}
/** 各 Tab 下动作优先级(越靠前越优先外露,最多 4 个) */
/** å<EFBFBD>?Tab 下动作优先级(越é<C5A0> å‰<C3A5>越优先外露,最å¤?4 个) */
const FULFILL_TAB_TOOLBAR_ACTION_ORDER: Record<string, ToolbarActionKey[]> = {
all: ['stockBind', 'pay', 'predeclare', 'batchPrint', 'modifyOrder', 'delete', 'export'],
pendingBind: ['stockBind', 'modifyOrder', 'predeclare', 'delete', 'pay', 'export'],
@ -293,7 +292,7 @@
outboundDone: ['batchPrint', 'export'],
};
/** 按当前状态收敛工具栏,避免客户在无关 Tab 看到一排无效按钮 */
/** 按当å‰<EFBFBD>状æ€<EFBFBD>收敛工具æ <EFBFBD>,é<EFBFBD>¿å…<EFBFBD>客户在无关 Tab 看到一排无效按é’?*/
const showPayAction = computed(() =>
isFulfillList.value
? isFulfillProgressTab('all', 'pendingBind')
@ -369,7 +368,7 @@
modifyOrder: 'order.myOrders.actions.modifyOrder',
};
/** 各 Tab 下动作优先级(越靠前越优先外露,最多 4 个) */
/** å<EFBFBD>?Tab 下动作优先级(越é<C5A0> å‰<C3A5>越优先外露,最å¤?4 个) */
const TAB_TOOLBAR_ACTION_ORDER: Record<string, ToolbarActionKey[]> = {
all: [
'pay',
@ -662,7 +661,7 @@
tabCounts.value = next;
handleCounts.value = handleCountsRaw ?? {};
} catch {
/* 角标失败不影响列表 */
/* 角标失败ä¸<EFBFBD>å½±å“<EFBFBD>列è¡?*/
}
}
@ -672,15 +671,15 @@
// 分页相关数据
const currentPage = ref(1);
const pageSize = ref(30);
const pageSize = ref(50);
const totalCount = ref(0);
/** 列表筛选:单号关键词 + 创建时间(默认近 90 天) */
/** 列表筛选:å<EFBFBD>•å<EFBFBD>·å…³é”®è¯?+ 创建时间(默认近 90 天) */
const listKeyword = ref('');
const createTimeRange = ref<(Date | string)[] | undefined>([
...buildDefaultCreateTimeRange(),
]);
/** 用户明确放宽时间窗(清空)后,不再自动填回默认 */
/** 用户明确放宽时间窗(清空)å<EFBFBD>Žï¼Œä¸<EFBFBD>å†<EFBFBD>自动填回默è®?*/
const timeWindowRelaxed = ref(false);
const dateFilterExpanded = ref(false);
const headerSearchParams = reactive<Record<string, string>>({});
@ -764,7 +763,7 @@
const timeWindowHelp = computed(() => {
if (timeWindowRelaxed.value && !createTimeRange.value?.length) {
return `已取消时间限制,全量查询可能较慢。建议按单号精确查询,或恢复近 ${ORDER_LIST_DEFAULT_DAYS} 天。`;
return `å·²å<EFBFBD>–消时间é™<EFBFBD>制,全é‡<EFBFBD>查询å<EFBFBD>¯èƒ½è¾ƒæ…¢ã€‚建议按å<EFBFBD>•å<EFBFBD>·ç²¾ç¡®æŸ¥è¯¢ï¼Œæˆ–æ<EFBFBD>¢å¤<EFBFBD>è¿?${ORDER_LIST_DEFAULT_DAYS} 天。`;
}
return `默认仅展示近 ${ORDER_LIST_DEFAULT_DAYS} 天订单;按单号精确查询时可清空时间。`;
});
@ -795,10 +794,10 @@
return;
}
Modal.confirm({
title: '清空时间范围?',
title: '清空æ—间范围ï¼?,
content: `未指定单号时查询全部历史可能较慢。建议保留近 ${ORDER_LIST_DEFAULT_DAYS} 天,或先输入单号再清空时间。`,
okText: '仍要清空',
cancelText: '保留近90天',
cancelText: 'ä¿<EFBFBD>ç•™è¿?0å¤?,
onOk: () => {
timeWindowRelaxed.value = true;
createTimeRange.value = undefined;
@ -838,9 +837,9 @@
}
const selectedRowCount = ref(0);
/** 表格选中行(用于批量操作) */
/** 表格选中行(用于批é‡<EFBFBD>æ“<EFBFBD>作ï¼?*/
const selectedOrderRows = ref<any[]>([]);
/** 当前正在执行的 toolbar 操作(仅对应按钮显示 loading,避免全部按钮联动) */
/** 当å‰<EFBFBD>正在执行çš?toolbar æ“<C3A6>作(仅对应按钮显示 loading,é<C592>¿å…<C3A5>全部按钮è<C2AE>”动) */
const actionLoadingKey = ref<string | null>(null);
function isActionLoading(key: string) {
@ -1012,8 +1011,7 @@
// 当前页数据(HandsontablePro 用)
const processedTableData = computed(() => orderList.value);
// 标签页切换
const handleTabChange = (key: string) => {
// 标签页切æ<EFBFBD>? const handleTabChange = (key: string) => {
if (activeTab.value === key) {
return;
}
@ -1038,8 +1036,7 @@
loadOrderList();
};
// 操作按钮处理(依赖表格多选行;导出按当前 tab 条件,无需选行)
function openPayConfirm(rows: any[]) {
// æ“<EFBFBD>作按钮处ç<EFBFBD>†ï¼ˆä¾<EFBFBD>赖表格多选行;导出按当å‰<EFBFBD> tab æ<EFBFBD>¡ä»ï¼Œæ— éœ€é€‰è¡Œï¼? function openPayConfirm(rows: any[]) {
payConfirmOrders.value = rows
.filter((row) => getOrderRowId(row) != null)
.map((row) => ({
@ -1105,12 +1102,12 @@
action: 'pre-declare',
rows,
loadingKey: 'predeclare',
successMessage: '预报已提交',
successMessage: '预报已æ<EFBFBD><EFBFBD>äº?,
syncRunner: async () => {
for (const row of rows) {
await postOrderPreDeclare({ id: getOrderRowId(row)! });
}
Message.success('预报已提交');
Message.success('预报已æ<EFBFBD><EFBFBD>äº?);
await refreshListAndTabCounts();
},
});
@ -1215,7 +1212,7 @@
}
Modal.confirm({
title: '确认删除',
content: `确定删除选中的 ${rows.length} 条订单吗?`,
content: `确定删除选中çš?${rows.length} æ<>¡è®¢å<C2A2>•å<E280A2>—?`,
onOk: async () => {
try {
await withOrderActionLoading('delete', async () => {
@ -1223,12 +1220,12 @@
action: 'delete',
rows,
loadingKey: 'delete',
successMessage: '已删除',
successMessage: '已删�,
syncRunner: async () => {
for (const row of rows) {
await postOrderDelete({ id: getOrderRowId(row)! });
}
Message.success('已删除');
Message.success('已删�);
await refreshListAndTabCounts();
},
});
@ -1252,7 +1249,7 @@
function isUsablePrintUrl(raw: unknown) {
const value = String(raw ?? '').trim();
if (!value) return false;
if (value.includes('获取中')) return false;
if (value.includes('获å<EFBFBD>–ä¸?)) return false;
return true;
}
@ -1264,7 +1261,7 @@
return;
}
if (!printConnectionState.enabled || !printConnectionState.connected) {
Message.warning('请先在顶部打开并连接打印开关');
Message.warning('请先在é¡éƒ¨æ‰“å¼€å¹è¿žæŽ¥æ‰“å<EFBFBD>°å¼€å…?);
return;
}
try {
@ -1291,7 +1288,7 @@
const rows = requireSelectedRows();
if (!rows.length) return;
if (!printConnectionState.enabled || !printConnectionState.connected) {
Message.warning('请先在顶部打开并连接打印开关');
Message.warning('请先在é¡éƒ¨æ‰“å¼€å¹è¿žæŽ¥æ‰“å<EFBFBD>°å¼€å…?);
return;
}
try {
@ -1378,12 +1375,12 @@
sortOrder,
});
if (downloadUrl) window.open(downloadUrl, '_blank');
Message.success(downloadUrl ? '导出链接已打开' : '导出已提交');
Message.success(downloadUrl ? '导出链接已打开' : '导出已æ<EFBFBD><EFBFBD>äº?);
});
} catch (e: any) {
if (e?.message !== 'busy') {
Message.error(
e?.message || `导出失败(单次最多 ${ORDER_EXPORT_MAX_ROWS.toLocaleString()} 条)`,
e?.message || `导出失败(å<EFBFBD>•次最å¤?${ORDER_EXPORT_MAX_ROWS.toLocaleString()} æ<EFBFBD>¡ï¼‰`,
);
}
}
@ -1539,7 +1536,7 @@
loadOrderList();
};
// HandsontablePro 列定义(支持命名方案配置,最多 30 列)
// HandsontablePro 列定义(支æŒ<EFBFBD>命å<EFBFBD><EFBFBD>方案é…<EFBFBD>置,最å¤?30 列)
const initialIsFulfillList = route.meta.listMode === 'fulfill';
const columnConfigState = ref(
loadColumnConfigState(
@ -1601,8 +1598,8 @@
filterType: 'enum' as const,
headerMode: 'icon' as const,
filterOptions: [
{ label: '已支付', value: '1' },
{ label: '未支付', value: '0' },
{ label: '已支�, value: '1' },
{ label: '未支�, value: '0' },
],
};
}
@ -1775,8 +1772,7 @@
const qty = Number(rowData?.stockBindQtyTotal ?? value ?? 0);
return qty > 0 ? String(qty) : '--';
}
// 首列添加 data-row-status 用于行背景样式
if (colKey === 'waybillNo') {
// 首列添加 data-row-status 用于行背景样� if (colKey === 'waybillNo') {
const statusCode = resolveOrderStatusCode(rowData);
const escaped = escapeHtml(String(value || '--'));
return `<span data-row-status="${statusCode}">${escaped}</span>`;
@ -1862,7 +1858,7 @@
<template>
<div class="my-orders-container">
<!-- 单一工作面:Tabs/筛选 + 子状态 + 工具栏 + 表格 -->
<!-- å<EFBFBD>•一工作é<EFBFBD>¢ï¼šTabs/ç­›é€?+ å­<EFBFBD>çŠæ€?+ 工具æ ?+ 表格 -->
<div class="table-section">
<div class="tabs-actions-section">
<div class="tabs-slot">
@ -1883,7 +1879,7 @@
@press-enter="handleSearch"
/>
<span class="list-date-filter">
<a-tooltip v-if="!dateFilterExpanded" content="日期筛选">
<a-tooltip v-if="!dateFilterExpanded" content="日期筛�>
<a-button class="list-date-toggle" @click="dateFilterExpanded = true">
<template #icon>
<IconCalendar />
@ -1914,8 +1910,7 @@
class="list-date-restore"
@click="restoreDefaultTimeWindow(); handleSearch()"
>
恢复近{{ ORDER_LIST_DEFAULT_DAYS }}天
</a-button>
æ<EFBFBD>¢å¤<EFBFBD>è¿‘{{ ORDER_LIST_DEFAULT_DAYS }}å¤? </a-button>
<a-button
v-if="dateFilterExpanded"
type="text"
@ -1960,7 +1955,7 @@
</div>
</div>
<!-- 表格操作按钮和分页控制 -->
<!-- 表格æ“<EFBFBD>作按钮和分页控åˆ?-->
<div class="table-toolbar">
<div class="actions-right">
<a-button
@ -1994,7 +1989,11 @@
<div class="toolbar-right">
<span class="selected-count">
{{ t('order.myOrders.selectedCount', { count: selectedRowCount }) }}
<i18n-t keypath="order.myOrders.selectedCount" tag="span">
<template #count>
<span class="count-number">{{ selectedRowCount }}</span>
</template>
</i18n-t>
</span>
<div class="column-config-controls">
<a-select
@ -2484,7 +2483,7 @@
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
border: none !important;
// 覆盖 Handsontable 默认的 table 边框
// 覆盖 Handsontable 默认�table 边框
table {
border: none !important;
}
@ -2591,8 +2590,7 @@
}
}
// 响应式设计
@media (max-width: 768px) {
// å“<EFBFBD>应å¼<EFBFBD>设è®? @media (max-width: 768px) {
.actions-right {
flex-wrap: wrap;
gap: 4px;

View File

@ -70,7 +70,7 @@ const STATUS_TAB_ALL = 'all'
const listLoading = ref(false)
const tableRows = ref<any[]>([])
const currentPage = ref(1)
const pageSize = ref(30)
const pageSize = ref(50)
const totalCount = ref(0)
const statusCounts = ref<Record<string, number>>({
[STATUS_TAB_ALL]: 0,
@ -323,7 +323,7 @@ async function loadStatusCounts() {
}
statusCounts.value = next
} catch {
/* 角标失败不影响列表 */
/* 角标失败ä¸<EFBFBD>å½±å“<EFBFBD>列è¡?*/
}
}
@ -571,8 +571,8 @@ async function doBatchPay() {
.filter((x: any) => !(x?.success ?? x?.Success))
.slice(0, 5)
.map((x: any) => `${x?.pickupNo || x?.PickupNo || x?.id || x?.Id}: ${x?.message || x?.Message || ''}`)
.join(';')
$baseMessage(failItems ? `${message}。${failItems}` : message, 'warning')
.join('ï¼?)
$baseMessage(failItems ? `${message}�{failItems}` : message, 'warning')
} else {
$baseMessage(message, 'success')
}
@ -1292,7 +1292,11 @@ onMounted(async () => {
</div>
<div class="toolbar-right">
<span class="selected-count">
{{ t('order.pickupOrder.selectedCount', { count: selectedRowCount }) }}
<i18n-t keypath="order.pickupOrder.selectedCount" tag="span">
<template #count>
<span class="count-number">{{ selectedRowCount }}</span>
</template>
</i18n-t>
</span>
<PaginationPro
:total-count="totalCount"
@ -1344,7 +1348,7 @@ onMounted(async () => {
</a-tag>
</div>
<div class="pk-detail-sub">
{{ t('order.pickupOrder.table.createTime') }}:
{{ t('order.pickupOrder.table.createTime') }}ï¼?
{{ formatDateTime(detail.createTime || detail.CreateTime) }}
</div>
</div>

View File

@ -32,7 +32,7 @@ const keyword = ref('')
const listLoading = ref(false)
const tableRows = ref<any[]>([])
const currentPage = ref(1)
const pageSize = ref(30)
const pageSize = ref(50)
const totalCount = ref(0)
const platformDefs = [
@ -53,12 +53,12 @@ const slidingPlatformTabs = computed(() =>
const lazadaSiteOptions = computed(() => [
{ value: 'cb', label: t('order.platformStore.site.crossBorder') },
{ value: 'sg', label: '新加坡' },
{ value: 'sg', label: '新加å<EFBFBD>? },
{ value: 'my', label: '马来西亚' },
{ value: 'th', label: '泰国' },
{ value: 'vn', label: '越南' },
{ value: 'ph', label: '菲律宾' },
{ value: 'id', label: '印度尼西亚' },
{ value: 'ph', label: 'è<EFBFBD>²å¾‹å®? },
{ value: 'id', label: 'å<EFBFBD>°åº¦å°¼è¥¿äº? },
])
const [registerFormModal, { openModal, closeModal }] = useModal() as any

View File

@ -249,7 +249,7 @@ async function loadCandidates() {
customerId: props.customerId,
keyword: keyword.value || undefined,
page: 1,
pageSize: 30,
pageSize: 50,
}
if (leftTab.value === 'inventory') {
const res = await postOrderStockBindInventoryCandidates(body)
@ -556,7 +556,7 @@ async function handleSubmit(): Promise<boolean> {
const msg = failItems
.slice(0, 3)
.map((x: any) => `${x.platformOrderNo || x.PlatformOrderNo || x.id}: ${x.message || x.Message}`)
.join(';')
.join('ï¼?)
$baseMessage(
t('order.platformStoreOrder.createLogistics.partial', { success, fail, detail: msg }),
'warning',
@ -752,7 +752,7 @@ watch(leftTab, () => {
</aside>
<section v-if="activeDraft" class="psc-detail">
<!-- 仅物流:收件人 + 商品纵向校对 -->
<!-- 仅物æµ<EFBFBD>:æ”ä»äº?+ 商å“<EFBFBD>纵å<EFBFBD>‘校对 -->
<template v-if="!isFulfill">
<div class="psc-section">
<h4>{{ t('order.platformStoreOrder.createLogistics.receiverTitle') }}</h4>
@ -789,7 +789,7 @@ watch(leftTab, () => {
</div>
</template>
<!-- 物流&代发:订单商品 ↔ 已绑资源 对照校对 -->
<!-- 物æµ<EFBFBD>&代å<EFBFBD>‘:订å<EFBFBD>•商å“?â†?已绑资æº<EFBFBD> 对照校对 -->
<template v-else>
<div class="psc-ship-strip" :class="{ missing: activeDraft.receiverMissing }">
<strong>{{ activeDraft.platformOrderNo }}</strong>
@ -1258,7 +1258,7 @@ watch(leftTab, () => {
}
}
/* ── 代发:收件摘要 ── */
/* ── 代å<C2A3>‘:收件摘è¦?── */
.psc-ship-strip {
flex-shrink: 0;
display: flex;
@ -1361,7 +1361,7 @@ watch(leftTab, () => {
}
}
/* ── 代发:订单商品 ↔ 已绑 对照 ── */
/* ── 代å<C2A3>‘:订å<C2A2>•商å“?â†?已绑 对照 ── */
.psc-compare {
flex: 1.2;
min-height: 220px;
@ -1505,7 +1505,7 @@ watch(leftTab, () => {
flex-shrink: 0;
}
/* ── 代发:底部挑选资源 ── */
/* ── 代å<C2A3>‘:底部挑选资æº?── */
.psc-pick {
flex: 0.9;
min-height: 180px;

View File

@ -50,7 +50,7 @@ const {
ensureMutationCustomerId,
} = useWarehouseAdminCustomer()
/** Tab active-key;全部用 'all',请求时再转成不传 fulfillStatus / platformCode */
/** Tab active-key;全部用 'all',请求时å†<C3A5>转æˆ<C3A6>ä¸<C3A4>ä¼?fulfillStatus / platformCode */
const STATUS_TAB_ALL = 'all'
const PLATFORM_TAB_ALL = 'all'
const ALLOWED_FULFILL_STATUS = new Set(['pending', 'toShip', 'shipped', 'canceled', 'completed'])
@ -87,7 +87,7 @@ const selectedRows = ref<any[]>([])
const selectedRowCount = computed(() => selectedRows.value.length)
const hasSelectedRows = computed(() => selectedRowCount.value > 0)
const currentPage = ref(1)
const pageSize = ref(30)
const pageSize = ref(50)
const totalCount = ref(0)
const pullVisible = ref(false)
const actionLoading = ref(false)
@ -758,7 +758,11 @@ watch(
</div>
<div class="toolbar-right">
<span class="selected-count">
{{ t('order.myOrders.selectedCount', { count: selectedRowCount }) }}
<i18n-t keypath="order.myOrders.selectedCount" tag="span">
<template #count>
<span class="count-number">{{ selectedRowCount }}</span>
</template>
</i18n-t>
</span>
<PaginationPro
:total-count="totalCount"

View File

@ -1,7 +1,7 @@
<script lang="ts" setup>
import { ref, computed, onMounted, nextTick, watch, reactive } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { IconCalendar, IconDown, IconQuestionCircleFill } from '@arco-design/web-vue/es/icon';
import { IconCalendar, IconDown, IconQuestionCircleFill, IconSettings } from '@arco-design/web-vue/es/icon';
import { Message, Modal } from '@arco-design/web-vue';
import dayjs from 'dayjs';
import HandsontablePro from '@/components/HandsontablePro/index.vue';
@ -14,11 +14,13 @@
withSortableColumns,
} from '@/utils/headerFilterAdapter';
import {
MAX_HEADER_BATCH_VALUES,
ORDER_EXPORT_MAX_ROWS,
ORDER_LIST_DEFAULT_DAYS,
buildDefaultCreateTimeRange,
formatCreateTimeRangeParam,
isDefaultCreateTimeRange,
normalizeBatchInputText,
} from '@/utils/headerFilterLimits';
import {
postOrderList,
@ -450,7 +452,7 @@
// 分页相关数据
const currentPage = ref(1);
const pageSize = ref(30);
const pageSize = ref(50);
const totalCount = ref(0);
/** 列表筛选:单号关键词 + 创建时间(默认近 90 天) */
@ -532,12 +534,30 @@
createTimeEnd = e.format('YYYY-MM-DD HH:mm:ss');
}
}
const keyword = (adapted.keyword || listKeyword.value.trim() || '').trim();
const toolbarBatch = normalizeBatchInputText(listKeyword.value);
let filters = adapted.filters.length ? [...adapted.filters] : [];
let keyword = '';
// 工具栏批量(多个单号)→ 运单号 IN;单值仍走 keyword 模糊(运单号/客户单号/跟踪号)
if (toolbarBatch.keptCount > 1) {
filters = filters.filter((f) => f.field !== 'serviceNo');
const values = toolbarBatch.value.split(',').map((v) => v.trim()).filter(Boolean);
filters.push({
field: 'serviceNo',
op: 'in',
value: values.join(','),
values,
});
} else {
keyword = (adapted.keyword || toolbarBatch.value || listKeyword.value.trim() || '').trim();
}
return {
keyword,
createTimeStart,
createTimeEnd,
filters: adapted.filters.length ? adapted.filters : undefined,
filters: filters.length ? filters : undefined,
sortField: mapSortFieldToApi(listSortField.value),
sortOrder: listSortOrder.value || undefined,
};
@ -914,20 +934,64 @@
loadTabCounts();
};
const handleResetSearch = () => {
listKeyword.value = '';
restoreDefaultTimeWindow();
Object.keys(headerSearchParams).forEach((k) => {
headerSearchParams[k] = '';
});
listSortField.value = null;
listSortOrder.value = null;
currentPage.value = 1;
selectedOrderRows.value = [];
selectedRowCount.value = 0;
loadOrderList();
loadTabCounts();
};
const keywordBatchVisible = ref(false);
const keywordBatchInput = ref('');
const keywordBatchCount = computed(() =>
keywordBatchInput.value
.split(/[\n\r]+/)
.flatMap((line) => line.split(/[,,\t;;]/))
.map((v) => v.trim())
.filter(Boolean).length,
);
function openKeywordBatchModal() {
const current = listKeyword.value.trim();
keywordBatchInput.value = current
? current
.split(/[,,\n\r\t;;]+/)
.map((v) => v.trim())
.filter(Boolean)
.join('\n')
: '';
keywordBatchVisible.value = true;
}
function saveKeywordBatch() {
const { value, truncated, originalCount, keptCount } = normalizeBatchInputText(
keywordBatchInput.value,
);
listKeyword.value = value;
keywordBatchVisible.value = false;
handleSearch();
if (truncated) {
Message.warning(
`最多 ${MAX_HEADER_BATCH_VALUES} 个,已保留前 ${keptCount} 个(原 ${originalCount} 个)`,
);
} else if (keptCount > 0) {
Message.success(
keptCount > 1
? `已批量输入 ${keptCount} 个运单号`
: `已输入 ${keptCount} 个值`,
);
}
}
function handleKeywordBatchPaste(event: ClipboardEvent) {
const pastedText = event.clipboardData?.getData('text') || '';
if (!pastedText.includes(',') && !pastedText.includes(',')) return;
event.preventDefault();
const lines = pastedText
.split(/[,,\n\r]+/)
.map((line) => line.trim())
.filter(Boolean);
const textarea = event.target as HTMLTextAreaElement;
const start = textarea.selectionStart || 0;
const end = textarea.selectionEnd || 0;
const text = keywordBatchInput.value;
keywordBatchInput.value =
text.substring(0, start) + lines.join('\n') + text.substring(end);
}
// 当前页数据(HandsontablePro 用)
const processedTableData = computed(() => orderList.value);
@ -1423,10 +1487,6 @@
);
const columnConfigModalRef = ref<InstanceType<typeof ColumnConfigModal>>();
const columnSchemeOptions = computed(() =>
columnConfigState.value.schemes.map((s) => ({ label: s.name, value: s.id })),
);
const activeColumnKeys = computed(() => getActiveScheme(columnConfigState.value).keys);
const hotColumns = computed(() => {
@ -1487,15 +1547,6 @@
columnCatalog.value = mergeDiscoveredColumns(getWaybillBaseCatalog(), rows);
}
function handleColumnSchemeChange(id: string) {
if (!columnConfigState.value.schemes.some((s) => s.id === id)) return;
columnConfigState.value = {
...columnConfigState.value,
activeId: id,
};
saveColumnConfigState(WAYBILL_MANAGE_COLUMN_STORAGE_KEY, columnConfigState.value);
}
function openColumnConfig() {
columnConfigModalRef.value?.open();
}
@ -1745,7 +1796,9 @@
v-model="listKeyword"
allow-clear
:placeholder="t('order.myOrders.search.keywordPlaceholder')"
title="双击支持批量输入"
style="width: 240px"
@dblclick="openKeywordBatchModal"
@press-enter="handleSearch"
/>
<span class="list-date-filter">
@ -1795,9 +1848,6 @@
<a-button class="app-btn-search" @click="handleSearch">
{{ t('order.myOrders.search.actions.search') }}
</a-button>
<a-button @click="handleResetSearch" class="app-btn-reset">
{{ t('order.myOrders.search.actions.reset') }}
</a-button>
</div>
</div>
</div>
@ -1917,20 +1967,12 @@
<div class="toolbar-right">
<span class="selected-count">
{{ t('order.myOrders.selectedCount', { count: selectedRowCount }) }}
<i18n-t keypath="order.myOrders.selectedCount" tag="span">
<template #count>
<span class="count-number">{{ selectedRowCount }}</span>
</template>
</i18n-t>
</span>
<div class="column-config-controls">
<a-select
:model-value="columnConfigState.activeId"
:options="columnSchemeOptions"
:placeholder="t('order.myOrders.columnConfig.scheme')"
style="width: 140px"
@change="handleColumnSchemeChange"
/>
<a-button @click="openColumnConfig" class="app-btn-toolbar">
{{ t('order.myOrders.columnConfig.open') }}
</a-button>
</div>
<div class="pagination-controls">
<PaginationPro
:total-count="totalCount"
@ -1939,6 +1981,16 @@
@page-change="handlePageChange"
@page-size-change="handlePageSizeChange"
/>
<a-tooltip content="表头配置">
<a-button
class="app-btn-toolbar column-config-icon-btn"
@click="openColumnConfig"
>
<template #icon>
<IconSettings />
</template>
</a-button>
</a-tooltip>
</div>
</div>
</div>
@ -1994,6 +2046,33 @@
<OrderPrintModal v-model:visible="singlePrintVisible" mode="single" :items="singlePrintItems" />
<OrderPrintModal v-model:visible="batchPrintVisible" mode="batch" :items="batchPrintItems" />
<a-modal
v-model:visible="keywordBatchVisible"
title="批量输入单号"
:width="600"
unmount-on-close
@ok="saveKeywordBatch"
>
<div class="keyword-batch-content">
<p class="keyword-batch-tips">
每行一个;支持粘贴逗号分隔。多个单号按运单号精确匹配(最多
{{ MAX_HEADER_BATCH_VALUES }} 个);单个仍支持运单号 / 客户单号 / 跟踪号模糊搜索。
</p>
<a-textarea
v-model="keywordBatchInput"
placeholder="请输入,每行一个,或粘贴逗号分隔的数据"
:auto-size="{ minRows: 10, maxRows: 20 }"
@paste="handleKeywordBatchPaste"
/>
<div v-if="keywordBatchCount" class="keyword-batch-count">
已输入 {{ keywordBatchCount }} 个
<span v-if="keywordBatchCount > MAX_HEADER_BATCH_VALUES" class="is-warn">
(超过上限将截断)
</span>
</div>
</div>
</a-modal>
<a-modal
v-model:visible="trackUpsertModalVisible"
title="轨迹同步结果"
@ -2343,15 +2422,15 @@
line-height: 32px;
}
.column-config-controls {
display: flex;
align-items: center;
gap: 8px;
}
.pagination-controls {
display: flex;
align-items: center;
gap: 8px;
.column-config-icon-btn {
width: 32px;
padding: 0;
}
}
}
}
@ -2550,4 +2629,23 @@
white-space: pre-wrap;
word-break: break-all;
}
.keyword-batch-content {
.keyword-batch-tips {
margin: 0 0 12px;
color: var(--color-text-3);
font-size: 13px;
line-height: 1.5;
}
.keyword-batch-count {
margin-top: 8px;
font-size: 12px;
color: var(--color-text-3);
.is-warn {
color: rgb(var(--warning-6));
}
}
}
</style>

View File

@ -249,7 +249,7 @@ const dataSource = ref<NoticeDto[]>([])
const activeTab = ref<'notice' | 'message'>('notice')
const keyword = ref('')
const currentPage = ref(1)
const pageSize = ref(30)
const pageSize = ref(50)
const totalCount = ref(0)
const modalVisible = ref(false)
@ -482,7 +482,7 @@ function handlePageSizeChange(size: number) {
function formatReceiverOptionLabel(opt: NoticeReceiverOption) {
if (!opt.extra) return opt.label
return `${opt.label}(${opt.extra})`
return `${opt.label}�{opt.extra})`
}
function mergeReceiverOptions(options: NoticeReceiverOption[]) {

View File

@ -30,14 +30,14 @@
</a-table-column>
<a-table-column title="目标 ID" data-index="targetId" ellipsis />
<a-table-column title="动作" data-index="action" :width="100" />
<a-table-column title="操作人 mapId" data-index="operatorMapId" :width="110" />
<a-table-column title="æ“<EFBFBD>作äº?mapId" data-index="operatorMapId" :width="110" />
</template>
</a-table>
</a-tab-pane>
<a-tab-pane key="access" title="数据访问日志">
<a-alert type="info" style="margin-bottom: 12px">
P1 后端暂不自动写入访问日志;本页为预留查询界面,待内审需求启用后再对接。
P1 å<EFBFBD>Žç«¯æš‚ä¸<EFBFBD>自动写入访问日志;本页为预留查询界é<EFBFBD>¢ï¼Œå¾…内审需求å<EFBFBD>¯ç”¨å<EFBFBD>Žå†<EFBFBD>对接ã€?
</a-alert>
<div class="table-toolbar">
<a-space wrap>
@ -94,7 +94,7 @@ const changeQuery = reactive({
targetId: '',
timeRange: [] as string[],
})
const changePagination = reactive({ current: 1, pageSize: 30, total: 0 })
const changePagination = reactive({ current: 1, pageSize: 50, total: 0 })
const accessLoading = ref(false)
const accessLogs = ref<AccessLogItem[]>([])
@ -103,7 +103,7 @@ const accessQuery = reactive({
dataResourceCode: '',
timeRange: [] as string[],
})
const accessPagination = reactive({ current: 1, pageSize: 30, total: 0 })
const accessPagination = reactive({ current: 1, pageSize: 50, total: 0 })
function buildTimeParams(range: string[]) {
if (!range?.length || range.length < 2) return {}

View File

@ -242,7 +242,7 @@ const query = reactive({
const pagination = reactive({
current: 1,
pageSize: 30,
pageSize: 50,
total: 0,
})
@ -345,7 +345,7 @@ function actionRenderer(_row: DictDto, _rowIndex: number): string {
])
}
/** 固定列序:操作 + 序号 → 数据从 col 2 起 */
/** 固定列åº<EFBFBD>:æ“<EFBFBD>ä½?+ åº<C3A5>å<EFBFBD>· â†?æ•°æ<C2B0>®ä»?col 2 èµ?*/
function customCellRenderer(_row: number, col: number, _value: unknown, rowData: DictDto): string | null {
const dataColIndex = col - 2
if (dataColIndex < 0 || dataColIndex >= hotColumns.value.length) return null
@ -391,7 +391,7 @@ function handleTypeNodeClick(key: string) {
}
function handleTypeSearch() {
// filteredTypeList 客户端过滤
// filteredTypeList 客户端过�
}
function handleSearch() {

View File

@ -287,7 +287,7 @@ const selectedOrganization = ref<OrganizationTreeDto | null>(null)
const userSearchKeyword = ref('')
const userLoading = ref(false)
const userDataSource = ref<UserDto[]>([])
const userPagination = reactive({ current: 1, pageSize: 30, total: 0 })
const userPagination = reactive({ current: 1, pageSize: 50, total: 0 })
const { tableContainerRef, tableHeight } = useTableContainerHeight()
const handsontableHeight = computed(() => Math.max(tableHeight.value || 0, 400))
@ -432,7 +432,7 @@ function handleOrgSearch() {
}
function customCellRenderer(_row: number, col: number, _value: any, rowData: UserDto): string | null {
// HandsontablePro 列序:操作 → 序号 → 数据;showActionColumn 时 dataColStart=2
// HandsontablePro 列åº<EFBFBD>:æ“<EFBFBD>ä½?â†?åº<EFBFBD>å<EFBFBD>· â†?æ•°æ<EFBFBD>®ï¼›showActionColumn æ—?dataColStart=2
const dataColIndex = col - 2
if (dataColIndex < 0 || dataColIndex >= hotColumns.value.length) return null
const key = hotColumns.value[dataColIndex].key

View File

@ -72,7 +72,7 @@
/** 搜索条件 */
const searchModel = reactive<any>({});
/** 搜索关键<EFBFBD>?*/
/** æ<EFBFBD>œç´¢å…³é”®ï¿?*/
const searchKeyword = ref('');
const pullListData = ref<any[]>([]);
@ -123,8 +123,8 @@
children: list.map((item: any) => {
const time = item.createTime ? dayjs(item.createTime).format('YYYY-MM-DD HH:mm') : '';
const app = item.appName || (item.appKey != null ? (appMap.value?.[item.appKey] ?? '') : '') || item.appKey || '-';
const appStr = app ? `【${app}】` : '';
const customerStr = item.customer ? `【${item.customer}】` : '';
const appStr = app ? `�{app}】` : '';
const customerStr = item.customer ? `�{item.customer}】` : '';
const codeLine = [time, appStr, customerStr].filter(Boolean).join(' ');
return {
key: `item-${item.id}`,
@ -167,7 +167,7 @@
const activeTab = ref<'detail' | 'priority'>('detail');
const slidingTabItems = computed(() => [
{ key: 'detail', label: '明细' },
{ key: 'priority', label: '优先级' },
{ key: 'priority', label: '优先� },
]);
const priorityModalRef = ref();
const priorityModalParams = reactive<any>({
@ -176,15 +176,15 @@
trackPullConfigId: undefined,
});
// 明细表分页
// 明细表分�
const totalCount = ref(0);
const currentPage = ref(1);
const pageSize = ref(30);
const pageSize = ref(50);
// 优先级 tab 分页
// 优先�tab 分页
const priorityTotalCount = ref(0);
const priorityCurrentPage = ref(1);
const priorityPageSize = ref(30);
const prioritypageSize = ref(50);
const detailColumns = computed(() => [
{ key: 'standardTrack', label: t('track.pull.table.standardTrack'), width: 280 },
@ -196,9 +196,9 @@
{ key: 'customer', label: '客户', width: 140 },
{ key: 'orderNoTypeText', label: '拉取单号类型', width: 140 },
{ key: 'pullTimesPerDay', label: '每天拉取次数', width: 120 },
{ key: 'priority', label: '优先级', width: 100 },
{ key: 'priority', label: '优先�, width: 100 },
{ key: 'updateTime', label: '修改时间', width: 160 },
{ key: 'updateUser', label: '修改人', width: 120 },
{ key: 'updateUser', label: '修改�, width: 120 },
]);
function formatOrderNoType(cellValue: any) {
@ -293,7 +293,7 @@
label: '每天拉取次数',
comp: 'AInputNumber',
compProps: {
placeholder: '请输入次数',
placeholder: '请输入次�,
min: 0,
max: 12,
precision: 0,
@ -303,7 +303,7 @@
{
required: true,
type: 'number',
message: '请输入每天拉取次数',
message: '请输入æ¯<EFBFBD>天拉å<EFBFBD>–次æ•?,
},
{
type: 'number',
@ -315,7 +315,7 @@
},
{
field: 'priority',
label: '优先级',
label: '优先�,
comp: 'AInputNumber',
compProps: {
placeholder: '请输入优先级',
@ -370,7 +370,7 @@
span: 8,
},
{
label: '服务商',
label: 'æœ<EFBFBD>务å•?,
field: 'serviceProviderCode',
comp: 'SelectPro',
compProps: (): any => {
@ -379,7 +379,7 @@
clearable: true,
labelKeys: ['name'],
titleStrs: ['name'],
placeholder: '请选择服务商',
placeholder: '请选择æœ<EFBFBD>务å•?,
style: { width: '100%', height: '40px' },
idKey: 'code',
isPage: false,
@ -394,17 +394,17 @@
rules: [
{
required: true,
message: '请选择服务商',
message: '请选择æœ<EFBFBD>务å•?,
},
],
},
{
label: '开始轨迹',
label: '开始轨�,
field: 'startTrackCode',
comp: 'SelectPro',
compProps: (): any => {
return {
placeholder: '请选择开始轨迹',
placeholder: '请选择开始轨�,
isPage: true,
clearable: true,
immediate: true,
@ -420,11 +420,11 @@
span: 8,
},
{
label: '开始睡眠多少小时',
label: '开始ç<EFBFBD>¡çœ å¤šå°‘å°<EFBFBD>æ—?,
field: 'silenceBeforeStart',
comp: 'AInputNumber',
compProps: {
placeholder: '请输入1-48的整数',
placeholder: '请输�-48的整�,
style: { width: '100%', height: '40px' },
min: 1,
max: 14,
@ -437,7 +437,7 @@
type: 'number',
min: 1,
max: 48,
message: '请输入1-48之间的整数',
message: '请输�-48之间的整�,
trigger: 'change',
},
],
@ -464,11 +464,11 @@
span: 8,
},
{
label: '结束继续拉取多久/天',
label: '结æ<EFBFBD>Ÿç»§ç»­æ‹‰å<EFBFBD>–多久/å¤?,
field: 'silenceAfterEnd',
comp: 'AInputNumber',
compProps: {
placeholder: '请输入1-14的整数',
placeholder: '请输�-14的整�,
style: { width: '100%', height: '40px' },
min: 1,
max: 14,
@ -481,7 +481,7 @@
type: 'number',
min: 1,
max: 14,
message: '请输入1-14之间的整数',
message: '请输�-14之间的整�,
trigger: 'change',
},
],
@ -553,7 +553,7 @@
};
const data: any[] = [
{
label: '拉取轨迹关键字',
label: '拉å<EFBFBD>–轨迹关键å­?,
field: 'trackKeywords',
comp: 'TagsAdd',
compProps: ({ formModel }: { formModel: any }) => {
@ -565,7 +565,7 @@
span: 24,
},
{
label: '拉取轨迹关键字(过滤)',
label: '拉å<EFBFBD>–轨迹关键字(过滤ï¼?,
field: 'trackKeywordReverses',
comp: 'TagsAdd',
compProps: ({ formModel }: { formModel: any }) => {
@ -607,7 +607,7 @@
// 单击行获取行数据
function cellClick(par: any) {
currentId.value = par;
// 点击行数据处<EFBFBD>?
// 点击行数æ<EFBFBD>®å¤„ï¿?
}
// 点击新增 编辑打开左侧弹窗
@ -714,10 +714,10 @@
});
}
// 删除优先级数<EFBFBD>?
// 删除优先级数�
function handleDelPriorityData(row: any) {
if (!row.customer) {
$baseMessage('客户字段为必填项,无法删除', 'warning');
$baseMessage('客户字段为必填项,无法删�, 'warning');
return;
}
$baseConfirm({
@ -768,9 +768,9 @@
priorityTotalCount.value = 0;
}
// 提交前数据处<EFBFBD>?
// æ<EFBFBD><EFBFBD>交å‰<EFBFBD>æ•°æ<EFBFBD>®å¤„ï¿?
function beforeSubmitLeft(formData: any) {
// 返参配置字段已废弃,提交时确保不带这些字段(兼容旧数据/缓存)
// è¿”å<EFBFBD>‚é…<EFBFBD>置字段已废弃,æ<EFBFBD><EFBFBD>交æ—ç¡®ä¿<EFBFBD>ä¸<EFBFBD>带这些字段(兼容旧数æ<EFBFBD>?缓存ï¼?
const deprecatedKeys = [
'parseMethod',
'orderPath',
@ -792,15 +792,15 @@
return formData;
}
// 右侧弹窗提交前处理:接口不再需要兜底轨迹发生地等字段
// å<EFBFBD>³ä¾§å¼¹çª—æ<EFBFBD><EFBFBD>交å‰<EFBFBD>处ç<EFBFBD>†ï¼šæŽ¥å<EFBFBD>£ä¸<EFBFBD>å†<EFBFBD>需è¦<EFBFBD>兜底轨迹å<EFBFBD>‘生地等字æ®?
function beforeSubmitRight(formData: any) {
if ('trackPlaceValue' in formData) delete formData.trackPlaceValue;
return formData;
}
// 从API获取数据后处<EFBFBD>?
// 从API获å<EFBFBD>–æ•°æ<EFBFBD>®å<EFBFBD>Žå¤„ï¿?
function onGetInfoLeft(data: any) {
// 返参配置已废弃:不再做默认值处理
// è¿”å<EFBFBD>‚é…<EFBFBD>置已废弃:ä¸<EFBFBD>å†<EFBFBD>å<EFBFBD>šé»˜è®¤å€¼å¤„ç<EFBFBD>?
}
// 左侧弹窗保存
@ -825,16 +825,16 @@
});
}
// 优先级弹<EFBFBD>?- 新增/编辑
// 优先级弹�- 新增/编辑
function handleOpenPriorityModal(type: IDialogConfig['type'], row?: any) {
if (!currentId.value || !currentId.value.id) {
$baseMessage('请先选择左侧一条拉取配置', 'warning');
$baseMessage('请先选择左侧一æ<EFBFBD>¡æ‹‰å<EFBFBD>–é…<EFBFBD>ç½?, 'warning');
return;
}
const titleObj: { [x in IDialogConfig['type']]: string } = {
add: '新增优先级',
edit: '编辑优先级',
add: '新增优先�,
edit: '编辑优先�,
};
priorityModalParams.trackPullConfigId = currentId.value.id;
@ -854,8 +854,8 @@
formData = {
customer: row.customer,
orderNoType: row.orderNoType,
// 24 / 每天拉取次数 * 60 = 接口<EFBFBD>?
// 每天拉取次数 = 24 * 60 / 接口<EFBFBD>?= 1440 / 接口<EFBFBD>?
// 24 / æ¯<EFBFBD>天拉å<EFBFBD>–次数 * 60 = 接å<EFBFBD>£ï¿?
// æ¯<EFBFBD>天拉å<EFBFBD>–次数 = 24 * 60 / 接å<EFBFBD>£ï¿?= 1440 / 接å<EFBFBD>£ï¿?
pullInterval: row.pullInterval && row.pullInterval !== 0 ? 1440 / row.pullInterval : undefined,
priority: row.priority,
};
@ -868,7 +868,7 @@
});
}
// 优先级弹窗提<EFBFBD>?
// 优先级弹窗æ<EFBFBD><EFBFBD>ï¿?
async function onSubmitPriority(formData: any) {
if (!currentId.value || !currentId.value.id) return;
@ -879,8 +879,8 @@
customer: formData.customer,
orderNoType: formData.orderNoType,
priority: formData.priority,
// 24 / 每天拉取次数 * 60 = 接口<EFBFBD>?
// 接口<EFBFBD>?= (24 / 每天拉取次数) * 60 = 1440 / 每天拉取次数
// 24 / æ¯<EFBFBD>天拉å<EFBFBD>–次数 * 60 = 接å<EFBFBD>£ï¿?
// 接å<EFBFBD>£ï¿?= (24 / æ¯<EFBFBD>天拉å<EFBFBD>–次数) * 60 = 1440 / æ¯<EFBFBD>天拉å<EFBFBD>–次数
pullInterval: formData.pullInterval && formData.pullInterval !== 0 ? 1440 / formData.pullInterval : undefined,
};
@ -1012,7 +1012,7 @@
// 确认串联拉取
function handleConfirmChainPull() {
if (!selectedServiceProviderId.value) {
$baseMessage('请选择服务商', 'warning');
$baseMessage('请选择æœ<EFBFBD>务å•?, 'warning');
return;
}
// TODO: 这里可以添加串联拉取的具体逻辑
@ -1043,7 +1043,7 @@
search-placeholder="请输入服务商名称或appkey"
:refresh-title="$t('track.push.actions.refresh')"
add-title="新增拉取"
loading-text="加载中..."
loading-text="加载�.."
delete-label="删除"
:show-method="false"
:show-map-count="false"
@ -1162,7 +1162,7 @@
</div>
</div>
<!-- 优先级 Tab -->
<!-- 优先�Tab -->
<div v-else class="detail-tab-wrapper">
<div class="table-toolbar">
<div class="toolbar-left">
@ -1223,7 +1223,7 @@
>
<div class="chain-pull-modal-content">
<a-form :model="{ serviceProviderId: selectedServiceProviderId }" layout="vertical">
<a-form-item label="服务商" required>
<a-form-item label="æœ<EFBFBD>务å•? required>
<SelectPro
v-model="selectedServiceProviderId"
:api="getServiceProviderGetServiceProviderDropdownList"
@ -1231,7 +1231,7 @@
:label-keys="['name']"
id-key="id"
:is-page="false"
placeholder="请选择服务商"
placeholder="请选择æœ<EFBFBD>务å•?
style="width: 100%"
/>
</a-form-item>
@ -1239,7 +1239,7 @@
</div>
</ModalPro>
<!-- 轨迹拉取优先级弹<EFBFBD>?-->
<!-- 轨迹拉å<EFBFBD>–优先级弹ï¿?-->
<ModalForm
ref="priorityModalRef"
:edit-api="postEditPriority"
@ -1323,7 +1323,7 @@
display: none;
}
/* 列表容器区域 - 与上游接口的interface-tree保持一<E68C81>?*/
/* 列表容器区域 - 与上游接å<C2A5>£çš„interface-treeä¿<C3A4>æŒ<C3A6>一ï¿?*/
:deep(.scrollbarplus) {
padding: 8px 0 0;
padding-right: 4px;
@ -1382,7 +1382,7 @@
border-radius: 4px;
&::before {
content: '↻';
content: '�;
font-size: 14px;
line-height: 1;
}
@ -1484,7 +1484,7 @@
display: flex;
justify-content: flex-start;
align-items: center;
// 让明细页工具栏(含“串联拉取”按钮)与左侧列表的左边距保持一致
// 让明细页工具æ <EFBFBD>(å<EFBFBD>«â€œä¸²è<EFBFBD>”拉å<EFBFBD>–â€<EFBFBD>按钮)与左侧列表的左边è·<EFBFBD>ä¿<EFBFBD>æŒ<EFBFBD>一è‡?
padding: 0 16px;
width: 100%;
@ -1535,7 +1535,7 @@
}
}
/* 确保trb-transform-left的padding与上游接口一<E58FA3>?*/
/* ç¡®ä¿<EFBFBD>trb-transform-leftçš„padding与上游接å<C2A5>£ä¸€ï¿?*/
:deep(.trb-transform-left) {
padding: 0 !important;
margin-right: 16px !important;
@ -1547,7 +1547,7 @@
height: 100%;
}
/* 输入框 / Select 焦点 - 符合设计规范 */
/* 输入æ¡?/ Select 焦点 - 符å<C2A6>ˆè®¾è®¡è§„范 */
.track-layout :deep(.arco-input:focus-within),
.track-layout :deep(.arco-input-number:focus-within),
.track-layout :deep(.arco-select-view-open) {

View File

@ -70,7 +70,7 @@
/** 搜索条件 */
const searchModel = reactive<any>({});
/** 搜索关键字 */
/** æ<EFBFBD>œç´¢å…³é”®å­?*/
const searchKeyword = ref('');
const pushListData = ref<any[]>([]);
@ -104,7 +104,7 @@
const items = res?.data?.items ?? res?.items ?? res?.data ?? [];
pushListData.value = Array.isArray(items) ? items : [];
} catch (e) {
$baseMessage('加载推送配置列表失败', 'error');
$baseMessage('加载推é€<EFBFBD>é…<EFBFBD>置列表失è´?, 'error');
pushListData.value = [];
} finally {
pushLoading.value = false;
@ -117,12 +117,12 @@
return [
{
key: 'category-1',
title: '推送配置',
title: '推é€<EFBFBD>é…<EFBFBD>ç½?,
children: list.map((item: any) => {
const time = item.createTime ? dayjs(item.createTime).format('YYYY-MM-DD HH:mm') : '';
const app = item.appName || (item.appKey != null ? (appMap.value?.[item.appKey] ?? '') : '') || item.appKey || '-';
const appStr = app ? `【${app}】` : '';
const customerStr = item.customer ? `【${item.customer}】` : '';
const appStr = app ? `�{app}】` : '';
const customerStr = item.customer ? `�{item.customer}】` : '';
const codeLine = [time, appStr, customerStr].filter(Boolean).join(' ');
return {
key: `item-${item.id}`,
@ -167,7 +167,7 @@
// 分页
const totalCount = ref(0);
const currentPage = ref(1);
const pageSize = ref(30);
const pageSize = ref(50);
const detailColumns = computed(() => {
const fixed = [
@ -221,7 +221,7 @@
function addDraftColumn() {
const name = newColumnName.value?.trim();
if (!name) {
$baseMessage('请输入列名', 'warning');
$baseMessage('请输入列å<EFBFBD>?, 'warning');
return;
}
if (draftColumns.value.some((c) => c.title === name)) {
@ -251,7 +251,7 @@
function saveDraftColumnTitle(col: CustomColumnItem, newTitle: string) {
const title = newTitle?.trim();
if (!title) {
$baseMessage('请输入列名', 'warning');
$baseMessage('请输入列å<EFBFBD>?, 'warning');
return;
}
if (draftColumns.value.some((c) => c !== col && c.title === title)) {
@ -499,7 +499,7 @@
field: col.field,
comp: 'AInput',
compProps: {
placeholder: '请输入',
placeholder: '请输�,
},
span: 12,
}));
@ -868,7 +868,7 @@
} catch { /* ignore */ }
}
});
// 根据接口返回的 extraConfig 完全替换自定义表头
// æ ¹æ<EFBFBD>®æŽ¥å<EFBFBD>£è¿”回çš?extraConfig 完全替æ<EFBFBD>¢è‡ªå®šä¹‰è¡¨å¤?
customColumns.value = Array.from(extraKeys).map((key) => ({ field: key, title: key }));
items.forEach((r: any) => {
r.standardTrack = formatStandardTrack(r);
@ -919,7 +919,7 @@
:search-placeholder="$t('track.push.search.placeholder')"
:refresh-title="$t('track.push.actions.refresh')"
:add-title="$t('track.push.actions.addPush')"
loading-text="加载中..."
loading-text="加载�.."
:delete-label="$t('track.push.actions.delete')"
:show-method="false"
:show-map-count="false"
@ -1043,7 +1043,7 @@
<a-button @click="closeColumnModal">取消</a-button>
<a-button type="primary" @click="saveDraftColumns">保存</a-button>
</template>
<div class="column-modal-tip">请填写上游接口-推送轨迹的字段名,用于传递入参</div>
<div class="column-modal-tip">请填写上游接å<EFBFBD>?推é€<EFBFBD>轨迹的字段å<EFBFBD><EFBFBD>,用于传递入å<EFBFBD>?/div>
<div class="column-management-section">
<div class="column-list">
<div v-for="column in draftColumns" :key="column.field" class="column-item">
@ -1051,7 +1051,7 @@
<a-input
v-model="column.title"
size="small"
placeholder="请输入列名"
placeholder="请输入列å<EFBFBD>?
@press-enter="saveDraftColumnTitle(column, column.title)"
@blur="saveDraftColumnTitle(column, column.title)"
@keyup.esc="cancelEditDraft(column)"
@ -1059,7 +1059,7 @@
</div>
<div v-else class="column-display">
<span class="column-name" @dblclick="startEditDraft(column)">{{ column.title }}</span>
<a-button type="text" size="mini" class="column-delete-btn" title="删除列" @click="deleteDraftColumn(column)">
<a-button type="text" size="mini" class="column-delete-btn" title="删除� @click="deleteDraftColumn(column)">
×
</a-button>
</div>
@ -1098,7 +1098,7 @@
width: 100%;
}
/* 左侧区域 - 符合设计规范:圆角 8px */
/* 左侧区域 - 符å<C2A6>ˆè®¾è®¡è§„范:圆è§?8px */
:deep(.trb-transform-left) {
background: var(--app-sidebar-bg) !important;
backdrop-filter: blur(20px) saturate(180%);
@ -1344,7 +1344,7 @@
}
}
/* 新增、保存按钮间距 */
/* 新增ã€<EFBFBD>ä¿<EFBFBD>存按钮间è·?*/
.toolbar-add-save-btns {
display: flex;
align-items: center;
@ -1494,7 +1494,7 @@
}
}
/* 左侧面板与轨迹拉取一致 */
/* 左侧é<EFBFBD>¢æ<EFBFBD>¿ä¸Žè½¨è¿¹æ‹‰å<EFBFBD>–一è‡?*/
:deep(.trb-transform-left) {
background: transparent !important;
border: none !important;
@ -1530,7 +1530,7 @@
height: 100%;
}
/* 输入框 / Select 焦点 - 符合设计规范 */
/* 输入æ¡?/ Select 焦点 - 符å<C2A6>ˆè®¾è®¡è§„范 */
.track-layout :deep(.arco-input:focus-within),
.track-layout :deep(.arco-input-number:focus-within),
.track-layout :deep(.arco-select-view-open) {

View File

@ -28,15 +28,15 @@
const page = reactive<any>({
pageIndex: 1,
pageSize: 30,
pageSize: 50,
});
const listData = ref<API.GetTrackPullConfigPageOutputVO[]>([]);
// 应用列表映射(appKey -> name)
// 蠎皮畑蛻苓。ィ譏<EFBFBD>蟆<EFBFBD>シ<EFBFBD>ppKey -> name<EFBFBD>?
const appMap = ref<Record<string, string>>({});
// 加载应用列表并创建映射
// 蜉<EFBFBD>霓ス蠎皮畑蛻苓。ィ蟷カ蛻帛サコ譏<EFBFBD>蟆?
const loadAppMap = async () => {
try {
const res = await getAppGetList({
@ -56,7 +56,7 @@
}
};
// 组件挂载时加载应用映射
// 扈<EFBFBD>サカ謖りスス譌カ蜉<EFBFBD>霓ス蠎皮畑譏<EFBFBD>蟆?
onMounted(() => {
loadAppMap();
});
@ -208,19 +208,19 @@
}
}
/* 修改滚动条轨道背景颜色 */
/* 菫ョ謾ケ貊壼勘譚。霓ィ驕楢レ譎ッ鬚懆<EFBFBD>?*/
.scrollbar::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.08);
border-radius: 8px;
}
/* 修改滚动条滑块背景颜色 */
/* 菫ョ謾ケ貊壼勘譚。貊大摎閭梧勹鬚懆<EFBFBD>?*/
.scrollbar::-webkit-scrollbar-thumb {
background: rgba(86, 144, 236, 0.65);
border-radius: 8px;
}
/* 修改滚动条滑块前景颜色 */
/* 菫ョ謾ケ貊壼勘譚。貊大摎蜑肴勹鬚懆<EFBFBD>?*/
.scrollbar::-webkit-scrollbar-thumb:hover {
background: rgba(59, 97, 148, 0.85);
}

View File

@ -28,14 +28,14 @@
const page = reactive<any>({
pageIndex: 1,
pageSize: 30,
pageSize: 50,
});
const listData = ref<API.GetTrackPushConfigPageOutputVO[]>([]);
// 应用列表映射(appKey -> name)
// 蠎皮畑蛻苓。ィ譏<EFBFBD>蟆<EFBFBD>シ<EFBFBD>ppKey -> name<EFBFBD>?
const appMap = ref<Record<string, string>>({});
// 加载应用列表并创建映射
// 蜉<EFBFBD>霓ス蠎皮畑蛻苓。ィ蟷カ蛻帛サコ譏<EFBFBD>蟆?
const loadAppMap = async () => {
try {
const res = await getAppGetList({
@ -55,7 +55,7 @@
}
};
// 组件挂载时加载应用映射
// 扈<EFBFBD>サカ謖りスス譌カ蜉<EFBFBD>霓ス蠎皮畑譏<EFBFBD>蟆?
onMounted(() => {
loadAppMap();
});
@ -205,19 +205,19 @@
}
}
/* 修改滚动条轨道背景颜色 */
/* 菫ョ謾ケ貊壼勘譚。霓ィ驕楢レ譎ッ鬚懆<EFBFBD>?*/
.scrollbar::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.08);
border-radius: 8px;
}
/* 修改滚动条滑块背景颜色 */
/* 菫ョ謾ケ貊壼勘譚。貊大摎閭梧勹鬚懆<EFBFBD>?*/
.scrollbar::-webkit-scrollbar-thumb {
background: rgba(86, 144, 236, 0.65);
border-radius: 8px;
}
/* 修改滚动条滑块前景颜色 */
/* 菫ョ謾ケ貊壼勘譚。貊大摎蜑肴勹鬚懆<EFBFBD>?*/
.scrollbar::-webkit-scrollbar-thumb:hover {
background: rgba(59, 97, 148, 0.85);
}

View File

@ -37,7 +37,7 @@ const keyword = ref('')
const listLoading = ref(false)
const tableRows = ref<any[]>([])
const currentPage = ref(1)
const pageSize = ref(30)
const pageSize = ref(50)
const totalCount = ref(0)
const saving = ref(false)

View File

@ -175,7 +175,7 @@ const mainCustomerOptions = ref<{ label: string; value: number }[]>([])
const tableData = ref<CustomerDto[]>([])
const totalCount = ref(0)
const currentPage = ref(1)
const pageSize = ref(30)
const pageSize = ref(50)
const hotColumns = computed(() => [
{ key: 'customerCode', label: t('user.subAccount.fields.customerCode'), width: 110 },
@ -304,7 +304,7 @@ async function loadMainCustomerOptions() {
const id = extractCustomerId(c as Record<string, unknown>)!
return {
value: id,
label: `${c.customerName || ''}(${c.customerCode || id})`,
label: `${c.customerName || ''}�{c.customerCode || id})`,
}
})
} catch {
@ -379,7 +379,7 @@ const formRules = computed(() => ({
password: [{ required: true, message: t('user.subAccount.rules.passwordRequired') }],
}))
// 权限树(子账号权限须 ⊆ 当前登录用户 permissions)
// æ<EFBFBD>ƒé™<EFBFBD>树(å­<EFBFBD>è´¦å<EFBFBD>·æ<EFBFBD>ƒé™<EFBFBD>é¡» âŠ?当å‰<EFBFBD>登录用户 permissionsï¼?
const permissionTreeLoading = ref(false)
const permissionTree = ref<PermissionGroupDto[]>([])
const permissionExpandedKeys = ref<string[]>([])

View File

@ -37,7 +37,7 @@ const tableRows = ref<any[]>([])
const keyword = ref('')
const enabledFilter = ref<boolean | undefined>(undefined)
const currentPage = ref(1)
const pageSize = ref(30)
const pageSize = ref(50)
const totalCount = ref(0)
const [registerFormModal, { openModal, closeModal }] = useModal() as any

View File

@ -78,7 +78,7 @@ const {
} = useWarehouseAdminCustomer()
const currentWarehouseStore = useCurrentWarehouseStore()
/** Tab active-key;全部用 'all',请求时再转成不传 receiptStatus */
/** Tab active-key;全部用 'all',请求时å†<C3A5>转æˆ<C3A6>ä¸<C3A4>ä¼?receiptStatus */
const STATUS_TAB_ALL = 'all'
const receiptStatus = ref<string>(STATUS_TAB_ALL)
const summary = ref({ forecasted: 0, received: 0, completed: 0 })
@ -90,7 +90,7 @@ const skuOptions = ref<Array<{ label: string; value: number; code: string }>>([]
const listLoading = ref(false)
const tableRows = ref<any[]>([])
const currentPage = ref(1)
const pageSize = ref(30)
const pageSize = ref(50)
const totalCount = ref(0)
const selectedBoxKeys = ref<string[]>([])
const activeBoxKey = ref('')
@ -699,7 +699,7 @@ onMounted(async () => {
<div v-for="box in receiveBoxes" :key="box.boxId" class="receive-box-block">
<div class="box-title">
{{ t('warehouse.inbound.box') }} {{ box.boxSeqNo }}
<span v-if="box.markBoxNo">({{ box.markBoxNo }})</span>
<span v-if="box.markBoxNo">({{ box.markBoxNo }}�/span>
</div>
<a-table :data="box.lines" :pagination="false" size="small" :bordered="{ cell: true }">
<template #columns>
@ -759,7 +759,7 @@ onMounted(async () => {
|| warehouseOptions.find((o) => o.value === formModel.warehouseId)?.label
|| currentWarehouseStore.warehouseName
|| formModel.warehouseId
|| '—'
|| '�
}}
</span>
</a-form-item>

View File

@ -46,7 +46,7 @@ const {
const listLoading = ref(false)
const tableRows = ref<any[]>([])
const currentPage = ref(1)
const pageSize = ref(30)
const pageSize = ref(50)
const totalCount = ref(0)
const keyword = ref('')
const headerSearchParams = reactive<Record<string, string>>({})
@ -69,7 +69,7 @@ const historyLoading = ref(false)
const historyTab = ref<'all' | 'inbound' | 'deduct'>('all')
const historyRows = ref<any[]>([])
const historyPage = ref(1)
const historyPageSize = ref(30)
const historypageSize = ref(50)
const historyTotal = ref(0)
const [registerRelocateModal, { openModal: openRelocate, closeModal: closeRelocate }] = useModal() as any
@ -180,7 +180,7 @@ async function loadSourceBinOptions(skuId?: number, whId?: number) {
const code = pickField(normalized, 'binCode', 'BinCode') ?? '-'
const name = pickField(normalized, 'binName', 'BinName')
return {
label: `${code}${name ? ` - ${name}` : ''}(${qty})`,
label: `${code}${name ? ` - ${name}` : ''}�{qty})`,
value: Number(pickField(normalized, 'binLocationId', 'BinLocationId') ?? 0),
qty,
}

View File

@ -63,7 +63,7 @@ const {
} = useWarehouseAdminCustomer()
const currentWarehouseStore = useCurrentWarehouseStore()
/** Tab active-key;全部用 'all',请求时再转成不传 lifecycleStatus */
/** Tab active-key;全部用 'all',请求时å†<C3A5>转æˆ<C3A6>ä¸<C3A4>ä¼?lifecycleStatus */
const STATUS_TAB_ALL = 'all'
const lifecycleStatus = ref<string>(STATUS_TAB_ALL)
const keyword = ref('')
@ -81,7 +81,7 @@ const warehouseNameMap = computed(() => buildWarehouseNameMap(warehouseOptions.v
const listLoading = ref(false)
const tableRows = ref<any[]>([])
const currentPage = ref(1)
const pageSize = ref(30)
const pageSize = ref(50)
const totalCount = ref(0)
const statusTabs = [
@ -156,13 +156,13 @@ function isMultiItemRow(row: any): boolean {
return getRowItemCount(row) > 1
}
/** 快递单号:纯文本 */
/** 快递å<EFBFBD>•å<EFBFBD>·ï¼šçº¯æ–‡æœ?*/
function renderTrackingNoCell(_row: any, value: any) {
const text = value == null || value === '' ? '-' : String(value)
return escapeHtml(text)
}
/** 多商品标识列:仅多商品显示红色「多」 */
/** 多商å“<EFBFBD>标识列:仅多商å“<EFBFBD>显示红色「多ã€?*/
function renderMultiBadgeCell(row: any) {
if (!isMultiItemRow(row)) {
return ''
@ -172,11 +172,11 @@ function renderMultiBadgeCell(row: any) {
`<span class="parcel-multi-badge" title="${escapeHtml(tip)}" ` +
`style="display:inline-flex;align-items:center;justify-content:center;` +
`min-width:16px;height:16px;padding:0 3px;border-radius:3px;background:#f53f3f;color:#fff;` +
`font-size:10px;font-weight:700;line-height:1;letter-spacing:0;">多</span>`
`font-size:10px;font-weight:700;line-height:1;letter-spacing:0;">�/span>`
)
}
/** 商品/属性/数量:仅多商品可点、主色;单商品普通文本 */
/** 商å“<EFBFBD>/属æ€?æ•°é‡<C3A9>:仅多商å“<C3A5>å<EFBFBD>¯ç‚¹ã€<C3A3>主色;å<E280BA>•商å“<C3A5>普通文æœ?*/
function renderDetailCell(row: any, value: any) {
const text = value == null || value === '' ? '-' : String(value)
if (!isMultiItemRow(row)) {
@ -191,7 +191,7 @@ function resolveItemImageSrc(url?: string | null) {
return resolveOmsAssetUrl(url) || String(url || '').trim()
}
/** 列表无图 / 加载失败时的缺省图片 icon(data URI)— 用中性灰,日夜间都可读 */
/** 列表无图 / 加载失败时的缺çœ<C3A7>图片 icon(data URI)â€?用中性ç<C2A7>°ï¼Œæ—¥å¤œé—´éƒ½å<C2BD>¯è¯?*/
const EMPTY_IMAGE_PLACEHOLDER =
'data:image/svg+xml,' +
encodeURIComponent(
@ -203,7 +203,7 @@ const EMPTY_IMAGE_PLACEHOLDER =
`</svg>`,
)
/** 图片列:独立缩略图,点击放大;无图显示缺省 icon */
/** 图片列:独立缩略图,点击放大;无图显示缺�icon */
function renderImageCell(row: any) {
const src = resolveItemImageSrc(row?.imageThumbUrl)
if (!src) {
@ -791,7 +791,7 @@ onMounted(async () => {
warehouseOptions.find((o) => o.value === formModel.warehouseId)?.label
|| currentWarehouseStore.warehouseName
|| formModel.warehouseId
|| '—'
|| '�
}}
</span>
</a-form-item>

View File

@ -149,7 +149,7 @@ const headerSearchParams = reactive<Record<string, string>>({})
const ARRIVAL_SERVER_FILTER_KEYS = ['trackingNo'] as const
const tableRows = ref<any[]>([])
const currentPage = ref(1)
const pageSize = ref(30)
const pageSize = ref(50)
const totalCount = ref(0)
const formData = reactive({
@ -253,7 +253,7 @@ async function handleSubmit() {
const serviceNos = linked
.map((o: any) => o.serviceNo || o.ServiceNo)
.filter(Boolean)
.join('、')
.join('�)
if (serviceNos) {
showSuccess(t('warehouse.parcelArrival.message.arriveSuccessWithOrders', { orders: serviceNos }))
if (hasPermission('Operation.FulfillPack')) {

View File

@ -47,7 +47,7 @@ const currentWarehouseStore = useCurrentWarehouseStore()
const listLoading = ref(false)
const tableRows = ref<any[]>([])
const currentPage = ref(1)
const pageSize = ref(30)
const pageSize = ref(50)
const totalCount = ref(0)
const keyword = ref('')
const headerSearchParams = reactive<Record<string, string>>({})
@ -439,7 +439,7 @@ onMounted(async () => {
:wrapper-col-props="{ span: 16 }"
>
<a-form-item :label="t('warehouse.sku.form.currentWarehouse')">
<span class="wh-form-readonly">{{ currentWarehouseLabel || '—' }}</span>
<span class="wh-form-readonly">{{ currentWarehouseLabel || '� }}</span>
</a-form-item>
<a-form-item :label="t('warehouse.sku.form.skuCode')" required>
<a-input
@ -469,7 +469,7 @@ onMounted(async () => {
<div class="wh-form-sku-preview">
<div class="wh-form-sku-preview__image">
<img v-if="skuImagePreview" :src="skuImagePreview" alt="sku" />
<span v-else class="wh-form-hint">—</span>
<span v-else class="wh-form-hint">�/span>
</div>
<div class="wh-form-sku-preview__fields">
<a-upload

View File

@ -4,12 +4,12 @@
<div class="toolbar-left">
<div class="toolbar-info">
<div class="xhs-panel-title">发布记录</div>
<div class="xhs-panel-desc">含定时待发布与已发布/失败记录;到期将自动尝试发布。</div>
<div class="xhs-panel-desc">å<EFBFBD>«å®šæ—å¾…å<EFBFBD>‘布与已å<EFBFBD>‘布/失败记录;到期将自动å°<EFBFBD>试å<EFBFBD>‘布ã€?/div>
</div>
</div>
<div class="toolbar-right">
<a-button :disabled="loading" @click="emit('refresh')">
{{ loading ? '刷新中…' : '刷新' }}
{{ loading ? '刷新中� : '刷新' }}
</a-button>
<a-pagination
v-if="showPagination"
@ -45,7 +45,7 @@ withDefaults(
}>(),
{
showPagination: false,
pagination: () => ({ current: 1, pageSize: 30, total: 0 }),
pagination: () => ({ current: 1, pageSize: 50, total: 0 }),
},
);

View File

@ -5,7 +5,7 @@
<div class="toolbar-left">
<span class="xhs-panel-title">启动服务</span>
<span class="xhs-panel-desc">
本机 Agent + 浏览器引擎:找对标、写稿、发笔记、互动留痕
<EFBFBD>祆㦤 Agent + 瘚讛<EFBFBD><EFBFBD>典<EFBFBD><EFBFBD>𠬍<EFBFBD><EFBFBD>曉笆<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>蝔踴<EFBFBD><EFBFBD><EFBFBD>蝚磰扇<EFBFBD><EFBFBD><EFBFBD><EFBFBD>函<EFBFBD><EFBFBD>?
</span>
</div>
<div class="toolbar-right">
@ -31,7 +31,7 @@
v-if="offlineCount > 0 && agents.length > 0"
type="warning"
style="margin-bottom: 12px"
:title="`有 ${offlineCount} 台 Agent 离线,请检查本机 Agent 是否在运行`"
:title="`<EFBFBD>?${offlineCount} <20>?Agent 蝳餌瑪嚗諹窈璉<E7AA88><E79289>交𧋦<E4BAA4>?Agent <20>臬炏<E887AC>刻<EFBFBD>銵䈣"
closable
/>
@ -39,26 +39,26 @@
v-if="!loading && agents.length === 0"
type="info"
style="margin-bottom: 12px"
title="尚未连接 Agent,请运行 xhs-agent-desktop 并连接 OMS"
title="撠𡁏𧊋餈墧𦻖 Agent嚗諹窈餈鞱<E9A488> xhs-agent-desktop 撟嗉<E6929F><E59789>?OMS"
closable
/>
<div class="service-cards-row">
<div class="service-card xhs-inner-card">
<div class="card-header">
<span class="card-title">小红书 · 浏览器引擎</span>
<span class="card-title">撠讐滯銋?繚 瘚讛<EFBFBD><EFBFBD>典<EFBFBD><EFBFBD>?/span>
<XhsStatusBadge :kind="engineBadge.kind" :text="engineBadge.text" />
</div>
<p class="card-hint">{{ bridgeUrlText }}</p>
<p class="card-hint card-note">
内嵌 xiaohongshu-skills:Bridge Server + Chrome 扩展,在本机真实浏览器中操作小红书。
<EFBFBD><EFBFBD><EFBFBD> xiaohongshu-skills嚗鋳ridge Server + Chrome <EFBFBD>拙<EFBFBD>嚗<EFBFBD>銁<EFBFBD>祆㦤<EFBFBD>笔<EFBFBD>瘚讛<EFBFBD><EFBFBD>其葉<EFBFBD>滢<EFBFBD>撠讐滯銋艾<EFBFBD>?
</p>
<div class="card-btn-row">
<a-button type="primary" :disabled="!canControl" @click="control('engine_start')">
启动引擎
</a-button>
<a-button :disabled="!canControl" @click="control('engine_stop')">停止引擎</a-button>
<a-button @click="loadAgents">刷新状态</a-button>
<a-button @click="loadAgents"><EFBFBD>瑟鰵<EFBFBD>嗆<EFBFBD>?/a-button>
</div>
</div>
@ -69,9 +69,9 @@
</div>
<p class="card-hint card-note">{{ loginGuideText }}</p>
<div class="card-btn-row">
<a-button :disabled="!canControl" @click="control('clear_cookies')">退出登录</a-button>
<a-button :disabled="!canControl" @click="control('clear_cookies')"><EFBFBD><EFBFBD><EFBFBD>箇蒈敶?/a-button>
<a-button type="primary" :disabled="!canControl" @click="control('browser_login')">
浏览器登录
瘚讛<EFBFBD><EFBFBD>函蒈敶?
</a-button>
<a-button :disabled="!canControl" @click="control('check_login')">登录校验</a-button>
</div>
@ -83,7 +83,7 @@
<XhsStatusBadge kind="idle" text="即将支持" />
</div>
<p class="card-hint card-note">
抖音版 Chrome 扩展与自动化引擎正在接入中。上线后可与本站一样使用分析、评论与创作能力。
<EFBFBD>㚚𨺗<EFBFBD>?Chrome <EFBFBD>拙<EFBFBD>銝舘䌊<EFBFBD>典<EFBFBD>撘閙<EFBFBD>甇<EFBFBD>銁<EFBFBD>亙<EFBFBD>銝准<EFBFBD><EFBFBD><EFBFBD>蝥踹<EFBFBD><EFBFBD>臭<EFBFBD><EFBFBD>祉<EFBFBD>銝<EFBFBD><EFBFBD>瑚蝙<EFBFBD>典<EFBFBD><EFBFBD>僐<EFBFBD><EFBFBD><EFBFBD>霈箔<EFBFBD><EFBFBD>𥕢<EFBFBD><EFBFBD>賢<EFBFBD><EFBFBD>?
</p>
<div class="card-btn-row">
<a-button disabled>敬请期待</a-button>
@ -94,7 +94,7 @@
<div v-if="agents.length > 0" class="agent-table-section">
<div class="table-toolbar">
<div class="toolbar-left">
<span class="section-title">已连接设备</span>
<span class="section-title">撌脰<EFBFBD><EFBFBD>亥挽憭?/span>
<a-button @click="loadAgents">
<template #icon><icon-refresh /></template>
刷新
@ -129,7 +129,7 @@
{{ record.deviceName || record.deviceId }}
</template>
</a-table-column>
<a-table-column title="状态" :width="90">
<a-table-column title="<EFBFBD>嗆<EFBFBD>? :width="90">
<template #cell="{ record }">
<XhsStatusBadge
:kind="record.status === 'online' ? 'ok' : 'idle'"
@ -141,7 +141,7 @@
<template #cell="{ record }">
<XhsStatusBadge
:kind="record.engineRunning ? 'ok' : 'err'"
:text="record.engineRunning ? '运行中' : '未启动'"
:text="record.engineRunning ? '餈鞱<EFBFBD>銝? : '<27>芸鍳<E88AB8>?"
/>
</template>
</a-table-column>
@ -149,20 +149,20 @@
<template #cell="{ record }">
<XhsStatusBadge
:kind="record.extensionConnected ? 'ok' : 'warn'"
:text="record.extensionConnected ? '已连接' : '未连接'"
:text="record.extensionConnected ? '撌脰<EFBFBD><EFBFBD>? : '<27>芾<EFBFBD><E88ABE>?"
/>
</template>
</a-table-column>
<a-table-column title="小红书" :width="90">
<a-table-column title="撠讐滯銋? :width="90">
<template #cell="{ record }">
<XhsStatusBadge
:kind="record.xhsLoggedIn ? 'ok' : 'warn'"
:text="record.xhsLoggedIn ? '已登录' : '未登录'"
:text="record.xhsLoggedIn ? '撌脩蒈敶? : '<27>芰蒈敶?"
/>
</template>
</a-table-column>
<a-table-column title="版本" data-index="agentVersion" :width="100" />
<a-table-column title="最后心跳" :width="170">
<a-table-column title="<EFBFBD><EFBFBD><EFBFBD>𤾸<EFBFBD>頝? :width="170">
<template #cell="{ record }">
{{ formatTime(record.lastHeartbeatTime) }}
</template>
@ -180,26 +180,26 @@
:footer="false"
>
<a-steps :current="0" direction="vertical" size="small" style="margin-bottom: 20px">
<a-step description="运行 xhs-agent-desktop/run.ps1 打开桌面端">
打开 Agent 桌面端
<a-step description="餈鞱<EFBFBD> xhs-agent-desktop/run.ps1 <EFBFBD>枏<EFBFBD>獢屸𢒰蝡?>
<EFBFBD>枏<EFBFBD> Agent 獢屸𢒰蝡?
</a-step>
<a-step description="桌面端点击「连接 OMS」;保持本页打开即可自动授权">
一键连接
<a-step description="獢屸𢒰蝡舐<EFBFBD><EFBFBD>颯<EFBFBD>諹<EFBFBD><EFBFBD>?OMS<4D>㵪<EFBFBD>靽脲<E99DBD><E884B2>祇△<E7A587>枏<EFBFBD><E69E8F>喳虾<E596B3>芸𢆡<E88AB8><F0A286A1><EFBFBD>">
銝<EFBFBD><EFBFBD>株<EFBFBD><EFBFBD>?
</a-step>
<a-step description="启动引擎,Chrome 加载内置扩展并登录小红书">
本机引擎与扩展
<EFBFBD>祆㦤撘閙<EFBFBD>銝擧<EFBFBD>撅?
</a-step>
<a-step description="在此页远程启停引擎、扫码登录小红书">
Web 远程控制
</a-step>
</a-steps>
<a-alert type="info" style="margin-bottom: 12px" title="无需填写 API 或 Token">
请先在浏览器登录 OMS,再打开桌面端点击「连接 OMS」。若本页已打开,授权会自动完成。
<a-alert type="info" style="margin-bottom: 12px" title="<EFBFBD>𣳇<EFBFBD>憛怠<EFBFBD> API <20>?Token">
霂瑕<EFBFBD><EFBFBD>冽<EFBFBD>閫<EFBFBD>膥<EFBFBD>餃<EFBFBD> OMS嚗<EFBFBD><EFBFBD><EFBFBD>枏<EFBFBD>獢屸𢒰蝡舐<EFBFBD><EFBFBD>颯<EFBFBD>諹<EFBFBD><EFBFBD>?OMS<EFBFBD>溻<EFBFBD><EFBFBD>𥅾<EFBFBD>祇△撌脫<EFBFBD>撘<EFBFBD>嚗峕<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>芸𢆡摰峕<EFBFBD><EFBFBD>?
</a-alert>
<div class="cmd-block">
<div class="cmd-label">桌面端</div>
<div class="cmd-label">獢屸𢒰蝡?/div>
<pre>{{ desktopAgentHint }}</pre>
</div>
</a-drawer>
@ -228,7 +228,7 @@ const selectedAgentId = ref<number | undefined>();
const showSetupDrawer = ref(false);
const pagination = reactive({
current: 1,
pageSize: 30,
pageSize: 50,
total: 0,
});
@ -257,41 +257,41 @@ const bridgeUrlText = computed(() => {
const engineBadge = computed((): { kind: XhsBadgeKind; text: string } => {
const agent = selectedAgent.value;
if (!agent || agent.status !== 'online') return { kind: 'idle', text: '待刷新' };
if (agent.engineRunning) return { kind: 'ok', text: '运行中' };
if (agent.extensionConnected) return { kind: 'warn', text: '待扩展' };
return { kind: 'err', text: '未启动' };
if (!agent || agent.status !== 'online') return { kind: 'idle', text: '敺<EFBFBD><EFBFBD><EFBFBD>? };
if (agent.engineRunning) return { kind: 'ok', text: '餈鞱<EFBFBD>銝? };
if (agent.extensionConnected) return { kind: 'warn', text: '敺<EFBFBD><EFBFBD>撅? };
return { kind: 'err', text: '<EFBFBD>芸鍳<EFBFBD>? };
});
const loginBadge = computed((): { kind: XhsBadgeKind; text: string } => {
const agent = selectedAgent.value;
if (!agent || agent.status !== 'online') return { kind: 'idle', text: '待刷新' };
if (agent.xhsLoggedIn) return { kind: 'ok', text: '已登录' };
if (!agent || agent.status !== 'online') return { kind: 'idle', text: '敺<EFBFBD><EFBFBD><EFBFBD>? };
if (agent.xhsLoggedIn) return { kind: 'ok', text: '撌脩蒈敶? };
if (agent.engineRunning && agent.extensionConnected) return { kind: 'ok', text: '扩展就绪' };
if (agent.engineRunning) return { kind: 'warn', text: '未登录' };
return { kind: 'warn', text: '待启动' };
if (agent.engineRunning) return { kind: 'warn', text: '<EFBFBD>芰蒈敶? };
return { kind: 'warn', text: '敺<EFBFBD>鍳<EFBFBD>? };
});
const loginGuideText = computed(() => {
const agent = selectedAgent.value;
if (!agent) {
return '只需:连接 Agent → 启动引擎 → 在 Chrome 加载扩展 → 打开小红书并登录。';
return '<EFBFBD>芷<EFBFBD>嚗朞<EFBFBD><EFBFBD>?Agent <EFBFBD>?<EFBFBD>臬𢆡撘閙<EFBFBD> <EFBFBD>?<EFBFBD>?Chrome <EFBFBD>㰘蝸<EFBFBD>拙<EFBFBD> <EFBFBD>?<EFBFBD>枏<EFBFBD>撠讐滯銋血僎<EFBFBD>餃<EFBFBD><EFBFBD>?;
}
if (agent.xhsLoggedIn) {
return '校验通过:可在「内容分析」或「爆款创作」页使用工具。';
return '<EFBFBD>⊿<EFBFBD><EFBFBD>朞<EFBFBD>嚗𡁜虾<EFBFBD>具<EFBFBD><EFBFBD><EFBFBD>摰孵<EFBFBD><EFBFBD>僐<EFBFBD>齿<EFBFBD><EFBFBD>𣬚<EFBFBD>甈曉<EFBFBD>雿栶<EFBFBD>漤△雿輻鍂撌亙<EFBFBD><EFBFBD>?;
}
if (agent.engineRunning && agent.extensionConnected) {
return '引擎与扩展已就绪。请在 Chrome 中打开小红书并登录;可点击扩展图标查看实时状态。';
return '撘閙<EFBFBD>銝擧<EFBFBD>撅訫歇撠梁貌<EFBFBD><EFBFBD>窈<EFBFBD>?Chrome 銝剜<EFBFBD>撘<EFBFBD>撠讐滯銋血僎<EFBFBD>餃<EFBFBD>嚗𥕦虾<EFBFBD>孵稬<EFBFBD>拙<EFBFBD><EFBFBD>暹<EFBFBD><EFBFBD>亦<EFBFBD>摰墧𧒄<EFBFBD>嗆<EFBFBD><EFBFBD><EFBFBD>?;
}
return '只需:启动引擎 → 在 Chrome 加载扩展 → 打开小红书并登录。扩展图标可查看 Bridge 与登录状态;下方按钮用于快捷操作。';
return '<EFBFBD>芷<EFBFBD>嚗𡁜鍳<EFBFBD>典<EFBFBD><EFBFBD>?<EFBFBD>?<EFBFBD>?Chrome <EFBFBD>㰘蝸<EFBFBD>拙<EFBFBD> <EFBFBD>?<EFBFBD>枏<EFBFBD>撠讐滯銋血僎<EFBFBD>餃<EFBFBD><EFBFBD><EFBFBD><EFBFBD>撅訫㦛<EFBFBD><EFBFBD>虾<EFBFBD>亦<EFBFBD> Bridge 銝𡒊蒈敶閧𠶖<EFBFBD><EFBFBD><EFBFBD>銝𧢲䲮<EFBFBD>厰僼<EFBFBD>其<EFBFBD>敹急㭘<EFBFBD>滢<EFBFBD><EFBFBD>?;
});
const desktopAgentHint = computed(() =>
[
'1. 运行 oms0414/xhs-agent-desktop/run.ps1',
'2. 桌面端点击「连接 OMS」(无需填 API / Token)',
'3. 保持本页打开 → 自动授权;或按提示打开授权页',
'4. 启动引擎 → Chrome 加载扩展目录 → 登录小红书',
'2. 獢屸𢒰蝡舐<EFBFBD><EFBFBD>颯<EFBFBD>諹<EFBFBD><EFBFBD>?OMS<EFBFBD>㵪<EFBFBD><EFBFBD>𣳇<EFBFBD>憛?API / Token嚗?,
'3. 靽脲<EFBFBD><EFBFBD>祇△<EFBFBD>枏<EFBFBD> <EFBFBD>?<EFBFBD>芸𢆡<EFBFBD><EFBFBD><EFBFBD>嚗𥟇<EFBFBD><EFBFBD>㗇<EFBFBD>蝷箸<EFBFBD>撘<EFBFBD><EFBFBD><EFBFBD><EFBFBD>憿?,
'4. <EFBFBD>臬𢆡撘閙<EFBFBD> <EFBFBD>?Chrome <EFBFBD>㰘蝸<EFBFBD>拙<EFBFBD><EFBFBD>桀<EFBFBD> <EFBFBD>?<EFBFBD>餃<EFBFBD>撠讐滯銋?,
].join('\n'),
);
@ -333,7 +333,7 @@ async function tryAutoPair() {
const ok = await pushCredentialsToAgent(token);
if (ok) {
pairPushed = true;
Message.success('已向桌面 Agent 发送授权');
Message.success('撌脣<EFBFBD>獢屸𢒰 Agent <EFBFBD>煾<EFBFBD><EFBFBD><EFBFBD><EFBFBD>?);
setTimeout(loadAgents, 1500);
}
}
@ -370,13 +370,13 @@ async function control(action: string) {
return;
}
if (agent.status !== 'online') {
Message.warning('所选 Agent 离线,请检查本机是否在运行');
Message.warning('<EFBFBD><EFBFBD><EFBFBD>?Agent 蝳餌瑪嚗諹窈璉<E7AA88><E79289>交𧋦<E4BAA4>箸糓<E7AEB8>血銁餈鞱<E9A488>');
return;
}
if (action === 'clear_cookies') {
Modal.confirm({
title: '退出登录',
content: '将在浏览器中退出小红书登录状态,确定继续?',
title: '<EFBFBD><EFBFBD><EFBFBD>箇蒈敶?,
content: '撠<EFBFBD>銁瘚讛<EFBFBD><EFBFBD>其葉<EFBFBD><EFBFBD><EFBFBD>箏<EFBFBD>蝥V髡<EFBFBD>餃<EFBFBD><EFBFBD>嗆<EFBFBD><EFBFBD><EFBFBD>蝖桀<EFBFBD>蝏抒賒嚗?,
onOk: () => sendControl(agent.id, action),
});
return;
@ -387,7 +387,7 @@ async function control(action: string) {
async function sendControl(agentId: number, action: string) {
try {
const res = await controlXhsAgent(agentId, action);
Message.success(`已创建任务 #${res.taskId}`);
Message.success(`撌脣<EFBFBD>撱箔遙<EFBFBD>?#${res.taskId}`);
setTimeout(loadAgents, 2000);
} catch (e: any) {
Message.error(e?.message || '操作失败');