refactor(order): 重构发货单功能模块
- 将 ShipOrderList 组件从 Delivery 模块迁移至 Order 模块 - 移除 Delivery 模块的导出并在 Order 模块中新增 OrderShipList 和相关组件 - 更新发货单列表的列配置,添加订单、经销商、公司等关联字段 - 修改发货单列表的字段映射,将经销商名称和车次号格式从短横线改为竖线分隔 - 在 BizPage 组件中添加 convertValue 属性支持数据转换 - 更新左侧菜单组件的依赖数组以包含 menuData - 调整发货单列表的国际化配置,更新字段标题和状态枚举值 - 新增 OrderShip 页面路由文件 - 移除项目根目录的 AGENTS.md 和 project.md 文件 - 从组件索引中移除 Delivery 模块并添加 SearchMenu 组件
This commit is contained in:
parent
f68b148319
commit
b4ce6e45dc
18
AGENTS.md
18
AGENTS.md
@ -1,18 +0,0 @@
|
|||||||
<!-- OPENSPEC:START -->
|
|
||||||
# OpenSpec Instructions
|
|
||||||
|
|
||||||
These instructions are for AI assistants working in this project.
|
|
||||||
|
|
||||||
Always open `@/openspec/AGENTS.md` when the request:
|
|
||||||
- Mentions planning or proposals (words like proposal, spec, change, plan)
|
|
||||||
- Introduces new capabilities, breaking changes, architecture shifts, or big performance/security work
|
|
||||||
- Sounds ambiguous and you need the authoritative spec before coding
|
|
||||||
|
|
||||||
Use `@/openspec/AGENTS.md` to learn:
|
|
||||||
- How to create and apply change proposals
|
|
||||||
- Spec format and conventions
|
|
||||||
- Project structure and guidelines
|
|
||||||
|
|
||||||
Keep this managed block so 'openspec update' can refresh the instructions.
|
|
||||||
|
|
||||||
<!-- OPENSPEC:END -->
|
|
||||||
332
CLAUDE.md
332
CLAUDE.md
@ -1,18 +1,324 @@
|
|||||||
<!-- OPENSPEC:START -->
|
# CLAUDE.md
|
||||||
# OpenSpec Instructions
|
|
||||||
|
|
||||||
These instructions are for AI assistants working in this project.
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
Always open `@/openspec/AGENTS.md` when the request:
|
## 项目概述
|
||||||
- Mentions planning or proposals (words like proposal, spec, change, plan)
|
|
||||||
- Introduces new capabilities, breaking changes, architecture shifts, or big performance/security work
|
|
||||||
- Sounds ambiguous and you need the authoritative spec before coding
|
|
||||||
|
|
||||||
Use `@/openspec/AGENTS.md` to learn:
|
ERPTurbo_Admin 是一个基于 React 生态系统的企业级管理后台系统,采用 Monorepo 架构。使用 UmiJS Max 作为应用框架,Ant Design 作为 UI 组件库。
|
||||||
- How to create and apply change proposals
|
|
||||||
- Spec format and conventions
|
|
||||||
- Project structure and guidelines
|
|
||||||
|
|
||||||
Keep this managed block so 'openspec update' can refresh the instructions.
|
## 项目结构
|
||||||
|
|
||||||
<!-- OPENSPEC:END -->
|
```
|
||||||
|
ERPTurbo_Admin/
|
||||||
|
├── packages/
|
||||||
|
│ ├── app-operation/ # 运营管理系统主应用
|
||||||
|
│ │ ├── src/
|
||||||
|
│ │ │ ├── pages/ # 页面组件(约定式路由)
|
||||||
|
│ │ │ ├── components/ # 业务组件
|
||||||
|
│ │ │ │ ├── Biz/ # Biz 核心组件库
|
||||||
|
│ │ │ │ ├── BasicData/ # 基础数据组件
|
||||||
|
│ │ │ │ ├── Company/ # 公司组件
|
||||||
|
│ │ │ │ ├── Order/ # 订单组件
|
||||||
|
│ │ │ │ └── ... # 其他业务组件
|
||||||
|
│ │ │ ├── services/ # API 服务层(自动生成)
|
||||||
|
│ │ │ ├── models/ # 状态管理
|
||||||
|
│ │ │ ├── locales/ # 国际化文件
|
||||||
|
│ │ │ └── app.tsx # 应用入口
|
||||||
|
│ │ └── dist/ # 构建输出
|
||||||
|
│ └── app-sso-server/ # SSO 单点登录系统
|
||||||
|
├── shared/ # 共享资源
|
||||||
|
├── swagger/ # OpenAPI 文档
|
||||||
|
├── .husky/ # Git Hooks 配置
|
||||||
|
├── .lingma/ # 开发规则文档
|
||||||
|
│ └── rules/
|
||||||
|
│ ├── design.md # 架构设计规则
|
||||||
|
│ ├── biz.md # Biz 组件设计规则
|
||||||
|
│ └── add-new-page.md # 添加页面指南
|
||||||
|
└── pnpm-workspace.yaml # PNPM 工作区配置
|
||||||
|
```
|
||||||
|
|
||||||
|
## 核心命令
|
||||||
|
|
||||||
|
### 包管理器
|
||||||
|
- **PNPM** - 项目使用 PNPM 作为包管理器
|
||||||
|
- 工作区配置在 `pnpm-workspace.yaml`
|
||||||
|
|
||||||
|
### 根级别命令(在项目根目录执行)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装依赖
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# 启动所有应用的开发服务器
|
||||||
|
pnpm dev
|
||||||
|
|
||||||
|
# 构建所有应用
|
||||||
|
pnpm build
|
||||||
|
|
||||||
|
# 格式化所有应用的代码
|
||||||
|
pnpm format
|
||||||
|
```
|
||||||
|
|
||||||
|
### 应用级别命令(在具体应用目录下执行)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 进入应用目录
|
||||||
|
cd packages/app-operation
|
||||||
|
|
||||||
|
# 启动开发服务器
|
||||||
|
pnpm dev
|
||||||
|
|
||||||
|
# 构建生产版本
|
||||||
|
pnpm build
|
||||||
|
|
||||||
|
# 代码格式化
|
||||||
|
pnpm format
|
||||||
|
|
||||||
|
# 生成 OpenAPI 客户端代码
|
||||||
|
pnpm openapi
|
||||||
|
|
||||||
|
# 初始化/设置
|
||||||
|
pnpm setup
|
||||||
|
```
|
||||||
|
|
||||||
|
### 单一组件/页面开发
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 在 app-operation 中开发特定功能
|
||||||
|
cd packages/app-operation
|
||||||
|
|
||||||
|
# 启动单个应用
|
||||||
|
pnpm dev
|
||||||
|
|
||||||
|
# 运行测试(如果需要)
|
||||||
|
pnpm test
|
||||||
|
```
|
||||||
|
|
||||||
|
## 架构设计
|
||||||
|
|
||||||
|
### 技术栈
|
||||||
|
|
||||||
|
| 技术 | 版本 | 用途 |
|
||||||
|
|------|------|------|
|
||||||
|
| React | ^18.0.0 | 前端核心框架 |
|
||||||
|
| UmiJS Max | ^4.4.9 | 应用框架和构建工具 |
|
||||||
|
| Ant Design | ^5.25.2 | UI 组件库 |
|
||||||
|
| Ant Design Pro Components | ^2.8.6 | 高级业务组件 |
|
||||||
|
| TypeScript | ^5.6.3 | 类型检查 |
|
||||||
|
| TailwindCSS | ^3.4.17 | CSS 框架 |
|
||||||
|
| PNPM | ^9.0.0 | 包管理器 |
|
||||||
|
|
||||||
|
### 核心架构模式
|
||||||
|
|
||||||
|
#### 1. BizContainer 组件模式
|
||||||
|
所有业务页面基于 `BizContainer` 组件构建,这是项目的核心业务组件:
|
||||||
|
|
||||||
|
- **位置**: `packages/app-operation/src/components/Biz/BizContainer.tsx`
|
||||||
|
- **功能**: 统一的增删改查容器组件
|
||||||
|
- **特点**: 支持列表、详情、创建、更新、删除、导入导出、树形结构等
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<BizContainer<
|
||||||
|
typeof business.newComponent, // API 函数类型
|
||||||
|
BusinessAPI.NewComponentVO, // 视图对象类型
|
||||||
|
BusinessAPI.NewComponentPageQry, // 查询参数类型
|
||||||
|
BusinessAPI.NewComponentCreateCmd, // 创建命令类型
|
||||||
|
BusinessAPI.NewComponentUpdateCmd // 更新命令类型
|
||||||
|
>
|
||||||
|
rowKey={'id'}
|
||||||
|
permission={'operation-new-component'}
|
||||||
|
func={business.newComponent}
|
||||||
|
method={'newComponent'}
|
||||||
|
methodUpper={'NewComponent'}
|
||||||
|
intlPrefix={'newComponent'}
|
||||||
|
// ... 其他配置
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Biz 组件族
|
||||||
|
围绕 BizContainer 的配套组件:
|
||||||
|
|
||||||
|
- **BizPage** - 分页表格组件
|
||||||
|
- **BizTree** - 树形结构组件
|
||||||
|
- **BizDrag** - 拖拽排序组件
|
||||||
|
- **BizCreate** - 创建操作组件
|
||||||
|
- **BizUpdate** - 更新操作组件
|
||||||
|
- **BizDetail** - 详情查看组件
|
||||||
|
- **BizDestroy** - 删除操作组件
|
||||||
|
- **BizImport** - 批量导入组件
|
||||||
|
|
||||||
|
#### 3. 权限控制系统
|
||||||
|
|
||||||
|
**页面级权限**:
|
||||||
|
```tsx
|
||||||
|
<PageContainer permission="operation-new-component">
|
||||||
|
{/* 页面内容 */}
|
||||||
|
</PageContainer>
|
||||||
|
```
|
||||||
|
|
||||||
|
**按钮级权限**:
|
||||||
|
```tsx
|
||||||
|
<ButtonAccess permission="operation-new-component-create">
|
||||||
|
<Button>新增</Button>
|
||||||
|
</ButtonAccess>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. 动态路由与菜单
|
||||||
|
|
||||||
|
- 使用约定式路由(pages 目录)
|
||||||
|
- 路由配置从服务端获取
|
||||||
|
- 支持嵌套路由和面包屑导航
|
||||||
|
|
||||||
|
## 开发指南
|
||||||
|
|
||||||
|
### 添加新页面
|
||||||
|
|
||||||
|
详细步骤请参考 `.lingma/rules/add-new-page.md`,基本流程:
|
||||||
|
|
||||||
|
1. **创建页面组件**
|
||||||
|
```tsx
|
||||||
|
// packages/app-operation/src/pages/NewPage.tsx
|
||||||
|
import { NewComponentList } from '@/components';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <NewComponentList />;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **创建业务组件**(如果需要)
|
||||||
|
- 使用 `BizContainer` 构建标准 CRUD 页面
|
||||||
|
- 定义 `columns`(表格列)和 `formContext`(表单字段)
|
||||||
|
- 配置国际化前缀 `intlPrefix`
|
||||||
|
|
||||||
|
3. **配置国际化**
|
||||||
|
- 在 `packages/app-operation/src/locales/zh-CN.ts` 中添加翻译
|
||||||
|
|
||||||
|
### API 集成
|
||||||
|
|
||||||
|
1. **OpenAPI 自动生成**
|
||||||
|
```bash
|
||||||
|
cd packages/app-operation
|
||||||
|
pnpm openapi
|
||||||
|
```
|
||||||
|
- 从 `swagger/` 目录读取 OpenAPI 规范
|
||||||
|
- 自动生成 `src/services/` 下的 API 客户端代码
|
||||||
|
- 生成 TypeScript 类型定义
|
||||||
|
|
||||||
|
2. **API 调用**
|
||||||
|
```tsx
|
||||||
|
import { business } from '@/services';
|
||||||
|
|
||||||
|
// 调用 API
|
||||||
|
const result = await business.newComponent.page({});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 代码规范
|
||||||
|
|
||||||
|
- **格式化**: 使用 Prettier,配置在各个包的 package.json 中
|
||||||
|
- **代码检查**: Husky + lint-staged,在提交前自动检查
|
||||||
|
- **类型检查**: TypeScript 严格模式
|
||||||
|
- **国际化**: 所有 UI 文本必须支持多语言
|
||||||
|
|
||||||
|
## 关键配置文件
|
||||||
|
|
||||||
|
### package.json (根目录)
|
||||||
|
- 定义了工作区结构
|
||||||
|
- 包含全局脚本:`dev`、`build`、`format`
|
||||||
|
|
||||||
|
### pnpm-workspace.yaml
|
||||||
|
- 定义 PNPM 工作区配置
|
||||||
|
- 包含 packages/* 和 shared/** 目录
|
||||||
|
|
||||||
|
### .husky/
|
||||||
|
- Git Hooks 配置
|
||||||
|
- pre-commit: 运行 lint-staged 检查
|
||||||
|
- commit-msg: 提交消息规范检查
|
||||||
|
|
||||||
|
### swagger/
|
||||||
|
- OpenAPI 规范文件
|
||||||
|
- 用于生成 API 客户端代码
|
||||||
|
|
||||||
|
## 重要目录说明
|
||||||
|
|
||||||
|
### packages/app-operation/src/components/
|
||||||
|
|
||||||
|
#### Biz/ 目录
|
||||||
|
核心业务组件库,包含:
|
||||||
|
- `BizContainer.tsx` - 核心容器组件
|
||||||
|
- `BizPage.tsx` - 列表页面组件
|
||||||
|
- `BizCreate.tsx` / `BizUpdate.tsx` / `BizDetail.tsx` / `BizDestroy.tsx` - CRUD 操作组件
|
||||||
|
- `BizTree.tsx` - 树形组件
|
||||||
|
- `BizDrag.tsx` - 拖拽组件
|
||||||
|
- `ButtonAccess.tsx` - 权限按钮组件
|
||||||
|
|
||||||
|
#### 业务组件目录
|
||||||
|
- `BasicData/` - 基础数据管理
|
||||||
|
- `Company/` - 公司管理
|
||||||
|
- `Order/` - 订单管理
|
||||||
|
- `Material/` - 素材库
|
||||||
|
- `Employee/` - 员工管理
|
||||||
|
- `Supplier/` - 供应商管理
|
||||||
|
|
||||||
|
### packages/app-operation/src/services/
|
||||||
|
- API 服务层
|
||||||
|
- 自动生成,不应手动编辑
|
||||||
|
- 通过 `pnpm openapi` 命令更新
|
||||||
|
|
||||||
|
### packages/app-operation/src/locales/
|
||||||
|
- 国际化文件
|
||||||
|
- `zh-CN.ts` - 中文翻译
|
||||||
|
- `en-US.ts` - 英文翻译
|
||||||
|
|
||||||
|
## 开发注意事项
|
||||||
|
|
||||||
|
1. **不要直接修改 services/ 目录**
|
||||||
|
- 这些文件由 OpenAPI 自动生成
|
||||||
|
- 修改 swagger/ 规范后重新生成
|
||||||
|
|
||||||
|
2. **遵循组件命名约定**
|
||||||
|
- 页面组件:文件名 + `Page` 后缀,导出为 `Page`
|
||||||
|
- 业务组件:语义化命名,如 `NewComponentList`
|
||||||
|
|
||||||
|
3. **国际化是必需的**
|
||||||
|
- 所有用户可见文本必须使用 `useIntl()` 和 `intl.formatMessage()`
|
||||||
|
- 定义 `intlPrefix` 前缀
|
||||||
|
|
||||||
|
4. **权限控制**
|
||||||
|
- 页面使用 `PageContainer` 包装
|
||||||
|
- 操作按钮使用 `ButtonAccess` 包装
|
||||||
|
- 权限标识格式:`operation-{resource}-{action}`
|
||||||
|
|
||||||
|
5. **响应式设计**
|
||||||
|
- 使用 `isMobile` 属性自动适配移动端
|
||||||
|
- 表单宽度和布局会自动调整
|
||||||
|
|
||||||
|
6. **状态管理**
|
||||||
|
- 使用 UmiJS 内置状态管理(initialState、model)
|
||||||
|
- 组件间通信使用 `actionRef`
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
|
||||||
|
### 构建问题
|
||||||
|
- 确保运行 `pnpm install` 安装依赖
|
||||||
|
- 清理构建缓存:`rm -rf packages/app-operation/.umi`
|
||||||
|
|
||||||
|
### API 更新后类型错误
|
||||||
|
- 重新生成 API 客户端:`pnpm openapi`
|
||||||
|
- 检查 swagger/ 目录下的规范文件
|
||||||
|
|
||||||
|
### 权限问题
|
||||||
|
- 检查 `access.ts` 文件中的权限配置
|
||||||
|
- 确认路由权限标识与服务端一致
|
||||||
|
|
||||||
|
### 国际化文本缺失
|
||||||
|
- 检查 `locales/zh-CN.ts` 中是否添加对应翻译
|
||||||
|
- 确认 `intlPrefix` 配置正确
|
||||||
|
|
||||||
|
## 相关文档
|
||||||
|
|
||||||
|
- 项目架构设计:`.lingma/rules/design.md`
|
||||||
|
- Biz 组件设计规则:`.lingma/rules/biz.md`
|
||||||
|
- 添加新页面指南:`.lingma/rules/add-new-page.md`
|
||||||
|
- UmiJS Max 文档:https://umijs.org/docs/max/introduce
|
||||||
|
- Ant Design 文档:https://ant.design
|
||||||
|
- PNPM 文档:https://pnpm.io
|
||||||
|
|||||||
@ -1,456 +0,0 @@
|
|||||||
# OpenSpec Instructions
|
|
||||||
|
|
||||||
Instructions for AI coding assistants using OpenSpec for spec-driven development.
|
|
||||||
|
|
||||||
## TL;DR Quick Checklist
|
|
||||||
|
|
||||||
- Search existing work: `openspec spec list --long`, `openspec list` (use `rg` only for full-text search)
|
|
||||||
- Decide scope: new capability vs modify existing capability
|
|
||||||
- Pick a unique `change-id`: kebab-case, verb-led (`add-`, `update-`, `remove-`, `refactor-`)
|
|
||||||
- Scaffold: `proposal.md`, `tasks.md`, `design.md` (only if needed), and delta specs per affected capability
|
|
||||||
- Write deltas: use `## ADDED|MODIFIED|REMOVED|RENAMED Requirements`; include at least one `#### Scenario:` per requirement
|
|
||||||
- Validate: `openspec validate [change-id] --strict` and fix issues
|
|
||||||
- Request approval: Do not start implementation until proposal is approved
|
|
||||||
|
|
||||||
## Three-Stage Workflow
|
|
||||||
|
|
||||||
### Stage 1: Creating Changes
|
|
||||||
Create proposal when you need to:
|
|
||||||
- Add features or functionality
|
|
||||||
- Make breaking changes (API, schema)
|
|
||||||
- Change architecture or patterns
|
|
||||||
- Optimize performance (changes behavior)
|
|
||||||
- Update security patterns
|
|
||||||
|
|
||||||
Triggers (examples):
|
|
||||||
- "Help me create a change proposal"
|
|
||||||
- "Help me plan a change"
|
|
||||||
- "Help me create a proposal"
|
|
||||||
- "I want to create a spec proposal"
|
|
||||||
- "I want to create a spec"
|
|
||||||
|
|
||||||
Loose matching guidance:
|
|
||||||
- Contains one of: `proposal`, `change`, `spec`
|
|
||||||
- With one of: `create`, `plan`, `make`, `start`, `help`
|
|
||||||
|
|
||||||
Skip proposal for:
|
|
||||||
- Bug fixes (restore intended behavior)
|
|
||||||
- Typos, formatting, comments
|
|
||||||
- Dependency updates (non-breaking)
|
|
||||||
- Configuration changes
|
|
||||||
- Tests for existing behavior
|
|
||||||
|
|
||||||
**Workflow**
|
|
||||||
1. Review `openspec/project.md`, `openspec list`, and `openspec list --specs` to understand current context.
|
|
||||||
2. Choose a unique verb-led `change-id` and scaffold `proposal.md`, `tasks.md`, optional `design.md`, and spec deltas under `openspec/changes/<id>/`.
|
|
||||||
3. Draft spec deltas using `## ADDED|MODIFIED|REMOVED Requirements` with at least one `#### Scenario:` per requirement.
|
|
||||||
4. Run `openspec validate <id> --strict` and resolve any issues before sharing the proposal.
|
|
||||||
|
|
||||||
### Stage 2: Implementing Changes
|
|
||||||
Track these steps as TODOs and complete them one by one.
|
|
||||||
1. **Read proposal.md** - Understand what's being built
|
|
||||||
2. **Read design.md** (if exists) - Review technical decisions
|
|
||||||
3. **Read tasks.md** - Get implementation checklist
|
|
||||||
4. **Implement tasks sequentially** - Complete in order
|
|
||||||
5. **Confirm completion** - Ensure every item in `tasks.md` is finished before updating statuses
|
|
||||||
6. **Update checklist** - After all work is done, set every task to `- [x]` so the list reflects reality
|
|
||||||
7. **Approval gate** - Do not start implementation until the proposal is reviewed and approved
|
|
||||||
|
|
||||||
### Stage 3: Archiving Changes
|
|
||||||
After deployment, create separate PR to:
|
|
||||||
- Move `changes/[name]/` → `changes/archive/YYYY-MM-DD-[name]/`
|
|
||||||
- Update `specs/` if capabilities changed
|
|
||||||
- Use `openspec archive <change-id> --skip-specs --yes` for tooling-only changes (always pass the change ID explicitly)
|
|
||||||
- Run `openspec validate --strict` to confirm the archived change passes checks
|
|
||||||
|
|
||||||
## Before Any Task
|
|
||||||
|
|
||||||
**Context Checklist:**
|
|
||||||
- [ ] Read relevant specs in `specs/[capability]/spec.md`
|
|
||||||
- [ ] Check pending changes in `changes/` for conflicts
|
|
||||||
- [ ] Read `openspec/project.md` for conventions
|
|
||||||
- [ ] Run `openspec list` to see active changes
|
|
||||||
- [ ] Run `openspec list --specs` to see existing capabilities
|
|
||||||
|
|
||||||
**Before Creating Specs:**
|
|
||||||
- Always check if capability already exists
|
|
||||||
- Prefer modifying existing specs over creating duplicates
|
|
||||||
- Use `openspec show [spec]` to review current state
|
|
||||||
- If request is ambiguous, ask 1–2 clarifying questions before scaffolding
|
|
||||||
|
|
||||||
### Search Guidance
|
|
||||||
- Enumerate specs: `openspec spec list --long` (or `--json` for scripts)
|
|
||||||
- Enumerate changes: `openspec list` (or `openspec change list --json` - deprecated but available)
|
|
||||||
- Show details:
|
|
||||||
- Spec: `openspec show <spec-id> --type spec` (use `--json` for filters)
|
|
||||||
- Change: `openspec show <change-id> --json --deltas-only`
|
|
||||||
- Full-text search (use ripgrep): `rg -n "Requirement:|Scenario:" openspec/specs`
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
### CLI Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Essential commands
|
|
||||||
openspec list # List active changes
|
|
||||||
openspec list --specs # List specifications
|
|
||||||
openspec show [item] # Display change or spec
|
|
||||||
openspec validate [item] # Validate changes or specs
|
|
||||||
openspec archive <change-id> [--yes|-y] # Archive after deployment (add --yes for non-interactive runs)
|
|
||||||
|
|
||||||
# Project management
|
|
||||||
openspec init [path] # Initialize OpenSpec
|
|
||||||
openspec update [path] # Update instruction files
|
|
||||||
|
|
||||||
# Interactive mode
|
|
||||||
openspec show # Prompts for selection
|
|
||||||
openspec validate # Bulk validation mode
|
|
||||||
|
|
||||||
# Debugging
|
|
||||||
openspec show [change] --json --deltas-only
|
|
||||||
openspec validate [change] --strict
|
|
||||||
```
|
|
||||||
|
|
||||||
### Command Flags
|
|
||||||
|
|
||||||
- `--json` - Machine-readable output
|
|
||||||
- `--type change|spec` - Disambiguate items
|
|
||||||
- `--strict` - Comprehensive validation
|
|
||||||
- `--no-interactive` - Disable prompts
|
|
||||||
- `--skip-specs` - Archive without spec updates
|
|
||||||
- `--yes`/`-y` - Skip confirmation prompts (non-interactive archive)
|
|
||||||
|
|
||||||
## Directory Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
openspec/
|
|
||||||
├── project.md # Project conventions
|
|
||||||
├── specs/ # Current truth - what IS built
|
|
||||||
│ └── [capability]/ # Single focused capability
|
|
||||||
│ ├── spec.md # Requirements and scenarios
|
|
||||||
│ └── design.md # Technical patterns
|
|
||||||
├── changes/ # Proposals - what SHOULD change
|
|
||||||
│ ├── [change-name]/
|
|
||||||
│ │ ├── proposal.md # Why, what, impact
|
|
||||||
│ │ ├── tasks.md # Implementation checklist
|
|
||||||
│ │ ├── design.md # Technical decisions (optional; see criteria)
|
|
||||||
│ │ └── specs/ # Delta changes
|
|
||||||
│ │ └── [capability]/
|
|
||||||
│ │ └── spec.md # ADDED/MODIFIED/REMOVED
|
|
||||||
│ └── archive/ # Completed changes
|
|
||||||
```
|
|
||||||
|
|
||||||
## Creating Change Proposals
|
|
||||||
|
|
||||||
### Decision Tree
|
|
||||||
|
|
||||||
```
|
|
||||||
New request?
|
|
||||||
├─ Bug fix restoring spec behavior? → Fix directly
|
|
||||||
├─ Typo/format/comment? → Fix directly
|
|
||||||
├─ New feature/capability? → Create proposal
|
|
||||||
├─ Breaking change? → Create proposal
|
|
||||||
├─ Architecture change? → Create proposal
|
|
||||||
└─ Unclear? → Create proposal (safer)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Proposal Structure
|
|
||||||
|
|
||||||
1. **Create directory:** `changes/[change-id]/` (kebab-case, verb-led, unique)
|
|
||||||
|
|
||||||
2. **Write proposal.md:**
|
|
||||||
```markdown
|
|
||||||
# Change: [Brief description of change]
|
|
||||||
|
|
||||||
## Why
|
|
||||||
[1-2 sentences on problem/opportunity]
|
|
||||||
|
|
||||||
## What Changes
|
|
||||||
- [Bullet list of changes]
|
|
||||||
- [Mark breaking changes with **BREAKING**]
|
|
||||||
|
|
||||||
## Impact
|
|
||||||
- Affected specs: [list capabilities]
|
|
||||||
- Affected code: [key files/systems]
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Create spec deltas:** `specs/[capability]/spec.md`
|
|
||||||
```markdown
|
|
||||||
## ADDED Requirements
|
|
||||||
### Requirement: New Feature
|
|
||||||
The system SHALL provide...
|
|
||||||
|
|
||||||
#### Scenario: Success case
|
|
||||||
- **WHEN** user performs action
|
|
||||||
- **THEN** expected result
|
|
||||||
|
|
||||||
## MODIFIED Requirements
|
|
||||||
### Requirement: Existing Feature
|
|
||||||
[Complete modified requirement]
|
|
||||||
|
|
||||||
## REMOVED Requirements
|
|
||||||
### Requirement: Old Feature
|
|
||||||
**Reason**: [Why removing]
|
|
||||||
**Migration**: [How to handle]
|
|
||||||
```
|
|
||||||
If multiple capabilities are affected, create multiple delta files under `changes/[change-id]/specs/<capability>/spec.md`—one per capability.
|
|
||||||
|
|
||||||
4. **Create tasks.md:**
|
|
||||||
```markdown
|
|
||||||
## 1. Implementation
|
|
||||||
- [ ] 1.1 Create database schema
|
|
||||||
- [ ] 1.2 Implement API endpoint
|
|
||||||
- [ ] 1.3 Add frontend component
|
|
||||||
- [ ] 1.4 Write tests
|
|
||||||
```
|
|
||||||
|
|
||||||
5. **Create design.md when needed:**
|
|
||||||
Create `design.md` if any of the following apply; otherwise omit it:
|
|
||||||
- Cross-cutting change (multiple services/modules) or a new architectural pattern
|
|
||||||
- New external dependency or significant data model changes
|
|
||||||
- Security, performance, or migration complexity
|
|
||||||
- Ambiguity that benefits from technical decisions before coding
|
|
||||||
|
|
||||||
Minimal `design.md` skeleton:
|
|
||||||
```markdown
|
|
||||||
## Context
|
|
||||||
[Background, constraints, stakeholders]
|
|
||||||
|
|
||||||
## Goals / Non-Goals
|
|
||||||
- Goals: [...]
|
|
||||||
- Non-Goals: [...]
|
|
||||||
|
|
||||||
## Decisions
|
|
||||||
- Decision: [What and why]
|
|
||||||
- Alternatives considered: [Options + rationale]
|
|
||||||
|
|
||||||
## Risks / Trade-offs
|
|
||||||
- [Risk] → Mitigation
|
|
||||||
|
|
||||||
## Migration Plan
|
|
||||||
[Steps, rollback]
|
|
||||||
|
|
||||||
## Open Questions
|
|
||||||
- [...]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Spec File Format
|
|
||||||
|
|
||||||
### Critical: Scenario Formatting
|
|
||||||
|
|
||||||
**CORRECT** (use #### headers):
|
|
||||||
```markdown
|
|
||||||
#### Scenario: User login success
|
|
||||||
- **WHEN** valid credentials provided
|
|
||||||
- **THEN** return JWT token
|
|
||||||
```
|
|
||||||
|
|
||||||
**WRONG** (don't use bullets or bold):
|
|
||||||
```markdown
|
|
||||||
- **Scenario: User login** ❌
|
|
||||||
**Scenario**: User login ❌
|
|
||||||
### Scenario: User login ❌
|
|
||||||
```
|
|
||||||
|
|
||||||
Every requirement MUST have at least one scenario.
|
|
||||||
|
|
||||||
### Requirement Wording
|
|
||||||
- Use SHALL/MUST for normative requirements (avoid should/may unless intentionally non-normative)
|
|
||||||
|
|
||||||
### Delta Operations
|
|
||||||
|
|
||||||
- `## ADDED Requirements` - New capabilities
|
|
||||||
- `## MODIFIED Requirements` - Changed behavior
|
|
||||||
- `## REMOVED Requirements` - Deprecated features
|
|
||||||
- `## RENAMED Requirements` - Name changes
|
|
||||||
|
|
||||||
Headers matched with `trim(header)` - whitespace ignored.
|
|
||||||
|
|
||||||
#### When to use ADDED vs MODIFIED
|
|
||||||
- ADDED: Introduces a new capability or sub-capability that can stand alone as a requirement. Prefer ADDED when the change is orthogonal (e.g., adding "Slash Command Configuration") rather than altering the semantics of an existing requirement.
|
|
||||||
- MODIFIED: Changes the behavior, scope, or acceptance criteria of an existing requirement. Always paste the full, updated requirement content (header + all scenarios). The archiver will replace the entire requirement with what you provide here; partial deltas will drop previous details.
|
|
||||||
- RENAMED: Use when only the name changes. If you also change behavior, use RENAMED (name) plus MODIFIED (content) referencing the new name.
|
|
||||||
|
|
||||||
Common pitfall: Using MODIFIED to add a new concern without including the previous text. This causes loss of detail at archive time. If you aren’t explicitly changing the existing requirement, add a new requirement under ADDED instead.
|
|
||||||
|
|
||||||
Authoring a MODIFIED requirement correctly:
|
|
||||||
1) Locate the existing requirement in `openspec/specs/<capability>/spec.md`.
|
|
||||||
2) Copy the entire requirement block (from `### Requirement: ...` through its scenarios).
|
|
||||||
3) Paste it under `## MODIFIED Requirements` and edit to reflect the new behavior.
|
|
||||||
4) Ensure the header text matches exactly (whitespace-insensitive) and keep at least one `#### Scenario:`.
|
|
||||||
|
|
||||||
Example for RENAMED:
|
|
||||||
```markdown
|
|
||||||
## RENAMED Requirements
|
|
||||||
- FROM: `### Requirement: Login`
|
|
||||||
- TO: `### Requirement: User Authentication`
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Common Errors
|
|
||||||
|
|
||||||
**"Change must have at least one delta"**
|
|
||||||
- Check `changes/[name]/specs/` exists with .md files
|
|
||||||
- Verify files have operation prefixes (## ADDED Requirements)
|
|
||||||
|
|
||||||
**"Requirement must have at least one scenario"**
|
|
||||||
- Check scenarios use `#### Scenario:` format (4 hashtags)
|
|
||||||
- Don't use bullet points or bold for scenario headers
|
|
||||||
|
|
||||||
**Silent scenario parsing failures**
|
|
||||||
- Exact format required: `#### Scenario: Name`
|
|
||||||
- Debug with: `openspec show [change] --json --deltas-only`
|
|
||||||
|
|
||||||
### Validation Tips
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Always use strict mode for comprehensive checks
|
|
||||||
openspec validate [change] --strict
|
|
||||||
|
|
||||||
# Debug delta parsing
|
|
||||||
openspec show [change] --json | jq '.deltas'
|
|
||||||
|
|
||||||
# Check specific requirement
|
|
||||||
openspec show [spec] --json -r 1
|
|
||||||
```
|
|
||||||
|
|
||||||
## Happy Path Script
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1) Explore current state
|
|
||||||
openspec spec list --long
|
|
||||||
openspec list
|
|
||||||
# Optional full-text search:
|
|
||||||
# rg -n "Requirement:|Scenario:" openspec/specs
|
|
||||||
# rg -n "^#|Requirement:" openspec/changes
|
|
||||||
|
|
||||||
# 2) Choose change id and scaffold
|
|
||||||
CHANGE=add-two-factor-auth
|
|
||||||
mkdir -p openspec/changes/$CHANGE/{specs/auth}
|
|
||||||
printf "## Why\n...\n\n## What Changes\n- ...\n\n## Impact\n- ...\n" > openspec/changes/$CHANGE/proposal.md
|
|
||||||
printf "## 1. Implementation\n- [ ] 1.1 ...\n" > openspec/changes/$CHANGE/tasks.md
|
|
||||||
|
|
||||||
# 3) Add deltas (example)
|
|
||||||
cat > openspec/changes/$CHANGE/specs/auth/spec.md << 'EOF'
|
|
||||||
## ADDED Requirements
|
|
||||||
### Requirement: Two-Factor Authentication
|
|
||||||
Users MUST provide a second factor during login.
|
|
||||||
|
|
||||||
#### Scenario: OTP required
|
|
||||||
- **WHEN** valid credentials are provided
|
|
||||||
- **THEN** an OTP challenge is required
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# 4) Validate
|
|
||||||
openspec validate $CHANGE --strict
|
|
||||||
```
|
|
||||||
|
|
||||||
## Multi-Capability Example
|
|
||||||
|
|
||||||
```
|
|
||||||
openspec/changes/add-2fa-notify/
|
|
||||||
├── proposal.md
|
|
||||||
├── tasks.md
|
|
||||||
└── specs/
|
|
||||||
├── auth/
|
|
||||||
│ └── spec.md # ADDED: Two-Factor Authentication
|
|
||||||
└── notifications/
|
|
||||||
└── spec.md # ADDED: OTP email notification
|
|
||||||
```
|
|
||||||
|
|
||||||
auth/spec.md
|
|
||||||
```markdown
|
|
||||||
## ADDED Requirements
|
|
||||||
### Requirement: Two-Factor Authentication
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
notifications/spec.md
|
|
||||||
```markdown
|
|
||||||
## ADDED Requirements
|
|
||||||
### Requirement: OTP Email Notification
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
### Simplicity First
|
|
||||||
- Default to <100 lines of new code
|
|
||||||
- Single-file implementations until proven insufficient
|
|
||||||
- Avoid frameworks without clear justification
|
|
||||||
- Choose boring, proven patterns
|
|
||||||
|
|
||||||
### Complexity Triggers
|
|
||||||
Only add complexity with:
|
|
||||||
- Performance data showing current solution too slow
|
|
||||||
- Concrete scale requirements (>1000 users, >100MB data)
|
|
||||||
- Multiple proven use cases requiring abstraction
|
|
||||||
|
|
||||||
### Clear References
|
|
||||||
- Use `file.ts:42` format for code locations
|
|
||||||
- Reference specs as `specs/auth/spec.md`
|
|
||||||
- Link related changes and PRs
|
|
||||||
|
|
||||||
### Capability Naming
|
|
||||||
- Use verb-noun: `user-auth`, `payment-capture`
|
|
||||||
- Single purpose per capability
|
|
||||||
- 10-minute understandability rule
|
|
||||||
- Split if description needs "AND"
|
|
||||||
|
|
||||||
### Change ID Naming
|
|
||||||
- Use kebab-case, short and descriptive: `add-two-factor-auth`
|
|
||||||
- Prefer verb-led prefixes: `add-`, `update-`, `remove-`, `refactor-`
|
|
||||||
- Ensure uniqueness; if taken, append `-2`, `-3`, etc.
|
|
||||||
|
|
||||||
## Tool Selection Guide
|
|
||||||
|
|
||||||
| Task | Tool | Why |
|
|
||||||
|------|------|-----|
|
|
||||||
| Find files by pattern | Glob | Fast pattern matching |
|
|
||||||
| Search code content | Grep | Optimized regex search |
|
|
||||||
| Read specific files | Read | Direct file access |
|
|
||||||
| Explore unknown scope | Task | Multi-step investigation |
|
|
||||||
|
|
||||||
## Error Recovery
|
|
||||||
|
|
||||||
### Change Conflicts
|
|
||||||
1. Run `openspec list` to see active changes
|
|
||||||
2. Check for overlapping specs
|
|
||||||
3. Coordinate with change owners
|
|
||||||
4. Consider combining proposals
|
|
||||||
|
|
||||||
### Validation Failures
|
|
||||||
1. Run with `--strict` flag
|
|
||||||
2. Check JSON output for details
|
|
||||||
3. Verify spec file format
|
|
||||||
4. Ensure scenarios properly formatted
|
|
||||||
|
|
||||||
### Missing Context
|
|
||||||
1. Read project.md first
|
|
||||||
2. Check related specs
|
|
||||||
3. Review recent archives
|
|
||||||
4. Ask for clarification
|
|
||||||
|
|
||||||
## Quick Reference
|
|
||||||
|
|
||||||
### Stage Indicators
|
|
||||||
- `changes/` - Proposed, not yet built
|
|
||||||
- `specs/` - Built and deployed
|
|
||||||
- `archive/` - Completed changes
|
|
||||||
|
|
||||||
### File Purposes
|
|
||||||
- `proposal.md` - Why and what
|
|
||||||
- `tasks.md` - Implementation steps
|
|
||||||
- `design.md` - Technical decisions
|
|
||||||
- `spec.md` - Requirements and behavior
|
|
||||||
|
|
||||||
### CLI Essentials
|
|
||||||
```bash
|
|
||||||
openspec list # What's in progress?
|
|
||||||
openspec show [item] # View details
|
|
||||||
openspec validate --strict # Is it correct?
|
|
||||||
openspec archive <change-id> [--yes|-y] # Mark complete (add --yes for automation)
|
|
||||||
```
|
|
||||||
|
|
||||||
Remember: Specs are truth. Changes are proposals. Keep them in sync.
|
|
||||||
@ -1,175 +0,0 @@
|
|||||||
# Project Context
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
ERPTurbo_Admin 是寻鸿科技的企业资源规划(ERP)管理系统,为企业提供全面的生产管理、库存管理、经销商管理、订单处理等核心业务功能。系统旨在提高企业运营效率,实现业务流程数字化和智能化管理。
|
|
||||||
|
|
||||||
## Tech Stack
|
|
||||||
|
|
||||||
### 前端框架
|
|
||||||
- **React 18** - 主要UI框架,使用TypeScript开发
|
|
||||||
- **UmiJS Max 4.4.9** - 企业级React应用框架,提供路由、构建、部署等一体化解决方案
|
|
||||||
- **Ant Design 5.25.2** - 企业级UI设计语言和组件库
|
|
||||||
- **Ant Design Pro Components 2.8.6** - 高级业务组件
|
|
||||||
- **NutUI React 3.0.18** - 移动端UI组件库(用于移动端适配)
|
|
||||||
|
|
||||||
### 状态管理
|
|
||||||
- **Valtio** - 现代化状态管理库
|
|
||||||
- **DVA** - 数据流方案(兼容性考虑)
|
|
||||||
- **React Query** - 服务端状态管理和数据获取
|
|
||||||
|
|
||||||
### 表单处理
|
|
||||||
- **Formily 2.3.2** - 企业级表单解决方案
|
|
||||||
- **@formily/antd-v5** - Formily与Ant Design v5的集成
|
|
||||||
|
|
||||||
### 样式和主题
|
|
||||||
- **TailwindCSS 3.4.17** - 原子化CSS框架
|
|
||||||
- **antd-style 3.7.1** - Ant Design样式增强方案
|
|
||||||
- **Styled Components 6.1.17** - CSS-in-JS样式库
|
|
||||||
|
|
||||||
### 工具库
|
|
||||||
- **Lodash 4.17.21** - 实用工具函数库
|
|
||||||
- **Day.js 1.11.13** - 轻量级日期处理库
|
|
||||||
- **Axios 1.7.4** - HTTP客户端
|
|
||||||
- **UUID 10.0.0** - 唯一标识符生成
|
|
||||||
- **CryptoJS 4.2.0** - 加密解密功能
|
|
||||||
|
|
||||||
### 编辑器和文档
|
|
||||||
- **@wangeditor-next/editor** - 富文本编辑器
|
|
||||||
- **Markdown-it 14.1.0** - Markdown解析器
|
|
||||||
- **XLSX 0.18.5** - Excel文件处理
|
|
||||||
|
|
||||||
### 地图服务
|
|
||||||
- **腾讯地图API** - 地图功能集成
|
|
||||||
|
|
||||||
### 开发工具
|
|
||||||
- **TypeScript 5.6.3** - 类型安全的JavaScript超集
|
|
||||||
- **PNPM** - 包管理器,支持工作空间
|
|
||||||
- **Prettier 3.3.3** - 代码格式化
|
|
||||||
- **ESLint** - 代码质量检查
|
|
||||||
- **Husky 9.1.6** - Git钩子管理
|
|
||||||
- **Lint-staged 15.2.10** - 暂存文件检查
|
|
||||||
|
|
||||||
## Project Conventions
|
|
||||||
|
|
||||||
### 代码风格
|
|
||||||
- **缩进**: 使用Tab缩进,大小为4个空格宽度
|
|
||||||
- **行长度**: 最大80字符
|
|
||||||
- **文件编码**: UTF-8
|
|
||||||
- **换行符**: LF (Unix风格)
|
|
||||||
- **尾随空格**: 删除所有尾随空格
|
|
||||||
- **文件结尾**: 所有文件以换行符结尾
|
|
||||||
|
|
||||||
### 命名约定
|
|
||||||
- **组件文件**: PascalCase (例: `UserList.tsx`)
|
|
||||||
- **函数/变量**: camelCase (例: `getUserList`)
|
|
||||||
- **常量**: SCREAMING_SNAKE_CASE (例: `API_BASE_URL`)
|
|
||||||
- **文件/目录**: kebab-case (例: `user-management/`)
|
|
||||||
- **接口/类型**: PascalCase,以I或T开头可选 (例: `UserVO`, `APIResponse`)
|
|
||||||
|
|
||||||
### 目录结构
|
|
||||||
```
|
|
||||||
packages/app-operation/src/
|
|
||||||
├── components/ # 通用组件
|
|
||||||
├── pages/ # 页面组件
|
|
||||||
├── services/ # API服务
|
|
||||||
├── models/ # 数据模型
|
|
||||||
├── utils/ # 工具函数
|
|
||||||
├── constants/ # 常量定义
|
|
||||||
├── layout/ # 布局组件
|
|
||||||
├── locales/ # 国际化文件
|
|
||||||
├── assets/ # 静态资源
|
|
||||||
└── wrappers/ # 高阶组件包装器
|
|
||||||
```
|
|
||||||
|
|
||||||
### 架构模式
|
|
||||||
- **微前端架构**: 使用PNPM工作空间管理多个应用
|
|
||||||
- **分层架构**:
|
|
||||||
- 表现层 (Presentation): React组件和页面
|
|
||||||
- 业务层 (Business): 业务逻辑和状态管理
|
|
||||||
- 数据层 (Data): API调用和数据持久化
|
|
||||||
- **模块化设计**: 按业务领域划分模块 (dealer, product, operation等)
|
|
||||||
- **组件化开发**: 优先使用可复用组件
|
|
||||||
|
|
||||||
### 测试策略
|
|
||||||
- **当前状态**: 项目暂无专门的测试配置
|
|
||||||
- **推荐策略**:
|
|
||||||
- 单元测试:使用Jest + Testing Library
|
|
||||||
- 集成测试:使用Cypress或Playwright
|
|
||||||
- 代码覆盖率:目标 > 80%
|
|
||||||
- 测试文件命名: `*.test.tsx` 或 `*.spec.tsx`
|
|
||||||
|
|
||||||
### Git工作流
|
|
||||||
- **分支策略**:
|
|
||||||
- `master`: 主分支,生产环境代码
|
|
||||||
- `feature/*`: 功能开发分支
|
|
||||||
- `hotfix/*`: 紧急修复分支
|
|
||||||
- **提交规范**: 使用Conventional Commits
|
|
||||||
- `feat:` 新功能
|
|
||||||
- `fix:` 修复bug
|
|
||||||
- `refactor:` 重构代码
|
|
||||||
- `docs:` 文档更新
|
|
||||||
- `style:` 代码格式调整
|
|
||||||
- `test:` 测试相关
|
|
||||||
- `chore:` 构建过程或辅助工具变动
|
|
||||||
- **代码质量**: 使用Husky + Lint-staged进行预提交检查
|
|
||||||
|
|
||||||
### API设计规范
|
|
||||||
- **RESTful API**: 遵循REST设计原则
|
|
||||||
- **OpenAPI**: 使用Swagger进行API文档化
|
|
||||||
- **数据格式**: 统一使用JSON
|
|
||||||
- **错误处理**: 标准化错误响应格式
|
|
||||||
- **分页**: 统一分页参数和响应格式
|
|
||||||
|
|
||||||
## Domain Context
|
|
||||||
|
|
||||||
### 业务模块
|
|
||||||
- **经销商管理 (Dealer)**: 经销商信息、返点计算、配置管理
|
|
||||||
- **产品管理 (Product)**: 产品数据、规格管理
|
|
||||||
- **订单管理 (Order)**: 订单处理、发货管理
|
|
||||||
- **基础数据 (Basic Data)**: 生产预付、工人预付、固定费用
|
|
||||||
- **纸箱管理 (Box)**: 纸箱规格管理
|
|
||||||
- **系统设置 (Setting)**: 智能识别配置、系统参数
|
|
||||||
|
|
||||||
### 核心概念
|
|
||||||
- **用户角色**: 普通用户、管理员、经销商
|
|
||||||
- **权限管理**: 基于角色的访问控制 (RBAC)
|
|
||||||
- **多租户**: 支持多平台/渠道隔离
|
|
||||||
- **国际化**: 默认中文,支持多语言扩展
|
|
||||||
|
|
||||||
## Important Constraints
|
|
||||||
|
|
||||||
### 技术约束
|
|
||||||
- **浏览器支持**: 现代浏览器 (Chrome 80+, Firefox 75+, Safari 13+)
|
|
||||||
- **响应式设计**: 支持桌面端和移动端
|
|
||||||
- **性能要求**: 页面加载时间 < 3秒
|
|
||||||
- **安全要求**:
|
|
||||||
- XSS防护
|
|
||||||
- CSRF防护
|
|
||||||
- 敏感数据加密
|
|
||||||
- API鉴权
|
|
||||||
|
|
||||||
### 业务约束
|
|
||||||
- **数据一致性**: 关键业务数据必须保持一致性
|
|
||||||
- **审计日志**: 重要操作需要记录审计日志
|
|
||||||
- **数据备份**: 定期备份关键业务数据
|
|
||||||
- **合规要求**: 符合企业数据管理规范
|
|
||||||
|
|
||||||
## External Dependencies
|
|
||||||
|
|
||||||
### 云服务
|
|
||||||
- **阿里云OSS**: 文件存储服务
|
|
||||||
- **腾讯地图API**: 地图功能服务
|
|
||||||
|
|
||||||
### API服务
|
|
||||||
- **业务API**: 自建后端API服务
|
|
||||||
- **认证服务**: SSO单点登录服务
|
|
||||||
|
|
||||||
### 第三方集成
|
|
||||||
- **支付接口**: 支付宝、微信支付(预留)
|
|
||||||
- **短信服务**: 短信通知服务(预留)
|
|
||||||
- **邮件服务**: 邮件通知服务(预留)
|
|
||||||
|
|
||||||
### 开发环境
|
|
||||||
- **Node.js**: >= 16.0.0
|
|
||||||
- **PNPM**: >= 8.0.0
|
|
||||||
- **Git**: >= 2.30.0
|
|
||||||
@ -2,13 +2,17 @@
|
|||||||
|
|
||||||
// 全局初始化数据配置,用于 Layout 用户信息和权限初始化
|
// 全局初始化数据配置,用于 Layout 用户信息和权限初始化
|
||||||
// 更多信息见文档:https://umijs.org/docs/api/runtime-config#getinitialstate
|
// 更多信息见文档:https://umijs.org/docs/api/runtime-config#getinitialstate
|
||||||
import { VersionChecker, LeftMenu } from '@/components';
|
import { LeftMenu, SearchMenu, VersionChecker } from '@/components';
|
||||||
import Avatar from '@/layout/guide/Avatar';
|
import Avatar from '@/layout/guide/Avatar';
|
||||||
import { auth } from '@/services';
|
import { auth } from '@/services';
|
||||||
import { Navigate } from '@@/exports';
|
import { Navigate } from '@@/exports';
|
||||||
import { RunTimeLayoutConfig } from '@@/plugin-layout/types';
|
import { RunTimeLayoutConfig } from '@@/plugin-layout/types';
|
||||||
import { RequestConfig } from '@@/plugin-request/request';
|
import { RequestConfig } from '@@/plugin-request/request';
|
||||||
import { InfoCircleFilled } from '@ant-design/icons';
|
import {
|
||||||
|
InfoCircleFilled,
|
||||||
|
NotificationFilled,
|
||||||
|
QuestionCircleFilled,
|
||||||
|
} from '@ant-design/icons';
|
||||||
import '@nutui/nutui-react/dist/style.scss';
|
import '@nutui/nutui-react/dist/style.scss';
|
||||||
import { history } from '@umijs/max';
|
import { history } from '@umijs/max';
|
||||||
import { Alert, Button, message, notification, Spin } from 'antd';
|
import { Alert, Button, message, notification, Spin } from 'antd';
|
||||||
@ -176,10 +180,7 @@ export const render = async (oldRender: () => void) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
window.localStorage.setItem('admin', JSON.stringify(adminVO));
|
window.localStorage.setItem('admin', JSON.stringify(adminVO));
|
||||||
window.localStorage.setItem(
|
window.localStorage.setItem('userRoleVOList', JSON.stringify([]));
|
||||||
'userRoleVOList',
|
|
||||||
JSON.stringify([]),
|
|
||||||
);
|
|
||||||
window.localStorage.setItem('roleSlug', 'operation');
|
window.localStorage.setItem('roleSlug', 'operation');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -298,18 +299,18 @@ export const layout: RunTimeLayoutConfig = ({ initialState }) => {
|
|||||||
// encodeURIComponent(process.env.UMI_APP_OPERATION_URL || '');
|
// encodeURIComponent(process.env.UMI_APP_OPERATION_URL || '');
|
||||||
// }
|
// }
|
||||||
},
|
},
|
||||||
// actionsRender: (props) => {
|
actionsRender: (props) => {
|
||||||
// if (props.isMobile) return [];
|
if (props.isMobile) return [];
|
||||||
// if (typeof window === 'undefined') return [];
|
if (typeof window === 'undefined') return [];
|
||||||
// return [
|
return [
|
||||||
// props.layout !== 'side' && document.body.clientWidth > 1400 ? (
|
props.layout !== 'side' && document.body.clientWidth > 1400 ? (
|
||||||
// <SearchInput key={'SearchInput'} />
|
<SearchMenu key={'SearchMenu'} menuData={props.menuData} />
|
||||||
// ) : undefined,
|
) : undefined,
|
||||||
// <InfoCircleFilled key="InfoCircleFilled" />,
|
<InfoCircleFilled key="InfoCircleFilled" />,
|
||||||
// <QuestionCircleFilled key="QuestionCircleFilled" />,
|
<QuestionCircleFilled key="QuestionCircleFilled" />,
|
||||||
// <NotificationFilled key="NotificationFilled" />,
|
<NotificationFilled key="NotificationFilled" />,
|
||||||
// ];
|
];
|
||||||
// },
|
},
|
||||||
headerRender: (props, defaultDom) => {
|
headerRender: (props, defaultDom) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@ -872,7 +872,7 @@ export default function BizContainer<
|
|||||||
return (
|
return (
|
||||||
data?.map((item) => {
|
data?.map((item) => {
|
||||||
return {
|
return {
|
||||||
label: item.fullName,
|
label: `${item.shortName} | ${item.fullName}`,
|
||||||
value: item.companyId,
|
value: item.companyId,
|
||||||
};
|
};
|
||||||
}) || []
|
}) || []
|
||||||
@ -1020,7 +1020,7 @@ export default function BizContainer<
|
|||||||
trigger={() => (
|
trigger={() => (
|
||||||
<Space>
|
<Space>
|
||||||
<a>
|
<a>
|
||||||
{`${orderVO.orderVehicle?.dealerName} - 第 ${orderVO.orderVehicle?.vehicleNo || '暂无'} 车 - ${orderVO.orderSn || '暂无'}`}
|
{`${orderVO.orderVehicle?.dealerName} | 第 ${orderVO.orderVehicle?.vehicleNo || '暂无'} 车 | ${orderVO.orderSn || '暂无'}`}
|
||||||
</a>
|
</a>
|
||||||
</Space>
|
</Space>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -22,6 +22,7 @@ export default function BizPage<
|
|||||||
trigger,
|
trigger,
|
||||||
isMobile,
|
isMobile,
|
||||||
intlPrefix,
|
intlPrefix,
|
||||||
|
convertValue,
|
||||||
} = props;
|
} = props;
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
@ -34,7 +35,7 @@ export default function BizPage<
|
|||||||
persistenceType: 'sessionStorage',
|
persistenceType: 'sessionStorage',
|
||||||
persistenceKey: method + 'ColumnStateKey',
|
persistenceKey: method + 'ColumnStateKey',
|
||||||
defaultValue: {
|
defaultValue: {
|
||||||
// option: { show: true, fixed: 'right' },
|
orderId: { show: true, fixed: 'left' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
scroll: { x: 'max-content' },
|
scroll: { x: 'max-content' },
|
||||||
@ -78,7 +79,12 @@ export default function BizPage<
|
|||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data,
|
data: data.map((item: BizVO) => {
|
||||||
|
if (convertValue) {
|
||||||
|
return convertValue(item);
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}),
|
||||||
success,
|
success,
|
||||||
total: totalCount,
|
total: totalCount,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -133,6 +133,7 @@ export type BizPageProps<
|
|||||||
) => React.ReactNode;
|
) => React.ReactNode;
|
||||||
fieldProps?: ProTableProps<BizVO, BizPageQry, BizValueType>;
|
fieldProps?: ProTableProps<BizVO, BizPageQry, BizValueType>;
|
||||||
trigger?: () => React.ReactNode;
|
trigger?: () => React.ReactNode;
|
||||||
|
convertValue?: (record: BizVO) => BizVO;
|
||||||
} & ApiProps<Func>;
|
} & ApiProps<Func>;
|
||||||
|
|
||||||
export interface BizTreeProps<
|
export interface BizTreeProps<
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
export { default as ShipOrderList } from './ShipOrderList';
|
|
||||||
@ -87,7 +87,7 @@ const LeftMenu: React.FC<ILeftMenuProps> = (props) => {
|
|||||||
);
|
);
|
||||||
setActiveMenuKey(activeMenuKey);
|
setActiveMenuKey(activeMenuKey);
|
||||||
setOpenKeys([...openKeys, ...activeMenuKey]);
|
setOpenKeys([...openKeys, ...activeMenuKey]);
|
||||||
}, [pathname]);
|
}, [pathname, menuData]);
|
||||||
|
|
||||||
if (!menuData) {
|
if (!menuData) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -120,7 +120,7 @@ export default function OrderCostPay(props: IOrderCostPayProps) {
|
|||||||
</div>
|
</div>
|
||||||
<div style={{ fontWeight: 'bold' }}>
|
<div style={{ fontWeight: 'bold' }}>
|
||||||
{orderCostVO.orderVO
|
{orderCostVO.orderVO
|
||||||
? `${orderCostVO.orderVO.orderVehicle.dealerName} - 第 ${orderCostVO.orderVO.orderVehicle.vehicleNo || '暂无'} 车`
|
? `${orderCostVO.orderVO.orderVehicle.dealerName} | 第 ${orderCostVO.orderVO.orderVehicle.vehicleNo || '暂无'} 车`
|
||||||
: '-'}
|
: '-'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -56,7 +56,7 @@ export default function OrderList(props: IOrderListProps) {
|
|||||||
trigger={() => (
|
trigger={() => (
|
||||||
<Space>
|
<Space>
|
||||||
<a>
|
<a>
|
||||||
{`${orderVO.orderVehicle?.dealerName} - 第 ${orderVO.orderVehicle?.vehicleNo || '暂无'} 车 - ${orderVO.orderSn || '暂无'}`}
|
{`${orderVO.orderVehicle?.dealerName} | 第 ${orderVO.orderVehicle?.vehicleNo || '暂无'} 车 | ${orderVO.orderSn || '暂无'}`}
|
||||||
</a>
|
</a>
|
||||||
</Space>
|
</Space>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { OrderFormItem, OrderList, SelectModal } from '@/components';
|
||||||
import { business } from '@/services';
|
import { business } from '@/services';
|
||||||
import { formatParam } from '@/utils/formatParam';
|
import { formatParam } from '@/utils/formatParam';
|
||||||
import { pagination } from '@/utils/pagination';
|
import { pagination } from '@/utils/pagination';
|
||||||
@ -8,7 +9,6 @@ import {
|
|||||||
ProColumns,
|
ProColumns,
|
||||||
ProFormSelect,
|
ProFormSelect,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { OrderFormItem, OrderList, SelectModal } from '@/components';
|
|
||||||
import { Alert, ModalProps, Row, Space, Tag } from 'antd';
|
import { Alert, ModalProps, Row, Space, Tag } from 'antd';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ export default function OrderModal(props: IOrderModalProps) {
|
|||||||
trigger={() => (
|
trigger={() => (
|
||||||
<Space>
|
<Space>
|
||||||
<a>
|
<a>
|
||||||
{`${orderVO.orderVehicle?.dealerName} - 第 ${orderVO.orderVehicle?.vehicleNo || '暂无'} 车 - ${orderVO.orderSn || '暂无'}`}
|
{`${orderVO.orderVehicle?.dealerName} | 第 ${orderVO.orderVehicle?.vehicleNo || '暂无'} 车 | ${orderVO.orderSn || '暂无'}`}
|
||||||
</a>
|
</a>
|
||||||
</Space>
|
</Space>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import { business } from '@/services';
|
|||||||
import { useIntl } from '@@/exports';
|
import { useIntl } from '@@/exports';
|
||||||
import { ProColumns } from '@ant-design/pro-components';
|
import { ProColumns } from '@ant-design/pro-components';
|
||||||
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
||||||
import { Tag } from 'antd';
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
interface IOrderShipListProps {
|
interface IOrderShipListProps {
|
||||||
@ -13,7 +12,7 @@ interface IOrderShipListProps {
|
|||||||
onValueChange?: () => void;
|
onValueChange?: () => void;
|
||||||
mode?: ModeType;
|
mode?: ModeType;
|
||||||
trigger?: () => React.ReactNode;
|
trigger?: () => React.ReactNode;
|
||||||
params: BusinessAPI.OrderShipPageQry;
|
params?: BusinessAPI.OrderShipPageQry;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function OrderShipList(props: IOrderShipListProps) {
|
export default function OrderShipList(props: IOrderShipListProps) {
|
||||||
@ -32,26 +31,40 @@ export default function OrderShipList(props: IOrderShipListProps) {
|
|||||||
const [activeKey, setActiveKey] = useState<string>('ALL');
|
const [activeKey, setActiveKey] = useState<string>('ALL');
|
||||||
|
|
||||||
const columns: ProColumns<BusinessAPI.OrderShipVO, BizValueType>[] = [
|
const columns: ProColumns<BusinessAPI.OrderShipVO, BizValueType>[] = [
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: intlPrefix + '.column.order' }),
|
||||||
|
dataIndex: 'orderVO',
|
||||||
|
key: 'orderId',
|
||||||
|
valueType: 'order',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: intlPrefix + '.column.dealer' }),
|
||||||
|
dataIndex: 'dealerVO',
|
||||||
|
key: 'dealerId',
|
||||||
|
valueType: 'dealer',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: intlPrefix + '.column.company' }),
|
||||||
|
dataIndex: 'companyVO',
|
||||||
|
key: 'companyId',
|
||||||
|
valueType: 'company',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: intlPrefix + '.column.orderSn' }),
|
title: intl.formatMessage({ id: intlPrefix + '.column.orderSn' }),
|
||||||
dataIndex: 'orderSn',
|
dataIndex: 'orderSn',
|
||||||
key: 'orderSn',
|
key: 'orderSn',
|
||||||
renderText: (text: string) => <span className="font-medium">{text}</span>,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: intlPrefix + '.column.dealerName' }),
|
title: intl.formatMessage({ id: intlPrefix + '.column.shippingAddress' }),
|
||||||
dataIndex: 'dealerName',
|
dataIndex: 'shippingAddress',
|
||||||
key: 'dealerName',
|
key: 'shippingAddress',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: intlPrefix + '.column.vehicleNo' }),
|
title: intl.formatMessage({
|
||||||
dataIndex: ['orderVehicle', 'vehicleNo'],
|
id: intlPrefix + '.column.receivingAddress',
|
||||||
key: 'vehicleNo',
|
}),
|
||||||
render: (_, record) => {
|
dataIndex: 'receivingAddress',
|
||||||
return record.orderVehicle?.vehicleNo
|
key: 'receivingAddress',
|
||||||
? '第' + record.orderVehicle?.vehicleNo + '车'
|
|
||||||
: '-';
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: intlPrefix + '.column.shippingDate' }),
|
title: intl.formatMessage({ id: intlPrefix + '.column.shippingDate' }),
|
||||||
@ -59,52 +72,69 @@ export default function OrderShipList(props: IOrderShipListProps) {
|
|||||||
key: 'shippingDate',
|
key: 'shippingDate',
|
||||||
valueType: 'date',
|
valueType: 'date',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({
|
||||||
|
id: intlPrefix + '.column.estimatedArrivalDate',
|
||||||
|
}),
|
||||||
|
dataIndex: 'estimatedArrivalDate',
|
||||||
|
key: 'estimatedArrivalDate',
|
||||||
|
valueType: 'date',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: intlPrefix + '.column.watermelonGrade' }),
|
||||||
|
dataIndex: 'watermelonGrade',
|
||||||
|
key: 'watermelonGrade',
|
||||||
|
search: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: intlPrefix + '.column.type' }),
|
||||||
|
dataIndex: 'type',
|
||||||
|
key: 'type',
|
||||||
|
valueType: 'select',
|
||||||
|
valueEnum: {
|
||||||
|
PURCHASE_SHIP: intl.formatMessage({
|
||||||
|
id: intlPrefix + '.column.type.enum.purchaseShip',
|
||||||
|
}),
|
||||||
|
TRANSFER_SHIP: intl.formatMessage({
|
||||||
|
id: intlPrefix + '.column.type.enum.transferShip',
|
||||||
|
}),
|
||||||
|
CHANGE_SHIP: intl.formatMessage({
|
||||||
|
id: intlPrefix + '.column.type.enum.changeShip',
|
||||||
|
}),
|
||||||
|
RETURN_SHIP: intl.formatMessage({
|
||||||
|
id: intlPrefix + '.column.type.enum.returnShip',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: intlPrefix + '.column.state' }),
|
title: intl.formatMessage({ id: intlPrefix + '.column.state' }),
|
||||||
dataIndex: 'state',
|
dataIndex: 'state',
|
||||||
key: 'state',
|
key: 'state',
|
||||||
valueType: 'select',
|
valueType: 'select',
|
||||||
render: (_, record) => {
|
|
||||||
const stateText = intl.formatMessage({
|
|
||||||
id: `${intlPrefix}.column.state.${record.state?.toLowerCase() || 'unknown'}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
let color = 'default';
|
|
||||||
switch (record.state) {
|
|
||||||
case 'DRAFT':
|
|
||||||
color = 'default';
|
|
||||||
break;
|
|
||||||
case 'WAIT_SHIPMENT':
|
|
||||||
color = 'default';
|
|
||||||
break;
|
|
||||||
case 'WAIT_PAYMENT':
|
|
||||||
color = 'default';
|
|
||||||
break;
|
|
||||||
case 'PARTIAL_PAYMENT':
|
|
||||||
color = 'processing';
|
|
||||||
break;
|
|
||||||
case 'FULL_PAYMENT':
|
|
||||||
color = 'success';
|
|
||||||
break;
|
|
||||||
case 'REJECT_FINISH':
|
|
||||||
color = 'error';
|
|
||||||
break;
|
|
||||||
case 'FINISH':
|
|
||||||
color = 'success';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
color = 'default';
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Tag color={color}>{stateText}</Tag>;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: intl.formatMessage({ id: intlPrefix + '.column.createdAt' }),
|
|
||||||
dataIndex: 'createdAt',
|
|
||||||
key: 'createdAt',
|
|
||||||
valueType: 'dateTime',
|
|
||||||
search: false,
|
search: false,
|
||||||
|
valueEnum: {
|
||||||
|
DRAFT: intl.formatMessage({
|
||||||
|
id: intlPrefix + '.column.state.enum.draft',
|
||||||
|
}),
|
||||||
|
WAIT_SHIPMENT: intl.formatMessage({
|
||||||
|
id: intlPrefix + '.column.state.enum.waitShipment',
|
||||||
|
}),
|
||||||
|
WAIT_PAYMENT: intl.formatMessage({
|
||||||
|
id: intlPrefix + '.column.state.enum.waitPayment',
|
||||||
|
}),
|
||||||
|
PARTIAL_PAYMENT: intl.formatMessage({
|
||||||
|
id: intlPrefix + '.column.state.enum.partialPayment',
|
||||||
|
}),
|
||||||
|
FULL_PAYMENT: intl.formatMessage({
|
||||||
|
id: intlPrefix + '.column.state.enum.fullPayment',
|
||||||
|
}),
|
||||||
|
REJECT_FINISH: intl.formatMessage({
|
||||||
|
id: intlPrefix + '.column.state.enum.rejectFinish',
|
||||||
|
}),
|
||||||
|
FINISH: intl.formatMessage({
|
||||||
|
id: intlPrefix + '.column.state.enum.finish',
|
||||||
|
}),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -210,6 +240,19 @@ export default function OrderShipList(props: IOrderShipListProps) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
columns,
|
columns,
|
||||||
|
convertValue: (orderShip: BusinessAPI.OrderShipVO) => {
|
||||||
|
return {
|
||||||
|
...orderShip,
|
||||||
|
dealerVO: {
|
||||||
|
dealerId: orderShip.dealerId,
|
||||||
|
shortName: orderShip.dealerName,
|
||||||
|
},
|
||||||
|
companyVO: {
|
||||||
|
companyId: orderShip.companyId,
|
||||||
|
shortName: orderShip.companyName,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
create={false}
|
create={false}
|
||||||
update={false}
|
update={false}
|
||||||
377
packages/app-operation/src/components/Order/OrderShipModal.tsx
Normal file
377
packages/app-operation/src/components/Order/OrderShipModal.tsx
Normal file
@ -0,0 +1,377 @@
|
|||||||
|
import { SelectModal } from '@/components';
|
||||||
|
import { business } from '@/services';
|
||||||
|
import { formatParam } from '@/utils/formatParam';
|
||||||
|
import { pagination } from '@/utils/pagination';
|
||||||
|
import { useIntl } from '@@/exports';
|
||||||
|
import {
|
||||||
|
ActionType,
|
||||||
|
LightFilter,
|
||||||
|
ProColumns,
|
||||||
|
ProFormSelect,
|
||||||
|
} from '@ant-design/pro-components';
|
||||||
|
import { Alert, ModalProps, Space, Tag } from 'antd';
|
||||||
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
export interface IOrderShipModalProps extends ModalProps {
|
||||||
|
title: string;
|
||||||
|
selectedList?: BusinessAPI.OrderShipVO[];
|
||||||
|
onFinish: (orderShipVOList: BusinessAPI.OrderShipVO[]) => void;
|
||||||
|
type: 'checkbox' | 'radio' | undefined;
|
||||||
|
params?: BusinessAPI.OrderShipPageQry;
|
||||||
|
num?: number;
|
||||||
|
tips?: string;
|
||||||
|
extraFilter?: React.ReactNode[];
|
||||||
|
extraColumns?: ProColumns<BusinessAPI.OrderShipVO>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function OrderShipModal(props: IOrderShipModalProps) {
|
||||||
|
const {
|
||||||
|
title,
|
||||||
|
onFinish,
|
||||||
|
type,
|
||||||
|
selectedList,
|
||||||
|
params: initParams,
|
||||||
|
num = 10,
|
||||||
|
tips,
|
||||||
|
extraFilter = [],
|
||||||
|
extraColumns: initExtraColumns = [],
|
||||||
|
...rest
|
||||||
|
} = props;
|
||||||
|
const actionRef = useRef<ActionType>();
|
||||||
|
const sessionKey = `orderShipList`;
|
||||||
|
const intl = useIntl();
|
||||||
|
const intlPrefix = 'orderShip';
|
||||||
|
const [params, setParams] = useState<BusinessAPI.OrderShipPageQry>(
|
||||||
|
initParams || {},
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (initParams) {
|
||||||
|
setParams({
|
||||||
|
...params,
|
||||||
|
...initParams,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [initParams]);
|
||||||
|
|
||||||
|
const columns: ProColumns<BusinessAPI.OrderShipVO>[] = [
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: intlPrefix + '.column.orderSn' }),
|
||||||
|
dataIndex: 'orderSn',
|
||||||
|
key: 'orderSn',
|
||||||
|
renderText: (text: string) => <span className="font-medium">{text}</span>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: intlPrefix + '.column.dealerName' }),
|
||||||
|
dataIndex: 'dealerName',
|
||||||
|
key: 'dealerName',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: intlPrefix + '.column.vehicleNo' }),
|
||||||
|
dataIndex: ['orderVO', 'orderVehicle', 'vehicleNo'],
|
||||||
|
key: 'vehicleNo',
|
||||||
|
search: false,
|
||||||
|
render: (_, record) => {
|
||||||
|
return record.orderVO?.orderVehicle?.vehicleNo
|
||||||
|
? '第' + record.orderVO?.orderVehicle?.vehicleNo + '车'
|
||||||
|
: '-';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: intlPrefix + '.column.shippingDate' }),
|
||||||
|
dataIndex: 'shippingDate',
|
||||||
|
key: 'shippingDate',
|
||||||
|
valueType: 'date',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: intlPrefix + '.column.state' }),
|
||||||
|
dataIndex: 'state',
|
||||||
|
key: 'state',
|
||||||
|
valueType: 'select',
|
||||||
|
render: (_, record) => {
|
||||||
|
const stateText = intl.formatMessage({
|
||||||
|
id: `${intlPrefix}.column.state.${record.state?.toLowerCase() || 'unknown'}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
let color = 'default';
|
||||||
|
switch (record.state) {
|
||||||
|
case 'DRAFT':
|
||||||
|
color = 'default';
|
||||||
|
break;
|
||||||
|
case 'WAIT_SHIPMENT':
|
||||||
|
color = 'default';
|
||||||
|
break;
|
||||||
|
case 'WAIT_PAYMENT':
|
||||||
|
color = 'processing';
|
||||||
|
break;
|
||||||
|
case 'PARTIAL_PAYMENT':
|
||||||
|
color = 'processing';
|
||||||
|
break;
|
||||||
|
case 'FULL_PAYMENT':
|
||||||
|
color = 'success';
|
||||||
|
break;
|
||||||
|
case 'REJECT_FINISH':
|
||||||
|
color = 'error';
|
||||||
|
break;
|
||||||
|
case 'FINISH':
|
||||||
|
color = 'success';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
color = 'default';
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Tag color={color}>{stateText}</Tag>;
|
||||||
|
},
|
||||||
|
valueEnum: {
|
||||||
|
DRAFT: {
|
||||||
|
text: intl.formatMessage({
|
||||||
|
id: intlPrefix + '.column.state.draft',
|
||||||
|
}),
|
||||||
|
status: 'default',
|
||||||
|
},
|
||||||
|
WAIT_SHIPMENT: {
|
||||||
|
text: intl.formatMessage({
|
||||||
|
id: intlPrefix + '.column.state.wait_shipment',
|
||||||
|
}),
|
||||||
|
status: 'default',
|
||||||
|
},
|
||||||
|
WAIT_PAYMENT: {
|
||||||
|
text: intl.formatMessage({
|
||||||
|
id: intlPrefix + '.column.state.wait_payment',
|
||||||
|
}),
|
||||||
|
status: 'processing',
|
||||||
|
},
|
||||||
|
PARTIAL_PAYMENT: {
|
||||||
|
text: intl.formatMessage({
|
||||||
|
id: intlPrefix + '.column.state.partial_payment',
|
||||||
|
}),
|
||||||
|
status: 'processing',
|
||||||
|
},
|
||||||
|
FULL_PAYMENT: {
|
||||||
|
text: intl.formatMessage({
|
||||||
|
id: intlPrefix + '.column.state.full_payment',
|
||||||
|
}),
|
||||||
|
status: 'success',
|
||||||
|
},
|
||||||
|
REJECT_FINISH: {
|
||||||
|
text: intl.formatMessage({
|
||||||
|
id: intlPrefix + '.column.state.reject_finish',
|
||||||
|
}),
|
||||||
|
status: 'error',
|
||||||
|
},
|
||||||
|
FINISH: {
|
||||||
|
text: intl.formatMessage({
|
||||||
|
id: intlPrefix + '.column.state.finish',
|
||||||
|
}),
|
||||||
|
status: 'success',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...(initExtraColumns || []),
|
||||||
|
];
|
||||||
|
|
||||||
|
function setOrderShipVOStorage(orderShipVO: BusinessAPI.OrderShipVO) {
|
||||||
|
const localOrderShipList = localStorage.getItem(sessionKey);
|
||||||
|
const orderShipList = localOrderShipList
|
||||||
|
? JSON.parse(localOrderShipList)
|
||||||
|
: [];
|
||||||
|
orderShipList.forEach((item: BusinessAPI.OrderShipVO, index: number) => {
|
||||||
|
if (item.orderShipId === orderShipVO.orderShipId) {
|
||||||
|
orderShipList.splice(index, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (orderShipList.length < 5) {
|
||||||
|
orderShipList.unshift(orderShipVO);
|
||||||
|
localStorage.setItem(sessionKey, JSON.stringify(orderShipList));
|
||||||
|
} else {
|
||||||
|
orderShipList.pop();
|
||||||
|
orderShipList.unshift(orderShipVO);
|
||||||
|
localStorage.setItem(sessionKey, JSON.stringify(orderShipList));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SelectModal<BusinessAPI.OrderShipVO, BusinessAPI.OrderShipPageQry>
|
||||||
|
rowKey={'orderShipId'}
|
||||||
|
modalProps={{
|
||||||
|
title: title || '选择发货单',
|
||||||
|
...rest,
|
||||||
|
destroyOnHidden: true,
|
||||||
|
afterOpenChange: (open) => {
|
||||||
|
if (!open) {
|
||||||
|
setParams({
|
||||||
|
...initParams,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
selectedList={selectedList}
|
||||||
|
tableProps={{
|
||||||
|
rowKey: 'orderShipId',
|
||||||
|
columns: columns,
|
||||||
|
columnsState: {
|
||||||
|
persistenceType: 'sessionStorage',
|
||||||
|
persistenceKey: 'orderShipModalColumnStateKey',
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
...params,
|
||||||
|
},
|
||||||
|
request: async (params, sorter, filter) => {
|
||||||
|
const { data, success, totalCount } =
|
||||||
|
await business.orderShip.pageOrderShip({
|
||||||
|
orderShipPageQry: formatParam<typeof params>(
|
||||||
|
params,
|
||||||
|
sorter,
|
||||||
|
filter,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: data || [],
|
||||||
|
total: totalCount,
|
||||||
|
success,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
pagination: {
|
||||||
|
...pagination(),
|
||||||
|
position: ['bottomRight'],
|
||||||
|
},
|
||||||
|
tableAlertRender: ({ selectedRowKeys, selectedRows }) => {
|
||||||
|
const selectedRowsMap = new Map<string, BusinessAPI.OrderShipVO>();
|
||||||
|
selectedRows.forEach((item: BusinessAPI.OrderShipVO) => {
|
||||||
|
if (item) {
|
||||||
|
if (!selectedRowsMap.has(item.orderShipId)) {
|
||||||
|
selectedRowsMap.set(item.orderShipId, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
selectedList?.forEach((item: BusinessAPI.OrderShipVO) => {
|
||||||
|
if (!selectedRowsMap.has(item.orderShipId)) {
|
||||||
|
selectedRowsMap.set(item.orderShipId, item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let selectedTempList: BusinessAPI.OrderShipVO[] = [];
|
||||||
|
selectedRowsMap.forEach((item: BusinessAPI.OrderShipVO) => {
|
||||||
|
if (selectedRowKeys.includes(item.orderShipId)) {
|
||||||
|
selectedTempList.push(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<Space size={12}>
|
||||||
|
<span>已选 {selectedRowKeys.length} 项</span>
|
||||||
|
<Space wrap={true}>
|
||||||
|
{selectedTempList?.map((item: BusinessAPI.OrderShipVO) => {
|
||||||
|
return (
|
||||||
|
item && <span key={item.orderShipId}>{item.orderSn}</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Space>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
...(tips && {
|
||||||
|
tableExtraRender: () => {
|
||||||
|
return tips && <Alert type={'info'} message={tips} />;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
...(type === 'radio' && {
|
||||||
|
tableExtraRender: () => {
|
||||||
|
const localOrderShipList = localStorage.getItem(sessionKey);
|
||||||
|
if (localOrderShipList) {
|
||||||
|
const orderShipList = JSON.parse(localOrderShipList);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{tips && <Alert type={'info'} message={tips} />}
|
||||||
|
<Space wrap={true} style={{ marginTop: 8 }}>
|
||||||
|
{orderShipList.map((item: BusinessAPI.OrderShipVO) => {
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
// 直接使用 localStorage 中保存的数据
|
||||||
|
onFinish([item]);
|
||||||
|
setOrderShipVOStorage(item);
|
||||||
|
}}
|
||||||
|
key={item.orderShipId}
|
||||||
|
>
|
||||||
|
{item.orderSn}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Space>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
actionRef: actionRef,
|
||||||
|
toolbar: {
|
||||||
|
filter: (
|
||||||
|
<LightFilter
|
||||||
|
onFinish={async (values) => {
|
||||||
|
setParams({
|
||||||
|
...initParams,
|
||||||
|
...values,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{extraFilter}
|
||||||
|
<ProFormSelect
|
||||||
|
label={'发货单状态'}
|
||||||
|
name={'state'}
|
||||||
|
placeholder={'请选择发货单状态'}
|
||||||
|
valueEnum={{
|
||||||
|
DRAFT: intl.formatMessage({
|
||||||
|
id: intlPrefix + '.column.state.draft',
|
||||||
|
}),
|
||||||
|
WAIT_SHIPMENT: intl.formatMessage({
|
||||||
|
id: intlPrefix + '.column.state.wait_shipment',
|
||||||
|
}),
|
||||||
|
WAIT_PAYMENT: intl.formatMessage({
|
||||||
|
id: intlPrefix + '.column.state.wait_payment',
|
||||||
|
}),
|
||||||
|
PARTIAL_PAYMENT: intl.formatMessage({
|
||||||
|
id: intlPrefix + '.column.state.partial_payment',
|
||||||
|
}),
|
||||||
|
FULL_PAYMENT: intl.formatMessage({
|
||||||
|
id: intlPrefix + '.column.state.full_payment',
|
||||||
|
}),
|
||||||
|
FINISH: intl.formatMessage({
|
||||||
|
id: intlPrefix + '.column.state.finish',
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
fieldProps={{
|
||||||
|
showSearch: true,
|
||||||
|
allowClear: true,
|
||||||
|
autoClearSearchValue: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</LightFilter>
|
||||||
|
),
|
||||||
|
search: {
|
||||||
|
placeholder: '请输入发货单编号',
|
||||||
|
onSearch: async (value: string) => {
|
||||||
|
setParams({
|
||||||
|
...params,
|
||||||
|
orderSn: value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onFinish={(orderShipVOList) => {
|
||||||
|
if (type === 'radio') {
|
||||||
|
if (orderShipVOList.length > 0) {
|
||||||
|
setOrderShipVOStorage(orderShipVOList[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onFinish(orderShipVOList);
|
||||||
|
}}
|
||||||
|
num={num}
|
||||||
|
type={type}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -63,8 +63,8 @@ export default function OrderSupplierModal(props: IOrderSupplierModalProps) {
|
|||||||
const columns: ProColumns<BusinessAPI.OrderSupplierVO>[] = [
|
const columns: ProColumns<BusinessAPI.OrderSupplierVO>[] = [
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: intlPrefix + '.column.order' }),
|
title: intl.formatMessage({ id: intlPrefix + '.column.order' }),
|
||||||
dataIndex: ['orderVO', 'orderSn'],
|
dataIndex: 'orderVO',
|
||||||
key: 'orderSn',
|
key: 'orderId',
|
||||||
search: false,
|
search: false,
|
||||||
render: (_, orderSupplierVO: BusinessAPI.OrderSupplierVO) => {
|
render: (_, orderSupplierVO: BusinessAPI.OrderSupplierVO) => {
|
||||||
const orderVO = orderSupplierVO.orderVO;
|
const orderVO = orderSupplierVO.orderVO;
|
||||||
@ -77,7 +77,7 @@ export default function OrderSupplierModal(props: IOrderSupplierModalProps) {
|
|||||||
trigger={() => (
|
trigger={() => (
|
||||||
<Space>
|
<Space>
|
||||||
<a>
|
<a>
|
||||||
{`${orderVO.orderVehicle?.dealerName} - 第 ${orderVO.orderVehicle?.vehicleNo || '暂无'} 车 - ${orderVO.orderSn || '暂无'}`}
|
{`${orderVO.orderVehicle?.dealerName} | 第 ${orderVO.orderVehicle?.vehicleNo || '暂无'} 车 | ${orderVO.orderSn || '暂无'}`}
|
||||||
</a>
|
</a>
|
||||||
</Space>
|
</Space>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -8,5 +8,9 @@ export type { IOrderModalProps } from './OrderModal';
|
|||||||
export { default as OrderRebateList } from './OrderRebateList';
|
export { default as OrderRebateList } from './OrderRebateList';
|
||||||
export { default as OrderSearch } from './OrderSearch';
|
export { default as OrderSearch } from './OrderSearch';
|
||||||
export { default as OrderSelect } from './OrderSelect';
|
export { default as OrderSelect } from './OrderSelect';
|
||||||
|
export { default as OrderShipList } from './OrderShipList';
|
||||||
|
export { default as OrderShipModal } from './OrderShipModal';
|
||||||
|
export type { IOrderShipModalProps } from './OrderShipModal';
|
||||||
export { default as OrderStallList } from './OrderStallList';
|
export { default as OrderStallList } from './OrderStallList';
|
||||||
export { default as OrderSupplierList } from './OrderSupplierList';
|
export { default as OrderSupplierList } from './OrderSupplierList';
|
||||||
|
export { default as OrderSupplierModal } from './OrderSupplierModal';
|
||||||
|
|||||||
@ -23,7 +23,7 @@ export default function OrderSupplierInvoiceList(
|
|||||||
trigger={() => (
|
trigger={() => (
|
||||||
<Space>
|
<Space>
|
||||||
<a>
|
<a>
|
||||||
{`${orderVO.orderVehicle?.dealerName} - 第 ${orderVO.orderVehicle?.vehicleNo || '暂无'} 车 - ${orderVO.orderSn || '暂无'}`}
|
{`${orderVO.orderVehicle?.dealerName} | 第 ${orderVO.orderVehicle?.vehicleNo || '暂无'} 车 | ${orderVO.orderSn || '暂无'}`}
|
||||||
</a>
|
</a>
|
||||||
</Space>
|
</Space>
|
||||||
)}
|
)}
|
||||||
|
|||||||
267
packages/app-operation/src/components/SearchMenu/index.tsx
Normal file
267
packages/app-operation/src/components/SearchMenu/index.tsx
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
import {
|
||||||
|
ClockCircleOutlined,
|
||||||
|
SearchOutlined,
|
||||||
|
StarOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import { MenuDataItem } from '@ant-design/pro-components';
|
||||||
|
import { history } from '@umijs/max';
|
||||||
|
import type { AutoCompleteProps } from 'antd';
|
||||||
|
import { AutoComplete, Input } from 'antd';
|
||||||
|
import React, { useMemo, useState } from 'react';
|
||||||
|
import useSearchMenuStyle from './style.style';
|
||||||
|
|
||||||
|
interface SearchMenuProps {
|
||||||
|
menuData?: MenuDataItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FrequentMenuItem extends MenuDataItem {
|
||||||
|
frequency: number;
|
||||||
|
lastUsed: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 本地存储键 */
|
||||||
|
const STORAGE_KEY = 'erp_frequent_menus';
|
||||||
|
/** 保留常用菜单数量 */
|
||||||
|
const MAX_FREQUENT_MENUS = 8;
|
||||||
|
/** 最低使用频率阈值 */
|
||||||
|
const MIN_FREQUENCY = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 递归扁平化菜单数据,生成搜索列表
|
||||||
|
*/
|
||||||
|
const flattenMenuData = (menuData: MenuDataItem[]): MenuDataItem[] => {
|
||||||
|
const result: MenuDataItem[] = [];
|
||||||
|
|
||||||
|
menuData.forEach((item) => {
|
||||||
|
if (item.hideInMenu) return;
|
||||||
|
|
||||||
|
const currentPath = item.path;
|
||||||
|
|
||||||
|
if (item.name) {
|
||||||
|
result.push({
|
||||||
|
...item,
|
||||||
|
path: currentPath,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.children && item.children.length > 0) {
|
||||||
|
result.push(...flattenMenuData(item.children));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 从本地获取常用菜单 */
|
||||||
|
const getFrequentMenus = (): Record<string, FrequentMenuItem> => {
|
||||||
|
try {
|
||||||
|
const data = localStorage.getItem(STORAGE_KEY);
|
||||||
|
return data ? JSON.parse(data) : {};
|
||||||
|
} catch {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 保存常用菜单到本地 */
|
||||||
|
const saveFrequentMenus = (menus: Record<string, FrequentMenuItem>) => {
|
||||||
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(menus));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录菜单使用
|
||||||
|
*/
|
||||||
|
const recordMenuUsage = (path: string, name?: string) => {
|
||||||
|
if (!path) return;
|
||||||
|
|
||||||
|
const frequentMenus = getFrequentMenus();
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
if (frequentMenus[path]) {
|
||||||
|
frequentMenus[path].frequency += 1;
|
||||||
|
frequentMenus[path].lastUsed = now;
|
||||||
|
} else {
|
||||||
|
frequentMenus[path] = {
|
||||||
|
path,
|
||||||
|
name: name || '',
|
||||||
|
frequency: 1,
|
||||||
|
lastUsed: now,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
saveFrequentMenus(frequentMenus);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SearchMenu: React.FC<SearchMenuProps> = ({ menuData = [] }) => {
|
||||||
|
const { styles } = useSearchMenuStyle();
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [searchValue, setSearchValue] = useState('');
|
||||||
|
|
||||||
|
// 扁平化菜单数据用于搜索
|
||||||
|
const flatMenus = useMemo(() => flattenMenuData(menuData), [menuData]);
|
||||||
|
|
||||||
|
// 根据搜索关键词过滤菜单
|
||||||
|
const searchResults = useMemo(() => {
|
||||||
|
if (!searchValue.trim()) return [];
|
||||||
|
|
||||||
|
const keyword = searchValue.toLowerCase();
|
||||||
|
return flatMenus.filter(
|
||||||
|
(item) =>
|
||||||
|
item.name?.toLowerCase().includes(keyword) ||
|
||||||
|
item.path?.toLowerCase().includes(keyword),
|
||||||
|
);
|
||||||
|
}, [flatMenus, searchValue]);
|
||||||
|
|
||||||
|
// 获取常用菜单(按频率和使用时间排序)
|
||||||
|
const frequentMenus = useMemo(() => {
|
||||||
|
const frequentData = getFrequentMenus();
|
||||||
|
|
||||||
|
// 将常用菜单与扁平化菜单合并,获取完整信息
|
||||||
|
const mergedMenus = flatMenus
|
||||||
|
.map((item) => {
|
||||||
|
const freq = frequentData[item.path!];
|
||||||
|
return freq
|
||||||
|
? {
|
||||||
|
...item,
|
||||||
|
frequency: freq.frequency,
|
||||||
|
lastUsed: freq.lastUsed,
|
||||||
|
}
|
||||||
|
: { ...item, frequency: 0, lastUsed: 0 };
|
||||||
|
})
|
||||||
|
.filter((item) => item.frequency >= MIN_FREQUENCY);
|
||||||
|
|
||||||
|
// 按频率降序,使用时间降序排序
|
||||||
|
return mergedMenus
|
||||||
|
.sort((a, b) => {
|
||||||
|
if (b.frequency !== a.frequency) {
|
||||||
|
return b.frequency - a.frequency;
|
||||||
|
}
|
||||||
|
return b.lastUsed - a.lastUsed;
|
||||||
|
})
|
||||||
|
.slice(0, MAX_FREQUENT_MENUS);
|
||||||
|
}, [flatMenus]);
|
||||||
|
|
||||||
|
// 处理菜单点击
|
||||||
|
const handleMenuClick = (path: string, name?: string) => {
|
||||||
|
if (path) {
|
||||||
|
recordMenuUsage(path, name);
|
||||||
|
history.push(path);
|
||||||
|
setOpen(false);
|
||||||
|
setSearchValue('');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 生成 AutoComplete 的 options
|
||||||
|
const options: AutoCompleteProps['options'] = useMemo(() => {
|
||||||
|
// 如果有搜索关键词,显示搜索结果
|
||||||
|
if (searchValue.trim()) {
|
||||||
|
return searchResults.map((item) => ({
|
||||||
|
value: item.path,
|
||||||
|
label: (
|
||||||
|
<div
|
||||||
|
className={styles.searchItem}
|
||||||
|
onClick={() => handleMenuClick(item.path!, item.name)}
|
||||||
|
>
|
||||||
|
<span className={styles.menuName}>{item.name}</span>
|
||||||
|
<span className={styles.menuPath}>{item.path}</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有搜索关键词,显示常用菜单
|
||||||
|
if (frequentMenus.length > 0) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
value: 'frequent-header',
|
||||||
|
label: (
|
||||||
|
<div className={styles.sectionHeader}>
|
||||||
|
<StarOutlined className={styles.sectionIcon} />
|
||||||
|
<span>常用菜单</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
...frequentMenus.map((item) => ({
|
||||||
|
value: item.path,
|
||||||
|
label: (
|
||||||
|
<div
|
||||||
|
className={styles.searchItem}
|
||||||
|
onClick={() => handleMenuClick(item.path!, item.name)}
|
||||||
|
>
|
||||||
|
<div className={styles.menuRow}>
|
||||||
|
<span className={styles.menuName}>{item.name}</span>
|
||||||
|
<span className={styles.frequency}>{item.frequency} 次</span>
|
||||||
|
</div>
|
||||||
|
<span className={styles.menuPath}>{item.path}</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 没有常用菜单时显示提示
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
value: 'empty-header',
|
||||||
|
label: (
|
||||||
|
<div className={styles.sectionHeader}>
|
||||||
|
<ClockCircleOutlined className={styles.sectionIcon} />
|
||||||
|
<span>暂无常用菜单</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'empty-tip',
|
||||||
|
label: (
|
||||||
|
<div className={styles.emptyTip}>点击菜单后会自动记录使用频率</div>
|
||||||
|
),
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}, [searchResults, searchValue, frequentMenus, styles]);
|
||||||
|
|
||||||
|
// 处理搜索值变化
|
||||||
|
const handleSearch: AutoCompleteProps['onSearch'] = (value) => {
|
||||||
|
setSearchValue(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理下拉框显示状态变化
|
||||||
|
const handleDropdownOpen = (open: boolean) => {
|
||||||
|
setOpen(open);
|
||||||
|
if (!open) {
|
||||||
|
setSearchValue('');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理选中选项
|
||||||
|
const handleSelect: AutoCompleteProps['onSelect'] = (value) => {
|
||||||
|
const selectedMenu = flatMenus.find((item) => item.path === value);
|
||||||
|
handleMenuClick(value as string, selectedMenu?.name);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.searchMenuContainer}>
|
||||||
|
<AutoComplete
|
||||||
|
open={open}
|
||||||
|
onDropdownVisibleChange={handleDropdownOpen}
|
||||||
|
onSearch={handleSearch}
|
||||||
|
onSelect={handleSelect}
|
||||||
|
options={options}
|
||||||
|
dropdownClassName={styles.dropdown}
|
||||||
|
dropdownMatchSelectWidth={320}
|
||||||
|
backfill
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
placeholder="搜索菜单..."
|
||||||
|
prefix={<SearchOutlined className={styles.searchIcon} />}
|
||||||
|
allowClear
|
||||||
|
className={styles.searchInput}
|
||||||
|
onClick={() => setOpen(true)}
|
||||||
|
/>
|
||||||
|
</AutoComplete>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SearchMenu;
|
||||||
100
packages/app-operation/src/components/SearchMenu/style.style.ts
Normal file
100
packages/app-operation/src/components/SearchMenu/style.style.ts
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import { createStyles } from 'antd-style';
|
||||||
|
|
||||||
|
const useSearchMenuStyle = createStyles(({ token }) => {
|
||||||
|
return {
|
||||||
|
searchMenuContainer: {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginRight: token.marginLG,
|
||||||
|
},
|
||||||
|
searchInput: {
|
||||||
|
width: '200px',
|
||||||
|
borderRadius: token.borderRadiusLG,
|
||||||
|
backgroundColor: token.colorBgLayout,
|
||||||
|
border: 'none',
|
||||||
|
transition: `width ${token.motionDurationSlow}, background-color ${token.motionDurationSlow}`,
|
||||||
|
|
||||||
|
'&:hover, &:focus': {
|
||||||
|
backgroundColor: token.colorBgElevated,
|
||||||
|
width: '280px',
|
||||||
|
},
|
||||||
|
|
||||||
|
'.anticon': {
|
||||||
|
color: token.colorTextPlaceholder,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
searchIcon: {
|
||||||
|
color: token.colorTextPlaceholder,
|
||||||
|
},
|
||||||
|
dropdown: {
|
||||||
|
padding: `${token.paddingXS}px ${token.paddingXXS}px`,
|
||||||
|
},
|
||||||
|
sectionHeader: {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: token.paddingXS,
|
||||||
|
padding: `${token.paddingXXS}px ${token.paddingXS}px`,
|
||||||
|
fontSize: token.fontSizeSM,
|
||||||
|
fontWeight: token.fontWeightMedium,
|
||||||
|
color: token.colorTextSecondary,
|
||||||
|
borderBottom: `1px solid ${token.colorBorderSecondary}`,
|
||||||
|
marginBottom: token.marginXXS,
|
||||||
|
},
|
||||||
|
sectionIcon: {
|
||||||
|
fontSize: token.fontSize,
|
||||||
|
},
|
||||||
|
searchItem: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
padding: `${token.paddingXXS}px ${token.paddingXS}px`,
|
||||||
|
borderRadius: token.borderRadiusSM,
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: `background-color ${token.motionDurationFast}`,
|
||||||
|
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: token.colorBgTextHover,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
menuRow: {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
gap: token.paddingSM,
|
||||||
|
},
|
||||||
|
menuName: {
|
||||||
|
fontSize: token.fontSize,
|
||||||
|
color: token.colorText,
|
||||||
|
fontWeight: token.fontWeightMedium,
|
||||||
|
lineHeight: token.lineHeight,
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
menuPath: {
|
||||||
|
fontSize: token.fontSizeSM,
|
||||||
|
color: token.colorTextSecondary,
|
||||||
|
marginTop: token.marginXXS,
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
maxWidth: '260px',
|
||||||
|
},
|
||||||
|
frequency: {
|
||||||
|
fontSize: token.fontSizeSM,
|
||||||
|
color: token.colorTextTertiary,
|
||||||
|
backgroundColor: token.colorBgLayout,
|
||||||
|
padding: `2px ${token.paddingXXS}px`,
|
||||||
|
borderRadius: token.borderRadiusSM,
|
||||||
|
flexShrink: 0,
|
||||||
|
},
|
||||||
|
emptyTip: {
|
||||||
|
padding: `${token.paddingSM}px ${token.paddingLG}px`,
|
||||||
|
fontSize: token.fontSizeSM,
|
||||||
|
color: token.colorTextTertiary,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export default useSearchMenuStyle;
|
||||||
@ -137,7 +137,7 @@ export default function SupplierInvoiceList(props: ISupplierInvoiceListProps) {
|
|||||||
trigger={() => (
|
trigger={() => (
|
||||||
<Space>
|
<Space>
|
||||||
<a>
|
<a>
|
||||||
{`${item?.dealerName} - 第 ${item?.vehicleNo || '暂无'} 车`}
|
{`${item?.dealerName} | 第 ${item?.vehicleNo || '暂无'} 车`}
|
||||||
</a>
|
</a>
|
||||||
</Space>
|
</Space>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -11,7 +11,6 @@ export { default as CaptchaModal } from './CaptchaModal';
|
|||||||
export * from './Channel';
|
export * from './Channel';
|
||||||
export * from './Company';
|
export * from './Company';
|
||||||
export * from './Dealer';
|
export * from './Dealer';
|
||||||
export * from './Delivery';
|
|
||||||
export * from './Editor';
|
export * from './Editor';
|
||||||
export * from './Employee';
|
export * from './Employee';
|
||||||
export * from './Expense';
|
export * from './Expense';
|
||||||
@ -30,6 +29,7 @@ export * from './Permission';
|
|||||||
export * from './Platform';
|
export * from './Platform';
|
||||||
export * from './Remark';
|
export * from './Remark';
|
||||||
export * from './Role';
|
export * from './Role';
|
||||||
|
export { default as SearchMenu } from './SearchMenu';
|
||||||
export * from './Setting';
|
export * from './Setting';
|
||||||
export * from './Supplier';
|
export * from './Supplier';
|
||||||
export { default as UploadMaterial } from './UploadMaterial';
|
export { default as UploadMaterial } from './UploadMaterial';
|
||||||
|
|||||||
@ -2110,17 +2110,27 @@ export default {
|
|||||||
},
|
},
|
||||||
column: {
|
column: {
|
||||||
orderSn: '发货单编号',
|
orderSn: '发货单编号',
|
||||||
dealerName: '经销商名称',
|
order: '采购单',
|
||||||
vehicleNo: '车次号',
|
dealer: '经销商',
|
||||||
|
shippingAddress: '发货地',
|
||||||
|
receivingAddress: '收货地',
|
||||||
shippingDate: '发货日期',
|
shippingDate: '发货日期',
|
||||||
|
estimatedArrivalDate: '预计到仓时间',
|
||||||
|
watermelonGrade: '西瓜品级',
|
||||||
|
company: '入账公司',
|
||||||
|
type: '发货单类型',
|
||||||
|
'type.enum.purchaseShip': '采购发货单',
|
||||||
|
'type.enum.transferShip': '调货发货',
|
||||||
|
'type.enum.changeShip': '改签发货',
|
||||||
|
'type.enum.returnShip': '退货发货',
|
||||||
state: '状态',
|
state: '状态',
|
||||||
'state.draft': '草稿',
|
'state.enum.draft': '草稿',
|
||||||
'state.wait_shipment': '待发货',
|
'state.enum.waitShipment': '待发货',
|
||||||
'state.wait_payment': '待回款',
|
'state.enum.waitPayment': '待回款',
|
||||||
'state.partial_payment': '部分回款',
|
'state.enum.partialPayment': '部分回款',
|
||||||
'state.full_payment': '已回款',
|
'state.enum.fullPayment': '已回款',
|
||||||
'state.reject_finish': '拒收完结',
|
'state.enum.rejectFinish': '拒收完结',
|
||||||
'state.finish': '已完结',
|
'state.enum.finish': '已完结',
|
||||||
createdAt: '创建时间',
|
createdAt: '创建时间',
|
||||||
option: '操作',
|
option: '操作',
|
||||||
},
|
},
|
||||||
|
|||||||
5
packages/app-operation/src/pages/OrderShip.tsx
Normal file
5
packages/app-operation/src/pages/OrderShip.tsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { OrderShipList } from '@/components';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <OrderShipList />;
|
||||||
|
}
|
||||||
@ -1,91 +1,394 @@
|
|||||||
import {
|
import { DealerSelect, PageContainer } from '@/components';
|
||||||
DealerSelect,
|
import OrderShipModal from '@/components/Order/OrderShipModal';
|
||||||
PageContainer,
|
import { formatCurrency } from '@/utils/format';
|
||||||
OrderList,
|
|
||||||
ShipOrderList,
|
|
||||||
} from '@/components';
|
|
||||||
import {
|
import {
|
||||||
ProCard,
|
ProCard,
|
||||||
ProForm,
|
ProForm,
|
||||||
ProFormDependency,
|
ProFormDependency,
|
||||||
|
ProFormTextArea,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { Space, Steps } from 'antd';
|
import { Button, Col, Row, Space, Table, Tag } from 'antd';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const [current, setCurrent] = useState(0);
|
const [current, setCurrent] = useState(0);
|
||||||
|
|
||||||
const onChange = (value: number) => {
|
const [selectedDealer, setSelectedDealer] =
|
||||||
console.log('onChange:', value);
|
useState<BusinessAPI.DealerVO | null>(null);
|
||||||
setCurrent(value);
|
const [selectedShipOrderList, setSelectedShipOrderList] = useState<
|
||||||
};
|
BusinessAPI.OrderShipVO[]
|
||||||
|
>([]);
|
||||||
|
const [orderShipModalOpen, setOrderShipModalOpen] = useState(false);
|
||||||
|
|
||||||
const [dealerVO, setDealerVO] = useState<BusinessAPI.DealerVO>();
|
// 计算总金额
|
||||||
|
const totalAmount = selectedShipOrderList.reduce((sum, order) => {
|
||||||
|
// 使用发货单明细的总金额
|
||||||
|
const orderTotal =
|
||||||
|
order.orderShipItemList?.reduce(
|
||||||
|
(total, item) => total + (item.totalAmount || 0),
|
||||||
|
0,
|
||||||
|
) || 0;
|
||||||
|
return sum + orderTotal;
|
||||||
|
}, 0);
|
||||||
|
const orderCount = selectedShipOrderList.length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer
|
<PageContainer
|
||||||
permission={''}
|
permission={''}
|
||||||
fieldProps={{
|
fieldProps={{
|
||||||
content: (
|
content: (
|
||||||
<Steps
|
<ProCard>
|
||||||
current={current}
|
<Space
|
||||||
onChange={onChange}
|
style={{ width: '100%', justifyContent: 'center' }}
|
||||||
items={[
|
size={16}
|
||||||
{
|
>
|
||||||
title: '步骤一',
|
<Space
|
||||||
subTitle: '选择客户与车次',
|
size={4}
|
||||||
},
|
style={{
|
||||||
{
|
color: current === 0 ? '#1890ff' : '#999',
|
||||||
title: '步骤二',
|
}}
|
||||||
subTitle: '核对并录入调整项',
|
>
|
||||||
},
|
<span
|
||||||
]}
|
style={{
|
||||||
></Steps>
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: current === 0 ? '#1890ff' : '#f0f0f0',
|
||||||
|
color: current === 0 ? '#fff' : '#999',
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
1
|
||||||
|
</span>
|
||||||
|
<span style={{ fontWeight: current === 0 ? 500 : 400 }}>
|
||||||
|
选择经销商与车次
|
||||||
|
</span>
|
||||||
|
</Space>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
color: current === 1 ? '#1890ff' : '#d9d9d9',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
→
|
||||||
|
</span>
|
||||||
|
<Space
|
||||||
|
size={4}
|
||||||
|
style={{
|
||||||
|
color: current === 1 ? '#1890ff' : '#999',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: current === 1 ? '#1890ff' : '#f0f0f0',
|
||||||
|
color: current === 1 ? '#fff' : '#999',
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
2
|
||||||
|
</span>
|
||||||
|
<span style={{ fontWeight: current === 1 ? 500 : 400 }}>
|
||||||
|
核对并录入调整项
|
||||||
|
</span>
|
||||||
|
</Space>
|
||||||
|
</Space>
|
||||||
|
</ProCard>
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{current === 0 && (
|
{/* 对账单表单 */}
|
||||||
<ProCard
|
<ProCard
|
||||||
title={
|
title={'创建对账单'}
|
||||||
|
headerBordered
|
||||||
|
subTitle={
|
||||||
<Space>
|
<Space>
|
||||||
选择客户与车次
|
<Tag color="blue">步骤 {current + 1}</Tag>
|
||||||
<ProForm
|
{current === 0 ? '选择经销商与车次' : '核对并录入调整项'}
|
||||||
submitter={false}
|
</Space>
|
||||||
onFinish={(formData) => {
|
}
|
||||||
setDealerVO(formData.dealerVO);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
|
<ProForm submitter={false}>
|
||||||
<ProFormDependency name={['dealerId', 'dealerVO']}>
|
<ProFormDependency name={['dealerId', 'dealerVO']}>
|
||||||
{(_, form) => (
|
{(_, form) => (
|
||||||
<DealerSelect
|
<DealerSelect
|
||||||
noStyle={true}
|
noStyle={true}
|
||||||
key={'dealerId'}
|
key={'dealerId'}
|
||||||
onFinish={() => {
|
onFinish={(dealerVOList: BusinessAPI.DealerVO[]) => {
|
||||||
|
setSelectedDealer(dealerVOList[0]);
|
||||||
|
// 切换经销商时清空已选车次
|
||||||
|
setSelectedShipOrderList([]);
|
||||||
form.submit();
|
form.submit();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</ProFormDependency>
|
</ProFormDependency>
|
||||||
</ProForm>
|
</ProForm>
|
||||||
</Space>
|
{current === 0 && selectedDealer && (
|
||||||
}
|
<>
|
||||||
headerBordered={true}
|
{/* 经销商信息 */}
|
||||||
direction={'column'}
|
<ProCard title={'经销商信息'} style={{ marginTop: 16 }} bordered>
|
||||||
|
<Row gutter={[16, 16]}>
|
||||||
|
<Col span={8}>
|
||||||
|
<div>
|
||||||
|
<div style={{ color: '#999', marginBottom: 4 }}>
|
||||||
|
经销商简称
|
||||||
|
</div>
|
||||||
|
<div style={{ fontWeight: 'bold' }}>
|
||||||
|
{selectedDealer.shortName || '-'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<div>
|
||||||
|
<div style={{ color: '#999', marginBottom: 4 }}>
|
||||||
|
经销商全称
|
||||||
|
</div>
|
||||||
|
<div style={{ fontWeight: 'bold' }}>
|
||||||
|
{selectedDealer.fullName || '-'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<div>
|
||||||
|
<div style={{ color: '#999', marginBottom: 4 }}>
|
||||||
|
经销商类型
|
||||||
|
</div>
|
||||||
|
<div style={{ fontWeight: 'bold' }}>
|
||||||
|
{selectedDealer.dealerType === 'MARKET' ? '市场' : '超市'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</ProCard>
|
||||||
|
|
||||||
|
{/* 选择车次 */}
|
||||||
|
<ProCard
|
||||||
|
title={'选择车次'}
|
||||||
|
style={{ marginTop: 16 }}
|
||||||
|
bordered
|
||||||
|
extra={
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
size="middle"
|
||||||
|
onClick={() => setOrderShipModalOpen(true)}
|
||||||
>
|
>
|
||||||
{dealerVO && (
|
添加车次
|
||||||
<ShipOrderList
|
</Button>
|
||||||
ghost={true}
|
}
|
||||||
mode={'page'}
|
>
|
||||||
params={{
|
{selectedShipOrderList.length > 0 ? (
|
||||||
dealerId: dealerVO.dealerId,
|
<Table<BusinessAPI.OrderShipVO>
|
||||||
|
rowKey="orderShipId"
|
||||||
|
dataSource={selectedShipOrderList}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: '发货单号',
|
||||||
|
dataIndex: 'orderSn',
|
||||||
|
key: 'orderSn',
|
||||||
|
render: (text: string) => (
|
||||||
|
<span className="font-medium">{text}</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '车辆编号',
|
||||||
|
key: 'vehicleNo',
|
||||||
|
render: (_, record: BusinessAPI.OrderShipVO) => {
|
||||||
|
return record.orderVO?.orderVehicle?.vehicleNo
|
||||||
|
? `第${record.orderVO?.orderVehicle?.vehicleNo}车`
|
||||||
|
: '-';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '发货日期',
|
||||||
|
dataIndex: 'shippingDate',
|
||||||
|
key: 'shippingDate',
|
||||||
|
render: (_, record: BusinessAPI.OrderShipVO) => {
|
||||||
|
return record.shippingDate || '-';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '总金额',
|
||||||
|
key: 'totalAmount',
|
||||||
|
render: (_, record: BusinessAPI.OrderShipVO) => {
|
||||||
|
const total =
|
||||||
|
record.orderShipItemList?.reduce(
|
||||||
|
(sum, item) => sum + (item.totalAmount || 0),
|
||||||
|
0,
|
||||||
|
) || 0;
|
||||||
|
return <span>{formatCurrency(total)}</span>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
render: (_, record: BusinessAPI.OrderShipVO) => (
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
danger
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedShipOrderList(
|
||||||
|
selectedShipOrderList.filter(
|
||||||
|
(item) =>
|
||||||
|
item.orderShipId !== record.orderShipId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
移除
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
pagination={false}
|
||||||
|
size="small"
|
||||||
|
summary={(pageData) => {
|
||||||
|
let totalAmount = 0;
|
||||||
|
pageData.forEach((record) => {
|
||||||
|
const total =
|
||||||
|
record.orderShipItemList?.reduce(
|
||||||
|
(sum, item) => sum + (item.totalAmount || 0),
|
||||||
|
0,
|
||||||
|
) || 0;
|
||||||
|
totalAmount += total;
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<Table.Summary fixed>
|
||||||
|
<Table.Summary.Row>
|
||||||
|
<Table.Summary.Cell index={0} colSpan={3}>
|
||||||
|
<strong>合计</strong>
|
||||||
|
</Table.Summary.Cell>
|
||||||
|
<Table.Summary.Cell index={1}>
|
||||||
|
<strong style={{ color: '#ff4d4f' }}>
|
||||||
|
{formatCurrency(totalAmount)}
|
||||||
|
</strong>
|
||||||
|
</Table.Summary.Cell>
|
||||||
|
<Table.Summary.Cell index={2} />
|
||||||
|
</Table.Summary.Row>
|
||||||
|
</Table.Summary>
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
textAlign: 'center',
|
||||||
|
padding: '40px 0',
|
||||||
|
color: '#999',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
暂无数据,请点击「添加车次」按钮添加
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</ProCard>
|
</ProCard>
|
||||||
|
|
||||||
|
{/* 任务摘要 */}
|
||||||
|
{selectedShipOrderList.length > 0 && (
|
||||||
|
<ProCard title={'对账摘要'} style={{ marginTop: 16 }} bordered>
|
||||||
|
<Row gutter={[16, 16]}>
|
||||||
|
<Col span={6}>
|
||||||
|
<div>
|
||||||
|
<div style={{ color: '#999', marginBottom: 4 }}>
|
||||||
|
对账经销商
|
||||||
|
</div>
|
||||||
|
<div style={{ fontWeight: 'bold' }}>
|
||||||
|
{selectedDealer?.fullName || selectedDealer?.shortName}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<div>
|
||||||
|
<div style={{ color: '#999', marginBottom: 4 }}>
|
||||||
|
车次数量
|
||||||
|
</div>
|
||||||
|
<div style={{ fontWeight: 'bold' }}>{orderCount} 车</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<div>
|
||||||
|
<div style={{ color: '#999', marginBottom: 4 }}>
|
||||||
|
对账总额
|
||||||
|
</div>
|
||||||
|
<div style={{ fontWeight: 'bold', color: '#ff4d4f' }}>
|
||||||
|
{formatCurrency(totalAmount)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<div>
|
||||||
|
<div style={{ color: '#999', marginBottom: 4 }}>
|
||||||
|
对账状态
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Tag color="orange">待对账</Tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</ProCard>
|
||||||
)}
|
)}
|
||||||
{current === 1 && (
|
|
||||||
<ProCard title={'核对并录入调整项'} headerBordered={true}></ProCard>
|
{/* 下一步按钮 */}
|
||||||
|
{selectedShipOrderList.length > 0 && (
|
||||||
|
<div style={{ marginTop: 16, textAlign: 'right' }}>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
setCurrent(1);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
下一步:核对并录入调整项
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{current === 0 && (
|
||||||
|
<>
|
||||||
|
<ProCard
|
||||||
|
title={'核对并录入调整项'}
|
||||||
|
style={{ marginTop: 16 }}
|
||||||
|
bordered
|
||||||
|
>
|
||||||
|
<ProFormTextArea
|
||||||
|
name="remark"
|
||||||
|
label="备注"
|
||||||
|
placeholder="请输入备注信息"
|
||||||
|
/>
|
||||||
|
</ProCard>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</ProCard>
|
||||||
|
{/* 选择发货单模态框 */}
|
||||||
|
<OrderShipModal
|
||||||
|
open={orderShipModalOpen}
|
||||||
|
title="选择需要对账的发货单"
|
||||||
|
type="checkbox"
|
||||||
|
selectedList={selectedShipOrderList}
|
||||||
|
onFinish={(orderShipList) => {
|
||||||
|
// 过滤掉已经选择的发货单
|
||||||
|
const newOrders = orderShipList.filter(
|
||||||
|
(item) =>
|
||||||
|
!selectedShipOrderList.some(
|
||||||
|
(selected) => selected.orderShipId === item.orderShipId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
setSelectedShipOrderList([...selectedShipOrderList, ...newOrders]);
|
||||||
|
setOrderShipModalOpen(false);
|
||||||
|
}}
|
||||||
|
onCancel={() => setOrderShipModalOpen(false)}
|
||||||
|
params={{
|
||||||
|
dealerId: selectedDealer?.dealerId,
|
||||||
|
}}
|
||||||
|
tips="请选择该经销商未对账的发货单"
|
||||||
|
num={999}
|
||||||
|
/>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +0,0 @@
|
|||||||
import { ShipOrderList } from '@/components';
|
|
||||||
|
|
||||||
export default function Page() {
|
|
||||||
return <ShipOrderList />;
|
|
||||||
}
|
|
||||||
@ -100,7 +100,7 @@ declare namespace AuthAPI {
|
|||||||
/** 用户ID */
|
/** 用户ID */
|
||||||
userId: string;
|
userId: string;
|
||||||
/** 角色ID */
|
/** 角色ID */
|
||||||
roleIdList: number[];
|
roleIdList: string[];
|
||||||
/** 角色信息 */
|
/** 角色信息 */
|
||||||
userRoleList?: UserRoleVO[];
|
userRoleList?: UserRoleVO[];
|
||||||
};
|
};
|
||||||
@ -195,7 +195,7 @@ declare namespace AuthAPI {
|
|||||||
|
|
||||||
type RoleMenuTreeQry = {
|
type RoleMenuTreeQry = {
|
||||||
/** 角色权限 */
|
/** 角色权限 */
|
||||||
roleId?: number[];
|
roleId?: string[];
|
||||||
/** 平台ID */
|
/** 平台ID */
|
||||||
platformId: string;
|
platformId: string;
|
||||||
};
|
};
|
||||||
@ -329,7 +329,7 @@ declare namespace AuthAPI {
|
|||||||
/** 备注 */
|
/** 备注 */
|
||||||
remark?: string;
|
remark?: string;
|
||||||
/** 客户标签 */
|
/** 客户标签 */
|
||||||
labelId?: number[];
|
labelId?: string[];
|
||||||
/** 用户ID */
|
/** 用户ID */
|
||||||
userId: string;
|
userId: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -208,7 +208,7 @@ declare namespace BusinessAPI {
|
|||||||
/** 品牌图片URL */
|
/** 品牌图片URL */
|
||||||
image?: string;
|
image?: string;
|
||||||
/** 纸箱规格ID */
|
/** 纸箱规格ID */
|
||||||
specIds?: number[];
|
specIds?: string[];
|
||||||
/** 备注 */
|
/** 备注 */
|
||||||
remark?: string;
|
remark?: string;
|
||||||
/** 状态:1_启用;0_禁用 */
|
/** 状态:1_启用;0_禁用 */
|
||||||
@ -279,7 +279,7 @@ declare namespace BusinessAPI {
|
|||||||
/** 品牌图片URL */
|
/** 品牌图片URL */
|
||||||
image?: string;
|
image?: string;
|
||||||
/** 纸箱规格ID */
|
/** 纸箱规格ID */
|
||||||
specIds?: number[];
|
specIds?: string[];
|
||||||
/** 备注 */
|
/** 备注 */
|
||||||
remark?: string;
|
remark?: string;
|
||||||
/** 状态:1_启用;0_禁用 */
|
/** 状态:1_启用;0_禁用 */
|
||||||
@ -296,7 +296,7 @@ declare namespace BusinessAPI {
|
|||||||
/** 品牌图片URL */
|
/** 品牌图片URL */
|
||||||
image?: string;
|
image?: string;
|
||||||
/** 纸箱规格ID */
|
/** 纸箱规格ID */
|
||||||
specIds?: number[];
|
specIds?: string[];
|
||||||
/** 备注 */
|
/** 备注 */
|
||||||
remark?: string;
|
remark?: string;
|
||||||
/** 状态:1_启用;0_禁用 */
|
/** 状态:1_启用;0_禁用 */
|
||||||
@ -1021,7 +1021,7 @@ declare namespace BusinessAPI {
|
|||||||
/** 状态:1_启用;0_禁用 */
|
/** 状态:1_启用;0_禁用 */
|
||||||
status: boolean;
|
status: boolean;
|
||||||
/** 成本项ID */
|
/** 成本项ID */
|
||||||
costItemIds?: number[];
|
costItemIds?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type CostDestroyCmd = {
|
type CostDestroyCmd = {
|
||||||
@ -1249,7 +1249,7 @@ declare namespace BusinessAPI {
|
|||||||
/** 状态:1_启用;0_禁用 */
|
/** 状态:1_启用;0_禁用 */
|
||||||
status: boolean;
|
status: boolean;
|
||||||
/** 成本项ID */
|
/** 成本项ID */
|
||||||
costItemIds?: number[];
|
costItemIds?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type CostVO = {
|
type CostVO = {
|
||||||
@ -1280,7 +1280,7 @@ declare namespace BusinessAPI {
|
|||||||
/** 状态:1_启用;0_禁用 */
|
/** 状态:1_启用;0_禁用 */
|
||||||
status: boolean;
|
status: boolean;
|
||||||
/** 项目id集合 */
|
/** 项目id集合 */
|
||||||
costItemIds?: number[];
|
costItemIds?: string[];
|
||||||
/** 创建时间 */
|
/** 创建时间 */
|
||||||
createdAt?: string;
|
createdAt?: string;
|
||||||
/** 项目列表 */
|
/** 项目列表 */
|
||||||
@ -1961,7 +1961,7 @@ declare namespace BusinessAPI {
|
|||||||
/** 登录密码 */
|
/** 登录密码 */
|
||||||
password: string;
|
password: string;
|
||||||
/** 角色ID */
|
/** 角色ID */
|
||||||
roleId: number[];
|
roleId: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type EmployeeDestroyCmd = {
|
type EmployeeDestroyCmd = {
|
||||||
@ -2054,7 +2054,7 @@ declare namespace BusinessAPI {
|
|||||||
/** 用户ID */
|
/** 用户ID */
|
||||||
userId: string;
|
userId: string;
|
||||||
/** 角色ID */
|
/** 角色ID */
|
||||||
roleIdList: number[];
|
roleIdList: string[];
|
||||||
/** 角色信息 */
|
/** 角色信息 */
|
||||||
userRoleList?: UserRoleVO[];
|
userRoleList?: UserRoleVO[];
|
||||||
};
|
};
|
||||||
@ -2487,7 +2487,7 @@ declare namespace BusinessAPI {
|
|||||||
/** 平台id */
|
/** 平台id */
|
||||||
platformId: string;
|
platformId: string;
|
||||||
/** 角色Id */
|
/** 角色Id */
|
||||||
roleId?: number[];
|
roleId?: string[];
|
||||||
/** 是否隐藏 */
|
/** 是否隐藏 */
|
||||||
hideInMenu?: boolean;
|
hideInMenu?: boolean;
|
||||||
/** 权限Id */
|
/** 权限Id */
|
||||||
@ -2560,7 +2560,7 @@ declare namespace BusinessAPI {
|
|||||||
/** 平台id */
|
/** 平台id */
|
||||||
platformId: string;
|
platformId: string;
|
||||||
/** 角色Id */
|
/** 角色Id */
|
||||||
roleId?: number[];
|
roleId?: string[];
|
||||||
/** 是否隐藏 */
|
/** 是否隐藏 */
|
||||||
hideInMenu?: boolean;
|
hideInMenu?: boolean;
|
||||||
/** 权限Id */
|
/** 权限Id */
|
||||||
@ -2978,7 +2978,7 @@ declare namespace BusinessAPI {
|
|||||||
| 'LOGISTICS_TYPE'
|
| 'LOGISTICS_TYPE'
|
||||||
| 'EXPENSE_TYPE';
|
| 'EXPENSE_TYPE';
|
||||||
/** 关联项目id */
|
/** 关联项目id */
|
||||||
costItemIds?: number[];
|
costItemIds?: string[];
|
||||||
/** 是否选中 */
|
/** 是否选中 */
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
/** 是否已付款 */
|
/** 是否已付款 */
|
||||||
@ -3015,7 +3015,7 @@ declare namespace BusinessAPI {
|
|||||||
| 'LOGISTICS_TYPE'
|
| 'LOGISTICS_TYPE'
|
||||||
| 'EXPENSE_TYPE';
|
| 'EXPENSE_TYPE';
|
||||||
/** 关联项目id */
|
/** 关联项目id */
|
||||||
costItemIds?: number[];
|
costItemIds?: string[];
|
||||||
/** 是否付款 */
|
/** 是否付款 */
|
||||||
isPaid?: boolean;
|
isPaid?: boolean;
|
||||||
};
|
};
|
||||||
@ -3148,7 +3148,7 @@ declare namespace BusinessAPI {
|
|||||||
| 'LOGISTICS_TYPE'
|
| 'LOGISTICS_TYPE'
|
||||||
| 'EXPENSE_TYPE';
|
| 'EXPENSE_TYPE';
|
||||||
/** 关联项目id */
|
/** 关联项目id */
|
||||||
costItemIds?: number[];
|
costItemIds?: string[];
|
||||||
/** 创建时间 */
|
/** 创建时间 */
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
/** 采购订单状态: 0_草稿;1_审核中;2_已完成;3_已关闭; */
|
/** 采购订单状态: 0_草稿;1_审核中;2_已完成;3_已关闭; */
|
||||||
@ -3870,7 +3870,7 @@ declare namespace BusinessAPI {
|
|||||||
/** 产品名称 */
|
/** 产品名称 */
|
||||||
productName?: string;
|
productName?: string;
|
||||||
/** 关联费用id */
|
/** 关联费用id */
|
||||||
costIds?: number[];
|
costIds?: string[];
|
||||||
/** 成本模板 */
|
/** 成本模板 */
|
||||||
costTemplate?: string;
|
costTemplate?: string;
|
||||||
/** 是否已付定金 */
|
/** 是否已付定金 */
|
||||||
@ -5180,7 +5180,7 @@ declare namespace BusinessAPI {
|
|||||||
/** 产品名称 */
|
/** 产品名称 */
|
||||||
name: string;
|
name: string;
|
||||||
/** 关联成本费用id */
|
/** 关联成本费用id */
|
||||||
costIds?: number[];
|
costIds?: string[];
|
||||||
/** 成本模板 */
|
/** 成本模板 */
|
||||||
costTemplate?: string;
|
costTemplate?: string;
|
||||||
/** 备注 */
|
/** 备注 */
|
||||||
@ -5247,7 +5247,7 @@ declare namespace BusinessAPI {
|
|||||||
/** 产品名称 */
|
/** 产品名称 */
|
||||||
name: string;
|
name: string;
|
||||||
/** 关联成本费用id */
|
/** 关联成本费用id */
|
||||||
costIds?: number[];
|
costIds?: string[];
|
||||||
/** 成本模板 */
|
/** 成本模板 */
|
||||||
costTemplate?: string;
|
costTemplate?: string;
|
||||||
/** 备注 */
|
/** 备注 */
|
||||||
@ -5272,7 +5272,7 @@ declare namespace BusinessAPI {
|
|||||||
/** 状态:1_启用;0_禁用 */
|
/** 状态:1_启用;0_禁用 */
|
||||||
status: boolean;
|
status: boolean;
|
||||||
/** 成本ID集合 */
|
/** 成本ID集合 */
|
||||||
costIds?: number[];
|
costIds?: string[];
|
||||||
/** 成本费用 */
|
/** 成本费用 */
|
||||||
costVOList?: CostVO[];
|
costVOList?: CostVO[];
|
||||||
/** 成本模板 */
|
/** 成本模板 */
|
||||||
@ -5299,7 +5299,7 @@ declare namespace BusinessAPI {
|
|||||||
/** 角色详情 */
|
/** 角色详情 */
|
||||||
description?: string;
|
description?: string;
|
||||||
/** 角色id */
|
/** 角色id */
|
||||||
menuId: number[];
|
menuId: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type RoleDestroyCmd = {
|
type RoleDestroyCmd = {
|
||||||
@ -5321,7 +5321,7 @@ declare namespace BusinessAPI {
|
|||||||
/** 角色编号 */
|
/** 角色编号 */
|
||||||
roleId?: string;
|
roleId?: string;
|
||||||
/** 应用角色Id */
|
/** 应用角色Id */
|
||||||
roleIdList?: number[];
|
roleIdList?: string[];
|
||||||
/** 平台Id */
|
/** 平台Id */
|
||||||
platformId?: string;
|
platformId?: string;
|
||||||
/** 平台Id */
|
/** 平台Id */
|
||||||
@ -5368,7 +5368,7 @@ declare namespace BusinessAPI {
|
|||||||
/** 角色详情 */
|
/** 角色详情 */
|
||||||
description?: string;
|
description?: string;
|
||||||
/** 角色id */
|
/** 角色id */
|
||||||
menuId: number[];
|
menuId: string[];
|
||||||
/** 角色ID */
|
/** 角色ID */
|
||||||
roleId: string;
|
roleId: string;
|
||||||
};
|
};
|
||||||
@ -5389,9 +5389,9 @@ declare namespace BusinessAPI {
|
|||||||
/** 平台 */
|
/** 平台 */
|
||||||
platformVO?: PlatformVO;
|
platformVO?: PlatformVO;
|
||||||
/** 权限列表 */
|
/** 权限列表 */
|
||||||
permissionId: number[];
|
permissionId: string[];
|
||||||
/** 菜单列表 */
|
/** 菜单列表 */
|
||||||
menuId: number[];
|
menuId: string[];
|
||||||
/** 创建时间 */
|
/** 创建时间 */
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
};
|
};
|
||||||
@ -6265,7 +6265,7 @@ declare namespace BusinessAPI {
|
|||||||
/** 备注 */
|
/** 备注 */
|
||||||
remark?: string;
|
remark?: string;
|
||||||
/** 客户标签 */
|
/** 客户标签 */
|
||||||
labelId?: number[];
|
labelId?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type UserDestroyCmd = {
|
type UserDestroyCmd = {
|
||||||
@ -6287,7 +6287,7 @@ declare namespace BusinessAPI {
|
|||||||
/** 状态:1_启用;0_禁用; */
|
/** 状态:1_启用;0_禁用; */
|
||||||
status?: boolean;
|
status?: boolean;
|
||||||
/** 用户ID */
|
/** 用户ID */
|
||||||
userIdList?: number[];
|
userIdList?: string[];
|
||||||
/** 用户名 */
|
/** 用户名 */
|
||||||
name?: string;
|
name?: string;
|
||||||
};
|
};
|
||||||
@ -6330,9 +6330,9 @@ declare namespace BusinessAPI {
|
|||||||
/** 是否是管理员 */
|
/** 是否是管理员 */
|
||||||
isAdmin?: boolean;
|
isAdmin?: boolean;
|
||||||
/** 会员id列表 */
|
/** 会员id列表 */
|
||||||
userIdList?: number[];
|
userIdList?: string[];
|
||||||
/** 排除的用户id列表 */
|
/** 排除的用户id列表 */
|
||||||
excludeUserIdList?: number[];
|
excludeUserIdList?: string[];
|
||||||
/** 小区id */
|
/** 小区id */
|
||||||
communityId?: number;
|
communityId?: number;
|
||||||
offset?: number;
|
offset?: number;
|
||||||
@ -6342,7 +6342,7 @@ declare namespace BusinessAPI {
|
|||||||
/** 用户ID */
|
/** 用户ID */
|
||||||
userId: string;
|
userId: string;
|
||||||
/** 角色ID */
|
/** 角色ID */
|
||||||
roleIdList?: number[];
|
roleIdList?: string[];
|
||||||
/** 是否覆盖 */
|
/** 是否覆盖 */
|
||||||
cover: boolean;
|
cover: boolean;
|
||||||
};
|
};
|
||||||
@ -6387,7 +6387,7 @@ declare namespace BusinessAPI {
|
|||||||
/** 备注 */
|
/** 备注 */
|
||||||
remark?: string;
|
remark?: string;
|
||||||
/** 客户标签 */
|
/** 客户标签 */
|
||||||
labelId?: number[];
|
labelId?: string[];
|
||||||
/** 用户ID */
|
/** 用户ID */
|
||||||
userId: string;
|
userId: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,3 +1,2 @@
|
|||||||
packages:
|
packages:
|
||||||
- 'packages/**'
|
- 'packages/**'
|
||||||
- 'shared/**'
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user