2026032301
This commit is contained in:
parent
dc801a0ba1
commit
de22605535
@ -262,6 +262,7 @@
|
||||
"useSwipe": true,
|
||||
"useSystemConfig": true,
|
||||
"useTabBarStore": true,
|
||||
"useTableContainerHeight": true,
|
||||
"useTemplateRef": true,
|
||||
"useTemplateRefsList": true,
|
||||
"useTextDirection": true,
|
||||
|
||||
@ -43,74 +43,6 @@ OMS(Order Management System,订单管理系统)作为独立服务运行和
|
||||
4. **出库扫描**:订单发货时完成**出库扫描**,进入待发货/运输中。
|
||||
5. **闭环完成**:出库扫描完成后,订单进入运输、派送环节,正常订单流程闭环结束。
|
||||
|
||||
----------------------------------------------------------------------------------------
|
||||
|
||||
## 一、通用设计规范
|
||||
|
||||
### 1.1 接口设计规范
|
||||
|
||||
#### HTTP 方法约束(仅 POST / GET)
|
||||
|
||||
| 方法 | 用途 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| **GET** | 查询、详情、列表、导出 | 幂等、无副作用,参数放 Query String |
|
||||
| **POST** | 新增、修改、删除、状态变更、提交 | 所有变更类操作统一用 POST,参数放 Request Body |
|
||||
|
||||
> 因网关/代理/防火墙限制,仅支持 GET/POST 时,更新、删除等操作均通过 POST + 业务动作区分。
|
||||
|
||||
#### URL 设计规范
|
||||
|
||||
| 规范 | 说明 | 示例 |
|
||||
| --- | --- | --- |
|
||||
| 路径小写 | 使用小写字母,短横线分隔 | `/api/order/list`、`/api/sales-product/detail` |
|
||||
| 资源名词 | URL 表示资源,不包含动词 | `/api/customer` 而非 `/api/getCustomer` |
|
||||
| 层级清晰 | 最多 3 级,避免过深 | `/api/channel/sales-product/price/list` |
|
||||
| 动作后缀 | POST 动作可用动词后缀 | `/api/order/delete`、`/api/recharge/approve` |
|
||||
|
||||
#### 统一响应格式
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0, // 0-成功,非0-业务错误码
|
||||
"msg": "success", // 提示信息
|
||||
"data": { } // 业务数据,可为 null
|
||||
}
|
||||
```
|
||||
|
||||
#### 分页规范
|
||||
|
||||
**请求**:`page`(页码,从 1 开始)、`pageSize`(每页条数,默认 20,最大 100)
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0, // 0-成功,非0-业务错误码
|
||||
"msg": "success", // 提示信息
|
||||
"data": {
|
||||
"items": [], // 数据列表
|
||||
"total": 100 // 总条数
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误码规范
|
||||
|
||||
| 错误码 | 说明 |
|
||||
| --- | --- |
|
||||
| 0 | 成功 |
|
||||
| 401 | 未登录 |
|
||||
| 403 | 无权限 |
|
||||
| 400 | 参数错误 |
|
||||
| 500 | 服务器内部错误 |
|
||||
| 1xxxx | 业务错误码(按模块划分) |
|
||||
|
||||
#### 其他规范
|
||||
|
||||
- **Content-Type**:`application/json`;文件上传用 `multipart/form-data`
|
||||
- **时间格式**:`yyyy-MM-dd HH:mm:ss` 或 ISO 8601
|
||||
- **认证**:`Authorization: Bearer {token}` 或 Cookie
|
||||
- **幂等**:涉及金额、库存等变更的接口需支持幂等(如传 `requestId`)
|
||||
|
||||
----------------------------------------------------------------------------------------
|
||||
|
||||
@ -81,6 +81,57 @@ a-layout (垂直)
|
||||
@screen-xxl: 1600px; // 超大屏
|
||||
```
|
||||
|
||||
### 2.5 页面壳、白卡片与列表区尺寸(列表 / Handsontable 类页面)
|
||||
|
||||
以下约定与 `default-layout` + `page-layout`、`*-container` / `*-page` 等实现一致,避免「灰底大块留白、列表高度塌缩、内外边距叠成 32px/40px」等问题。
|
||||
|
||||
#### 2.5.1 页面层(灰底壳)
|
||||
|
||||
| 项目 | 约定 |
|
||||
|------|------|
|
||||
| 背景色 | `#f0f3f4`(与布局主内容区一致) |
|
||||
| 页面根容器 padding | **`16px 16px 24px`**:上/左/右 **16px**,**底 24px** —— 与视口底边留白,列表区随 flex 撑满后,整块内容与屏幕底边保持 **24px** 间距 |
|
||||
| 参与主内容区高度 | `flex: 1; min-height: 0; display: flex; flex-direction: column;`,与 `PageLayout` 子节点 flex 链衔接 |
|
||||
| 纵向滚动 | 页面根容器宜 `overflow-y: hidden`(或仅最外层允许滚动),**表格/Handsontable 在内部 `.list-wrap` / `.wtHolder` 滚动**,避免整页与表格双滚动条 |
|
||||
|
||||
#### 2.5.2 白卡片(白色内容块)
|
||||
|
||||
| 项目 | 约定 |
|
||||
|------|------|
|
||||
| 视觉 | 背景 `#fff`,`border-radius: 8px`,`box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08)`(可按页面微调透明度) |
|
||||
| 撑满剩余高度 | `flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: hidden` |
|
||||
| 内边距(与列表区配合) | **二选一,避免与 `.list-wrap` 重复叠加**:<br>• **推荐**:白卡片 **`padding: 0`**,由 **页头/搜索行、`.table-toolbar`、`.list-wrap`** 分区控制留白(见下);<br>• **简化页**:白卡片统一 `padding: 16px`,则内部列表区不再重复加左右底 padding |
|
||||
|
||||
#### 2.5.3 表格工具栏(`.table-toolbar`)
|
||||
|
||||
| 项目 | 约定 |
|
||||
|------|------|
|
||||
| 与列表横向对齐 | `padding: 16px 16px 0`(上 16、左右 16,底由与列表的间距承担) |
|
||||
| 与分页/标题间距 | `margin-bottom: 16px`(分页组件(`PaginationPro`)与列表区 `.list-wrap` 的固定间距) |
|
||||
| 收缩 | `flex-shrink: 0` |
|
||||
|
||||
分页组件放置约束:`PaginationPro` 放在 `.toolbar-right` 内,并与 `toolbar-left` 保持在同一行(同一个 `flex` 行中)展示;工具栏外层使用 `padding: 16px 16px 0` 以保证分页组件距上边/右边均为 16px,且不需要在 `PaginationPro` 外再额外套一层 padding。
|
||||
|
||||
有**独立搜索行**(如轨迹配置页)时:搜索框可用 `margin: 16px 16px 16px`,与工具栏、列表区 **左右 16px** 对齐。
|
||||
|
||||
#### 2.5.4 列表区(`.list-wrap`)
|
||||
|
||||
用于包裹 **HandsontablePro** 或需 **固定测量高度** 的表格容器(配合 `useTableContainerHeight` 的 `tableContainerRef` **挂在本层**)。
|
||||
|
||||
| 项目 | 约定 |
|
||||
|------|------|
|
||||
| 与内容区四边距离 | **默认**:`padding: 16px`(上、右、下、左均为 **16px**,相对**白卡片内容边缘**或分区后的可用区域,勿与白卡片 padding 重复叠加)。<br>**与 `.list-toolbar` 联用**:当 `.list-toolbar` 使用 `margin-bottom: 16px` 作为“分页组件到列表”的固定间距时,为避免重复留白,建议 `.list-wrap` 使用 `padding: 0 16px 16px`(上 0、左右/下 16)。 |
|
||||
| 布局 | `flex: 1; min-height: 0; width: 100%; box-sizing: border-box; overflow: hidden; display: flex; flex-direction: column` |
|
||||
| 触摸/滚动冒泡 | 可选:`overscroll-behavior: contain; touch-action: pan-x pan-y` |
|
||||
| 子级 Handsontable | 建议 `:deep(.handsontable-pro-container) { flex: 1; min-height: 0; display: flex; flex-direction: column; }`,保证纵向撑满 |
|
||||
|
||||
分页组件同一行约束(列表工具栏场景):当页面存在 `.list-toolbar` / `table-toolbar` 时,`PaginationPro` 必须放在右侧同一行的工具栏区域(如 `.toolbar-right`),不要将分页组件单独再做成一行(避免“工具栏一行 + 分页下一行”的不一致布局)。
|
||||
|
||||
#### 2.5.5 与布局组件的关系
|
||||
|
||||
- `PageLayout` 下直接子节点(路由页根)需能被主内容区赋予高度:项目中通过 `flex: 1; min-height: 0` 等与 `a-layout-content` 链式配合。
|
||||
- `default-layout` 中对 `[class$='-container']` 的样式仅作用于布局模板内节点;**业务页根类名**(如 `xxx-page`、`track-order-container`)需在**页面自身样式**中按上表补齐 flex 与 padding。
|
||||
|
||||
---
|
||||
|
||||
## 三、色彩规范
|
||||
@ -426,8 +477,11 @@ const { t } = useI18n();
|
||||
|
||||
### 13.2 容器类
|
||||
|
||||
- 页面根容器:`xxx-container`(如 `role-page`、业务相关命名),保证 `padding: 16px`
|
||||
- 表格工具栏:`table-toolbar`、`toolbar-left`、`toolbar-right`
|
||||
- 页面根容器:`xxx-container` / `xxx-page`(如 `role-page`、`track-order-container`),**列表类页面**推荐 `padding: 16px 16px 24px`,详见 **§2.5 页面壳、白卡片与列表区尺寸**
|
||||
- 白卡片:`*-page-card`、`*-section` 等,与 `.list-wrap` 的 padding **勿重复叠加**
|
||||
- 列表区:`.list-wrap`,**四边 16px**(相对分区后的内容边缘)
|
||||
- 表格工具栏:`table-toolbar`、`toolbar-left`、`toolbar-right`,工具栏 **`padding: 16px 16px 0`** 与列表对齐
|
||||
- Handsontable/列表分页工具栏:`.list-toolbar`(当页面没有标准 `table-toolbar` 时),与 `table-toolbar` 保持一致,使用 **`padding: 16px 16px 0`** 且 `margin-bottom: 16px`;`PaginationPro` 必须与 `toolbar-left` 同一行展示(放在 `.toolbar-right` 内,禁止分页另起行),同时 `.list-wrap` 在联用场景建议 `padding-top: 0`(例如 `padding: 0 16px 16px`),实现分页距上/右 16px、距列表 16px
|
||||
|
||||
### 13.3 按钮间距
|
||||
|
||||
@ -438,8 +492,10 @@ const { t } = useI18n();
|
||||
|
||||
## 十四、检查清单(新页面开发)
|
||||
|
||||
- [ ] 使用 `xxx-container` 或等效布局结构
|
||||
- [ ] 工具栏使用 `table-toolbar`、`toolbar-left`、`toolbar-right`
|
||||
- [ ] 使用 `xxx-container` / `xxx-page` 或等效布局结构;列表页见 **§2.5**:灰底 **`padding: 16px 16px 24px`**、`flex: 1; min-height: 0`
|
||||
- [ ] 白卡片与 **`.list-wrap`** 内边距勿重复叠加(推荐白卡片 `padding: 0` + 分区 padding)
|
||||
- [ ] **`.list-wrap`**:`padding: 16px`、`flex: 1; min-height: 0`;需测高时 `ref` 挂在本层
|
||||
- [ ] 工具栏使用 `table-toolbar`、`toolbar-left`、`toolbar-right`,工具栏 **`padding: 16px 16px 0`**
|
||||
- [ ] 按钮、输入框符合全局样式(高度、圆角)
|
||||
- [ ] 使用主题色变量,不硬编码 `#2D8C8C`
|
||||
- [ ] 弹窗 / 抽屉使用 ModalPro / DrawerPro + useModal
|
||||
@ -517,4 +573,4 @@ const { t } = useI18n();
|
||||
|
||||
---
|
||||
|
||||
*文档版本:1.0 | 更新日期:2025-03-21*
|
||||
*文档版本:1.1 | 更新日期:2026-03-23*
|
||||
|
||||
2
src/auto-imports.d.ts
vendored
2
src/auto-imports.d.ts
vendored
@ -251,6 +251,7 @@ declare global {
|
||||
const useSwipe: typeof import('@vueuse/core')['useSwipe']
|
||||
const useSystemConfig: typeof import('./composables/useSystemConfig')['useSystemConfig']
|
||||
const useTabBarStore: typeof import('./store/index')['useTabBarStore']
|
||||
const useTableContainerHeight: typeof import('./composables/useTableContainerHeight')['useTableContainerHeight']
|
||||
const useTemplateRef: typeof import('vue')['useTemplateRef']
|
||||
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
|
||||
const useTextDirection: typeof import('@vueuse/core')['useTextDirection']
|
||||
@ -561,6 +562,7 @@ declare module 'vue' {
|
||||
readonly useSwipe: UnwrapRef<typeof import('@vueuse/core')['useSwipe']>
|
||||
readonly useSystemConfig: UnwrapRef<typeof import('./composables/useSystemConfig')['useSystemConfig']>
|
||||
readonly useTabBarStore: UnwrapRef<typeof import('./store/index')['useTabBarStore']>
|
||||
readonly useTableContainerHeight: UnwrapRef<typeof import('./composables/useTableContainerHeight')['useTableContainerHeight']>
|
||||
readonly useTemplateRef: UnwrapRef<typeof import('vue')['useTemplateRef']>
|
||||
readonly useTemplateRefsList: UnwrapRef<typeof import('@vueuse/core')['useTemplateRefsList']>
|
||||
readonly useTextDirection: UnwrapRef<typeof import('@vueuse/core')['useTextDirection']>
|
||||
|
||||
@ -58,6 +58,10 @@
|
||||
showCheckboxColumn?: boolean;
|
||||
// 复选框列表头文案(空则表头为空)
|
||||
checkboxColumnHeader?: string;
|
||||
// 序号列表头文案
|
||||
serialNoHeader?: string;
|
||||
// 操作列表头文案
|
||||
actionColumnHeader?: string;
|
||||
// 复选框变化回调
|
||||
onCheckboxChange?: (row: any, checked: boolean, rowIndex: number) => void;
|
||||
// 操作列渲染器
|
||||
@ -105,6 +109,8 @@
|
||||
actionColumnWidth: 60,
|
||||
showCheckboxColumn: false,
|
||||
checkboxColumnHeader: '',
|
||||
serialNoHeader: '序号',
|
||||
actionColumnHeader: '操作',
|
||||
readOnly: true,
|
||||
multiValueFields: () => [],
|
||||
height: undefined,
|
||||
@ -176,16 +182,23 @@
|
||||
|
||||
// 计算表格高度
|
||||
const calculatedHeight = computed(() => {
|
||||
if (props.height) {
|
||||
if (props.height != null && props.height > 0) {
|
||||
return props.height;
|
||||
}
|
||||
const headerHeight = 40;
|
||||
const rowHeight = 50;
|
||||
const maxRows = 20;
|
||||
const minHeight = 1000;
|
||||
const minHeight = 400;
|
||||
return Math.max(headerHeight + maxRows * rowHeight, minHeight);
|
||||
});
|
||||
|
||||
/** 外部传入 height 为 0 时不能用 ?? 回退(0 非 nullish),需显式判断 */
|
||||
const resolveTableHeight = (containerH: number): number => {
|
||||
if (props.height != null && props.height > 0) return props.height;
|
||||
if (containerH > 0) return containerH;
|
||||
return calculatedHeight.value;
|
||||
};
|
||||
|
||||
// 创建省略号渲染器
|
||||
const createEllipsisRenderer = () => {
|
||||
return function(instance: any, td: any, row: number, col: number, prop: any, value: any) {
|
||||
@ -241,7 +254,7 @@
|
||||
// 操作列(在前面)
|
||||
if (props.showActionColumn && props.actionColumnPosition === 'start') {
|
||||
columns.push({ data: currentDataIndex, type: 'text', readOnly: true, width: props.actionColumnWidth, className: 'htCenter', headerClassName: 'htLeft' });
|
||||
headerTexts.push('操作');
|
||||
headerTexts.push(props.actionColumnHeader);
|
||||
currentDataIndex++;
|
||||
}
|
||||
|
||||
@ -255,7 +268,7 @@
|
||||
headerClassName: 'htLeft',
|
||||
renderer: ellipsisRenderer
|
||||
});
|
||||
headerTexts.push('序号');
|
||||
headerTexts.push(props.serialNoHeader);
|
||||
currentDataIndex++;
|
||||
|
||||
// 数据列(beforeLast 时最后一列在操作列之后)
|
||||
@ -264,7 +277,7 @@
|
||||
// beforeLast:在倒数第二列后插入操作列
|
||||
if (props.showActionColumn && props.actionColumnPosition === 'beforeLast' && index === dataColCount - 1) {
|
||||
columns.push({ data: currentDataIndex, type: 'text', readOnly: true, width: props.actionColumnWidth, className: 'htCenter', headerClassName: 'htLeft' });
|
||||
headerTexts.push('操作');
|
||||
headerTexts.push(props.actionColumnHeader);
|
||||
currentDataIndex++;
|
||||
}
|
||||
columns.push({
|
||||
@ -282,7 +295,7 @@
|
||||
// 操作列(在后面,非 beforeLast 时)
|
||||
if (props.showActionColumn && props.actionColumnPosition === 'end') {
|
||||
columns.push({ data: currentDataIndex, type: 'text', readOnly: true, width: props.actionColumnWidth, className: 'htCenter', headerClassName: 'htLeft' });
|
||||
headerTexts.push('操作');
|
||||
headerTexts.push(props.actionColumnHeader);
|
||||
currentDataIndex++;
|
||||
}
|
||||
|
||||
@ -343,7 +356,7 @@
|
||||
try {
|
||||
// 未传 height 时使用容器实际高度,使列表高度与容器一致
|
||||
const containerH = containerRef.value?.clientHeight ?? 0;
|
||||
const tableHeight = props.height ?? (containerH > 0 ? containerH : calculatedHeight.value);
|
||||
const tableHeight = resolveTableHeight(containerH);
|
||||
|
||||
const hotConfig: any = {
|
||||
data: data,
|
||||
@ -511,7 +524,7 @@
|
||||
|
||||
// 操作列、序号列:用极简表头替换,去除 div.relative/span.colHeader 等多余容器,确保 60px 下完整显示
|
||||
if (col === actionColStartIndex || col === serialColIndex || col === actionColEndIndex) {
|
||||
const label = col === serialColIndex ? '序号' : '操作';
|
||||
const label = col === serialColIndex ? props.serialNoHeader : props.actionColumnHeader;
|
||||
TH.innerHTML = '';
|
||||
TH.classList.add('ht-header-simple');
|
||||
const span = document.createElement('span');
|
||||
@ -1069,17 +1082,12 @@
|
||||
}, 50);
|
||||
};
|
||||
|
||||
// 初始化完成后再次刷新尺寸
|
||||
// 初始化完成后刷新尺寸(避免与 ResizeObserver 重复狂刷导致 stretchH 列宽错乱)
|
||||
const finalizeInit = () => {
|
||||
setTimeout(() => {
|
||||
if (hotInstance) {
|
||||
hotInstance.refreshDimensions();
|
||||
|
||||
setTimeout(() => {
|
||||
if (hotInstance) {
|
||||
hotInstance.refreshDimensions();
|
||||
}
|
||||
}, 100);
|
||||
setTimeout(() => hotInstance?.refreshDimensions(), 100);
|
||||
}
|
||||
}, 300);
|
||||
};
|
||||
@ -1193,13 +1201,15 @@
|
||||
if (!containerRef.value) return;
|
||||
resizeObserver = new ResizeObserver((entries) => {
|
||||
const entry = entries[0];
|
||||
if (entry && hotInstance && entry.contentRect.height > 0) {
|
||||
const h = Math.floor(entry.contentRect.height);
|
||||
hotInstance.updateSettings({ height: h });
|
||||
nextTick(() => {
|
||||
hotInstance?.refreshDimensions();
|
||||
});
|
||||
}
|
||||
if (!entry || !hotInstance) return;
|
||||
const h = entry.contentRect.height;
|
||||
// 高度以本组件根容器实测为准;勿用外层 props.height 覆盖,否则与 flex 内真实高度不一致,stretchH 会把列压满导致横向条消失
|
||||
if (h <= 0) return;
|
||||
const fh = Math.floor(h);
|
||||
hotInstance.updateSettings({ height: fh });
|
||||
nextTick(() => {
|
||||
hotInstance?.refreshDimensions();
|
||||
});
|
||||
});
|
||||
resizeObserver.observe(containerRef.value);
|
||||
};
|
||||
|
||||
@ -80,7 +80,7 @@
|
||||
:model-value="props.pageSize"
|
||||
size="small"
|
||||
class="page-size-select"
|
||||
:style="{ width: '110px' }"
|
||||
:style="{ width: '85px' }"
|
||||
@update:model-value="handlePageSizeChange"
|
||||
>
|
||||
<a-option
|
||||
@ -97,7 +97,7 @@
|
||||
v-model="jumpPage"
|
||||
size="small"
|
||||
class="jump-input"
|
||||
:style="{ width: '60px' }"
|
||||
:style="{ width: '50px' }"
|
||||
@keyup.enter="handleJumpPage"
|
||||
/>
|
||||
<span class="page-text">{{ t('common.pagination.page') }}</span>
|
||||
|
||||
@ -52,6 +52,9 @@
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
min-height: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.layout {
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.page-layout-root > * {
|
||||
.page-layout-root :deep(> *) {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
@ -109,6 +109,16 @@ export default {
|
||||
'menu.api.apiDebugger': 'API Debugger',
|
||||
'menu.api.apiLike': 'API LIKE',
|
||||
'menu.api.apiTest': 'API Test',
|
||||
|
||||
// API - Call Statistics
|
||||
'api.callStatistics.name.app': 'Application Name',
|
||||
'api.callStatistics.name.provider': 'Service Provider Name',
|
||||
'api.callStatistics.table.date': 'Date',
|
||||
'api.callStatistics.table.interfaceName': 'Interface Name',
|
||||
'api.callStatistics.table.total': 'Total Calls',
|
||||
'api.callStatistics.table.succeedCount': 'Successful Calls',
|
||||
'api.callStatistics.table.failCount': 'Failed Calls',
|
||||
'api.callStatistics.table.successRate': 'Success Rate',
|
||||
'menu.system': 'System',
|
||||
'menu.system.org': 'Organization',
|
||||
'menu.system.organization': 'Organization',
|
||||
|
||||
@ -109,6 +109,16 @@ export default {
|
||||
'menu.api.apiDebugger': 'API调试',
|
||||
'menu.api.apiLike': 'API LIKE',
|
||||
'menu.api.apiTest': 'API测试',
|
||||
|
||||
// API - 调用统计
|
||||
'api.callStatistics.name.app': '应用名称',
|
||||
'api.callStatistics.name.provider': '服务商名称',
|
||||
'api.callStatistics.table.date': '日期',
|
||||
'api.callStatistics.table.interfaceName': '接口名称',
|
||||
'api.callStatistics.table.total': '总调用次数',
|
||||
'api.callStatistics.table.succeedCount': '成功调用次数',
|
||||
'api.callStatistics.table.failCount': '失败调用次数',
|
||||
'api.callStatistics.table.successRate': '成功率',
|
||||
'menu.system': '系统',
|
||||
'menu.system.org': '组织管理',
|
||||
'menu.system.organization': '组织管理',
|
||||
|
||||
@ -6,14 +6,16 @@
|
||||
import HandsontablePro from '@/components/HandsontablePro/index.vue';
|
||||
import PaginationPro from '@/components/PaginationPro/index.vue';
|
||||
import { useTableContainerHeight } from '@/composables/useTableContainerHeight';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
defineOptions({ name: 'CallStatistics' });
|
||||
const { t } = useI18n();
|
||||
|
||||
const activeName = ref('1'); // 1:应用,2:服务商
|
||||
const { tableContainerRef, tableHeight } = useTableContainerHeight();
|
||||
const tabList = [
|
||||
{ key: '1', label: '应用' },
|
||||
{ key: '2', label: '服务商' },
|
||||
{ key: '1', label: t('system.log.options.app') },
|
||||
{ key: '2', label: t('system.log.options.provider') },
|
||||
];
|
||||
|
||||
const tableData = ref<any[]>([]);
|
||||
@ -30,16 +32,16 @@
|
||||
});
|
||||
|
||||
const typeParam = computed(() => (activeName.value === '2' ? 2 : 1));
|
||||
const nameLabel = computed(() => (activeName.value === '2' ? '服务商名称' : '应用名称'));
|
||||
const nameLabel = computed(() => (activeName.value === '2' ? t('api.callStatistics.name.provider') : t('api.callStatistics.name.app')));
|
||||
|
||||
const columns = computed(() => [
|
||||
{ key: 'date', label: '日期', width: 120, searchable: false },
|
||||
{ key: 'date', label: t('api.callStatistics.table.date'), width: 120, searchable: false },
|
||||
{ key: 'name', label: nameLabel.value, width: 180, searchable: true, searchField: 'name' },
|
||||
{ key: 'interfaceName', label: '接口名称', width: 180, searchable: true, searchField: 'interfaceName' },
|
||||
{ key: 'total', label: '总调用次数', width: 120, searchable: false },
|
||||
{ key: 'succeedCount', label: '成功调用次数', width: 140, searchable: false },
|
||||
{ key: 'failCount', label: '失败调用次数', width: 140, searchable: false },
|
||||
{ key: 'successRate', label: '成功率', width: 100, searchable: false },
|
||||
{ key: 'interfaceName', label: t('api.callStatistics.table.interfaceName'), width: 180, searchable: true, searchField: 'interfaceName' },
|
||||
{ key: 'total', label: t('api.callStatistics.table.total'), width: 120, searchable: false },
|
||||
{ key: 'succeedCount', label: t('api.callStatistics.table.succeedCount'), width: 140, searchable: false },
|
||||
{ key: 'failCount', label: t('api.callStatistics.table.failCount'), width: 140, searchable: false },
|
||||
{ key: 'successRate', label: t('api.callStatistics.table.successRate'), width: 100, searchable: false },
|
||||
]);
|
||||
|
||||
const processedTableData = computed(() =>
|
||||
@ -121,105 +123,122 @@
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="call-statistics-container">
|
||||
<!-- 标签页导航 -->
|
||||
<div class="tabs-section">
|
||||
<div class="tabs-container">
|
||||
<div
|
||||
v-for="tab in tabList"
|
||||
:key="tab.key"
|
||||
:class="['tab-item', { active: activeName === tab.key }]"
|
||||
@click="handleTabChange(tab.key)"
|
||||
>
|
||||
<span class="tab-label">{{ tab.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<div class="table-section">
|
||||
<!-- 筛选栏 + 分页 -->
|
||||
<div class="table-toolbar">
|
||||
<div class="toolbar-left">
|
||||
<div class="filter-bar">
|
||||
<a-range-picker
|
||||
v-model="headerDateRange"
|
||||
type="daterange"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
:style="{ width: '240px' }"
|
||||
class="filter-date-picker"
|
||||
/>
|
||||
<a-input
|
||||
v-model="searchParams.name"
|
||||
:placeholder="nameLabel"
|
||||
class="filter-input"
|
||||
style="width: 160px"
|
||||
allow-clear
|
||||
@keyup.enter="handleHeaderSearch"
|
||||
@blur="handleHeaderSearch"
|
||||
/>
|
||||
<a-input
|
||||
v-model="searchParams.interfaceName"
|
||||
placeholder="接口名称"
|
||||
class="filter-input"
|
||||
style="width: 160px"
|
||||
allow-clear
|
||||
@keyup.enter="handleHeaderSearch"
|
||||
@blur="handleHeaderSearch"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toolbar-right">
|
||||
<div class="pagination-controls">
|
||||
<PaginationPro
|
||||
:total-count="totalCount"
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
@page-change="handlePageChange"
|
||||
@page-size-change="handlePageSizeChange"
|
||||
/>
|
||||
<div class="callStatistics-container">
|
||||
<div class="callStatistics-page-card">
|
||||
<!-- 标签页导航 -->
|
||||
<div class="tabs-section">
|
||||
<div class="tabs-container">
|
||||
<div
|
||||
v-for="tab in tabList"
|
||||
:key="tab.key"
|
||||
:class="['tab-item', { active: activeName === tab.key }]"
|
||||
@click="handleTabChange(tab.key)"
|
||||
>
|
||||
<span class="tab-label">{{ tab.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Handsontable 表格 -->
|
||||
<div ref="tableContainerRef" class="table-wrapper">
|
||||
<HandsontablePro
|
||||
:key="`table-${activeName}-${currentPage}`"
|
||||
:columns="columns"
|
||||
:data="processedTableData"
|
||||
:total="Number(totalCount) || 0"
|
||||
:current-page="currentPage"
|
||||
:page-size="pageSize"
|
||||
:search-params="searchParams"
|
||||
:height="tableHeight"
|
||||
:stretch-columns="true"
|
||||
:show-action-column="false"
|
||||
:show-checkbox-column="false"
|
||||
@search="handleHeaderSearch"
|
||||
/>
|
||||
<!-- 表格区域 -->
|
||||
<div class="table-section">
|
||||
<!-- 筛选栏 + 分页 -->
|
||||
<div class="table-toolbar">
|
||||
<div class="toolbar-left">
|
||||
<div class="filter-bar">
|
||||
<a-range-picker
|
||||
v-model="headerDateRange"
|
||||
type="daterange"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
:style="{ width: '240px' }"
|
||||
class="filter-date-picker"
|
||||
/>
|
||||
<a-input
|
||||
v-model="searchParams.name"
|
||||
:placeholder="nameLabel"
|
||||
class="filter-input"
|
||||
style="width: 160px"
|
||||
allow-clear
|
||||
@keyup.enter="handleHeaderSearch"
|
||||
@blur="handleHeaderSearch"
|
||||
/>
|
||||
<a-input
|
||||
v-model="searchParams.interfaceName"
|
||||
:placeholder="t('system.log.placeholder.interfaceName')"
|
||||
class="filter-input"
|
||||
style="width: 160px"
|
||||
allow-clear
|
||||
@keyup.enter="handleHeaderSearch"
|
||||
@blur="handleHeaderSearch"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toolbar-right">
|
||||
<div class="pagination-controls">
|
||||
<PaginationPro
|
||||
:total-count="totalCount"
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
@page-change="handlePageChange"
|
||||
@page-size-change="handlePageSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Handsontable 表格 -->
|
||||
<div ref="tableContainerRef" class="list-wrap">
|
||||
<HandsontablePro
|
||||
:key="`table-${activeName}-${currentPage}`"
|
||||
:columns="columns"
|
||||
:data="processedTableData"
|
||||
:total="Number(totalCount) || 0"
|
||||
:current-page="currentPage"
|
||||
:page-size="pageSize"
|
||||
:search-params="searchParams"
|
||||
:height="tableHeight"
|
||||
:stretch-columns="true"
|
||||
:show-action-column="false"
|
||||
:show-checkbox-column="false"
|
||||
@search="handleHeaderSearch"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.call-statistics-container {
|
||||
padding: 16px;
|
||||
.callStatistics-container {
|
||||
padding: 16px 16px 24px;
|
||||
background-color: #f0f3f4;
|
||||
height: 100vh;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.callStatistics-page-card {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.tabs-section {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 0 24px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
padding: 16px 16px 0;
|
||||
margin-bottom: 8px;
|
||||
flex-shrink: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.tabs-container {
|
||||
@ -255,18 +274,20 @@
|
||||
}
|
||||
|
||||
.table-section {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
height: calc(100vh - 80px);
|
||||
overflow: visible;
|
||||
background: transparent;
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
box-shadow: none;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.table-toolbar {
|
||||
padding: 16px 16px 0;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@ -305,8 +326,26 @@
|
||||
.table-wrapper {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.list-wrap {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 16px;
|
||||
|
||||
:deep(.handsontable-pro-container) {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -681,10 +681,11 @@
|
||||
}
|
||||
|
||||
.table-toolbar {
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
.toolbar-left {
|
||||
flex: 1;
|
||||
|
||||
@ -1,7 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue'
|
||||
import { computed, nextTick, reactive, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { $baseMessage } from '@/utils'
|
||||
import HandsontablePro from '@/components/HandsontablePro/index.vue'
|
||||
import { useTableContainerHeight } from '@/composables/useTableContainerHeight'
|
||||
import ModalPro from '@/components/ModalPro/index.vue'
|
||||
import useModal from '@/components/ModalPro/useModal'
|
||||
import FormPro from '@/components/FormPro/index.vue'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
defineOptions({
|
||||
name: 'AutoTrigger'
|
||||
@ -9,8 +15,20 @@ defineOptions({
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
// 表格数据
|
||||
const tableData = ref([
|
||||
type AutoTriggerRow = {
|
||||
id: number
|
||||
status: boolean
|
||||
operation: string
|
||||
triggerTiming: string
|
||||
triggerCondition: string
|
||||
promptMessage: string
|
||||
notifier: string
|
||||
createTime: string
|
||||
creator: string
|
||||
}
|
||||
|
||||
// 表格数据(模拟)
|
||||
const tableData = ref<AutoTriggerRow[]>([
|
||||
{
|
||||
id: 1,
|
||||
status: true,
|
||||
@ -49,27 +67,221 @@ const tableData = ref([
|
||||
// 分页相关
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const totalCount = ref(tableData.value.length)
|
||||
const totalCount = computed(() => tableData.value.length)
|
||||
|
||||
// 当前页数据(HandsontablePro 内部不做分页,只负责渲染当前 data)
|
||||
const processedTableData = computed(() => {
|
||||
const start = (currentPage.value - 1) * pageSize.value
|
||||
return tableData.value.slice(start, start + pageSize.value)
|
||||
})
|
||||
|
||||
// 切换状态
|
||||
const handleToggleStatus = (row: any) => {
|
||||
function handleToggleStatus(row: AutoTriggerRow) {
|
||||
row.status = !row.status
|
||||
$baseMessage(row.status ? t('channel.autoTrigger.message.statusEnabled') : t('channel.autoTrigger.message.statusDisabled'), 'success')
|
||||
$baseMessage(
|
||||
row.status ? t('channel.autoTrigger.message.statusEnabled') : t('channel.autoTrigger.message.statusDisabled'),
|
||||
'success'
|
||||
)
|
||||
}
|
||||
|
||||
// 删除
|
||||
const handleDelete = (row: any) => {
|
||||
function handleDelete(row: AutoTriggerRow) {
|
||||
$baseMessage(t('channel.autoTrigger.message.deletePending'), 'info')
|
||||
}
|
||||
|
||||
// 编辑
|
||||
const handleEdit = (row: any) => {
|
||||
$baseMessage(t('channel.autoTrigger.message.editPending'), 'info')
|
||||
type ModalMode = 'add' | 'edit'
|
||||
const modalMode = ref<ModalMode>('add')
|
||||
const modalTitle = computed(() =>
|
||||
modalMode.value === 'add' ? t('channel.autoTrigger.actions.add') : t('channel.autoTrigger.actions.edit')
|
||||
)
|
||||
|
||||
const [registerAutoTriggerModal, modalMethods] = useModal() as any
|
||||
|
||||
const formRef = ref<InstanceType<typeof FormPro> | null>(null)
|
||||
const editId = ref<number | null>(null)
|
||||
|
||||
const formState = reactive({
|
||||
status: true,
|
||||
operation: '拦截',
|
||||
triggerTiming: '',
|
||||
triggerCondition: '',
|
||||
promptMessage: '',
|
||||
notifier: '',
|
||||
createTime: '',
|
||||
creator: '',
|
||||
})
|
||||
|
||||
const formItemList = computed(() => {
|
||||
const requiredMsg = t('system.serviceProviders.modal.rules.requiredInput')
|
||||
return [
|
||||
{
|
||||
label: t('channel.autoTrigger.table.status'),
|
||||
field: 'status',
|
||||
comp: 'ASelect',
|
||||
span: 24,
|
||||
rules: [{ required: true, message: requiredMsg }],
|
||||
compProps: {
|
||||
placeholder: t('common.selectPlaceholder'),
|
||||
options: [
|
||||
{ label: t('channel.autoTrigger.message.statusEnabled'), value: true },
|
||||
{ label: t('channel.autoTrigger.message.statusDisabled'), value: false },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('channel.autoTrigger.table.operation'),
|
||||
field: 'operation',
|
||||
comp: 'ASelect',
|
||||
span: 24,
|
||||
rules: [{ required: true, message: requiredMsg }],
|
||||
compProps: {
|
||||
placeholder: t('common.selectPlaceholder'),
|
||||
options: [
|
||||
{ label: '拦截', value: '拦截' },
|
||||
{ label: '警告', value: '警告' },
|
||||
{ label: '问题件', value: '问题件' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('channel.autoTrigger.table.triggerTiming'),
|
||||
field: 'triggerTiming',
|
||||
comp: 'AInput',
|
||||
span: 24,
|
||||
rules: [{ required: true, message: requiredMsg }],
|
||||
compProps: { placeholder: t('channel.area.form.placeholder.input') },
|
||||
},
|
||||
{
|
||||
label: t('channel.autoTrigger.table.triggerCondition'),
|
||||
field: 'triggerCondition',
|
||||
comp: 'ATextarea',
|
||||
span: 24,
|
||||
rules: [{ required: true, message: requiredMsg }],
|
||||
compProps: {
|
||||
placeholder: t('channel.area.form.placeholder.input'),
|
||||
autoSize: { minRows: 3, maxRows: 6 },
|
||||
showWordLimit: true,
|
||||
maxlength: 1000,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('channel.autoTrigger.table.promptMessage'),
|
||||
field: 'promptMessage',
|
||||
comp: 'AInput',
|
||||
span: 24,
|
||||
rules: [{ required: true, message: requiredMsg }],
|
||||
compProps: { placeholder: t('channel.area.form.placeholder.input') },
|
||||
},
|
||||
{
|
||||
label: t('channel.autoTrigger.table.notifier'),
|
||||
field: 'notifier',
|
||||
comp: 'AInput',
|
||||
span: 24,
|
||||
compProps: { placeholder: t('channel.area.form.placeholder.input'), allowClear: true },
|
||||
},
|
||||
{
|
||||
label: t('channel.autoTrigger.table.createTime'),
|
||||
field: 'createTime',
|
||||
comp: 'AInput',
|
||||
span: 24,
|
||||
compProps: { disabled: true },
|
||||
},
|
||||
{
|
||||
label: t('channel.autoTrigger.table.creator'),
|
||||
field: 'creator',
|
||||
comp: 'AInput',
|
||||
span: 24,
|
||||
compProps: { disabled: true },
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
async function openAddModal() {
|
||||
modalMode.value = 'add'
|
||||
editId.value = null
|
||||
Object.assign(formState, {
|
||||
status: true,
|
||||
operation: '拦截',
|
||||
triggerTiming: '',
|
||||
triggerCondition: '',
|
||||
promptMessage: '',
|
||||
notifier: '',
|
||||
createTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||
creator: '当前用户',
|
||||
})
|
||||
modalMethods.setModalProps({ title: modalTitle.value, width: 720 })
|
||||
modalMethods.openModal(true)
|
||||
await nextTick()
|
||||
formRef.value?.setValues?.(formState)
|
||||
}
|
||||
|
||||
// 新增
|
||||
const handleAdd = () => {
|
||||
$baseMessage(t('channel.autoTrigger.message.addPending'), 'info')
|
||||
async function openEditModal(row: AutoTriggerRow) {
|
||||
modalMode.value = 'edit'
|
||||
editId.value = row.id
|
||||
Object.assign(formState, {
|
||||
status: row.status,
|
||||
operation: row.operation,
|
||||
triggerTiming: row.triggerTiming,
|
||||
triggerCondition: row.triggerCondition,
|
||||
promptMessage: row.promptMessage,
|
||||
notifier: row.notifier,
|
||||
createTime: row.createTime,
|
||||
creator: row.creator,
|
||||
})
|
||||
modalMethods.setModalProps({ title: modalTitle.value, width: 720 })
|
||||
modalMethods.openModal(true)
|
||||
await nextTick()
|
||||
formRef.value?.setValues?.(formState)
|
||||
}
|
||||
|
||||
// 编辑入口(Handsontable 操作列)
|
||||
function handleEdit(row: AutoTriggerRow) {
|
||||
openEditModal(row)
|
||||
}
|
||||
|
||||
// 新增入口(页面新增按钮)
|
||||
function handleAdd() {
|
||||
openAddModal()
|
||||
}
|
||||
|
||||
async function handleAutoTriggerModalOk() {
|
||||
try {
|
||||
await formRef.value?.validate()
|
||||
const values = formRef.value?.getValues?.() || {}
|
||||
|
||||
if (modalMode.value === 'add') {
|
||||
const newRow: AutoTriggerRow = {
|
||||
id: Date.now(),
|
||||
...values,
|
||||
status: !!values.status,
|
||||
notifier: values.notifier || '',
|
||||
}
|
||||
tableData.value.unshift(newRow)
|
||||
currentPage.value = 1
|
||||
$baseMessage(t('common.addSuccess'), 'success')
|
||||
} else {
|
||||
const id = editId.value
|
||||
if (id == null) return
|
||||
const idx = tableData.value.findIndex((d) => d.id === id)
|
||||
if (idx >= 0) {
|
||||
tableData.value[idx] = {
|
||||
...tableData.value[idx],
|
||||
...values,
|
||||
status: !!values.status,
|
||||
notifier: values.notifier || '',
|
||||
}
|
||||
$baseMessage(t('common.editSuccess'), 'success')
|
||||
}
|
||||
}
|
||||
|
||||
modalMethods.closeModal()
|
||||
} catch (_) {
|
||||
// 校验失败由表单组件自行提示
|
||||
}
|
||||
}
|
||||
|
||||
function handleAutoTriggerModalCancel() {
|
||||
modalMethods.closeModal()
|
||||
}
|
||||
|
||||
// 分页改变
|
||||
@ -82,33 +294,142 @@ const handlePageSizeChange = (size: number) => {
|
||||
pageSize.value = size
|
||||
currentPage.value = 1
|
||||
}
|
||||
|
||||
const hotColumns = computed(() => [
|
||||
{ key: 'status', label: t('channel.autoTrigger.table.status'), width: 80 },
|
||||
{ key: 'operation', label: t('channel.autoTrigger.table.operation'), width: 110 },
|
||||
{ key: 'triggerTiming', label: t('channel.autoTrigger.table.triggerTiming'), width: 110 },
|
||||
{ key: 'triggerCondition', label: t('channel.autoTrigger.table.triggerCondition'), width: 320 },
|
||||
{ key: 'promptMessage', label: t('channel.autoTrigger.table.promptMessage'), width: 240 },
|
||||
{ key: 'notifier', label: t('channel.autoTrigger.table.notifier'), width: 120 },
|
||||
{ key: 'createTime', label: t('channel.autoTrigger.table.createTime'), width: 170 },
|
||||
{ key: 'creator', label: t('channel.autoTrigger.table.creator'), width: 110 }
|
||||
])
|
||||
|
||||
// 与其他 Handsontable 页面一致:捕获阶段处理内联开关,避免 Handsontable 抢事件
|
||||
const { tableContainerRef, tableHeight } = useTableContainerHeight()
|
||||
const handsontableHeight = computed(() => Math.max(tableHeight.value || 0, 400))
|
||||
|
||||
function handleStatusSwitchMouseDown(e: MouseEvent) {
|
||||
const switchEl = (e.target as HTMLElement | null)?.closest?.('.customer-status-switch') as HTMLElement | null
|
||||
if (!switchEl) return
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
const rowIndex = parseInt(switchEl.getAttribute('data-row-index') || '0', 10)
|
||||
const idx = (currentPage.value - 1) * pageSize.value + rowIndex
|
||||
const record = tableData.value[idx]
|
||||
if (record) {
|
||||
handleToggleStatus(record)
|
||||
}
|
||||
}
|
||||
|
||||
function customCellRenderer(row: number, col: number, value: any, rowData: AutoTriggerRow): string | null {
|
||||
// HandsontablePro 列结构(本页不显示 checkbox,action 列在末尾):
|
||||
// col=0: 序号;col=1..n: 数据列
|
||||
const dataColIndex = col - 1
|
||||
if (dataColIndex < 0 || dataColIndex >= hotColumns.value.length) return null
|
||||
const colKey = hotColumns.value[dataColIndex].key
|
||||
|
||||
if (colKey === 'status') {
|
||||
const enabled = !!rowData.status
|
||||
return `
|
||||
<label class="customer-status-switch" data-row-index="${row}" style="display:inline-flex;align-items:center;cursor:pointer;">
|
||||
<input type="checkbox" ${enabled ? 'checked' : ''} style="display:none;" />
|
||||
<span class="switch-track" style="position:relative;width:36px;height:20px;background:${enabled ? '#00b42a' : '#e5e6eb'};border-radius:10px;transition:background .2s;">
|
||||
<span class="switch-thumb" style="position:absolute;top:2px;left:${enabled ? '18px' : '2px'};width:16px;height:16px;background:#fff;border-radius:50%;box-shadow:0 2px 4px rgba(0,0,0,.2);transition:left .2s;"></span>
|
||||
</span>
|
||||
</label>
|
||||
`
|
||||
}
|
||||
|
||||
if (colKey === 'operation') {
|
||||
const op = rowData.operation
|
||||
const cls = op === '拦截' ? 'intercept' : op === '警告' ? 'warning' : op === '问题件' ? 'problem' : ''
|
||||
return `<span class="operation-tag ${cls}">${op}</span>`
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function actionRenderer(row: AutoTriggerRow, rowIndex: number): string {
|
||||
return `
|
||||
<div class="action-btns" style="display:flex;gap:12px;align-items:center;justify-content:center;">
|
||||
<span
|
||||
data-action="delete"
|
||||
data-row-index="${rowIndex}"
|
||||
title="${t('channel.autoTrigger.actions.delete')}"
|
||||
style="cursor:pointer;color:#f53f3f;display:inline-flex;align-items:center;justify-content:center;"
|
||||
>
|
||||
<svg viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="2" style="width:16px;height:16px;">
|
||||
<path d="M5 11h5.5m0 0v29a1 1 0 0 0 1 1h25a1 1 0 0 0 1-1V11m-27 0H16m21.5 0H43m-5.5 0H32m-16 0V7h16v4m-16 0h16M20 18v15m8-15v15"/>
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
data-action="edit"
|
||||
data-row-index="${rowIndex}"
|
||||
title="${t('channel.autoTrigger.actions.edit')}"
|
||||
style="cursor:pointer;color:var(--app-primary-color);display:inline-flex;align-items:center;justify-content:center;"
|
||||
>
|
||||
<svg viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="2" style="width:16px;height:16px;">
|
||||
<path d="m30.48 19.038 5.733-5.734a1 1 0 0 0 0-1.414l-5.586-5.586a1 1 0 0 0-1.414 0l-5.734 5.734m7 7L15.763 33.754a1 1 0 0 1-.59.286l-6.048.708a1 1 0 0 1-1.113-1.069l.477-6.31a1 1 0 0 1 .29-.631l14.7-14.7m7 7-7-7M6 42h36" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
function handleActionClick(row: AutoTriggerRow, rowIndex: number, action?: string) {
|
||||
const idx = (currentPage.value - 1) * pageSize.value + rowIndex
|
||||
const record = tableData.value[idx] || row
|
||||
switch (action) {
|
||||
case 'delete':
|
||||
handleDelete(record)
|
||||
break
|
||||
case 'edit':
|
||||
default:
|
||||
handleEdit(record)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const handsontableRef = ref<InstanceType<typeof HandsontablePro> | null>(null)
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.auto-trigger-layout {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
padding: 16px;
|
||||
gap: 16px;
|
||||
background-color: var(--color-fill-2);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.right-content.content-panel {
|
||||
flex: 1;
|
||||
background-color: var(--color-bg-1);
|
||||
border-radius: 24px;
|
||||
.autoTrigger-container {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 16px 16px 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #f0f3f4;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.autoTrigger-page-card {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
overflow: hidden;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.table-toolbar {
|
||||
padding: 16px 16px 0;
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
.toolbar-left {
|
||||
flex: 1;
|
||||
@ -229,6 +550,28 @@ const handlePageSizeChange = (size: number) => {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.list-wrap {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
padding: 0 16px 16px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overscroll-behavior: contain;
|
||||
touch-action: pan-x pan-y;
|
||||
|
||||
:deep(.handsontable-pro-container) {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.custom-table-header {
|
||||
background: #f7f8fa;
|
||||
border-bottom: 1px solid #e5e6eb;
|
||||
@ -431,8 +774,12 @@ const handlePageSizeChange = (size: number) => {
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
padding: 40px 0;
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
// 状态开关样式
|
||||
@ -444,32 +791,33 @@ const handlePageSizeChange = (size: number) => {
|
||||
}
|
||||
|
||||
// 操作类型样式
|
||||
.operation-tag {
|
||||
:deep(.operation-tag) {
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
|
||||
&.intercept {
|
||||
background: #ffe7ba;
|
||||
color: #d46b08;
|
||||
}
|
||||
|
||||
&.warning {
|
||||
background: #fff2e8;
|
||||
color: #d4380d;
|
||||
}
|
||||
|
||||
&.problem {
|
||||
background: #fff1f0;
|
||||
color: #cf1322;
|
||||
}
|
||||
}
|
||||
|
||||
// HandsontablePro 单元格使用 innerHTML 注入,需用 :deep 让样式穿透 scoped
|
||||
:deep(.operation-tag.intercept) {
|
||||
background: #ffe7ba;
|
||||
color: #d46b08;
|
||||
}
|
||||
|
||||
:deep(.operation-tag.warning) {
|
||||
background: #fff2e8;
|
||||
color: #d4380d;
|
||||
}
|
||||
|
||||
:deep(.operation-tag.problem) {
|
||||
background: #fff1f0;
|
||||
color: #cf1322;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="auto-trigger-layout">
|
||||
<div class="right-content content-panel">
|
||||
<div class="autoTrigger-container">
|
||||
<div class="autoTrigger-page-card">
|
||||
<!-- 表格操作按钮和分页控制 -->
|
||||
<div class="table-toolbar">
|
||||
<div class="toolbar-left">
|
||||
@ -517,116 +865,54 @@ const handlePageSizeChange = (size: number) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 自定义表格 -->
|
||||
<div class="custom-table-container">
|
||||
<!-- 自定义表头 -->
|
||||
<div class="custom-table-header">
|
||||
<div class="header-row">
|
||||
<div class="header-cell index-cell">
|
||||
{{ t('channel.autoTrigger.table.serialNo') }}
|
||||
</div>
|
||||
<div class="header-cell status-cell">
|
||||
{{ t('channel.autoTrigger.table.status') }}
|
||||
</div>
|
||||
<div class="header-cell operation-cell">
|
||||
{{ t('channel.autoTrigger.table.operation') }}
|
||||
</div>
|
||||
<div class="header-cell timing-cell">
|
||||
{{ t('channel.autoTrigger.table.triggerTiming') }}
|
||||
</div>
|
||||
<div class="header-cell condition-cell">
|
||||
{{ t('channel.autoTrigger.table.triggerCondition') }}
|
||||
</div>
|
||||
<div class="header-cell message-cell">
|
||||
{{ t('channel.autoTrigger.table.promptMessage') }}
|
||||
</div>
|
||||
<div class="header-cell notifier-cell">
|
||||
{{ t('channel.autoTrigger.table.notifier') }}
|
||||
</div>
|
||||
<div class="header-cell time-cell">
|
||||
{{ t('channel.autoTrigger.table.createTime') }}
|
||||
</div>
|
||||
<div class="header-cell creator-cell">
|
||||
{{ t('channel.autoTrigger.table.creator') }}
|
||||
</div>
|
||||
<div class="header-cell action-cell">
|
||||
{{ t('channel.autoTrigger.table.actions') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表格内容 -->
|
||||
<div class="custom-table-body">
|
||||
<div
|
||||
v-for="(row, index) in tableData"
|
||||
:key="row.id"
|
||||
class="table-row"
|
||||
>
|
||||
<div class="table-cell index-cell">
|
||||
{{ index + 1 }}
|
||||
</div>
|
||||
<div class="table-cell status-cell">
|
||||
<a-switch
|
||||
v-model="row.status"
|
||||
class="status-switch"
|
||||
@change="handleToggleStatus(row)"
|
||||
/>
|
||||
</div>
|
||||
<div class="table-cell operation-cell">
|
||||
<span
|
||||
class="operation-tag"
|
||||
:class="{
|
||||
intercept: row.operation === '拦截',
|
||||
warning: row.operation === '警告',
|
||||
problem: row.operation === '问题件'
|
||||
}"
|
||||
>
|
||||
{{ row.operation }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="table-cell timing-cell">
|
||||
{{ row.triggerTiming }}
|
||||
</div>
|
||||
<div class="table-cell condition-cell">
|
||||
{{ row.triggerCondition }}
|
||||
</div>
|
||||
<div class="table-cell message-cell">
|
||||
{{ row.promptMessage }}
|
||||
</div>
|
||||
<div class="table-cell notifier-cell">
|
||||
{{ row.notifier || '--' }}
|
||||
</div>
|
||||
<div class="table-cell time-cell">
|
||||
{{ row.createTime }}
|
||||
</div>
|
||||
<div class="table-cell creator-cell">
|
||||
{{ row.creator }}
|
||||
</div>
|
||||
<div class="table-cell action-cell">
|
||||
<div class="action-buttons">
|
||||
<a
|
||||
class="action-link"
|
||||
@click="handleDelete(row)"
|
||||
>
|
||||
{{ t('channel.autoTrigger.actions.delete') }}
|
||||
</a>
|
||||
<a
|
||||
class="action-link"
|
||||
@click="handleEdit(row)"
|
||||
>
|
||||
{{ t('channel.autoTrigger.actions.edit') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-if="!tableData.length" class="empty-state">
|
||||
<a-empty :description="t('channel.autoTrigger.empty')" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- HandsontablePro 列表区 -->
|
||||
<div v-if="!tableData.length" class="empty-state">
|
||||
<a-empty :description="t('channel.autoTrigger.empty')" />
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
ref="tableContainerRef"
|
||||
class="list-wrap"
|
||||
@mousedown.capture="handleStatusSwitchMouseDown"
|
||||
>
|
||||
<HandsontablePro
|
||||
ref="handsontableRef"
|
||||
:columns="hotColumns"
|
||||
:data="processedTableData"
|
||||
:total="totalCount"
|
||||
:current-page="currentPage"
|
||||
:page-size="pageSize"
|
||||
:show-action-column="true"
|
||||
:action-column-position="'end'"
|
||||
:action-column-width="100"
|
||||
:serial-no-header="t('channel.autoTrigger.table.serialNo')"
|
||||
:action-column-header="t('channel.autoTrigger.table.actions')"
|
||||
:action-renderer="actionRenderer"
|
||||
:custom-cell-renderer="customCellRenderer"
|
||||
:stretch-columns="true"
|
||||
:height="handsontableHeight"
|
||||
@action-click="handleActionClick"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ModalPro
|
||||
:title="modalTitle"
|
||||
:width="720"
|
||||
@register="registerAutoTriggerModal"
|
||||
@ok="handleAutoTriggerModalOk"
|
||||
@cancel="handleAutoTriggerModalCancel"
|
||||
>
|
||||
<FormPro
|
||||
ref="formRef"
|
||||
:item-list="formItemList"
|
||||
:label-col-props="{ span: 6 }"
|
||||
:wrapper-col-props="{ span: 16 }"
|
||||
:row-gutter="20"
|
||||
:default-span="6"
|
||||
form-layout="horizontal"
|
||||
/>
|
||||
</ModalPro>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -1,16 +1,18 @@
|
||||
<template>
|
||||
<div class="label-design-page">
|
||||
<FullDesigner
|
||||
ref="designerRef"
|
||||
default-lang="cn"
|
||||
:hi-auto-connect="false"
|
||||
@save="onSave"
|
||||
>
|
||||
<!-- 覆盖默认左侧区域,隐藏 logo + 标题(插槽可能不生效,配合 CSS 隐藏 .header-left) -->
|
||||
<template #headerLeft></template>
|
||||
<!-- 覆盖默认右侧按钮区域,彻底隐藏“反馈”等按钮 -->
|
||||
<template #headerRight></template>
|
||||
</FullDesigner>
|
||||
<div class="labelDesign-container">
|
||||
<div class="labelDesign-page-card">
|
||||
<FullDesigner
|
||||
ref="designerRef"
|
||||
default-lang="cn"
|
||||
:hi-auto-connect="false"
|
||||
@save="onSave"
|
||||
>
|
||||
<!-- 覆盖默认左侧区域,隐藏 logo + 标题(插槽可能不生效,配合 CSS 隐藏 .header-left) -->
|
||||
<template #headerLeft></template>
|
||||
<!-- 覆盖默认右侧按钮区域,彻底隐藏“反馈”等按钮 -->
|
||||
<template #headerRight></template>
|
||||
</FullDesigner>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -43,35 +45,57 @@ function onSave(payload: { template: unknown; data: unknown }) {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.label-design-page {
|
||||
padding: 16px;
|
||||
background-color: var(--color-bg-1);
|
||||
min-height: calc(100vh - 86px);
|
||||
.labelDesign-container {
|
||||
padding: 16px 16px 24px;
|
||||
background-color: #f0f3f4;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.labelDesign-page-card {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 顶部栏背景透明(第一行 .primary-toolbar + 第二行 .secondary-toolbar) */
|
||||
.label-design-page :deep(.primary-toolbar),
|
||||
.label-design-page :deep(.primary-toolbar .toolbar-content),
|
||||
.label-design-page :deep(.secondary-toolbar),
|
||||
.label-design-page :deep(.secondary-toolbar .toolbar-content) {
|
||||
.labelDesign-page-card :deep(.primary-toolbar),
|
||||
.labelDesign-page-card :deep(.primary-toolbar .toolbar-content),
|
||||
.labelDesign-page-card :deep(.secondary-toolbar),
|
||||
.labelDesign-page-card :deep(.secondary-toolbar .toolbar-content) {
|
||||
background: transparent !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* 顶部中间/右侧区域也透明 */
|
||||
.label-design-page :deep(.header-center),
|
||||
.label-design-page :deep(.header-right.toolbar-right) {
|
||||
.labelDesign-page-card :deep(.header-center),
|
||||
.labelDesign-page-card :deep(.header-right.toolbar-right) {
|
||||
background: transparent !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* 隐藏 vg-print 顶部左侧 logo + 文本(打包代码中为 .header-left / .toolbar-logo / .toolbar-title) */
|
||||
.label-design-page :deep(.header-left) {
|
||||
.labelDesign-page-card :deep(.header-left) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* 隐藏 vg-print 内置的反馈/帮助按钮(class: btn-feedback) */
|
||||
.label-design-page :deep(.btn-feedback) {
|
||||
.labelDesign-page-card :deep(.btn-feedback) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* 让 FullDesigner 在白卡片内纵向撑满 */
|
||||
.labelDesign-page-card :deep(.vg-print),
|
||||
.labelDesign-page-card :deep(.full-designer),
|
||||
.labelDesign-page-card :deep(.fullDesigner),
|
||||
.labelDesign-page-card :deep(.designer-root) {
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,16 +1,5 @@
|
||||
<template>
|
||||
<div class="waybill-container">
|
||||
<!-- 页面标题栏 -->
|
||||
<div class="page-header">
|
||||
<div class="header-left">
|
||||
<h2 class="page-title">{{ $t('operation.inbound.pageTitle') }}</h2>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<icon-notification class="sound-icon" />
|
||||
<span class="broadcast-text">{{ $t('operation.inbound.broadcastSettings') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表单区域 -->
|
||||
<div class="form-section">
|
||||
<a-form :model="formData" layout="vertical" class="waybill-form">
|
||||
@ -110,7 +99,7 @@
|
||||
<div class="recent-orders-section">
|
||||
<div class="list-toolbar">
|
||||
<div class="toolbar-left">
|
||||
<span class="section-subtitle">{{ $t('operation.inbound.recentOrders.subtitle') }}</span>
|
||||
|
||||
</div>
|
||||
<div class="toolbar-right">
|
||||
<div class="pagination-controls">
|
||||
@ -144,7 +133,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
import { IconNotification } from '@arco-design/web-vue/es/icon';
|
||||
import { $baseMessage } from '@/utils';
|
||||
import HandsontablePro from '@/components/HandsontablePro/index.vue';
|
||||
import PaginationPro from '@/components/PaginationPro/index.vue';
|
||||
@ -303,8 +291,8 @@ onMounted(() => {
|
||||
.waybill-container {
|
||||
padding: 24px;
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
height: auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
@ -449,9 +437,10 @@ onMounted(() => {
|
||||
.recent-orders-section {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
padding: 0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
flex: 0 0 auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@ -459,8 +448,10 @@ onMounted(() => {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
padding: 16px 16px 0;
|
||||
margin-bottom: 16px;
|
||||
flex-shrink: 0;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
.toolbar-left .section-subtitle {
|
||||
font-size: 12px;
|
||||
@ -490,14 +481,17 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
// Handsontable 列表区固定高度,父级 flex 链供 HandsontablePro 撑满
|
||||
.list-wrap {
|
||||
flex: 0 0 auto;
|
||||
height: 850px;
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 0 16px 16px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
overscroll-behavior: contain;
|
||||
touch-action: pan-x pan-y;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,16 +1,5 @@
|
||||
<template>
|
||||
<div class="waybill-container">
|
||||
<!-- 页面标题栏 -->
|
||||
<div class="page-header">
|
||||
<div class="header-left">
|
||||
<h2 class="page-title">{{ $t('operation.outbound.pageTitle') }}</h2>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<icon-notification class="sound-icon" />
|
||||
<span class="broadcast-text">{{ $t('operation.outbound.broadcastSettings') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表单区域 -->
|
||||
<div class="form-section">
|
||||
<a-form :model="formData" layout="vertical" class="waybill-form">
|
||||
@ -39,7 +28,7 @@
|
||||
<div class="recent-orders-section">
|
||||
<div class="list-toolbar">
|
||||
<div class="toolbar-left">
|
||||
<span class="section-subtitle">{{ $t('operation.outbound.recentOrders.subtitle') }}</span>
|
||||
|
||||
</div>
|
||||
<div class="toolbar-right">
|
||||
<div class="pagination-controls">
|
||||
@ -73,7 +62,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
import { IconNotification } from '@arco-design/web-vue/es/icon';
|
||||
import { $baseMessage } from '@/utils';
|
||||
import HandsontablePro from '@/components/HandsontablePro/index.vue';
|
||||
import PaginationPro from '@/components/PaginationPro/index.vue';
|
||||
@ -189,8 +177,8 @@ onMounted(() => {
|
||||
.waybill-container {
|
||||
padding: 24px;
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
height: auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
@ -273,9 +261,10 @@ onMounted(() => {
|
||||
.recent-orders-section {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
padding: 0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
flex: 0 0 auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@ -283,8 +272,10 @@ onMounted(() => {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
padding: 16px 16px 0;
|
||||
margin-bottom: 16px;
|
||||
flex-shrink: 0;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
.toolbar-left .section-subtitle {
|
||||
font-size: 12px;
|
||||
@ -315,12 +306,16 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.list-wrap {
|
||||
flex: 0 0 auto;
|
||||
height: 850px;
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 0 16px 16px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
overscroll-behavior: contain;
|
||||
touch-action: pan-x pan-y;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,16 +1,5 @@
|
||||
<template>
|
||||
<div class="waybill-container">
|
||||
<!-- 页面标题栏 -->
|
||||
<div class="page-header">
|
||||
<div class="header-left">
|
||||
<h2 class="page-title">{{ $t('operation.printLabel.pageTitle') }}</h2>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<icon-notification class="sound-icon" />
|
||||
<span class="broadcast-text">{{ $t('operation.printLabel.broadcastSettings') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表单区域 -->
|
||||
<div class="form-section">
|
||||
<a-form :model="formData" layout="vertical" class="waybill-form">
|
||||
@ -86,7 +75,7 @@
|
||||
<div class="recent-orders-section">
|
||||
<div class="list-toolbar">
|
||||
<div class="toolbar-left">
|
||||
<span class="section-subtitle">{{ $t('operation.printLabel.recentOrders.subtitle') }}</span>
|
||||
|
||||
</div>
|
||||
<div class="toolbar-right">
|
||||
<div class="pagination-controls">
|
||||
@ -120,7 +109,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
import { IconNotification } from '@arco-design/web-vue/es/icon';
|
||||
import { $baseMessage } from '@/utils';
|
||||
import HandsontablePro from '@/components/HandsontablePro/index.vue';
|
||||
import PaginationPro from '@/components/PaginationPro/index.vue';
|
||||
@ -302,8 +290,8 @@ function handleReset() {
|
||||
.waybill-container {
|
||||
padding: 24px;
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
height: auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
@ -400,9 +388,10 @@ function handleReset() {
|
||||
.recent-orders-section {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
padding: 0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
flex: 0 0 auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@ -410,8 +399,10 @@ function handleReset() {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
padding: 16px 16px 0;
|
||||
margin-bottom: 16px;
|
||||
flex-shrink: 0;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
.toolbar-left .section-subtitle {
|
||||
font-size: 12px;
|
||||
@ -442,12 +433,16 @@ function handleReset() {
|
||||
}
|
||||
|
||||
.list-wrap {
|
||||
flex: 0 0 auto;
|
||||
height: 850px;
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 0 16px 16px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
overscroll-behavior: contain;
|
||||
touch-action: pan-x pan-y;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -521,12 +521,14 @@
|
||||
|
||||
<style lang="less" scoped>
|
||||
.my-orders-container {
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 16px 16px 24px;
|
||||
background-color: #f0f3f4;
|
||||
min-height: 100vh;
|
||||
height: auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
overflow-y: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@ -781,18 +783,22 @@
|
||||
.table-section {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
padding: 0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
flex: 0 0 auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.table-toolbar {
|
||||
margin-bottom: 8px;
|
||||
padding: 16px 16px 0;
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
.toolbar-left {
|
||||
flex: 1;
|
||||
@ -820,11 +826,22 @@
|
||||
.list-wrap {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
flex: 0 0 auto;
|
||||
height: 1150px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 0 16px 16px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overscroll-behavior: contain;
|
||||
touch-action: pan-x pan-y;
|
||||
|
||||
:deep(.handsontable-pro-container) {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.handsontable-wrapper {
|
||||
width: 100%;
|
||||
|
||||
@ -319,10 +319,15 @@
|
||||
|
||||
<style lang="less" scoped>
|
||||
.place-order-container {
|
||||
padding: 24px;
|
||||
background-color: #f5f5f5;
|
||||
height: 100vh;
|
||||
padding: 16px 16px 24px;
|
||||
background-color: #f0f3f4;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
@ -339,7 +344,9 @@
|
||||
.main-content {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
margin-bottom: 24px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.left-column {
|
||||
@ -394,7 +401,7 @@
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
border-color: #2D8C8C;
|
||||
border-color: var(--app-primary-color);
|
||||
background: #f0f9f9;
|
||||
}
|
||||
|
||||
@ -525,7 +532,7 @@
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #2D8C8C !important;
|
||||
color: var(--app-primary-color) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -556,7 +563,7 @@
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
border-color: #2D8C8C;
|
||||
border-color: var(--app-primary-color);
|
||||
background: #f0f9f9;
|
||||
}
|
||||
|
||||
|
||||
@ -332,12 +332,13 @@
|
||||
|
||||
<style lang="less" scoped>
|
||||
.price-check-container {
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 16px 16px 24px;
|
||||
background-color: #f0f3f4;
|
||||
min-height: 100vh;
|
||||
height: auto;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
overflow-y: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
@ -428,9 +429,9 @@
|
||||
.table-section {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
padding: 0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
flex: 0 0 auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -438,6 +439,7 @@
|
||||
}
|
||||
|
||||
.table-toolbar {
|
||||
padding: 16px 16px 0;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@ -447,9 +449,10 @@
|
||||
|
||||
.list-wrap {
|
||||
width: 100%;
|
||||
flex: 0 0 auto;
|
||||
height: 900px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
@ -457,6 +460,14 @@
|
||||
touch-action: pan-x pan-y;
|
||||
}
|
||||
|
||||
.list-wrap :deep(.handsontable-pro-container) {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
:deep(.order-action-link) {
|
||||
color: var(--app-primary-color);
|
||||
font-weight: 500;
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<div class="organization-table-wrap">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data="dataSource"
|
||||
@ -81,6 +82,7 @@
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 新增/编辑组织弹窗 -->
|
||||
<a-modal
|
||||
@ -350,31 +352,47 @@ async function handleDelete(id: string | undefined) {
|
||||
.organization-page {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 16px;
|
||||
padding: 16px 16px 24px;
|
||||
background-color: #f0f3f4;
|
||||
min-height: 100vh;
|
||||
height: auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.organization-page-card {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
padding: 0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
padding: 16px 16px 0;
|
||||
margin-bottom: 8px;
|
||||
flex-shrink: 0;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.organization-table-wrap {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
padding: 0 16px 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@ -522,20 +522,24 @@ async function loadOrganizations() {
|
||||
.user-page {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 16px;
|
||||
padding: 16px 16px 24px;
|
||||
background-color: #f0f3f4;
|
||||
min-height: 100vh;
|
||||
height: auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
overflow-y: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.user-page-card {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
padding: 0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
@ -544,16 +548,19 @@ async function loadOrganizations() {
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 16px 16px 0;
|
||||
margin-bottom: 16px;
|
||||
flex-shrink: 0;
|
||||
gap: 12px;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.list-wrap {
|
||||
width: 100%;
|
||||
flex: 0 0 auto;
|
||||
height: 1200px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 0 16px 16px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
overscroll-behavior: contain;
|
||||
touch-action: pan-x pan-y;
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<div class="menu-table-wrap">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data="dataSource"
|
||||
@ -86,6 +87,7 @@
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 新增/编辑菜单弹窗 -->
|
||||
<a-modal
|
||||
@ -394,31 +396,47 @@ function getIcon(iconName: string) {
|
||||
.menu-page {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 16px;
|
||||
padding: 16px 16px 24px;
|
||||
background-color: #f0f3f4;
|
||||
min-height: 100vh;
|
||||
height: auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.menu-page-card {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
padding: 0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 16px 0;
|
||||
margin-bottom: 16px;
|
||||
flex-shrink: 0;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.menu-table-wrap {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
padding: 0 16px 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@ -470,20 +470,24 @@ onMounted(() => {
|
||||
.role-page {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 16px;
|
||||
padding: 16px 16px 24px;
|
||||
background-color: #f0f3f4;
|
||||
min-height: 100vh;
|
||||
height: auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
overflow-y: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.role-page-card {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
padding: 0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
@ -492,16 +496,19 @@ onMounted(() => {
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 16px 16px 0;
|
||||
margin-bottom: 16px;
|
||||
flex-shrink: 0;
|
||||
gap: 12px;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.list-wrap {
|
||||
width: 100%;
|
||||
flex: 0 0 auto;
|
||||
height: 1200px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 0 16px 16px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
overscroll-behavior: contain;
|
||||
touch-action: pan-x pan-y;
|
||||
|
||||
@ -296,24 +296,32 @@ onMounted(() => {
|
||||
.warehouse-address-page {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 16px;
|
||||
padding: 16px 16px 24px;
|
||||
background-color: #f0f3f4;
|
||||
min-height: 100vh;
|
||||
height: auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.warehouse-address-page-card {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
padding: 0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 16px;
|
||||
flex-shrink: 0;
|
||||
padding: 16px 16px 0;
|
||||
|
||||
.page-title {
|
||||
margin: 0;
|
||||
@ -324,6 +332,11 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.card-grid {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 20px;
|
||||
|
||||
@ -528,20 +528,24 @@
|
||||
.track-content-page {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 16px;
|
||||
padding: 16px 16px 24px;
|
||||
background-color: #f0f3f4;
|
||||
min-height: 100vh;
|
||||
height: auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.track-content-page-card {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
padding: 0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
@ -550,21 +554,24 @@
|
||||
.search-input {
|
||||
width: 20%;
|
||||
max-width: 320px;
|
||||
margin-bottom: 16px;
|
||||
margin: 16px 16px 16px;
|
||||
}
|
||||
|
||||
.table-toolbar {
|
||||
margin-bottom: 8px;
|
||||
padding: 0 16px;
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.list-wrap {
|
||||
width: 100%;
|
||||
flex: 0 0 auto;
|
||||
height: 1200px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 0 16px 16px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@ -375,20 +375,24 @@
|
||||
.track-flag-page {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 16px;
|
||||
padding: 16px 16px 24px;
|
||||
background-color: #f0f3f4;
|
||||
min-height: 100vh;
|
||||
height: auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.track-flag-page-card {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
padding: 0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
@ -397,21 +401,24 @@
|
||||
.search-input {
|
||||
width: 20%;
|
||||
max-width: 320px;
|
||||
margin-bottom: 16px;
|
||||
margin: 16px 16px 16px;
|
||||
}
|
||||
|
||||
.table-toolbar {
|
||||
margin-bottom: 8px;
|
||||
padding: 0 16px;
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.list-wrap {
|
||||
width: 100%;
|
||||
flex: 0 0 auto;
|
||||
height: 1200px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 0 16px 16px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@ -2191,12 +2191,12 @@
|
||||
|
||||
<style scoped lang="less">
|
||||
.track-order-container {
|
||||
padding: 16px; // 符合设计规范
|
||||
padding: 16px 16px 24px; // 底边 24px 与主内容区对齐
|
||||
background-color: #f0f3f4; // 与主背景统一
|
||||
min-height: 100vh;
|
||||
height: auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
overflow-y: hidden;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
@ -2251,18 +2251,17 @@
|
||||
/*
|
||||
* 列表滚动相关(Handsontable 实际滚动发生在 .ht_master > .wtHolder):
|
||||
* - 高度:useTableContainerHeight 读取 .list-wrap 的 clientHeight → :height="tableHeight",决定 wtHolder 可视区高度。
|
||||
* - 布局:.table-section 固定 1230px + 纵向 flex;.list-wrap flex:1 + min-height:0;HandsontablePro 根节点 flex 填满。
|
||||
* - 布局:.table-section flex:1 + min-height:0;.list-wrap flex:1 + min-height:0 + 四边 16px 内边距;HandsontablePro 根节点 flex 填满。
|
||||
* - 外层 .track-order-container 为 overflow-y:auto 时,滚轮易冒泡到整页,体感像「拖不动」;对表区域加 overscroll-behavior:contain 优先把滚轮留在表内。
|
||||
* - HandsontablePro 内 .ht_master 为 overflow:visible(克隆表头需要),横向/纵向滚动条样式写在 .wtHolder 上。
|
||||
*/
|
||||
.table-section {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 12px; // 减少padding,增加内容空间
|
||||
padding: 0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
box-sizing: border-box;
|
||||
height: 1230px;
|
||||
flex: 0 0 auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
@ -2273,8 +2272,11 @@
|
||||
}
|
||||
|
||||
.list-wrap {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 0 16px 16px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden; // 仅裁剪外层,纵向/横向滚动交给 Handsontable .wtHolder
|
||||
@ -3382,10 +3384,12 @@
|
||||
|
||||
|
||||
.table-toolbar {
|
||||
margin-bottom: 8px;
|
||||
padding: 16px 16px 0;
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
flex-shrink: 0;
|
||||
|
||||
.toolbar-left {
|
||||
|
||||
@ -410,23 +410,28 @@
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
/* 参与 page-layout 主内容区 flex,撑满高度;底部仅 24px 留白(列表容器随区域增高,不再出现大块灰底) */
|
||||
.track-state-page {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 16px;
|
||||
padding: 16px 16px 24px;
|
||||
background-color: #f0f3f4;
|
||||
min-height: 100vh;
|
||||
height: auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.track-state-page-card {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
padding: 0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
@ -435,21 +440,24 @@
|
||||
.search-input {
|
||||
width: 20%;
|
||||
max-width: 320px;
|
||||
margin-bottom: 16px;
|
||||
margin: 16px 16px 16px;
|
||||
}
|
||||
|
||||
.table-toolbar {
|
||||
margin-bottom: 8px;
|
||||
padding: 0 16px;
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.list-wrap {
|
||||
width: 100%;
|
||||
flex: 0 0 auto;
|
||||
height: 1200px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 0 16px 16px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@ -513,23 +513,24 @@ const handsontableRef = ref<InstanceType<typeof HandsontablePro>>()
|
||||
.customer-page {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 16px;
|
||||
padding: 16px 16px 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #f0f3f4;
|
||||
min-height: 100vh;
|
||||
height: auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.customer-page-card {
|
||||
flex: 0 0 auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
padding: 0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
@ -539,8 +540,10 @@ const handsontableRef = ref<InstanceType<typeof HandsontablePro>>()
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
padding: 16px 16px 0;
|
||||
margin-bottom: 16px;
|
||||
flex-shrink: 0;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
.toolbar-left {
|
||||
display: flex;
|
||||
@ -559,9 +562,10 @@ const handsontableRef = ref<InstanceType<typeof HandsontablePro>>()
|
||||
.list-wrap {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
flex: 0 0 auto;
|
||||
height: 1200px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 0 16px 16px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
@ -234,23 +234,24 @@ function handleViewPayImageClick(event: MouseEvent) {
|
||||
.recharge-record-page {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 16px;
|
||||
padding: 16px 16px 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #f0f3f4;
|
||||
min-height: 100vh;
|
||||
height: auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.recharge-record-page-card {
|
||||
flex: 0 0 auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
padding: 0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
@ -260,8 +261,10 @@ function handleViewPayImageClick(event: MouseEvent) {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
padding: 16px 16px 0;
|
||||
margin-bottom: 16px;
|
||||
flex-shrink: 0;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
.toolbar-right {
|
||||
.pagination-controls {
|
||||
@ -274,9 +277,10 @@ function handleViewPayImageClick(event: MouseEvent) {
|
||||
.list-wrap {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
flex: 0 0 auto;
|
||||
height: 1200px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 0 16px 16px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user