feat(app-client): 重构采购审批页面并优化样式配置
- 重构采购审批页面,移除冗余的表单逻辑和校验代码 - 新增基础信息模块自动获取上一车次号功能 - 优化自定义主题配置,统一使用 Taro.pxTransform 处理单位 - 调整页面列表组件的数据加载逻辑,支持分页追加数据 - 优化成本相关组件的价格展示样式,统一字体大小和颜色 - 移除页面中冗余的状态管理和副作用逻辑 - 调整审批页面布局结构,提升用户体验
This commit is contained in:
parent
bd4723b6ed
commit
9213b90d61
141
.lingma/rules/api-implementation.md
Normal file
141
.lingma/rules/api-implementation.md
Normal file
@ -0,0 +1,141 @@
|
||||
# API 接口实现规范
|
||||
|
||||
## 概述
|
||||
|
||||
本系统采用基于 axios 的 HTTP 客户端来与后端进行通信,所有 API 请求都经过统一的封装和管理。该文档旨在说明如何在系统中添加和使用新的 API 接口。
|
||||
|
||||
## 核心架构
|
||||
|
||||
### 1. 请求客户端 (request.ts)
|
||||
|
||||
系统在 `packages/app-client/src/services/request.ts` 文件中创建了一个全局的 axios 实例,用于处理所有 HTTP 请求。
|
||||
|
||||
```typescript
|
||||
import axios from "axios";
|
||||
import Taro from "@tarojs/taro";
|
||||
|
||||
const request = axios.create({
|
||||
baseURL: process.env.TARO_API_DOMAIN,
|
||||
});
|
||||
```
|
||||
|
||||
该实例具有以下特性:
|
||||
- 设置了基础 URL,从环境变量 `TARO_API_DOMAIN` 获取
|
||||
- 包含请求拦截器,自动添加认证信息(如 saToken 和角色信息)
|
||||
- 包含响应拦截器,处理通用的响应逻辑(如权限验证、错误提示等)
|
||||
|
||||
### 2. 请求拦截器
|
||||
|
||||
在发送请求前,系统会自动添加以下头部信息:
|
||||
- `saToken`: 用户的身份认证令牌,从本地存储中获取
|
||||
- `Xh-Role-Slug`: 用户当前角色标识,从本地存储中获取
|
||||
|
||||
### 3. 响应拦截器
|
||||
|
||||
响应拦截器负责处理通用的业务逻辑:
|
||||
- 成功响应时检查是否需要更新本地存储的认证信息
|
||||
- 错误响应时根据错误码显示相应提示或执行特定操作(如 401 时跳转登录页)
|
||||
|
||||
## API 接口组织
|
||||
|
||||
### 1. 模块划分
|
||||
|
||||
API 接口按照业务模块进行组织,每个模块对应一个文件:
|
||||
- 所有业务 API 存放在 `packages/app-client/src/services/business/` 目录下
|
||||
- 每个文件代表一个业务模块(如 [platform.ts](file:///D:/xinfaleisheng/ERPTurbo_Client/packages/app-client/src/services/business/platform.ts)、[user.ts](file:///D:/xinfaleisheng/ERPTurbo_Client/packages/app-client/src/services/business/user.ts) 等)
|
||||
- 每个模块导出一组相关的 API 函数
|
||||
|
||||
### 2. 接口定义格式
|
||||
|
||||
每个 API 函数遵循统一的格式:
|
||||
|
||||
```typescript
|
||||
/** 接口说明 GET|POST|PUT|DELETE /api/path */
|
||||
export async function apiFunctionName(
|
||||
// 参数类型定义
|
||||
params: BusinessAPI.paramsType,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
return request<BusinessAPI.ResponseType>("/api/path", {
|
||||
method: "GET|POST|PUT|DELETE",
|
||||
// 根据请求方法设置 headers、params 或 data
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 模块导出
|
||||
|
||||
所有业务模块在 `packages/app-client/src/services/business/index.ts` 中统一导出:
|
||||
|
||||
```typescript
|
||||
import * as moduleName from "./moduleName";
|
||||
export default {
|
||||
moduleName,
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
## 添加新接口步骤
|
||||
|
||||
### 1. 确定业务模块
|
||||
|
||||
首先确定新接口属于哪个业务模块,如果现有模块都不合适,则需要创建新的模块文件。
|
||||
|
||||
### 2. 定义数据类型
|
||||
|
||||
在 `typings.d.ts` 文件中定义接口所需的请求参数类型和响应数据类型。
|
||||
|
||||
### 3. 编写接口函数
|
||||
|
||||
在对应的模块文件中添加新的接口函数,参考以下模板:
|
||||
|
||||
```typescript
|
||||
/** 接口说明 动作 /api/path */
|
||||
export async function functionName(
|
||||
// 根据实际参数类型修改
|
||||
body: BusinessAPI.RequestType,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
// 根据实际响应类型修改
|
||||
return request<BusinessAPI.ResponseType>("/api/path", {
|
||||
method: "POST", // 根据实际情况修改为 POST/GET/PUT/DELETE
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
data: body, // GET 请求使用 params,其他请求使用 data
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 导出模块
|
||||
|
||||
确保在 `packages/app-client/src/services/business/index.ts` 中导出了包含新接口的模块。
|
||||
|
||||
## 使用示例
|
||||
|
||||
在组件或其他服务中使用 API 接口:
|
||||
|
||||
```typescript
|
||||
import businessServices from "@/services/business";
|
||||
|
||||
// 调用平台列表接口
|
||||
const {data: {data: platformList}} = await businessServices.platform.listPlatform({
|
||||
// 参数
|
||||
});
|
||||
|
||||
// 调用创建平台接口
|
||||
const {data: {data: platform}} = await businessServices.platform.createPlatform({
|
||||
// 数据体
|
||||
});
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **类型安全**: 始终使用 TypeScript 类型定义请求参数和响应数据
|
||||
2. **错误处理**: 在调用 API 的地方适当处理可能发生的错误
|
||||
3. **命名规范**: 接口函数名应该清晰表达其功能,通常采用动词+名词的形式
|
||||
4. **注释说明**: 每个接口函数都需要添加注释说明接口用途和路径
|
||||
5. **统一管理**: 所有 API 接口都应该按业务模块分类管理
|
||||
6. **避免重复**: 相似的接口可以复用已有模式,保持一致性
|
||||
293
.lingma/rules/dialog-implementation.md
Normal file
293
.lingma/rules/dialog-implementation.md
Normal file
@ -0,0 +1,293 @@
|
||||
---
|
||||
trigger: manual
|
||||
---
|
||||
|
||||
# Dialog 功能实现指南
|
||||
|
||||
## 概述
|
||||
|
||||
Dialog 是系统中用于显示重要信息或请求用户确认操作的模态对话框组件。它通常用于警告、确认操作或显示关键信息。本指南将详细介绍如何在系统中实现 Dialog 功能。
|
||||
|
||||
## 技术栈
|
||||
|
||||
系统使用的是基于 Taro 的 React 框架,Dialog 组件来自 `@nutui/nutui-react-taro` 库。
|
||||
|
||||
## 基本用法
|
||||
|
||||
### 1. 导入 Dialog 组件
|
||||
|
||||
```tsx
|
||||
import { Dialog } from "@nutui/nutui-react-taro";
|
||||
```
|
||||
|
||||
### 2. 基本属性
|
||||
|
||||
Dialog 组件常用属性包括:
|
||||
|
||||
- `visible`: 控制 Dialog 是否显示(boolean)
|
||||
- `title`: Dialog 标题
|
||||
- `content`: Dialog 内容(可以是字符串或 JSX 元素)
|
||||
- `onCancel`: 取消按钮的回调函数
|
||||
- `onConfirm`: 确认按钮的回调函数
|
||||
- `cancelText`: 取消按钮文本
|
||||
- `confirmText`: 确认按钮文本
|
||||
|
||||
### 3. 基本结构
|
||||
|
||||
```tsx
|
||||
<Dialog
|
||||
visible={visible}
|
||||
title="Dialog 标题"
|
||||
content="Dialog 内容"
|
||||
onCancel={() => setVisible(false)}
|
||||
onConfirm={handleConfirm}
|
||||
/>
|
||||
```
|
||||
|
||||
## 实现方式
|
||||
|
||||
系统中有两种主要使用 Dialog 的方式:
|
||||
|
||||
### 1. JSX 组件形式
|
||||
|
||||
直接在 JSX 中使用 Dialog 组件:
|
||||
|
||||
```tsx
|
||||
<Dialog
|
||||
visible={inputVisible}
|
||||
title="手动输入经销商"
|
||||
content={
|
||||
<View className={`flex h-10 w-full items-center rounded-md border-4 border-gray-300`}>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="请输入经销商名称"
|
||||
value={inputValue}
|
||||
onChange={(value) => setInputValue(value)}
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
onCancel={() => setInputVisible(false)}
|
||||
onConfirm={handleConfirm}
|
||||
/>
|
||||
```
|
||||
|
||||
### 2. 函数调用形式
|
||||
|
||||
通过 Dialog 提供的静态方法调用:
|
||||
|
||||
```tsx
|
||||
// 使用 Dialog.confirm
|
||||
const onWithdraw = async () => {
|
||||
Dialog.confirm({
|
||||
title: "提示",
|
||||
content: "确认撤回审核?",
|
||||
onConfirm: async () => {
|
||||
// 确认操作
|
||||
},
|
||||
onCancel: () => {
|
||||
// 取消操作
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 使用 Dialog.open 和 Dialog.close
|
||||
const handleApprove = () => {
|
||||
Dialog.open("dialog", {
|
||||
title: "审批通过",
|
||||
content: "确定要审批通过该采购订单吗?",
|
||||
confirmText: "确认",
|
||||
cancelText: "取消",
|
||||
onConfirm: async () => {
|
||||
await confirmApprove();
|
||||
Dialog.close("dialog");
|
||||
},
|
||||
onCancel: () => {
|
||||
Dialog.close("dialog");
|
||||
},
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
## 实现步骤
|
||||
|
||||
### 1. 状态管理
|
||||
|
||||
对于 JSX 组件形式的 Dialog,需要定义控制 Dialog 显示/隐藏的状态:
|
||||
|
||||
```tsx
|
||||
const [visible, setVisible] = useState(false);
|
||||
```
|
||||
|
||||
### 2. 触发 Dialog 显示
|
||||
|
||||
通过事件触发 Dialog 显示,例如点击按钮:
|
||||
|
||||
```tsx
|
||||
<Button onClick={() => setVisible(true)}>打开 Dialog</Button>
|
||||
```
|
||||
|
||||
### 3. 处理确认和取消操作
|
||||
|
||||
实现 onConfirm 和 onCancel 回调函数:
|
||||
|
||||
```tsx
|
||||
const handleConfirm = () => {
|
||||
// 执行确认操作
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
// 执行取消操作
|
||||
setVisible(false);
|
||||
};
|
||||
```
|
||||
|
||||
### 4. 关闭 Dialog
|
||||
|
||||
Dialog 可以通过以下方式关闭:
|
||||
- 点击取消按钮
|
||||
- 点击确认按钮
|
||||
- 在 onCancel 或 onConfirm 回调中手动设置 visible 为 false
|
||||
- 使用 Dialog.close() 方法(函数调用形式)
|
||||
|
||||
## 常见使用场景
|
||||
|
||||
### 1. 确认操作
|
||||
|
||||
用于确认删除、撤回等重要操作:
|
||||
|
||||
```tsx
|
||||
const handleDelete = () => {
|
||||
Dialog.confirm({
|
||||
title: "确认删除",
|
||||
content: "确定要删除这条记录吗?",
|
||||
onConfirm: async () => {
|
||||
// 执行删除操作
|
||||
},
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
### 2. 输入信息
|
||||
|
||||
用于请求用户输入信息:
|
||||
|
||||
```tsx
|
||||
<Dialog
|
||||
visible={inputVisible}
|
||||
title="输入信息"
|
||||
content={
|
||||
<Input
|
||||
placeholder="请输入信息"
|
||||
value={inputValue}
|
||||
onChange={(value) => setInputValue(value)}
|
||||
/>
|
||||
}
|
||||
onCancel={() => setInputVisible(false)}
|
||||
onConfirm={handleConfirm}
|
||||
/>
|
||||
```
|
||||
|
||||
### 3. 显示重要信息
|
||||
|
||||
用于显示重要提示或警告信息:
|
||||
|
||||
```tsx
|
||||
const handleSubmit = async () => {
|
||||
Dialog.open("submit-dialog", {
|
||||
title: "提交提醒",
|
||||
content: "提交后将无法修改,请确认信息无误。",
|
||||
confirmText: "确认提交",
|
||||
cancelText: "取消",
|
||||
onConfirm: async () => {
|
||||
// 执行提交操作
|
||||
Dialog.close("submit-dialog");
|
||||
},
|
||||
onCancel: () => {
|
||||
Dialog.close("submit-dialog");
|
||||
},
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 状态管理
|
||||
|
||||
- 使用 useState 管理 Dialog 的显示状态(JSX 组件形式)
|
||||
- 对于函数调用形式,使用唯一标识符管理不同的 Dialog 实例
|
||||
|
||||
### 2. 用户体验
|
||||
|
||||
- 提供明确的标题和内容说明
|
||||
- 使用合适的确认和取消按钮文本
|
||||
- 在操作完成后及时关闭 Dialog
|
||||
|
||||
### 3. 错误处理
|
||||
|
||||
- 在 onConfirm 回调中处理可能的错误
|
||||
- 提供友好的错误提示信息
|
||||
|
||||
### 4. 样式统一
|
||||
|
||||
- 保持 Dialog 标题、按钮等样式与系统一致
|
||||
- 使用系统定义的颜色和间距变量
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 确保在组件卸载时关闭 Dialog
|
||||
2. 注意 Dialog 内容的可访问性
|
||||
3. 避免同时显示多个 Dialog
|
||||
4. 在异步操作中正确处理 Dialog 的关闭时机
|
||||
5. 对于函数调用形式,记得在适当时机调用 Dialog.close()
|
||||
|
||||
## 示例代码
|
||||
|
||||
```tsx
|
||||
import { Dialog, Button, View, Input } from "@nutui/nutui-react-taro";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function MyComponent() {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
|
||||
const handleConfirm = () => {
|
||||
// 处理确认逻辑
|
||||
console.log("用户输入:", inputValue);
|
||||
setVisible(false);
|
||||
setInputValue("");
|
||||
};
|
||||
|
||||
const showConfirmDialog = () => {
|
||||
Dialog.confirm({
|
||||
title: "确认操作",
|
||||
content: "确定要执行此操作吗?",
|
||||
onConfirm: () => {
|
||||
// 确认操作
|
||||
console.log("用户确认操作");
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Button onClick={() => setVisible(true)}>打开输入 Dialog</Button>
|
||||
<Button onClick={showConfirmDialog}>显示确认 Dialog</Button>
|
||||
|
||||
<Dialog
|
||||
visible={visible}
|
||||
title="请输入信息"
|
||||
content={
|
||||
<Input
|
||||
placeholder="请输入信息"
|
||||
value={inputValue}
|
||||
onChange={(value) => setInputValue(value)}
|
||||
/>
|
||||
}
|
||||
onCancel={() => setVisible(false)}
|
||||
onConfirm={handleConfirm}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
```
|
||||
210
.lingma/rules/popup-implementation.md
Normal file
210
.lingma/rules/popup-implementation.md
Normal file
@ -0,0 +1,210 @@
|
||||
---
|
||||
trigger: manual
|
||||
---
|
||||
|
||||
# Popup 功能实现指南
|
||||
|
||||
## 概述
|
||||
|
||||
Popup 是系统中常用的 UI 组件,用于在当前页面上显示一个浮层,通常用于显示额外信息、表单或选择器等。本指南将详细介绍如何在系统中实现 Popup 功能。
|
||||
|
||||
## 技术栈
|
||||
|
||||
系统使用的是基于 Taro 的 React 框架,Popup 组件来自 `@nutui/nutui-react-taro` 库。
|
||||
|
||||
## 基本用法
|
||||
|
||||
### 1. 导入 Popup 组件
|
||||
|
||||
```tsx
|
||||
import { Popup } from "@nutui/nutui-react-taro";
|
||||
```
|
||||
|
||||
### 2. 基本属性
|
||||
|
||||
Popup 组件常用属性包括:
|
||||
|
||||
- `visible`: 控制 Popup 是否显示(boolean)
|
||||
- `position`: Popup 出现的位置("top" | "bottom" | "left" | "right" | "center")
|
||||
- `title`: Popup 标题
|
||||
- `onClose`: 关闭时的回调函数
|
||||
- `onOverlayClick`: 点击遮罩层时的回调函数
|
||||
- `closeable`: 是否显示关闭按钮
|
||||
- `round`: 是否显示圆角
|
||||
- `lockScroll`: 是否锁定背景滚动
|
||||
|
||||
### 3. 基本结构
|
||||
|
||||
```tsx
|
||||
<Popup
|
||||
visible={visible}
|
||||
position="bottom"
|
||||
title="Popup 标题"
|
||||
onClose={() => setVisible(false)}
|
||||
closeable
|
||||
round
|
||||
lockScroll
|
||||
>
|
||||
<View>Popup 内容</View>
|
||||
</Popup>
|
||||
```
|
||||
|
||||
## 实现步骤
|
||||
|
||||
### 1. 状态管理
|
||||
|
||||
在组件中定义控制 Popup 显示/隐藏的状态:
|
||||
|
||||
```tsx
|
||||
const [visible, setVisible] = useState(false);
|
||||
```
|
||||
|
||||
### 2. 触发 Popup 显示
|
||||
|
||||
通过事件触发 Popup 显示,例如点击按钮:
|
||||
|
||||
```tsx
|
||||
<Button onClick={() => setVisible(true)}>打开 Popup</Button>
|
||||
```
|
||||
|
||||
### 3. Popup 内容实现
|
||||
|
||||
Popup 内容根据具体需求实现,可以包含表单、列表、文本等。
|
||||
|
||||
### 4. 关闭 Popup
|
||||
|
||||
Popup 可以通过以下方式关闭:
|
||||
- 点击关闭按钮(如果设置了 `closeable`)
|
||||
- 点击遮罩层(如果未阻止)
|
||||
- 调用 `onClose` 回调函数
|
||||
- 在 Popup 内部通过按钮等操作手动设置 visible 为 false
|
||||
|
||||
## 常见使用场景
|
||||
|
||||
### 1. 选择器
|
||||
|
||||
常用于实现各种选择器,如经销商选择、供应商选择等:
|
||||
|
||||
```tsx
|
||||
<Popup
|
||||
visible={visible}
|
||||
position="bottom"
|
||||
title="选择经销商"
|
||||
onClose={() => setVisible(false)}
|
||||
>
|
||||
<View>经销商列表</View>
|
||||
</Popup>
|
||||
```
|
||||
|
||||
### 2. 表单编辑
|
||||
|
||||
用于编辑某一项信息:
|
||||
|
||||
```tsx
|
||||
<Popup
|
||||
visible={visible}
|
||||
position="bottom"
|
||||
title={`编辑${supplier.name}销售单价`}
|
||||
onClose={() => setVisible(false)}
|
||||
>
|
||||
<View className="flex flex-col gap-3 p-2.5">
|
||||
<Input
|
||||
placeholder="请输入销售单价"
|
||||
type="digit"
|
||||
value={price}
|
||||
onChange={(value) => setPrice(value)}
|
||||
/>
|
||||
<Button onClick={handleSave}>保存</Button>
|
||||
</View>
|
||||
</Popup>
|
||||
```
|
||||
|
||||
### 3. 确认弹窗
|
||||
|
||||
用于确认操作:
|
||||
|
||||
```tsx
|
||||
<Popup
|
||||
visible={visible}
|
||||
position="bottom"
|
||||
title="确认操作"
|
||||
onClose={() => setVisible(false)}
|
||||
>
|
||||
<View className="p-4">
|
||||
<View className="mb-4">确定要执行此操作吗?</View>
|
||||
<View className="flex gap-2">
|
||||
<Button onClick={() => setVisible(false)}>取消</Button>
|
||||
<Button onClick={handleConfirm}>确认</Button>
|
||||
</View>
|
||||
</View>
|
||||
</Popup>
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 状态管理
|
||||
|
||||
- 使用 useState 管理 Popup 的显示状态
|
||||
- 对于复杂场景,可以使用 useReducer 或状态管理库
|
||||
|
||||
### 2. 性能优化
|
||||
|
||||
- 对于内容较多的 Popup,考虑使用懒加载
|
||||
- 在 Popup 关闭时清理相关状态
|
||||
|
||||
### 3. 用户体验
|
||||
|
||||
- 合理设置 Popup 的位置和大小
|
||||
- 提供明确的关闭方式
|
||||
- 添加适当的动画效果
|
||||
|
||||
### 4. 样式统一
|
||||
|
||||
- 保持 Popup 标题、按钮等样式与系统一致
|
||||
- 使用系统定义的颜色和间距变量
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 确保在组件卸载时清理相关状态和副作用
|
||||
2. 注意 Popup 内容的可访问性
|
||||
3. 避免同时显示多个 Popup
|
||||
4. 考虑移动端的适配问题
|
||||
5. 在 Popup 关闭时重置相关表单数据(如需要)
|
||||
|
||||
## 示例代码
|
||||
|
||||
```tsx
|
||||
import { Popup, Button, View } from "@nutui/nutui-react-taro";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function MyComponent() {
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Button onClick={() => setVisible(true)}>打开 Popup</Button>
|
||||
|
||||
<Popup
|
||||
visible={visible}
|
||||
position="bottom"
|
||||
title="示例 Popup"
|
||||
onClose={() => setVisible(false)}
|
||||
closeable
|
||||
round
|
||||
>
|
||||
<View className="p-4">
|
||||
<View>这是 Popup 内容</View>
|
||||
<Button
|
||||
block
|
||||
type="primary"
|
||||
className="mt-4"
|
||||
onClick={() => setVisible(false)}
|
||||
>
|
||||
关闭
|
||||
</Button>
|
||||
</View>
|
||||
</Popup>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
```
|
||||
304
.lingma/rules/upload-implementation.md
Normal file
304
.lingma/rules/upload-implementation.md
Normal file
@ -0,0 +1,304 @@
|
||||
---
|
||||
trigger: manual
|
||||
---
|
||||
|
||||
# 文件上传功能实现指南
|
||||
|
||||
## 概述
|
||||
|
||||
文件上传是系统中常见的功能,用于上传图片、文档等文件。本指南将详细介绍如何在系统中实现文件上传功能。
|
||||
|
||||
## 技术栈
|
||||
|
||||
系统使用的是基于 Taro 的 React 框架,上传组件来自 `@nutui/nutui-react-taro` 库,文件上传通过 Taro 的 uploadFile API 实现。
|
||||
|
||||
## 基本用法
|
||||
|
||||
### 1. 导入相关组件和工具
|
||||
|
||||
```tsx
|
||||
import { Uploader, UploaderFileItem } from "@nutui/nutui-react-taro";
|
||||
import { uploadFile } from "@/utils/uploader";
|
||||
```
|
||||
|
||||
### 2. 基本属性
|
||||
|
||||
Uploader 组件常用属性包括:
|
||||
|
||||
- `value`: 当前已上传的文件列表(UploaderFileItem[])
|
||||
- `onChange`: 文件列表变化时的回调函数
|
||||
- `upload`: 自定义上传函数
|
||||
- `sourceType`: 选择图片的来源(["album", "camera"])
|
||||
- `maxCount`: 最大上传数量
|
||||
- `multiple`: 是否支持多选
|
||||
- `uploadIcon`: 自定义上传图标
|
||||
- `uploadLabel`: 自定义上传标签文本
|
||||
|
||||
### 3. 基本结构
|
||||
|
||||
```tsx
|
||||
<Uploader
|
||||
value={fileList}
|
||||
onChange={handleFileChange}
|
||||
sourceType={["album", "camera"]}
|
||||
maxCount={1}
|
||||
upload={uploadFile}
|
||||
multiple
|
||||
/>
|
||||
```
|
||||
|
||||
## 实现步骤
|
||||
|
||||
### 1. 状态管理
|
||||
|
||||
定义文件列表状态:
|
||||
|
||||
```tsx
|
||||
const [fileList, setFileList] = useState<UploaderFileItem[]>([]);
|
||||
```
|
||||
|
||||
### 2. 实现上传工具函数
|
||||
|
||||
系统中使用统一的上传工具函数 [utils/uploader.ts](file:///D:/xinfaleisheng/ERPTurbo_Client/packages/app-client/src/utils/uploader.ts):
|
||||
|
||||
```tsx
|
||||
import Taro from "@tarojs/taro";
|
||||
import { Toast } from "@nutui/nutui-react-taro";
|
||||
|
||||
export const uploadFile = async (file: File | string) => {
|
||||
console.log("file", file);
|
||||
const res = await Taro.uploadFile({
|
||||
url: process.env.TARO_API_DOMAIN + `/auth/upload`,
|
||||
// @ts-ignore
|
||||
filePath: file.tempFilePath || file,
|
||||
name: "file",
|
||||
header: {
|
||||
saToken: Taro.getStorageSync("saToken"),
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
});
|
||||
|
||||
if (res.errMsg == "uploadFile:ok") {
|
||||
const data = JSON.parse(res.data);
|
||||
|
||||
if (data.errCode == "401") {
|
||||
Taro.removeStorageSync("user");
|
||||
Toast.show("toast", {
|
||||
icon: "warn",
|
||||
title: "",
|
||||
content: "超时请重试",
|
||||
});
|
||||
return Promise.reject();
|
||||
} else {
|
||||
return {
|
||||
url: data?.data,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
Toast.show("toast", {
|
||||
icon: "fail",
|
||||
title: "",
|
||||
content: "上传失败",
|
||||
});
|
||||
return Promise.reject();
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 3. 处理文件变化
|
||||
|
||||
实现 onChange 回调函数处理文件变化:
|
||||
|
||||
```tsx
|
||||
const handleFileChange = (files: UploaderFileItem[]) => {
|
||||
setFileList(files);
|
||||
|
||||
// 处理上传后的逻辑
|
||||
if (files.length > 0 && files[0].status === 'success') {
|
||||
// 文件上传成功,可以保存URL到业务数据中
|
||||
const fileUrls = files.map(file => file.url).filter(url => url) as string[];
|
||||
// 保存到业务状态中
|
||||
setBusinessData(fileUrls);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 4. 初始化已上传文件
|
||||
|
||||
如果有已上传的文件需要显示,需要将其转换为 UploaderFileItem 格式:
|
||||
|
||||
```tsx
|
||||
useEffect(() => {
|
||||
if (existingFileUrls && existingFileUrls.length > 0) {
|
||||
const fileList = existingFileUrls.map((url, index) => ({
|
||||
url: url,
|
||||
name: `file-${index}`,
|
||||
status: 'success'
|
||||
}));
|
||||
setFileList(fileList);
|
||||
}
|
||||
}, [existingFileUrls]);
|
||||
```
|
||||
|
||||
## 常见使用场景
|
||||
|
||||
### 1. 单图片上传
|
||||
|
||||
用于头像上传等只需要一张图片的场景:
|
||||
|
||||
```tsx
|
||||
<Uploader
|
||||
value={avatarList}
|
||||
onChange={handleAvatarChange}
|
||||
sourceType={["album", "camera"]}
|
||||
maxCount={1}
|
||||
upload={uploadFile}
|
||||
/>
|
||||
```
|
||||
|
||||
### 2. 多图片上传
|
||||
|
||||
用于需要上传多张图片的场景,如合同、发票等:
|
||||
|
||||
```tsx
|
||||
<Uploader
|
||||
value={contractImgList}
|
||||
onChange={handleContractImgChange}
|
||||
sourceType={["album", "camera"]}
|
||||
uploadIcon={<Icon name={"camera"} size={36} />}
|
||||
uploadLabel={
|
||||
<View className={"flex flex-col items-center"}>
|
||||
<View className="text-sm">拍照上传合同</View>
|
||||
</View>
|
||||
}
|
||||
maxCount={5}
|
||||
upload={uploadFile}
|
||||
multiple
|
||||
/>
|
||||
```
|
||||
|
||||
### 3. 不同类型文件上传
|
||||
|
||||
系统中常见的文件上传场景包括:
|
||||
|
||||
1. 空磅照片上传
|
||||
2. 总磅照片上传
|
||||
3. 发票照片上传
|
||||
4. 合同照片上传
|
||||
5. 头像上传
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 状态管理
|
||||
|
||||
- 使用 useState 管理文件列表状态
|
||||
- 将上传成功的文件URL保存到业务数据中
|
||||
- 在组件卸载时清理相关状态
|
||||
|
||||
### 2. 用户体验
|
||||
|
||||
- 提供清晰的上传指引说明
|
||||
- 显示上传进度和状态
|
||||
- 支持图片预览功能
|
||||
- 合理设置最大上传数量
|
||||
|
||||
### 3. 错误处理
|
||||
|
||||
- 在上传工具函数中统一处理上传错误
|
||||
- 提供友好的错误提示信息
|
||||
- 处理网络异常情况
|
||||
|
||||
### 4. 性能优化
|
||||
|
||||
- 对大文件上传提供进度提示
|
||||
- 支持断点续传(如果后端支持)
|
||||
- 合理设置图片压缩参数
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 确保在组件卸载时清理相关状态和副作用
|
||||
2. 注意文件大小和格式限制
|
||||
3. 处理上传失败的重试机制
|
||||
4. 考虑移动端网络环境的适配
|
||||
5. 遵循系统统一的上传接口规范
|
||||
|
||||
## 示例代码
|
||||
|
||||
```tsx
|
||||
import { View } from "@tarojs/components";
|
||||
import { Uploader, UploaderFileItem } from "@nutui/nutui-react-taro";
|
||||
import { useState, useEffect } from "react";
|
||||
import { uploadFile } from "@/utils/uploader";
|
||||
|
||||
export default function UploadExample() {
|
||||
// 头像上传状态
|
||||
const [avatarList, setAvatarList] = useState<UploaderFileItem[]>([]);
|
||||
|
||||
// 合同照片上传状态
|
||||
const [contractImgList, setContractImgList] = useState<UploaderFileItem[]>([]);
|
||||
|
||||
// 初始化已上传的文件
|
||||
useEffect(() => {
|
||||
// 假设从服务器获取到已上传的文件URL
|
||||
const existingAvatarUrl = "https://example.com/avatar.jpg";
|
||||
if (existingAvatarUrl) {
|
||||
setAvatarList([{
|
||||
url: existingAvatarUrl,
|
||||
name: 'avatar',
|
||||
status: 'success'
|
||||
}]);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 处理头像上传变化
|
||||
const handleAvatarChange = (files: UploaderFileItem[]) => {
|
||||
setAvatarList(files);
|
||||
|
||||
// 如果上传成功,保存URL到业务数据
|
||||
if (files.length > 0 && files[0].status === 'success' && files[0].url) {
|
||||
// 保存头像URL到用户信息中
|
||||
saveAvatarUrl(files[0].url);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理合同照片上传变化
|
||||
const handleContractImgChange = (files: UploaderFileItem[]) => {
|
||||
setContractImgList(files);
|
||||
|
||||
// 保存所有成功上传的文件URL
|
||||
const urls = files
|
||||
.filter(file => file.status === 'success' && file.url)
|
||||
.map(file => file.url!) as string[];
|
||||
|
||||
// 保存到业务数据中
|
||||
saveContractImgUrls(urls);
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="p-4">
|
||||
<View className="mb-6">
|
||||
<View className="mb-2 text-sm font-medium">头像上传</View>
|
||||
<Uploader
|
||||
value={avatarList}
|
||||
onChange={handleAvatarChange}
|
||||
sourceType={["album", "camera"]}
|
||||
maxCount={1}
|
||||
upload={uploadFile}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View>
|
||||
<View className="mb-2 text-sm font-medium">合同照片上传</View>
|
||||
<Uploader
|
||||
value={contractImgList}
|
||||
onChange={handleContractImgChange}
|
||||
sourceType={["album", "camera"]}
|
||||
maxCount={5}
|
||||
upload={uploadFile}
|
||||
multiple
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
```
|
||||
95
docs/采购订单计算逻辑.md
Normal file
95
docs/采购订单计算逻辑.md
Normal file
@ -0,0 +1,95 @@
|
||||
# 采购订单计算逻辑
|
||||
|
||||
## 1. 总包装费
|
||||
- 计算公式: 辅料费 + 人工费 + 纸箱费 + 固定费用 + 其他费用 + 草帘费(根据开关决定是否计入)
|
||||
- 开关控制: orderDealer?.strawMatCostFlag 控制草帘费是否计入成本
|
||||
|
||||
## 2. 西瓜成本1
|
||||
- 计算公式: 采购成本 + 运费
|
||||
- 开关控制: orderDealer?.freightCostFlag 控制运费是否计入成本
|
||||
|
||||
## 3. 采购成本
|
||||
- 计算公式: 供应商采购成本 + 包装费
|
||||
|
||||
## 4. 销售金额
|
||||
- 计算公式: Σ(各供应商重量 * 销售单价)
|
||||
- 数据来源: orderSupplierList
|
||||
- 重量选择: 根据 pricingMethod 决定使用 grossWeight 或 netWeight
|
||||
|
||||
## 5. 单斤成本
|
||||
- 计算公式: 采购成本 / 总毛重
|
||||
|
||||
## 6. 纸箱利润
|
||||
- 计算公式: 纸箱售卖费 - 纸箱成本费
|
||||
|
||||
## 7. 总毛重
|
||||
- 计算公式: Σ(各供应商毛重)
|
||||
- 数据来源: orderSupplierList
|
||||
|
||||
## 8. 总净重
|
||||
- 计算公式: Σ(各供应商净重)
|
||||
- 数据来源: orderSupplierList
|
||||
|
||||
## 9. 纸箱售卖费
|
||||
- 计算公式: Σ(各供应商纸箱数量 * 纸箱售价)
|
||||
- 数据来源: orderSupplierList -> orderPackageList
|
||||
|
||||
## 10. 纸箱成本费
|
||||
- 计算公式: Σ(各供应商纸箱数量 * 纸箱成本价)
|
||||
- 数据来源: orderSupplierList -> orderPackageList
|
||||
|
||||
## 11. 草帘费
|
||||
- 计算公式: 根据开关和价格确定
|
||||
- 数据来源: orderVehicle
|
||||
- 开关控制:
|
||||
- orderVehicle?.openStrawCurtain 必须开启
|
||||
- orderVehicle?.strawCurtainPrice 必须存在
|
||||
|
||||
## 12. 运费
|
||||
- 计算公式: 直接取值
|
||||
- 数据来源: orderVehicle?.price
|
||||
|
||||
## 13. 个人利润
|
||||
- 计算公式: (西瓜毛利 - 个人返点 - 成本差异) * 0.6 + 成本差异
|
||||
- 涉及数据: 西瓜毛利、个人返点、成本差异
|
||||
|
||||
## 14. 分成后净利润(CXZY)
|
||||
- 计算公式: 西瓜净利润 * 分成比例
|
||||
- 数据来源: dealerVO
|
||||
- 开关控制: dealerVO.enableShare 控制是否启用分成
|
||||
|
||||
## 15. 西瓜净利润
|
||||
- 计算公式: 西瓜毛利 - 个人返点 - 成本差异
|
||||
|
||||
## 16. 西瓜毛利
|
||||
- 计算公式: 市场报价 - 税费补贴 - 西瓜成本1 - 计提税金
|
||||
|
||||
## 17. 西瓜成本2
|
||||
- 计算公式: 西瓜成本1 + 成本差异
|
||||
|
||||
## 18. 成本差异
|
||||
- 计算公式: 直接取值
|
||||
- 数据来源: orderDealer.costDifference
|
||||
|
||||
## 19. 个人返点
|
||||
- 计算公式: 直接取值
|
||||
- 数据来源: orderRebate?.amount
|
||||
|
||||
## 20. 计提税金
|
||||
- 计算公式: 直接取值
|
||||
- 数据来源: orderDealer.taxProvision
|
||||
|
||||
## 21. 税费补贴
|
||||
- 计算公式: 直接取值
|
||||
- 数据来源: orderDealer.taxSubsidy
|
||||
|
||||
## 22. 市场报价
|
||||
- 计算公式: 销售金额 + 总包装费
|
||||
|
||||
## 23. 供应商采购成本
|
||||
- 计算公式: Σ(各供应商净重 * 采购价)
|
||||
- 数据来源: orderSupplierList
|
||||
|
||||
## 24. 辅料费/人工费等其他费用
|
||||
- 计算公式: Σ(各费用项目价格 * 数量)
|
||||
- 数据来源: orderCostList
|
||||
@ -28,6 +28,7 @@ config = {
|
||||
"reviewer/list",
|
||||
"reviewer/audit",
|
||||
"reviewer/history",
|
||||
"reviewer/submitted",
|
||||
|
||||
// 审批员(老板)
|
||||
"approver/list",
|
||||
@ -48,7 +49,7 @@ config = {
|
||||
},
|
||||
},
|
||||
tabBar: {
|
||||
custom: true,
|
||||
custom: false,
|
||||
color: "#000000",
|
||||
selectedColor: "#DC143C",
|
||||
backgroundColor: "#ffffff",
|
||||
|
||||
@ -3,7 +3,7 @@ import { useEffect, useMemo, useState } from "react";
|
||||
import { View } from "@tarojs/components";
|
||||
import { SafeArea, Tabbar } from "@nutui/nutui-react-taro";
|
||||
import { globalStore } from "@/store/global-store";
|
||||
import { Icon } from "@/components";
|
||||
import { CustomTheme, Icon } from "@/components";
|
||||
|
||||
interface ICustomTabBarProps {
|
||||
role: AuthAPI.UserRoleVO["slug"];
|
||||
@ -86,7 +86,7 @@ export default function CustomTabBar(props: ICustomTabBarProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<CustomTheme>
|
||||
<View
|
||||
style={{
|
||||
height: `calc(92rpx * ${scaleFactor})`,
|
||||
@ -115,6 +115,6 @@ export default function CustomTabBar(props: ICustomTabBarProps) {
|
||||
);
|
||||
})}
|
||||
</Tabbar>
|
||||
</>
|
||||
</CustomTheme>
|
||||
);
|
||||
}
|
||||
|
||||
@ -41,12 +41,12 @@ export function CustomTheme(props: CustomThemeProps) {
|
||||
>
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
nutuiCellGroupWrapMargin: "0rpx",
|
||||
nutuiCellGroupWrapMargin: "0",
|
||||
nutuiCellGroupTitleFontSize: "var(--text-lg)",
|
||||
nutuiCellGroupTitleLineHeight: "56rpx",
|
||||
nutuiCellGroupTitlePadding: "0rpx 32rpx",
|
||||
nutuiCellGroupTitleLineHeight: Taro.pxTransform(56),
|
||||
nutuiCellGroupTitlePadding: `0 ${Taro.pxTransform(32)}`,
|
||||
nutuiCellFontSize: "var(--text-xs)",
|
||||
nutuiCellLineHeight: "56rpx",
|
||||
nutuiCellLineHeight: Taro.pxTransform(56),
|
||||
nutuiFontSizeS: "var(--text-sm)",
|
||||
nutuiTabbarInactiveColor: "#6B7280",
|
||||
nutuiPriceLineColor: "var(--color-primary)",
|
||||
@ -57,21 +57,21 @@ export function CustomTheme(props: CustomThemeProps) {
|
||||
nutuiColorPrimaryDisabled: "#D1D5DB",
|
||||
nutuiColorPrimaryDisabledSpecial: "#D1D5DB",
|
||||
nutuiDisabledColor: "#D1D5DB",
|
||||
nutuiStepsBaseHeadTextSize: "36rpx",
|
||||
nutuiStepsBaseIconSize: "24rpx",
|
||||
nutuiStepsBaseHeadTextSize: Taro.pxTransform(36),
|
||||
nutuiStepsBaseIconSize: Taro.pxTransform(24),
|
||||
nutuiFontSizeBase: "var(--text-sm)",
|
||||
|
||||
nutuiFormItemRequiredColor: "#ff0f23",
|
||||
nutuiFormItemBodySlotsTextAlign: "right",
|
||||
nutuiNoticebarHeight: "84rpx",
|
||||
nutuiNoticebarHeight: Taro.pxTransform(84),
|
||||
|
||||
nutuiSwitchHeight: "54rpx",
|
||||
nutuiSwitchWidth: "54rpx",
|
||||
nutuiSwitchLabelFontSize: "28rpx",
|
||||
nutuiSwitchHeight: Taro.pxTransform(54),
|
||||
nutuiSwitchWidth: Taro.pxTransform(54),
|
||||
nutuiSwitchLabelFontSize: Taro.pxTransform(28),
|
||||
|
||||
// nutuiInputFontSize: "40rpx",
|
||||
nutuiInputBackgroundColor: "transparent",
|
||||
nutuiInputPadding: "0 40rpx",
|
||||
nutuiInputPadding: `0 ${Taro.pxTransform(40)}`,
|
||||
nutuiInputFontSize: "var(--text-sm)",
|
||||
nutuiInputLineheight:
|
||||
"var(--tw-leading, var(--text-sm--line-height))",
|
||||
@ -79,26 +79,26 @@ export function CustomTheme(props: CustomThemeProps) {
|
||||
|
||||
nutuiButtonXlargeHeight: "calc(var(--spacing) * 12)",
|
||||
nutuiButtonXlargeFontSize: "var(--text-lg)",
|
||||
nutuiButtonXlargePadding: "0rpx calc(var(--spacing) * 3)",
|
||||
nutuiButtonXlargePadding: "0 calc(var(--spacing) * 3)",
|
||||
nutuiButtonXlargeBorderRadius: "calc(var(--spacing) * 2)",
|
||||
|
||||
nutuiButtonLargeHeight: "calc(var(--spacing) * 10)",
|
||||
nutuiButtonLargeFontSize: "var(--text-base)",
|
||||
nutuiButtonLargePadding: "0rpx calc(var(--spacing) * 3)",
|
||||
nutuiButtonLargePadding: "0 calc(var(--spacing) * 3)",
|
||||
nutuiButtonLargeBorderRadius: "calc(var(--spacing) * 2)",
|
||||
|
||||
// --nutui-button-small-height
|
||||
nutuiButtonSmallHeight: "calc(var(--spacing) * 8)",
|
||||
nutuiButtonSmallFontSize: "var(--text-sm)",
|
||||
nutuiButtonSmallPadding: "0rpx calc(var(--spacing) * 3)",
|
||||
nutuiButtonSmallPadding: "0 calc(var(--spacing) * 3)",
|
||||
nutuiButtonSmallBorderRadius: "calc(var(--spacing) * 2)",
|
||||
|
||||
// --nutui-textarea-padding
|
||||
nutuiTextareaPadding: "0rpx",
|
||||
nutuiTextareaPadding: "0",
|
||||
|
||||
nutuiUploaderImageBorder: "2px dashed var(--color-gray-300)",
|
||||
nutuiUploaderImageBorder: `${Taro.pxTransform(4)} dashed var(--color-gray-300)`,
|
||||
nutuiUploaderImageWidth: "100%",
|
||||
nutuiUploaderImageHeight: "320rpx",
|
||||
nutuiUploaderImageHeight: Taro.pxTransform(320),
|
||||
nutuiUploaderPreviewMarginRight: "0",
|
||||
nutuiUploaderPreviewMarginBottom: "0",
|
||||
|
||||
@ -106,9 +106,7 @@ export function CustomTheme(props: CustomThemeProps) {
|
||||
nutuiTabbarHeight: "calc(92rpx * var(--scale-factor))",
|
||||
}}
|
||||
>
|
||||
<View className={"flex min-h-screen w-screen flex-col bg-neutral-100"}>
|
||||
{children}
|
||||
</View>
|
||||
</ConfigProvider>
|
||||
</View>
|
||||
);
|
||||
|
||||
@ -69,7 +69,6 @@ export default <T extends {}, Q extends Query = Query>(
|
||||
useImperativeHandle(actionRef, () => {
|
||||
return {
|
||||
reload: () => {
|
||||
setData([]);
|
||||
setQuery({
|
||||
...query,
|
||||
...params,
|
||||
@ -78,10 +77,9 @@ export default <T extends {}, Q extends Query = Query>(
|
||||
});
|
||||
},
|
||||
} as ActionType;
|
||||
}, [pagination.pageSize]);
|
||||
}, [pagination.pageSize, params]);
|
||||
|
||||
useEffect(() => {
|
||||
setData([]);
|
||||
setQuery({
|
||||
...query,
|
||||
...params,
|
||||
@ -119,11 +117,17 @@ export default <T extends {}, Q extends Query = Query>(
|
||||
request(query as any).then((res: Record<any>) => {
|
||||
const list = res.data;
|
||||
if (res.success) {
|
||||
if (data) {
|
||||
data.push(...(list as any));
|
||||
setData(data.slice());
|
||||
} else {
|
||||
if (query.pageIndex === 1) {
|
||||
// 如果是第一页,则替换数据
|
||||
setData(list as any);
|
||||
} else {
|
||||
// 如果不是第一页,则追加数据
|
||||
if (data) {
|
||||
data.push(...(list as any));
|
||||
setData(data.slice());
|
||||
} else {
|
||||
setData(list as any);
|
||||
}
|
||||
}
|
||||
if (res.hasMore !== undefined) {
|
||||
setHasMore(res.hasMore);
|
||||
@ -146,7 +150,7 @@ export default <T extends {}, Q extends Query = Query>(
|
||||
}
|
||||
};
|
||||
const refresh = async () => {
|
||||
actionRef.current.reload();
|
||||
await actionRef.current.reload();
|
||||
};
|
||||
|
||||
// 可视区域条数
|
||||
@ -211,10 +215,10 @@ export default <T extends {}, Q extends Query = Query>(
|
||||
}
|
||||
onClear={() => actionRef.current.reload()}
|
||||
onSearch={(val) => {
|
||||
setData([]);
|
||||
setQuery({
|
||||
...query,
|
||||
[searchType]: val == "" ? undefined : val,
|
||||
pageIndex: 1,
|
||||
});
|
||||
}}
|
||||
leftIn={
|
||||
|
||||
@ -2,7 +2,8 @@ import { ScrollView, Text, View } from "@tarojs/components";
|
||||
import { Button, Input, Popup, Radio, SafeArea } from "@nutui/nutui-react-taro";
|
||||
import dayjs from "dayjs";
|
||||
import { formatCurrency } from "@/utils/format";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import businessServices from "@/services/business";
|
||||
|
||||
export default function BasicInfoSection(props: {
|
||||
purchaseOrderVO: BusinessAPI.PurchaseOrderVO;
|
||||
@ -32,6 +33,58 @@ export default function BasicInfoSection(props: {
|
||||
strawCurtainPrice: orderVehicle?.strawCurtainPrice || 0,
|
||||
});
|
||||
|
||||
// 上一车次号
|
||||
const [lastVehicleNo, setLastVehicleNo] = useState<string | null>(null);
|
||||
// 是否正在获取上一车次号
|
||||
const [loadingLastVehicleNo, setLoadingLastVehicleNo] = useState(false);
|
||||
|
||||
// 获取上一车次号
|
||||
const fetchLastVehicleNo = async () => {
|
||||
// 如果已经有车次号,则不需要获取上一车次号
|
||||
if (orderVehicle?.vehicleNo) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 避免重复请求
|
||||
if (loadingLastVehicleNo || lastVehicleNo) {
|
||||
return;
|
||||
}
|
||||
|
||||
setLoadingLastVehicleNo(true);
|
||||
try {
|
||||
const { data: res } =
|
||||
await businessServices.purchaseOrder.getLastVehicleNo({
|
||||
lastVehicleNoQry: {},
|
||||
});
|
||||
|
||||
if (res.success && res.data) {
|
||||
setLastVehicleNo(res.data);
|
||||
|
||||
// 解析车次号中的数字部分并加1
|
||||
const numberPart = res.data.match(/(\d+)$/);
|
||||
if (numberPart) {
|
||||
const nextNumber = parseInt(numberPart[1]) + 1;
|
||||
const nextVehicleNo = res.data.replace(
|
||||
/(\d+)$/,
|
||||
nextNumber.toString(),
|
||||
);
|
||||
|
||||
// 更新车次号
|
||||
updateVehicleNo(nextVehicleNo);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取上一车次号失败:", error);
|
||||
} finally {
|
||||
setLoadingLastVehicleNo(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 组件加载时获取上一车次号
|
||||
useEffect(() => {
|
||||
fetchLastVehicleNo();
|
||||
}, []);
|
||||
|
||||
// 打开基础信息弹窗
|
||||
const openBasicInfoPopup = () => {
|
||||
if (readOnly) return;
|
||||
@ -83,7 +136,7 @@ export default function BasicInfoSection(props: {
|
||||
};
|
||||
|
||||
// 更新运费类型
|
||||
const updatePriceType = (value: BusinessAPI.OrderVehicle['priceType']) => {
|
||||
const updatePriceType = (value: BusinessAPI.OrderVehicle["priceType"]) => {
|
||||
if (onChange) {
|
||||
const updatedOrder = {
|
||||
...purchaseOrderVO,
|
||||
@ -96,6 +149,14 @@ export default function BasicInfoSection(props: {
|
||||
}
|
||||
};
|
||||
|
||||
// 显示的参考车次号
|
||||
const displayReferenceVehicleNo = () => {
|
||||
if (lastVehicleNo) {
|
||||
return lastVehicleNo;
|
||||
}
|
||||
return "获取中...";
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 基础信息编辑弹窗 */}
|
||||
@ -408,12 +469,21 @@ export default function BasicInfoSection(props: {
|
||||
<View className="text-neutral-darkest mb-1 text-sm font-bold">
|
||||
本车次号
|
||||
</View>
|
||||
<View className="flex flex-row items-center justify-between">
|
||||
{readOnly ? (
|
||||
{readOnly ? (
|
||||
<View
|
||||
className="flex flex-row items-center justify-between rounded-md p-4"
|
||||
style={{
|
||||
background: "linear-gradient(101deg, #F8FAFF 25%, #F0F5FF 74%)",
|
||||
}}
|
||||
>
|
||||
<View className="text-neutral-darkest text-sm font-medium">
|
||||
{orderVehicle?.vehicleNo || "请填写"}
|
||||
{orderVehicle?.vehicleNo
|
||||
? "第" + orderVehicle?.vehicleNo + "车"
|
||||
: "暂未生成车次"}
|
||||
</View>
|
||||
) : (
|
||||
</View>
|
||||
) : (
|
||||
<View className="flex flex-row items-center justify-between">
|
||||
<View
|
||||
className={`flex h-10 flex-1 items-center rounded-md border-4 border-gray-300`}
|
||||
>
|
||||
@ -424,11 +494,11 @@ export default function BasicInfoSection(props: {
|
||||
onChange={(value) => updateVehicleNo(value)}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
<View className="flex-1 text-center text-xs text-gray-500">
|
||||
参考上一车:xxx
|
||||
<View className="flex-1 text-center text-xs text-gray-500">
|
||||
参考上一车:{displayReferenceVehicleNo()}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* 运费信息 */}
|
||||
@ -442,7 +512,7 @@ export default function BasicInfoSection(props: {
|
||||
background: "linear-gradient(101deg, #F8FAFF 25%, #F0F5FF 74%)",
|
||||
}}
|
||||
>
|
||||
<Text className="text-red-500 pb-2 text-3xl font-bold">
|
||||
<Text className="pb-2 text-3xl font-bold text-red-500">
|
||||
{formatCurrency(orderVehicle?.price || 0)}
|
||||
</Text>
|
||||
<View className="text-neutral-darkest text-sm font-medium">
|
||||
@ -458,7 +528,11 @@ export default function BasicInfoSection(props: {
|
||||
<Radio.Group
|
||||
direction={"horizontal"}
|
||||
value={orderVehicle?.priceType}
|
||||
onChange={(value) => updatePriceType(value as BusinessAPI.OrderVehicle['priceType'])}
|
||||
onChange={(value) =>
|
||||
updatePriceType(
|
||||
value as BusinessAPI.OrderVehicle["priceType"],
|
||||
)
|
||||
}
|
||||
>
|
||||
<Radio value="MAIN_FREIGHT">主运费</Radio>
|
||||
<Radio value="SHORT_TRANSPORT">短驳费</Radio>
|
||||
@ -481,7 +555,7 @@ export default function BasicInfoSection(props: {
|
||||
}}
|
||||
>
|
||||
{orderVehicle?.openStrawCurtain ? (
|
||||
<Text className="text-red-500 pb-2 text-3xl font-bold">
|
||||
<Text className="pb-2 text-3xl font-bold text-red-500">
|
||||
{formatCurrency(orderVehicle?.strawCurtainPrice || 0)}
|
||||
</Text>
|
||||
) : (
|
||||
|
||||
@ -102,7 +102,7 @@ export default function CostDifferenceSection(props: {
|
||||
<Text className="text-sm text-gray-500">元</Text>
|
||||
</View>
|
||||
<View className="relative">
|
||||
<Text className="price-input text-primary w-full py-2 font-bold">
|
||||
<Text className="w-full py-2 text-3xl font-bold text-red-500">
|
||||
{costDifference || "0.00"}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@ -53,18 +53,15 @@ export default function CostSummarySection(props: {
|
||||
const columns = [
|
||||
{
|
||||
title: "成本合计",
|
||||
width: 100,
|
||||
key: "costType",
|
||||
},
|
||||
{
|
||||
title: "金额(元)",
|
||||
width: 30,
|
||||
key: "amount",
|
||||
align: "center",
|
||||
},
|
||||
{
|
||||
title: "单斤成本(元/斤)",
|
||||
width: 60,
|
||||
key: "unitCost",
|
||||
align: "center",
|
||||
},
|
||||
|
||||
@ -142,7 +142,7 @@ export default function MarketPriceSection(props: {
|
||||
const salePrice =
|
||||
editValues[supplier.orderSupplierId || ""]?.salePrice !== undefined
|
||||
? editValues[supplier.orderSupplierId || ""].salePrice
|
||||
: supplier.salePrice;
|
||||
: supplier.purchasePrice;
|
||||
return sum + (salePrice || 0);
|
||||
}, 0) / purchaseOrderVO.orderSupplierList.length
|
||||
: 0;
|
||||
@ -248,7 +248,7 @@ export default function MarketPriceSection(props: {
|
||||
<Text className="text-sm text-gray-500">元/斤</Text>
|
||||
</View>
|
||||
<View className="relative flex">
|
||||
<Text className="text-primary border-primary w-full border-b-2 pb-2 text-3xl font-bold focus:outline-none">
|
||||
<Text className="text-red-500 border-red-500 w-full border-b-2 pb-2 text-3xl font-bold focus:outline-none">
|
||||
{salePrice?.toFixed(2) || "0.00"}
|
||||
</Text>
|
||||
</View>
|
||||
@ -260,7 +260,7 @@ export default function MarketPriceSection(props: {
|
||||
<Text className="text-sm text-gray-500">元/斤</Text>
|
||||
</View>
|
||||
<View className="relative">
|
||||
<Text className="price-input text-primary w-full py-2 font-bold">
|
||||
<Text className="w-full py-2 text-3xl font-bold text-red-500">
|
||||
{salePrice?.toFixed(2) || "0.00"}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@ -242,7 +242,7 @@ export default function MaterialCostSection(props: {
|
||||
<Text className="text-sm text-gray-500">元</Text>
|
||||
</View>
|
||||
<View className="relative">
|
||||
<Text className="price-input text-primary w-full py-2 font-bold">
|
||||
<Text className="w-full py-2 text-3xl font-bold text-red-500">
|
||||
{amount.toFixed(2) || "0.00"}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@ -134,7 +134,7 @@ export default function RebateCalcSection(props: {
|
||||
<Text className="text-sm text-gray-500">元</Text>
|
||||
</View>
|
||||
<View className="relative">
|
||||
<Text className="price-input text-primary w-full py-2 font-bold">
|
||||
<Text className="w-full py-2 text-3xl font-bold text-red-500">
|
||||
{calculateRebateAmount(orderRebate).toFixed(2) || "0.00"}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@ -105,7 +105,7 @@ export default function TaxProvisionSection(props: {
|
||||
<Text className="text-sm text-gray-500">元</Text>
|
||||
</View>
|
||||
<View className="relative">
|
||||
<Text className="price-input text-primary w-full py-2 font-bold">
|
||||
<Text className="w-full py-2 text-3xl font-bold text-red-500">
|
||||
{taxProvision || "0.00"}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@ -99,7 +99,7 @@ export default function TaxSubsidySection(props: {
|
||||
<Text className="text-sm text-gray-500">元</Text>
|
||||
</View>
|
||||
<View className="relative">
|
||||
<Text className="price-input text-primary w-full py-2 font-bold">
|
||||
<Text className="w-full py-2 text-3xl font-bold text-red-500">
|
||||
{taxSubsidy || "0.00"}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@ -20,7 +20,7 @@ const hocAuth = (
|
||||
|
||||
const [longitude, setLongitude] = useState<number>();
|
||||
const [latitude, setLatitude] = useState<number>();
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
// 控制是否已初始化
|
||||
const [isInitialized, setIsInitialized] = useState(false);
|
||||
@ -53,36 +53,39 @@ const hocAuth = (
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
Taro.onUserCaptureScreen(function () {
|
||||
const query = buildParams({
|
||||
...router?.params,
|
||||
from: "screenshot",
|
||||
userId: user.userId,
|
||||
if (process.env.TARO_ENV === "weapp") {
|
||||
useEffect(() => {
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
|
||||
Taro.onUserCaptureScreen(function () {
|
||||
const query = buildParams({
|
||||
...router?.params,
|
||||
from: "screenshot",
|
||||
userId: user.userId,
|
||||
});
|
||||
|
||||
return {
|
||||
query: query, // 通过截屏图片打开小程序的query参数
|
||||
// promise: new Promise((resolve) => {
|
||||
// // 通过promise延时传递小程序的query参数
|
||||
// setTimeout(() => {
|
||||
// resolve({
|
||||
// query: query,
|
||||
// });
|
||||
// }, 1000); // 在1秒内对query进行解析
|
||||
// }),
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
query: query, // 通过截屏图片打开小程序的query参数
|
||||
// promise: new Promise((resolve) => {
|
||||
// // 通过promise延时传递小程序的query参数
|
||||
// setTimeout(() => {
|
||||
// resolve({
|
||||
// query: query,
|
||||
// });
|
||||
// }, 1000); // 在1秒内对query进行解析
|
||||
// }),
|
||||
return () => {
|
||||
Taro.offUserCaptureScreen(function (res) {
|
||||
console.log("取消截屏事件", res);
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
return () => {
|
||||
Taro.offUserCaptureScreen(function (res) {
|
||||
console.log("取消截屏事件", res);
|
||||
});
|
||||
};
|
||||
}, [user]);
|
||||
}, [user]);
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
if (skeleton) {
|
||||
|
||||
@ -6,6 +6,7 @@ import { userStore } from "@/store/user-store";
|
||||
import { Dialog, Toast } from "@nutui/nutui-react-taro";
|
||||
import { CustomTheme, ShareActionSheet } from "@/components";
|
||||
import { getCurrentInstance } from "@tarojs/runtime";
|
||||
import { View } from "@tarojs/components";
|
||||
|
||||
let startTime: number; // 记录开始时间
|
||||
let endTime: number; // 记录结束时间
|
||||
@ -97,20 +98,22 @@ const base = (Component: React.FC) => (props: any) => {
|
||||
return (
|
||||
<SWRConfig value={{ provider: () => map, revalidateOnFocus: false }}>
|
||||
<CustomTheme>
|
||||
<Component
|
||||
{...props}
|
||||
shareOptions={shareOptions}
|
||||
router={router}
|
||||
options={options}
|
||||
/>
|
||||
<Toast id="toast" />
|
||||
<Dialog id="dialog" />
|
||||
<ShareActionSheet
|
||||
visible={shareVisible}
|
||||
setVisible={setShareVisible}
|
||||
onCancel={() => setShareVisible(false)}
|
||||
callback={shareOptions?.callback}
|
||||
/>
|
||||
<View className={"flex min-h-screen w-screen flex-col bg-neutral-100"}>
|
||||
<Component
|
||||
{...props}
|
||||
shareOptions={shareOptions}
|
||||
router={router}
|
||||
options={options}
|
||||
/>
|
||||
<Toast id="toast" />
|
||||
<Dialog id="dialog" />
|
||||
<ShareActionSheet
|
||||
visible={shareVisible}
|
||||
setVisible={setShareVisible}
|
||||
onCancel={() => setShareVisible(false)}
|
||||
callback={shareOptions?.callback}
|
||||
/>
|
||||
</View>
|
||||
</CustomTheme>
|
||||
</SWRConfig>
|
||||
);
|
||||
|
||||
@ -338,12 +338,12 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
|
||||
{/* 选择身份 */}
|
||||
<Popup
|
||||
className={"w-full"}
|
||||
closeable
|
||||
destroyOnClose
|
||||
duration={150}
|
||||
visible={visible}
|
||||
title="选择身份"
|
||||
position="bottom"
|
||||
onClose={async () => {
|
||||
if (visible) {
|
||||
setVisible(false);
|
||||
|
||||
@ -4,130 +4,15 @@ import Taro, { useDidShow } from "@tarojs/taro";
|
||||
import { business } from "@/services";
|
||||
import { useEffect, useState } from "react";
|
||||
import { View } from "@tarojs/components";
|
||||
import purchaseOrder from "@/constant/purchaseOrder";
|
||||
import { SafeArea } from "@nutui/nutui-react-taro";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
Input,
|
||||
SafeArea,
|
||||
Toast,
|
||||
} from "@nutui/nutui-react-taro";
|
||||
import {
|
||||
BasicInfoSection,
|
||||
CompanyInfoSection,
|
||||
CostSummarySection,
|
||||
DealerInfoSection,
|
||||
EmptyBoxInfoSection,
|
||||
LaborInfoSection,
|
||||
MarketPriceSection,
|
||||
PackageInfoSection,
|
||||
PackagingCostSection,
|
||||
PurchaseCostInfoSection,
|
||||
PurchaseOrderFinalApprove,
|
||||
PurchaseOrderRejectApprove,
|
||||
PurchaseOrderRejectFinal,
|
||||
RebateCalcSection,
|
||||
SupplierInfoSection,
|
||||
} from "@/components";
|
||||
import buildUrl from "@/utils/buildUrl";
|
||||
|
||||
const sectionList = {
|
||||
// 市场报价
|
||||
marketPrice: MarketPriceSection,
|
||||
// 销售方信息卡片
|
||||
supplierInfo: CompanyInfoSection,
|
||||
// 下游经销商信息
|
||||
dealerInfo: DealerInfoSection,
|
||||
// 基础信息
|
||||
basicInfo: BasicInfoSection,
|
||||
// 瓜农信息
|
||||
farmerInfo: SupplierInfoSection,
|
||||
// 采购成本
|
||||
purchaseCostInfo: PurchaseCostInfoSection,
|
||||
// 包装纸箱费
|
||||
packageInfo: PackageInfoSection,
|
||||
// 空箱费用
|
||||
emptyBoxInfo: EmptyBoxInfoSection,
|
||||
// 用工信息
|
||||
laborInfo: LaborInfoSection,
|
||||
// 包装费
|
||||
packagingCost: PackagingCostSection,
|
||||
// 成本合计
|
||||
costSummary: CostSummarySection,
|
||||
// 返点计算
|
||||
rebateCalc: RebateCalcSection,
|
||||
};
|
||||
|
||||
const sectionConfig = {
|
||||
marketPrice: {
|
||||
title: "市场报价",
|
||||
containerClass: "border-l-8 border-l-lime-500",
|
||||
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
|
||||
},
|
||||
supplierInfo: {
|
||||
title: "销售方信息",
|
||||
containerClass: "border-l-8 border-l-blue-500",
|
||||
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
|
||||
},
|
||||
dealerInfo: {
|
||||
title: "下游经销商信息",
|
||||
containerClass: "border-l-8 border-l-green-500",
|
||||
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
|
||||
},
|
||||
basicInfo: {
|
||||
title: "基础信息",
|
||||
containerClass: "border-l-8 border-l-yellow-500",
|
||||
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
|
||||
},
|
||||
farmerInfo: {
|
||||
title: "瓜农信息",
|
||||
containerClass: "border-l-8 border-l-purple-500",
|
||||
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
|
||||
},
|
||||
purchaseCostInfo: {
|
||||
title: "采购成本",
|
||||
containerClass: "border-l-8 border-l-red-500",
|
||||
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
|
||||
},
|
||||
packageInfo: {
|
||||
title: "包装纸箱费",
|
||||
containerClass: "border-l-8 border-l-indigo-500",
|
||||
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
|
||||
},
|
||||
emptyBoxInfo: {
|
||||
title: "空箱费用",
|
||||
containerClass: "border-l-8 border-l-pink-500",
|
||||
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
|
||||
},
|
||||
laborInfo: {
|
||||
title: "用工信息",
|
||||
containerClass: "border-l-8 border-l-teal-500",
|
||||
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
|
||||
},
|
||||
packagingCost: {
|
||||
title: "包装费",
|
||||
containerClass: "border-l-8 border-l-orange-500",
|
||||
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
|
||||
},
|
||||
costSummary: {
|
||||
title: "成本合计",
|
||||
containerClass: "border-l-8 border-l-cyan-500",
|
||||
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
|
||||
},
|
||||
rebateCalc: {
|
||||
title: "返点计算",
|
||||
containerClass: "border-l-8 border-l-amber-500",
|
||||
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
|
||||
},
|
||||
profitCalc: {
|
||||
title: "利润计算",
|
||||
containerClass: "border-l-8 border-l-emerald-500",
|
||||
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
|
||||
},
|
||||
};
|
||||
|
||||
export default hocAuth(function Page(props: CommonComponent) {
|
||||
const { router, isInitialized, setIsInitialized, role } = props;
|
||||
const { router, isInitialized, setIsInitialized } = props;
|
||||
|
||||
const orderId = router.params
|
||||
.orderId as BusinessAPI.PurchaseOrderVO["orderId"];
|
||||
@ -135,147 +20,6 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
const [purchaseOrderVO, setPurchaseOrderVO] =
|
||||
useState<BusinessAPI.PurchaseOrderVO>();
|
||||
|
||||
const [originPrincipal, setOriginPrincipal] = useState("");
|
||||
|
||||
console.log("purchaseOrderVO-----", purchaseOrderVO);
|
||||
|
||||
const [collapsedSections, setCollapsedSections] = useState<
|
||||
Record<string, boolean>
|
||||
>({
|
||||
supplierInfo: false,
|
||||
dealerInfo: false,
|
||||
basicInfo: false,
|
||||
farmerInfo: false,
|
||||
purchaseCostInfo: false,
|
||||
packageInfo: false,
|
||||
emptyBoxInfo: false,
|
||||
laborInfo: false,
|
||||
packagingCost: false,
|
||||
costSummary: false,
|
||||
marketPrice: false,
|
||||
rebateCalc: false,
|
||||
profitCalc: false,
|
||||
});
|
||||
|
||||
// 暂存和提交审核的Dialog状态
|
||||
const [saveDialogVisible, setSaveDialogVisible] = useState(false);
|
||||
const [submitDialogVisible, setSubmitDialogVisible] = useState(false);
|
||||
|
||||
const toggleSection = (section: string) => {
|
||||
setCollapsedSections((prev) => ({
|
||||
...prev,
|
||||
[section]: !prev[section],
|
||||
}));
|
||||
};
|
||||
|
||||
// 暂存操作
|
||||
const handleSave = () => {
|
||||
setSaveDialogVisible(true);
|
||||
};
|
||||
|
||||
// 确认暂存
|
||||
const confirmSave = async () => {
|
||||
// 关闭对话框
|
||||
setSaveDialogVisible(false);
|
||||
|
||||
// 这里应该调用暂存API
|
||||
console.log("暂存数据:", purchaseOrderVO);
|
||||
|
||||
const { data } = await business.purchaseOrder.approvePurchaseOrder({
|
||||
...purchaseOrderVO!,
|
||||
draft: true,
|
||||
});
|
||||
|
||||
if (data.success) {
|
||||
Toast.show("toast", {
|
||||
icon: "success",
|
||||
title: "提示",
|
||||
content: "暂存成功",
|
||||
});
|
||||
// 返回采购单页面
|
||||
Taro.redirectTo({ url: "/pages/purchase/approver/list" });
|
||||
}
|
||||
};
|
||||
|
||||
// 提交老板审核操作
|
||||
const handleSubmit = () => {
|
||||
setSubmitDialogVisible(true);
|
||||
};
|
||||
|
||||
// 确认提交老板审核
|
||||
const confirmSubmit = async () => {
|
||||
// 关闭对话框
|
||||
setSubmitDialogVisible(false);
|
||||
|
||||
// 表单校验
|
||||
const errorMsg = validateForm();
|
||||
if (errorMsg) {
|
||||
Toast.show("toast", {
|
||||
icon: "fail",
|
||||
title: "校验失败",
|
||||
content: errorMsg,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 这里应该调用提交审核API
|
||||
console.log("提交老板审核:", purchaseOrderVO);
|
||||
|
||||
const { data } = await business.purchaseOrder.approvePurchaseOrder({
|
||||
...purchaseOrderVO!,
|
||||
draft: false,
|
||||
});
|
||||
|
||||
if (data.success) {
|
||||
Toast.show("toast", {
|
||||
icon: "success",
|
||||
title: "提示",
|
||||
content: "暂存成功",
|
||||
});
|
||||
// 返回采购单页面
|
||||
Taro.redirectTo({ url: "/pages/purchase/approver/list" });
|
||||
}
|
||||
};
|
||||
|
||||
// 表单校验
|
||||
const validateForm = () => {
|
||||
// 校验销售方
|
||||
if (!purchaseOrderVO?.orderCompany?.companyId) {
|
||||
return "请选择销售方";
|
||||
}
|
||||
|
||||
// 校验经销商
|
||||
if (!purchaseOrderVO?.orderDealer?.dealerId) {
|
||||
return "请选择经销商";
|
||||
}
|
||||
|
||||
// 校验本车次号
|
||||
if (!purchaseOrderVO?.orderVehicle?.vehicleNo) {
|
||||
return "请输入本车次号";
|
||||
}
|
||||
|
||||
// 校验运费类型
|
||||
if (!purchaseOrderVO?.orderVehicle?.priceType) {
|
||||
return "请选择运费类型";
|
||||
}
|
||||
|
||||
// 校验市场报价的报价方式
|
||||
if (!purchaseOrderVO?.pricingMethod) {
|
||||
return "请选择市场报价的报价方式";
|
||||
}
|
||||
|
||||
// 校验市场报价的销售单价
|
||||
purchaseOrderVO.orderSupplierList.forEach(
|
||||
(supplier: BusinessAPI.OrderSupplier) => {
|
||||
if (!supplier.salePrice || supplier.salePrice <= 0) {
|
||||
return "请填写市场报价的销售单价";
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const init = async (orderId: BusinessAPI.PurchaseOrderVO["orderId"]) => {
|
||||
const { data } = await business.purchaseOrder.showPurchaseOrder({
|
||||
purchaseOrderShowQry: {
|
||||
@ -307,101 +51,120 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
}
|
||||
|
||||
return (
|
||||
<View className={"flex flex-col gap-2.5"} id={"purchase-order-audit"}>
|
||||
{/* 顶部导航 */}
|
||||
<View className="bg-white p-4">
|
||||
{/* 展示产地负责人*/}
|
||||
<View className="mb-3 flex flex-row items-center gap-3">
|
||||
<View className="flex-shrink-0 text-base font-bold text-gray-900">
|
||||
产地负责人
|
||||
</View>
|
||||
|
||||
<View
|
||||
className={`flex h-12 w-full items-center rounded-lg border-2 border-gray-300 bg-gray-50 px-3`}
|
||||
>
|
||||
<Input
|
||||
type="text"
|
||||
disabled={
|
||||
role === "boss" ||
|
||||
(role === "reviewer" &&
|
||||
purchaseOrderVO.state !== "WAITING_AUDIT")
|
||||
}
|
||||
placeholder="请输入产地负责人姓名"
|
||||
value={originPrincipal || purchaseOrderVO.createdByName}
|
||||
onChange={(value) => setOriginPrincipal(value)}
|
||||
onBlur={() => {
|
||||
// 更新采购订单中的产地负责人
|
||||
setPurchaseOrderVO((prev) => ({
|
||||
...prev!,
|
||||
originPrincipal: originPrincipal,
|
||||
}));
|
||||
}}
|
||||
className="w-full bg-transparent"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className="mb-2 rounded-lg bg-blue-50 p-3 text-lg font-bold text-gray-800">
|
||||
{`第${purchaseOrderVO?.orderVehicle?.vehicleNo || "-"}车 - ${purchaseOrderVO?.orderVehicle.origin} 至 ${purchaseOrderVO?.orderVehicle.destination}`}
|
||||
</View>
|
||||
{purchaseOrderVO?.orderDealer && (
|
||||
<View className="text-neutral-darker mb-1 text-sm font-medium">
|
||||
经销商名称: {purchaseOrderVO?.orderDealer?.shortName}
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View className="inline-block rounded-md border-1 bg-gradient-to-r from-amber-500 to-orange-500 px-2 py-1 text-sm font-bold text-white shadow">
|
||||
{
|
||||
purchaseOrder.stateList.find(
|
||||
(item) => item.value === purchaseOrderVO?.state,
|
||||
)?.title
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 循环渲染各部分内容 */}
|
||||
{Object.keys(sectionList).map((sectionKey) => {
|
||||
const SectionComponent = sectionList[sectionKey];
|
||||
const config = sectionConfig[sectionKey];
|
||||
const isCollapsed = collapsedSections[sectionKey];
|
||||
|
||||
return (
|
||||
<View className="overflow-hidden bg-white" key={sectionKey}>
|
||||
<View
|
||||
className={`z-10 flex items-center justify-between p-4 shadow-sm ${config.containerClass}`}
|
||||
onClick={() => toggleSection(sectionKey)}
|
||||
>
|
||||
<View className="text-base font-bold text-gray-800">
|
||||
{config.title}
|
||||
<>
|
||||
<View
|
||||
className={"flex flex-1 flex-col gap-2.5 p-2.5"}
|
||||
id={"purchase-order-approve"}
|
||||
>
|
||||
<View>
|
||||
<View className="flex justify-between space-x-2.5">
|
||||
<View className="price-card flex-1 rounded-lg bg-white p-3">
|
||||
<View className="mb-1 text-center text-sm text-gray-500">
|
||||
收购单价
|
||||
</View>
|
||||
<View className="text-neutral-darker text-lg">
|
||||
{isCollapsed ? "▲" : "▼"}
|
||||
<View className="text-center">
|
||||
<View className="price-highlight text-primary">5.80</View>
|
||||
<View className="text-sm text-gray-500">元/斤</View>
|
||||
</View>
|
||||
</View>
|
||||
{!isCollapsed && (
|
||||
<View className={config.contentClass}>
|
||||
<SectionComponent
|
||||
purchaseOrderVO={purchaseOrderVO}
|
||||
onChange={setPurchaseOrderVO}
|
||||
readOnly={
|
||||
role === "boss" ||
|
||||
(role === "reviewer" &&
|
||||
purchaseOrderVO.state !== "WAITING_AUDIT")
|
||||
}
|
||||
/>
|
||||
<View className="price-card flex-1 rounded-lg bg-white p-3">
|
||||
<View className="mb-1 text-center text-sm text-gray-500">
|
||||
市场报价
|
||||
</View>
|
||||
)}
|
||||
<View className="text-center">
|
||||
<View className="price-highlight text-green-500">8.20</View>
|
||||
<View className="text-sm text-gray-500">元/斤</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
<View className="overflow-hidden rounded-lg bg-white shadow-sm">
|
||||
<View className="border-b border-gray-100 px-4 py-3">
|
||||
<View className="text-sm font-bold">成本明细</View>
|
||||
</View>
|
||||
<View className="grid grid-cols-2 divide-x divide-y divide-gray-100">
|
||||
<View className="cost-item flex flex-col px-3 py-2">
|
||||
<View className="text-sm text-gray-500">西瓜采购成本</View>
|
||||
<View className="font-medium">
|
||||
¥{purchaseOrderVO.orderVehicle.price}
|
||||
</View>
|
||||
</View>
|
||||
<View className="cost-item flex flex-col px-3 py-2">
|
||||
<View className="text-sm text-gray-500">运输费</View>
|
||||
<View className="font-medium">
|
||||
¥{purchaseOrderVO.orderVehicle.price}
|
||||
</View>
|
||||
</View>
|
||||
<View className="cost-item flex flex-col px-3 py-2">
|
||||
<View className="text-sm text-gray-500">商标费</View>
|
||||
<View className="font-medium">¥850</View>
|
||||
</View>
|
||||
<View className="cost-item flex flex-col px-3 py-2">
|
||||
<View className="text-sm text-gray-500">网套</View>
|
||||
<View className="font-medium">¥1,500</View>
|
||||
</View>
|
||||
<View className="cost-item flex flex-col px-3 py-2">
|
||||
<View className="text-sm text-gray-500">人工</View>
|
||||
<View className="font-medium">¥3,500</View>
|
||||
<View className="text-xs text-gray-500">工头: 张三</View>
|
||||
</View>
|
||||
<View className="cost-item flex flex-col px-3 py-2">
|
||||
<View className="text-sm text-gray-500">计提费</View>
|
||||
<View className="font-medium">¥600</View>
|
||||
</View>
|
||||
<View className="cost-item flex flex-col px-3 py-2">
|
||||
<View className="text-sm text-gray-500">空箱费</View>
|
||||
<View className="font-medium">¥300</View>
|
||||
</View>
|
||||
<View className="cost-item flex flex-col px-3 py-2">
|
||||
<View className="text-sm text-gray-500">代办费</View>
|
||||
<View className="font-medium">¥800</View>
|
||||
</View>
|
||||
<View className="cost-item flex flex-col px-3 py-2">
|
||||
<View className="text-sm text-gray-500">打码</View>
|
||||
<View className="font-medium">¥200</View>
|
||||
</View>
|
||||
<View className="cost-item flex flex-col px-3 py-2">
|
||||
<View className="text-sm text-gray-500">果多美返点</View>
|
||||
<View className="font-medium">
|
||||
¥{purchaseOrderVO.orderDealer.taxSubsidy}
|
||||
</View>
|
||||
</View>
|
||||
<View className="cost-item flex flex-col px-3 py-2">
|
||||
<View className="text-sm text-gray-500">计提税金</View>
|
||||
<View className="font-medium">
|
||||
¥{purchaseOrderVO.orderDealer.taxSubsidy}
|
||||
</View>
|
||||
</View>
|
||||
<View className="cost-total col-span-2 grid grid-cols-2 bg-yellow-50 px-3 py-2">
|
||||
<View className="flex flex-col">
|
||||
<View className="text-sm text-gray-500">成本合计</View>
|
||||
<View className="font-bold">¥11,150</View>
|
||||
</View>
|
||||
<View className="flex flex-col">
|
||||
<View className="text-sm text-gray-500">成本单价</View>
|
||||
<View className="font-bold">
|
||||
¥0.58 <View className="text-sm text-gray-500">元/斤</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<View className="rounded-lg bg-white p-2.5 shadow-sm">
|
||||
<View className="flex items-center justify-between">
|
||||
<View className="text-gray-500">分成利润</View>
|
||||
<View className="profit-highlight">¥4,250</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 按钮操作 */}
|
||||
<View className={"sticky bottom-0 z-10 bg-white"} id={"bottomBar"}>
|
||||
<View className="flex justify-between gap-2 border-t border-gray-200 p-2.5">
|
||||
{role === "reviewer" && purchaseOrderVO.state === "WAITING_AUDIT" && (
|
||||
{purchaseOrderVO.state === "WAITING_BOSS_APPROVE" && (
|
||||
<>
|
||||
<View className={"flex-1"}>
|
||||
<PurchaseOrderRejectApprove
|
||||
<PurchaseOrderRejectFinal
|
||||
purchaseOrderVO={purchaseOrderVO}
|
||||
size={"xlarge"}
|
||||
onFinish={() => {
|
||||
@ -411,79 +174,24 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
/>
|
||||
</View>
|
||||
<View className={"flex-1"}>
|
||||
<Button
|
||||
block
|
||||
type={"default"}
|
||||
<PurchaseOrderFinalApprove
|
||||
purchaseOrderVO={purchaseOrderVO}
|
||||
size={"xlarge"}
|
||||
className="bg-gray-200 text-gray-700"
|
||||
onClick={handleSave}
|
||||
>
|
||||
暂存
|
||||
</Button>
|
||||
</View>
|
||||
<View className={"flex-1"}>
|
||||
<Button
|
||||
block
|
||||
type={"primary"}
|
||||
size={"xlarge"}
|
||||
className="bg-primary text-white"
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
提交老板审核
|
||||
</Button>
|
||||
onFinish={() => {
|
||||
// 关闭当前页面并跳转到采购单审核通过页面
|
||||
Taro.redirectTo({
|
||||
url: buildUrl(`/pages/purchase/approver/approved`, {
|
||||
orderId: purchaseOrderVO?.orderId,
|
||||
}),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
{role === "boss" &&
|
||||
purchaseOrderVO.state === "WAITING_BOSS_APPROVE" && (
|
||||
<>
|
||||
<View className={"flex-1"}>
|
||||
<PurchaseOrderRejectFinal
|
||||
purchaseOrderVO={purchaseOrderVO}
|
||||
size={"xlarge"}
|
||||
onFinish={() => {
|
||||
// 返回首页
|
||||
Taro.redirectTo({ url: "/pages/purchase/approver/list" });
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<View className={"flex-1"}>
|
||||
<PurchaseOrderFinalApprove
|
||||
purchaseOrderVO={purchaseOrderVO}
|
||||
size={"xlarge"}
|
||||
onFinish={() => {
|
||||
// 关闭当前页面并跳转到采购单审核通过页面
|
||||
Taro.redirectTo({
|
||||
url: buildUrl(`/pages/purchase/approver/approved`, {
|
||||
orderId: purchaseOrderVO?.orderId,
|
||||
}),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
<SafeArea position={"bottom"} />
|
||||
</View>
|
||||
|
||||
{/* 暂存确认对话框 */}
|
||||
<Dialog
|
||||
visible={saveDialogVisible}
|
||||
title="确认暂存"
|
||||
content="确定要暂存当前采购订单吗?"
|
||||
onCancel={() => setSaveDialogVisible(false)}
|
||||
onConfirm={confirmSave}
|
||||
/>
|
||||
|
||||
{/* 提交审核确认对话框 */}
|
||||
<Dialog
|
||||
visible={submitDialogVisible}
|
||||
title="提交审核"
|
||||
content="确定要提交给老板审核吗?"
|
||||
onCancel={() => setSubmitDialogVisible(false)}
|
||||
onConfirm={confirmSubmit}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
@ -168,7 +168,9 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
<View className="flex flex-row justify-between">
|
||||
<Text className="text-sm text-gray-600">车次号:</Text>
|
||||
<Text className="text-sm font-medium">
|
||||
{purchaseOrder?.orderVehicle?.vehicleNo || "-"}
|
||||
{purchaseOrder?.orderVehicle?.vehicleNo
|
||||
? "第" + purchaseOrder?.orderVehicle?.vehicleNo + "车"
|
||||
: "暂未生成车次"}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
|
||||
@ -160,8 +160,9 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
<Text
|
||||
className={"text-neutral-darkest text-xl font-bold"}
|
||||
>
|
||||
{purchaseOrderVO.orderVehicle?.vehicleNo ||
|
||||
"暂未生成车次"}
|
||||
{purchaseOrderVO.orderVehicle?.vehicleNo
|
||||
? "第" + purchaseOrderVO.orderVehicle?.vehicleNo + "车"
|
||||
: "暂未生成车次"}
|
||||
</Text>
|
||||
</View>
|
||||
<Text className={"text-neutral-dark text-sm"}>
|
||||
|
||||
@ -53,8 +53,9 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
<Text
|
||||
className={"text-neutral-darkest text-xl font-bold"}
|
||||
>
|
||||
{purchaseOrderVO.orderVehicle?.vehicleNo ||
|
||||
"暂未生成车次"}
|
||||
{purchaseOrderVO.orderVehicle?.vehicleNo
|
||||
? "第" + purchaseOrderVO.orderVehicle?.vehicleNo + "车"
|
||||
: "暂未生成车次"}
|
||||
</Text>
|
||||
</View>
|
||||
<Text className={"text-neutral-dark text-sm"}>
|
||||
|
||||
@ -84,8 +84,9 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
<Text
|
||||
className={"text-neutral-darkest text-xl font-bold"}
|
||||
>
|
||||
{purchaseOrderVO.orderVehicle?.vehicleNo ||
|
||||
"暂未生成车次"}
|
||||
{purchaseOrderVO.orderVehicle?.vehicleNo
|
||||
? "第" + purchaseOrderVO.orderVehicle?.vehicleNo + "车"
|
||||
: "暂未生成车次"}
|
||||
</Text>
|
||||
</View>
|
||||
<Text className={"text-neutral-dark text-sm"}>
|
||||
|
||||
@ -160,8 +160,9 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
<Text
|
||||
className={"text-neutral-darkest text-xl font-bold"}
|
||||
>
|
||||
{purchaseOrderVO.orderVehicle?.vehicleNo ||
|
||||
"暂未生成车次"}
|
||||
{purchaseOrderVO.orderVehicle?.vehicleNo
|
||||
? "第" + purchaseOrderVO.orderVehicle?.vehicleNo + "车"
|
||||
: "暂未生成车次"}
|
||||
</Text>
|
||||
</View>
|
||||
<Text className={"text-neutral-dark text-sm"}>
|
||||
|
||||
@ -31,6 +31,7 @@ import {
|
||||
TaxSubsidySection,
|
||||
} from "@/components";
|
||||
import { getPersonalProfit } from "@/utils/calcutePurchaseOrder";
|
||||
import buildUrl from "@/utils/buildUrl";
|
||||
|
||||
const sections = {
|
||||
// 市场报价
|
||||
@ -248,10 +249,14 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
Toast.show("toast", {
|
||||
icon: "success",
|
||||
title: "提示",
|
||||
content: "暂存成功",
|
||||
content: "提交审核成功",
|
||||
});
|
||||
// 跳转到提交结果页面
|
||||
Taro.redirectTo({
|
||||
url: buildUrl("/pages/purchase/reviewer/submitted", {
|
||||
orderId: purchaseOrderVO!.orderId
|
||||
})
|
||||
});
|
||||
// 返回采购单页面
|
||||
Taro.redirectTo({ url: "/pages/purchase/reviewer/list" });
|
||||
}
|
||||
};
|
||||
|
||||
@ -452,7 +457,7 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
{/* 按钮操作 */}
|
||||
<View className={"sticky bottom-0 z-10 bg-white"} id={"bottomBar"}>
|
||||
<View className="flex justify-between gap-2 border-t border-gray-200 p-2.5">
|
||||
{role === "reviewer" && purchaseOrderVO.state === "WAITING_AUDIT" && (
|
||||
{purchaseOrderVO.state === "WAITING_AUDIT" && (
|
||||
<>
|
||||
<View className={"flex-1"}>
|
||||
<PurchaseOrderRejectApprove
|
||||
@ -488,6 +493,22 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
{purchaseOrderVO.state === "WAITING_BOSS_APPROVE" && (
|
||||
<>
|
||||
<View className={"flex-1"}>
|
||||
<Button
|
||||
block
|
||||
type={"default"}
|
||||
size={"xlarge"}
|
||||
onClick={() => {
|
||||
Taro.redirectTo({ url: "/pages/main/index/index" });
|
||||
}}
|
||||
>
|
||||
返回首页
|
||||
</Button>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
<SafeArea position={"bottom"} />
|
||||
</View>
|
||||
|
||||
@ -160,8 +160,11 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
<Text
|
||||
className={"text-neutral-darkest text-xl font-bold"}
|
||||
>
|
||||
{purchaseOrderVO.orderVehicle?.vehicleNo ||
|
||||
"暂未生成车次"}
|
||||
{purchaseOrderVO.orderVehicle?.vehicleNo
|
||||
? "第" +
|
||||
purchaseOrderVO.orderVehicle?.vehicleNo +
|
||||
"车"
|
||||
: "暂未生成车次"}
|
||||
</Text>
|
||||
</View>
|
||||
<Text className={"text-neutral-dark text-sm"}>
|
||||
@ -300,6 +303,24 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
</Button>
|
||||
</View>
|
||||
)}
|
||||
{purchaseOrderVO.state === "WAITING_BOSS_APPROVE" && (
|
||||
<View className={"flex flex-row justify-end gap-2"}>
|
||||
<Button
|
||||
type={"primary"}
|
||||
size={"small"}
|
||||
onClick={(e) => {
|
||||
Taro.navigateTo({
|
||||
url: buildUrl("/pages/purchase/reviewer/submitted", {
|
||||
orderId: purchaseOrderVO.orderId,
|
||||
}),
|
||||
});
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
审核结果
|
||||
</Button>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@ -53,8 +53,9 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
<Text
|
||||
className={"text-neutral-darkest text-xl font-bold"}
|
||||
>
|
||||
{purchaseOrderVO.orderVehicle?.vehicleNo ||
|
||||
"暂未生成车次"}
|
||||
{purchaseOrderVO.orderVehicle?.vehicleNo
|
||||
? "第" + purchaseOrderVO.orderVehicle?.vehicleNo + "车"
|
||||
: "暂未生成车次"}
|
||||
</Text>
|
||||
</View>
|
||||
<Text className={"text-neutral-dark text-sm"}>
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: "提交审核结果",
|
||||
navigationBarBackgroundColor: "#fff",
|
||||
});
|
||||
151
packages/app-client/src/pages/purchase/reviewer/submitted.tsx
Normal file
151
packages/app-client/src/pages/purchase/reviewer/submitted.tsx
Normal file
@ -0,0 +1,151 @@
|
||||
import hocAuth from "@/hocs/auth";
|
||||
import { CommonComponent } from "@/types/typings";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Text, View } from "@tarojs/components";
|
||||
import { Button, SafeArea, Toast } from "@nutui/nutui-react-taro";
|
||||
import Taro from "@tarojs/taro";
|
||||
import { business } from "@/services";
|
||||
import buildUrl from "@/utils/buildUrl";
|
||||
|
||||
export default hocAuth(function Page(props: CommonComponent) {
|
||||
const { router, setLoading } = props;
|
||||
const orderId = router.params.orderId as string;
|
||||
|
||||
const [purchaseOrder, setPurchaseOrder] =
|
||||
useState<BusinessAPI.PurchaseOrderVO>();
|
||||
|
||||
const init = async (orderId: string) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
// 获取采购单信息
|
||||
const { data: purchaseData } =
|
||||
await business.purchaseOrder.showPurchaseOrder({
|
||||
purchaseOrderShowQry: {
|
||||
orderId: orderId,
|
||||
},
|
||||
});
|
||||
|
||||
if (purchaseData.success) {
|
||||
setPurchaseOrder(purchaseData.data);
|
||||
}
|
||||
} catch (error) {
|
||||
Toast.show("toast", {
|
||||
icon: "fail",
|
||||
title: "提示",
|
||||
content: "获取采购单信息失败",
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (orderId) {
|
||||
init(orderId);
|
||||
}
|
||||
}, [orderId]);
|
||||
|
||||
// 查看采购单详情
|
||||
const viewPurchaseOrderDetail = () => {
|
||||
if (purchaseOrder?.orderId) {
|
||||
Taro.navigateTo({
|
||||
url: buildUrl("/pages/purchase/reviewer/audit", {
|
||||
orderId: purchaseOrder.orderId,
|
||||
}),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="flex flex-1 flex-col gap-2.5">
|
||||
<View className="flex flex-1 flex-col items-center justify-start bg-gray-100 p-2.5">
|
||||
<View className="mb-2.5 flex h-16 w-16 items-center justify-center rounded-full bg-green-100">
|
||||
<Text className="text-2xl text-green-600">✓</Text>
|
||||
</View>
|
||||
|
||||
<View className="mb-2.5 text-xl font-bold text-gray-800">
|
||||
采购单已提交审核
|
||||
</View>
|
||||
|
||||
<View className="mb-2.5 text-sm text-gray-600">等待老板审核中</View>
|
||||
|
||||
<View className="mb-2.5 text-xs text-gray-400">
|
||||
您可以通过以下操作继续处理
|
||||
</View>
|
||||
|
||||
<View className="w-full rounded-lg bg-white p-2.5 shadow-md">
|
||||
<View className="mb-2.5 border-b border-gray-200 pb-2">
|
||||
<Text className="text-lg font-semibold">单据信息</Text>
|
||||
</View>
|
||||
|
||||
<View className="mb-2.5 flex flex-col gap-2.5">
|
||||
<View className="flex flex-row justify-between">
|
||||
<Text className="text-sm text-gray-600">采购单号:</Text>
|
||||
<Text className="text-sm font-medium">
|
||||
{purchaseOrder?.orderSn || "-"}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View className="flex flex-row justify-between">
|
||||
<Text className="text-sm text-gray-600">车次号:</Text>
|
||||
<Text className="text-sm font-medium">
|
||||
{purchaseOrder?.orderVehicle?.vehicleNo || "-"}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View className="flex flex-row justify-between">
|
||||
<Text className="text-sm text-gray-600">经销商:</Text>
|
||||
<Text className="text-sm font-medium">
|
||||
{purchaseOrder?.orderVehicle?.dealerName || "-"}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View className="flex flex-row justify-between">
|
||||
<Text className="text-sm text-gray-600">状态:</Text>
|
||||
<Text className="text-sm font-medium text-green-600">
|
||||
已提交审核
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className="border-t border-gray-200 pt-2.5">
|
||||
<View className="mb-2">
|
||||
<Text className="text-lg font-semibold">快捷操作</Text>
|
||||
</View>
|
||||
|
||||
<View className="flex flex-col gap-3">
|
||||
<Button
|
||||
type="primary"
|
||||
size={"xlarge"}
|
||||
block
|
||||
onClick={viewPurchaseOrderDetail}
|
||||
>
|
||||
查看采购单详情
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className={"sticky bottom-0 z-10 bg-white"}>
|
||||
<View className="flex justify-between gap-2 border-t border-gray-200 p-2.5">
|
||||
<View className="flex-1">
|
||||
<Button
|
||||
type="default"
|
||||
size={"xlarge"}
|
||||
block
|
||||
onClick={() =>
|
||||
Taro.switchTab({
|
||||
url: buildUrl("/pages/main/index/index"),
|
||||
})
|
||||
}
|
||||
>
|
||||
返回首页
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
<SafeArea position={"bottom"} />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
});
|
||||
@ -85,6 +85,26 @@ export async function finalApprovePurchaseOrder(
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取上一车车次号 GET /operation/getLastVehicleNo */
|
||||
export async function getLastVehicleNo(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
params: BusinessAPI.getLastVehicleNoParams,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
return request<BusinessAPI.SingleResponseString>(
|
||||
"/operation/getLastVehicleNo",
|
||||
{
|
||||
method: "GET",
|
||||
params: {
|
||||
...params,
|
||||
lastVehicleNoQry: undefined,
|
||||
...params["lastVehicleNoQry"],
|
||||
},
|
||||
...(options || {}),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/** 采购订单列表 GET /operation/listPurchaseOrder */
|
||||
export async function listPurchaseOrder(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
|
||||
@ -1688,6 +1688,10 @@ declare namespace BusinessAPI {
|
||||
userRoleList?: UserRoleVO[];
|
||||
};
|
||||
|
||||
type getLastVehicleNoParams = {
|
||||
lastVehicleNoQry: LastVehicleNoQry;
|
||||
};
|
||||
|
||||
type GiftBoxCreateCmd = {
|
||||
/** 礼盒ID */
|
||||
boxId: string;
|
||||
@ -1786,6 +1790,11 @@ declare namespace BusinessAPI {
|
||||
createdAt?: string;
|
||||
};
|
||||
|
||||
type LastVehicleNoQry = {
|
||||
/** 状态:1_启用;0_禁用; */
|
||||
status?: boolean;
|
||||
};
|
||||
|
||||
type listAgreementParams = {
|
||||
agreementListQry: AgreementListQry;
|
||||
};
|
||||
@ -4020,6 +4029,13 @@ declare namespace BusinessAPI {
|
||||
data?: ShipOrderVO;
|
||||
};
|
||||
|
||||
type SingleResponseString = {
|
||||
success?: boolean;
|
||||
errCode?: string;
|
||||
errMessage?: string;
|
||||
data?: string;
|
||||
};
|
||||
|
||||
type SingleResponseSupplierVO = {
|
||||
success?: boolean;
|
||||
errCode?: string;
|
||||
|
||||
@ -26,7 +26,7 @@ export function calculateSupplierWeights(suppliers) {
|
||||
|
||||
// 计算本次使用纸箱的总重量(斤)
|
||||
const usedBoxesWeight = calculateBoxesTotalWeight(supplier.orderPackageList, 'USED') + calculateBoxesTotalWeight(supplier.orderPackageList, 'OWN');
|
||||
console.log("usedBoxesWeight", usedBoxesWeight, supplier)
|
||||
|
||||
if (!supplier.isPaper) {
|
||||
// 如果不是纸箱包装,直接使用原始重量(kg转斤)
|
||||
supplier.grossWeight = (supplier.totalWeight - supplier.emptyWeight) * 2;
|
||||
|
||||
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user