diff --git a/.umirc.ts b/.umirc.ts index 405bad9782d8d017794ccbfa640abf31821bc2f1..af2bbbf585f827d3a4947824d9c2a5ee2eaef6f6 100644 --- a/.umirc.ts +++ b/.umirc.ts @@ -1,6 +1,5 @@ import { defineConfig } from 'umi'; -const path = require('path'); const MomentLocalesPlugin = require('moment-locales-webpack-plugin'); export default defineConfig({ @@ -8,12 +7,20 @@ export default defineConfig({ history: { type: 'hash', }, + define: { + appenv: process.env.ENV, + }, routes: [ { name: '订单详情页', path: '/detail', component: './Detail', }, + { + name: '订单配置页', + path: '/config', + component: './Config', + }, { name: '订单列表页', path: '/', diff --git a/package.json b/package.json index dd7e009d5cb5e0970b27dcddfa8e2d55898c74c2..404b405f92b2d4bb7dfd99ccc85e988ffe616023 100644 --- a/package.json +++ b/package.json @@ -3,15 +3,13 @@ "private": true, "scripts": { "start": "umi dev", - "build": "umi build", + "build": "npx cross-env ENV=prod umi build && npx bestzip dist.zip dist/*", + "build:sit": "npx cross-env ENV=sit umi build && npx bestzip dist.zip dist/*", "postinstall": "umi generate tmp", "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'", "test": "umi-test", "test:coverage": "umi-test --coverage" }, - "gitHooks": { - "pre-commit": "lint-staged" - }, "lint-staged": { "*.{js,jsx,less,md,json}": [ "prettier --write" @@ -26,15 +24,17 @@ "@ant-design/pro-form": "^1.61.0", "@ant-design/pro-layout": "^6.34.8", "@ant-design/pro-table": "^2.70.0", - "@manycore/miniapp-sdk": "^1.1.0", + "@manycore/miniapp-sdk": "1.6.2-alpha.1", "ahooks": "^3.1.9", "antd": "^4.18.8", "classnames": "^2.2.6", "decimal.js": "^10.3.1", "lodash": "^4.17.21", "moment": "^2.22.2", + "rc-virtual-list": "^3.4.8", "react": "17.x", "react-dom": "17.x", + "react-infinite-scroll-component": "^6.1.0", "swiper": "^8.0.6", "umi": "^3.5.20" }, @@ -53,5 +53,8 @@ "prettier": "^2.2.0", "typescript": "^4.1.2", "yorkie": "^2.0.0" + }, + "gitHooks": { + "pre-commit": "lint-staged" } } diff --git a/src/access.ts b/src/access.ts index 15569c7bafad62b234cb2cb87fc3edf2e9ce0074..e2f0037cc4c03531e82a30655e8ac805725672ad 100644 --- a/src/access.ts +++ b/src/access.ts @@ -2,10 +2,10 @@ import { AppInitialState } from './types'; export default function (initialState: AppInitialState) { // strictMode - const { visibleFields } = initialState; + const { visibleFields, permissions } = initialState; let accessResult: { [key: string]: boolean } = {}; - visibleFields.forEach((field) => { + [...visibleFields, ...permissions].forEach((field) => { accessResult[field] = true; }); diff --git a/src/app.ts b/src/app.ts index 88313ab14a6be59f4c700fa3dba0b8e5590b533a..a71ae1cfb599159661992c2a5649101c02048f94 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,7 +1,7 @@ +import '@manycore/miniapp-sdk'; import { message } from 'antd'; import { history } from 'umi'; -import { AppInitialState } from './types'; -import '@manycore/miniapp-sdk'; +import { AppInitialState, Permission } from './types'; // 打开小程序根据 miniappUploadData 自动进入详情页 Manycore.Miniapp.getUploadedDataAsync() @@ -21,6 +21,8 @@ Manycore.Miniapp.getUploadedDataAsync() export async function getInitialState(): Promise { let visibleFields: string[]; + let permissions: Permission[]; + let orderFields: GetOrderFieldsResult = { systemFields: [], customFields: [] }; try { visibleFields = await Manycore.Integration.FOP.getOrderVisibleFieldsAsync(); @@ -29,7 +31,24 @@ export async function getInitialState(): Promise { message.error('获取订单可见字段列表失败'); } + try { + const permissionsRes = await Manycore.Integration.FOP.getPermissionsAsync(); + + permissions = permissionsRes.permissions as Permission[]; + } catch (error) { + permissions = []; + message.error('获取当前用户权限数据失败'); + } + + try { + orderFields = await Manycore.Integration.FOP.getOrderFieldsAsync(); + } catch (error) { + message.error('获取订单字段配置失败'); + } + return { visibleFields, + permissions, + orderFields, }; } diff --git a/src/consts/index.tsx b/src/consts/index.tsx index 102a4adc6f73ad57bb44faf5cda3b8388d6c4964..964b925036493bd12e02af8e167f740b25ceb711 100644 --- a/src/consts/index.tsx +++ b/src/consts/index.tsx @@ -1,32 +1,76 @@ -import qs from 'qs'; -import { ColumnsState } from '@ant-design/pro-table'; import { + AppInitialState, + CustomerQueryType, + DetailPageTab, OrderEventKey, OrderState, + OrderStateAction, OrderStateChangeModal, OrderType, ProductType, - UserSource, - OrderStateAction, TOrder, - DetailPageTab, - AppInitialState, + UserSource, } from '@/types'; +import { ColumnsState } from '@ant-design/pro-table'; import { message } from 'antd'; import { template } from 'lodash'; +import qs from 'qs'; export const CDefaultInitialState: AppInitialState = { visibleFields: [], + permissions: [], + orderFields: { systemFields: [], customFields: [] }, }; export const CTableListScrollWidth = 1400; -export const COrderStateNameMap: Map = new Map([ - [OrderState.claimed, '已认领'], - [OrderState.productionScheduling, '排产中'], - [OrderState.reviewed, '已审核'], - [OrderState.returned, '已退回'], +/** + * 订单配置权限key + */ +export const ORDER_CONF_ACCESS_KEY = 'Order.orderConfig'; + +/** + * 订单事件 => label 映射 + */ +export const OrderEventKeyLabelMap: Map = new Map([ + ['simpleCreate', '创建订单'], + [OrderEventKey.designClaim, '设计认领'], + [OrderEventKey.designAssign, '设计分配'], + [OrderEventKey.designSubmit, '设计提交'], + [OrderEventKey.arraigned, '订单提审'], + [OrderEventKey.arraignAssign, '审核分配'], + [OrderEventKey.payRegister, '付款登记'], + [OrderEventKey.payCollectConfirm, '收款确认'], + [OrderEventKey.splitOrderConfirm, '拆单确认'], + [OrderEventKey.genBatch, '生成批次'], + [OrderEventKey.batchPreprocessing, '合批预处理'], + [OrderEventKey.batchConfirm, '合批确认'], + [OrderEventKey.scheduleProductionConfirm, '排产确认'], + [OrderEventKey.prepareMaterialConfirm, '备料确认'], + [OrderEventKey.openMaterialConfirm, '开料确认'], + [OrderEventKey.edgeSealedConfirm, '封边确认'], + [OrderEventKey.holesArrangedConfirm, '排孔确认'], + [OrderEventKey.pickConfirm, '分拣确认'], + [OrderEventKey.packedConfirm, '包装确认'], + [OrderEventKey.warehousedConfirm, '入库确认'], + [OrderEventKey.shippedConfirm, '发货确认'], + [OrderEventKey.completedConfirm, '完成确认'], + [OrderEventKey.resubmit, '重新提交'], + [OrderEventKey.auditClaim, '审核认领'], + [OrderEventKey.financeClaim, '财务认领'], + [OrderEventKey.financeAssign, '财务分配'], + [OrderEventKey.splitOrderClaim, '拆单认领'], + [OrderEventKey.splitOrderAssign, '拆单分配'], + [OrderEventKey.pass, '通过'], + [OrderEventKey.reject, '驳回'], + [OrderEventKey.sendBack, '退回'], + [OrderEventKey.cancel, '取消'], + [OrderEventKey.revoke, '撤回'], +]); +export const COrderStateNameMap: Map = new Map([ + [OrderState.created, '已创建'], + [OrderState.designPending, '待设计'], [OrderState.confirmPending, '待确定'], [OrderState.confirmed, '已确定'], [OrderState.ordered, '已下单'], @@ -37,7 +81,9 @@ export const COrderStateNameMap: Map = new Map([ [OrderState.payCollected, '已收款'], [OrderState.splitOrderPending, '待拆单'], [OrderState.splited, '已拆单'], - [OrderState.batchGenerated, '已合批'], + [OrderState.merging, '合批中'], + [OrderState.merged, '已合批'], + [OrderState.productionScheduled, '已排产'], [OrderState.materialPrepared, '已备料'], [OrderState.openedMaterial, '已开料'], [OrderState.edgeSealed, '已封边'], @@ -48,6 +94,11 @@ export const COrderStateNameMap: Map = new Map([ [OrderState.shipped, '已发货'], [OrderState.completed, '已完成'], [OrderState.cancelled, '已取消'], + + [OrderState.claimed, '已认领'], + [OrderState.productionScheduling, '排产中'], + [OrderState.reviewed, '已审核'], + [OrderState.returned, '已退回'], ]); export const COrderTypeNameMap: Map = new Map([ @@ -90,6 +141,21 @@ export const CColumnsStateMap: Record = { }, }; +export const CCustomerQueryTypeMap: Map = new Map([ + ['customerName', CustomerQueryType.customerName], + ['customerPhone', CustomerQueryType.customerPhone], + ['customerAddr', CustomerQueryType.customerAddr], +]); + +export const CCustomerListColumnsStateMap: Record = { + customerName: { + fixed: 'left', + }, + creatorName: { + fixed: 'right', + }, +}; + export const CDetailTabList = [ { key: DetailPageTab.orderInfo, @@ -137,23 +203,33 @@ export const CTabFieldsListMap: Record = { [DetailPageTab.paymentInfo]: ['payAmt', 'actualPayAmt', 'payRecordCreator', 'payCertificates'], }; -export const CYunDesignUrl = 'https://yun.kujiale.com/cloud/tool/h5/diy'; -// export const CYunDesignUrl = 'https://sit.kujiale.com/cloud/tool/h5/diy'; +export const CYunDesignUrl = + appenv === 'prod' + ? 'https://yun.kujiale.com/cloud/tool/h5/diy' + : 'https://sit.kujiale.com/cloud/tool/h5/diy'; + +export const CDesignDetailUrl = + appenv === 'prod' + ? 'https://www.kujiale.com/pub/saas/workbench/design/detail/' + : 'https://sit.kujiale.com/pub/saas/workbench/design/detail/'; -export const CDesignDetailUrl = 'https://www.kujiale.com/pub/saas/workbench/design/detail/'; -// export const CDesignDetailUrl = 'https://sit.kujiale.com/pub/saas/workbench/design/detail/'; +export const CMerchantBackstageUrl = + appenv === 'prod' + ? 'https://www.kujiale.com/pub/saas/workbench/miniapp' + : 'https://sit.kujiale.com/pub/saas/workbench/miniapp'; -export const CYunDesignMiniapp = '3FO4K4VOPKU6'; -// export const CYunDesignMiniapp = '3FO4K4VVK6RT'; +export const CCustomerMiniappId = appenv === 'prod' ? '3FO4K4VPEXHG' : '3FO4K4VWYH4K'; + +export const CYunDesignMiniapp = appenv === 'prod' ? '3FO4K4VOPKU6' : '3FO4K4VVK6RT'; export const CCancelAction: OrderStateAction = { - actionName: '取消', + actionName: '作废', actionType: 'confirm', eventKey: [OrderEventKey.cancel], batchable: false, isReverseOperation: true, confirm: { - title: '确认「取消」?', + title: '确认「作废」?', desc: template('确认后将作废订单,订单状态将变更为「${ nextStateName }」'), }, }; @@ -171,6 +247,30 @@ export const CRevokeAction: OrderStateAction = { }; export const COrderStateActionsMap: Map = new Map([ + [ + OrderState.created, + [ + { + actionName: '认领', // 设计师 + actionType: 'confirm', + eventKey: [OrderEventKey.designClaim], + batchable: true, + confirm: { + title: '确认「认领」?', + desc: template('确认后订单状态将变更为「${ nextStateName }」'), + }, + }, + { + actionName: '分配', + actionType: 'modalForm', + modalName: OrderStateChangeModal.DesignAssign, + eventKey: [OrderEventKey.designAssign], + batch: false, + }, + CCancelAction, + ], + ], + [OrderState.designPending, [CRevokeAction, CCancelAction]], [ OrderState.confirmPending, [ @@ -204,6 +304,18 @@ export const COrderStateActionsMap: Map = new Ma desc: template('确认后订单将流转到工厂端,订单状态将变更为「${ nextStateName }」'), }, }, + { + actionName: '退回', + actionType: 'confirm', + eventKey: [OrderEventKey.sendBack], + batchable: false, + isReverseOperation: true, + confirm: { + title: '确认「退回 」?', + desc: template('确认后将作废订单,订单状态将变更为「${ nextStateName }」'), + }, + }, + CRevokeAction, CCancelAction, ], ], @@ -220,6 +332,13 @@ export const COrderStateActionsMap: Map = new Ma desc: template('确认后订单状态将变更为「${ nextStateName }」'), }, }, + { + actionName: '分配', + actionType: 'modalForm', + modalName: OrderStateChangeModal.ArraignAssign, + eventKey: [OrderEventKey.arraignAssign], //todo [OrderEventKey.arraignAssign] + batch: false, + }, CRevokeAction, CCancelAction, ], @@ -242,7 +361,6 @@ export const COrderStateActionsMap: Map = new Ma designid: order.designId, customtype: order.orderType[0], launchMiniapp: CYunDesignMiniapp, - miniappOrderId: order.orderId, miniappUploadId: uniqueId, }); window.open(`${CYunDesignUrl}?${queryStr}`); @@ -305,6 +423,13 @@ export const COrderStateActionsMap: Map = new Ma desc: template('确认后订单状态将变更为「${ nextStateName }」'), }, }, + { + actionName: '分配', + actionType: 'modalForm', + modalName: OrderStateChangeModal.FinanceAssign, + eventKey: [OrderEventKey.financeAssign], //todo [OrderEventKey.arraignAssign] + batch: false, + }, CRevokeAction, CCancelAction, ], @@ -347,6 +472,13 @@ export const COrderStateActionsMap: Map = new Ma desc: template('确认后订单状态将变更为「${ nextStateName }」'), }, }, + { + actionName: '分配', + actionType: 'modalForm', + modalName: OrderStateChangeModal.SplittingAssign, + eventKey: [OrderEventKey.splitOrderAssign], + batch: false, + }, CRevokeAction, CCancelAction, ], @@ -405,8 +537,18 @@ export const COrderStateActionsMap: Map = new Ma ], ], [ - OrderState.batchGenerated, + OrderState.merged, [ + { + actionName: '排产确认', + actionType: 'confirm', + eventKey: [OrderEventKey.scheduleProductionConfirm], + batchable: true, + confirm: { + title: '确认订单「已排产」?', + desc: template('确认后订单状态将变更为「${ nextStateName }」'), + }, + }, { actionName: '备料确认', actionType: 'confirm', @@ -568,6 +710,53 @@ export const COrderStateActionsMap: Map = new Ma CCancelAction, ], ], + [ + OrderState.productionScheduled, + [ + { + actionName: '备料确认', + actionType: 'confirm', + eventKey: [OrderEventKey.prepareMaterialConfirm], + batchable: true, + confirm: { + title: '确认订单「备料确认」?', + desc: template('确认后订单状态将变更为「${ nextStateName }」'), + }, + }, + { + actionName: '退回', + actionType: 'confirm', + eventKey: [OrderEventKey.sendBack], + batchable: false, + isReverseOperation: true, + confirm: { + title: '确认「退回 」?', + desc: template('确认后将作废订单,订单状态将变更为「${ nextStateName }」'), + }, + }, + CRevokeAction, + CCancelAction, + ], + ], [OrderState.completed, []], [OrderState.cancelled, []], ]); + +export const AssignModalFieldMap: Map< + OrderStateChangeModal, + { title: string; eventKey: OrderEventKey } +> = new Map([ + [OrderStateChangeModal.DesignAssign, { title: '设计分配', eventKey: OrderEventKey.designAssign }], + [ + OrderStateChangeModal.ArraignAssign, + { title: '审核分配', eventKey: OrderEventKey.arraignAssign }, + ], + [ + OrderStateChangeModal.FinanceAssign, + { title: '财务分配', eventKey: OrderEventKey.financeAssign }, + ], + [ + OrderStateChangeModal.SplittingAssign, + { title: '拆单分配', eventKey: OrderEventKey.splitOrderAssign }, + ], +]); diff --git a/src/models/orderList.ts b/src/models/orderList.ts index 9a868803e86a52b226c3684a2613b1bdaa055608..8e4a14bb5c13f5610dfdfe1c58c77ca20cb35a77 100644 --- a/src/models/orderList.ts +++ b/src/models/orderList.ts @@ -1,12 +1,9 @@ import { useState, useCallback, useRef, useMemo } from 'react'; import { ActionType } from '@ant-design/pro-table'; -import { FilterValue } from 'antd/lib/table/interface'; -import { OrderStateStatistics } from '@/types'; +import { OrderStateStatistics, TFilterInfo } from '@/types'; import { COrderStateNameMap } from '@/consts'; import { message } from 'antd'; -export type TFilterInfo = Record; - export default function useOrderListModel() { const tableActionRef = useRef(); const [filterInfo, setFilterInfo] = useState({}); diff --git a/src/models/orderStateOperate.ts b/src/models/orderStateOperate.ts index 1601198e3d0255e529ccc4120b10719e706e90b1..dbcc148339602ea3874c220d8db0e4df6acab26c 100644 --- a/src/models/orderStateOperate.ts +++ b/src/models/orderStateOperate.ts @@ -1,13 +1,13 @@ -import { useState, useCallback, useMemo } from 'react'; import { COrderStateActionsMap, COrderStateNameMap } from '@/consts'; -import { message, Modal } from 'antd'; import { + OperateOrderEventOption, OrderEventKey, - OrderStateChangeModal, OrderStateAction, - OperateOrderEventOption, + OrderStateChangeModal, TOrder, } from '@/types'; +import { message, Modal } from 'antd'; +import { useCallback, useMemo, useState } from 'react'; export default function useOrderStateOperateModel() { const [batchOperatingOrders, setBatchOperatingOrders] = useState([]); diff --git a/src/pages/Config/OrderVisibleCard/index.less b/src/pages/Config/OrderVisibleCard/index.less new file mode 100644 index 0000000000000000000000000000000000000000..fe97462ad47e550799e9c8899ddc9cbf91da62e1 --- /dev/null +++ b/src/pages/Config/OrderVisibleCard/index.less @@ -0,0 +1,9 @@ +.order-visible-conf { + height: calc(100vh - 86px); + overflow: hidden; + + .form-wrapper { + height: calc(100% - 52px); + overflow: scroll; + } +} diff --git a/src/pages/Config/OrderVisibleCard/index.tsx b/src/pages/Config/OrderVisibleCard/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4182027b061ac9ef636c619c8fea0ed299d62716 --- /dev/null +++ b/src/pages/Config/OrderVisibleCard/index.tsx @@ -0,0 +1,340 @@ +import { COrderStateNameMap, OrderEventKeyLabelMap } from '@/consts'; +import { ProFormDigit, ProFormSelect } from '@ant-design/pro-form'; +import { useUpdate } from 'ahooks'; +import { Button, Card, Descriptions, Empty, Form, message, Tag } from 'antd'; +import { cloneDeep } from 'lodash'; +import { memo, useMemo, useState } from 'react'; +import { useRequest } from 'umi'; +import { EOrderVisibilityRuleType, orderVisibleRuleLabel, visibleRuleOpts } from '../helper'; +import './index.less'; + +/** + * 角色信息 + */ +interface Role { + /** + * 角色ID + */ + roleId: string; + /** + * 角色名称 + */ + roleName: string; +} + +interface IRule { + /** + * 角色ID + */ + roleId: string; + /** + * 搜索类型,限制可见规则生效范围 + */ + searchType: number; + /** + * 规则类别 + */ + ruleId: EOrderVisibilityRuleType; + /** + * 筛选字段值 + * >`OrderVisibilityRuleType.STATE`, `OrderVisibilityRuleType.EVENT` 需要指定返回的状态值和事件类型 + */ + filterFieldValue: any[]; +} + +interface IProps { + /** + * 更新配置回调 + */ + onUpdate: () => void; + /** + * 配置ID + */ + configId?: number; + /** + * 配置数据 + */ + data: IRule[]; +} + +const requireRules = [ + { + required: true, + message: '必填', + }, +]; + +/** + * 订单可见性卡片 + * @returns + */ +function OrderVisibleCard(props: IProps) { + const { data: roles = [] } = useRequest( + (): Promise => (Manycore.User as any).getCustomRoles(), + ); + + const { data, configId, onUpdate } = props; + const update = useUpdate(); + /** + * 更新配置loading + */ + const [loading, setLoading] = useState(false); + + /** + * form实例 + */ + const [form] = Form.useForm(); + /** + * 编辑模式 + */ + const [editMode, setEditMode] = useState(false); + + /** + * 进入编辑模式 + */ + const enterEditMode = () => { + form.setFieldsValue(cloneDeep(data)); + setEditMode(true); + }; + + /** + * 保存订单可见性配置 + */ + const saveOrderConfig = () => { + form.validateFields().then((fields) => { + const values: IRule[] = Object.values(fields); + const payload = { + configId, + orderVisibilityRules: values.map((f: any) => ({ + ...f, + filterFieldValue: Array.isArray(f.filterFieldValue) ? f.filterFieldValue.join(',') : '', + })), + }; + setLoading(true); + // @ts-ignore + Manycore.Integration.FOP.Config.updateOrderVisible(payload) + .then(() => { + message.success('更新配置成功'); + setEditMode(false); + onUpdate(); + }) + .catch(() => { + message.error('更新配置失败!'); + }) + .finally(() => { + setLoading(false); + }); + }); + }; + + /** + * 取消编辑 + */ + const cancelEdit = () => { + setEditMode(false); + form.resetFields(); + }; + + /** + * 根据id删除配置 + */ + const delVisibleRule = (idx: number) => { + const fields = form.getFieldsValue(true); + const clone = [...Object.values(fields)]; + clone.splice(idx, 1); + form.resetFields(); + form.setFieldsValue(clone); + update(); // re render + }; + + /** + * 新增一条新的规则 + */ + const addNewRule = () => { + form.setFieldsValue([ + ...Object.values(form.getFieldsValue()), + { + roleId: undefined, + searchType: undefined, + ruleId: undefined, + filterFieldValue: [], + }, + ]); + update(); // re-render + }; + + /** + * 操作按钮 + */ + const extras = useMemo(() => { + if (editMode) { + return ( + <> + + + + ); + } + return ( + + ); + }, [editMode, enterEditMode, saveOrderConfig]); + + let orderVisibleRules; + + // 编辑模式 + if (editMode) { + orderVisibleRules = Object.values(form.getFieldsValue(true)).map((d: any, idx) => { + const ruleKey = idx; + const filterLabel = + d.ruleId === EOrderVisibilityRuleType.state + ? '可见订单状态' + : d.ruleId === EOrderVisibilityRuleType.event + ? '可见订单操作事件' + : ''; + const filterValue = + d.ruleId === EOrderVisibilityRuleType.state ? ( + ({ + label: COrderStateNameMap.get(k), + value: k, + }))} + name={[ruleKey, 'filterFieldValue']} + fieldProps={{ showSearch: true, placeholder: '请选择', mode: 'multiple' }} + /> + ) : d.ruleId === EOrderVisibilityRuleType.event ? ( + ({ + label: OrderEventKeyLabelMap.get(k), + value: k, + }))} + name={[ruleKey, 'filterFieldValue']} + fieldProps={{ showSearch: true, placeholder: '请选择', mode: 'multiple' }} + /> + ) : ( + '' + ); + return ( + { + delVisibleRule(idx); + }} + type="link" + danger + > + 删除 + + } + > + + + ({ + label: r.roleName, + value: r.roleId, + }))} + name={[ruleKey, 'roleId']} + fieldProps={{ showSearch: true, placeholder: '请选择' }} + /> + + + + + + { + form.setFieldValue([ruleKey, 'filterFieldValue'], undefined); + update(); + }, + }} + /> + + {filterValue} + + + ); + }); + } else { + if (data.length === 0) { + orderVisibleRules = ; + } else { + orderVisibleRules = data.map((d) => { + const filterLabel = + d.ruleId === EOrderVisibilityRuleType.state + ? '可见订单状态' + : d.ruleId === EOrderVisibilityRuleType.event + ? '可见订单操作事件' + : ''; + const filterValue = + d.ruleId === EOrderVisibilityRuleType.state + ? d.filterFieldValue.map((v) => {COrderStateNameMap.get(v)}) + : d.ruleId === EOrderVisibilityRuleType.event + ? d.filterFieldValue.map((v) => {OrderEventKeyLabelMap.get(v)}) + : ''; + return ( + + + + {roles.find((r) => r.roleId === d.roleId)?.roleName} + + {d.searchType} + + {orderVisibleRuleLabel.get(d.ruleId)} + + {filterValue} + + + ); + }); + } + } + + return ( +
+
{extras}
+
+
{orderVisibleRules}
+ {editMode && ( + + )} +
+
+ ); +} + +export default memo(OrderVisibleCard); diff --git a/src/pages/Config/helper.ts b/src/pages/Config/helper.ts new file mode 100644 index 0000000000000000000000000000000000000000..ba092a5fe81812197e76a774deb9fff1f9ecaeb5 --- /dev/null +++ b/src/pages/Config/helper.ts @@ -0,0 +1,88 @@ +/** + * 订单可见规则类别 + */ +export enum EOrderVisibilityRuleType { + /** + * 当前订单创建人可见 + */ + create = 1, + /** + * 特定订单状态可见 + */ + state, + /** + * 当前门店的订单可见 + */ + store, + /** + * 特定事件操作人可见 + */ + event, + /** + * 当前订单设计师可见 + */ + designer, +} + +/** + * 订单可见规则 => label 映射 + */ +export const orderVisibleRuleLabel = new Map([ + [EOrderVisibilityRuleType.create, '当前订单创建人可见'], + [EOrderVisibilityRuleType.state, '特定订单状态可见'], + [EOrderVisibilityRuleType.store, '当前门店的订单可见'], + [EOrderVisibilityRuleType.event, '特定事件操作人可见'], + [EOrderVisibilityRuleType.designer, '当前订单设计师可见'], +]); + +/** + * 订单可见规则类型select options + */ +export const visibleRuleOpts = [ + { + label: '当前订单创建人可见', + value: EOrderVisibilityRuleType.create, + }, + { + label: '特定订单状态可见', + value: EOrderVisibilityRuleType.state, + }, + { + label: '当前门店的订单可见', + value: EOrderVisibilityRuleType.store, + }, + { + label: '特定事件操作人可见', + value: EOrderVisibilityRuleType.event, + }, + { + label: '当前订单设计师可见', + value: EOrderVisibilityRuleType.designer, + }, +]; + +/** + * 订单配置类型 + */ +export enum EOrderConfigType { + // /** + // * 自定义字段配置 + // */ + // customField = 0, + // /** + // * 订单流程 + // */ + // orderProcess = 1, + /** + * 订单可见性 + */ + orderVisible = 2, + // /** + // * 订单字段权限(可见性、可编辑性) + // */ + // orderFieldAuth = 3, + // /** + // * 订单非流程事件 + // */ + // orderEvent = 4 +} diff --git a/src/pages/Config/index.less b/src/pages/Config/index.less new file mode 100644 index 0000000000000000000000000000000000000000..aee2c2590139408755816740eba4f3bf61535159 --- /dev/null +++ b/src/pages/Config/index.less @@ -0,0 +1,5 @@ +.ordermvp-config { + height: 100%; + padding: 24px; + background-color: #fff; +} diff --git a/src/pages/Config/index.tsx b/src/pages/Config/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5838384a62af85d867c561d05615162d9ae1d2c8 --- /dev/null +++ b/src/pages/Config/index.tsx @@ -0,0 +1,88 @@ +import { Breadcrumb, Spin, Tabs } from 'antd'; +import { memo } from 'react'; +import { history, useRequest } from 'umi'; +import { EOrderVisibilityRuleType } from './helper'; +import './index.less'; +import OrderVisibleCard from './OrderVisibleCard'; + +// @ts-ignore +const ConfigApi = Manycore.Integration.FOP.Config; + +const { TabPane } = Tabs; + +const orderConfRenderMap = new Map([ + [ + 2, + { + title: '订单可见性配置', + key: 'order-visible', + comp: (conf: any, onUpdate: () => void) => ( + { + const filterFieldValue = + item.ruleId === EOrderVisibilityRuleType.state + ? item.filterFieldValue.split(',').map(Number) + : item.ruleId === EOrderVisibilityRuleType.event + ? item.filterFieldValue.split(',') + : []; + return { + ...item, + filterFieldValue, + }; + })} + /> + ), + }, + ], +]); + +/** + * 订单配置列表 + * @returns + */ +function OrderConfig() { + const { data = [], loading, refresh } = useRequest(() => ConfigApi.getConfigList()); + + const orderVisibleConf = data.find((d: any) => d.type === 2) || { value: [] }; + + return ( +
+ + { + history.push('/'); + }} + > + FOP订单管理 + + 订单管理列表 + + + + + { + const filterFieldValue = + item.ruleId === EOrderVisibilityRuleType.state + ? item.filterFieldValue.split(',').map(Number) + : item.ruleId === EOrderVisibilityRuleType.event + ? item.filterFieldValue.split(',') + : []; + return { + ...item, + filterFieldValue, + }; + })} + /> + + + +
+ ); +} + +export default memo(OrderConfig); diff --git a/src/pages/Detail/OrderAttachmentsRemarks/attachment.tsx b/src/pages/Detail/OrderAttachmentsRemarks/attachment.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a98e33c78b6af439e559fbcb27148e82f4329e32 --- /dev/null +++ b/src/pages/Detail/OrderAttachmentsRemarks/attachment.tsx @@ -0,0 +1,308 @@ +import { + Attachment, + GetOrderAttachmentsOption, + OrderAttachmentItem, + OrderDetail, + OrderSortRuleDirect, + UploadFileBucket, +} from '@/types'; +import { useSize } from 'ahooks'; +import { Badge, Button, message, notification, Spin, TableProps, Tooltip, UploadProps } from 'antd'; +import Dragger from 'antd/lib/upload/Dragger'; +import { memo, useEffect, useMemo, useState, useCallback } from 'react'; +import { CloudUploadOutlined, PaperClipOutlined } from '@ant-design/icons'; + +import styles from './index.less'; +import { RcFile } from 'antd/lib/upload'; +import { isArray, isEmpty, mapKeys, omit } from 'lodash'; +import ProTable, { ProColumns } from '@ant-design/pro-table'; +import { COrderStateNameMap } from '@/consts'; +import { generateColumnSearchProps } from '@/utils'; +import moment from 'moment'; + +interface OrderOperationHistoryProps { + orderId: string; + orderDetail: OrderDetail; + onAttachmentToTalCount: (count: number) => void; +} + +const defaultQuery = { + start: 0, + num: 10, + sortRule: OrderSortRuleDirect.descending, + sortField: 'createTime', +}; + +// uploader conf +export const defaultUploadConf = { + sizeLimit: 20, // file size limit: mb + accept: '.png,.jpg,.bmp,.xls,.xlsx,.dwg,.dxf,.json,.rar,.zip', // file type limit +}; + +const OrderAttachments = memo((props: OrderOperationHistoryProps) => { + const { orderId, onAttachmentToTalCount } = props; + const [attachmentList, setAttachmentList] = useState([]); + const [totalCount, setTotalCount] = useState(0); + const [filterInfo, setFilterInfo] = useState({ + ...defaultQuery, + orderId, + }); + const [spinLoading, setSpinLoading] = useState(false); + const [tableLoading, setTableLoading] = useState(false); + + const columns: ProColumns[] = [ + { + title: '附件名称', + dataIndex: 'name', + width: '30%', + ...generateColumnSearchProps('name'), + render: (dom, entity) => { + return ( + +
+ + {dom} +
+
+ ); + }, + }, + { + title: '对应订单状态', + dataIndex: 'orderState', + filters: true, + onFilter: false, + filterMultiple: false, + ellipsis: true, + valueType: 'select', + valueEnum: COrderStateNameMap, + render: (dom, entity) => { + return ; + }, + }, + { + title: '操作人', + dataIndex: 'userName', + ...generateColumnSearchProps('userName'), + }, + { + title: '上传时间', + dataIndex: 'createTime', + sorter: true, + render: (dom, entity) => moment(entity.createTime).format('YYYY-MM-DD HH:mm:ss'), + }, + { + title: '操作', + dataIndex: 'operateResult', + width: '60px', + render: (dom, entity) => ( + + ), + }, + ]; + + useEffect(() => { + getOrderAttachmentList(); + }, [filterInfo]); + + const getOrderAttachmentList = useCallback(async () => { + try { + setTableLoading(true); + const data = await Manycore.Integration.FOP.getOrderAttachmentsAsync(filterInfo); + setAttachmentList(data.result); + setTotalCount(data.totalCount); + onAttachmentToTalCount(data.totalCount); + } catch { + message.error('获取附件失败'); + } finally { + setTableLoading(false); + } + }, [filterInfo]); + + // 表格高度 + const tableListClassName = 'order-attachment-remark-container'; + const BodySize = useSize(document.querySelector('body')); + + const tableHeight = useMemo(() => { + if (BodySize) { + // 减去表格上方元素的高度 + return BodySize.height - 172 - 46; + } + }, [BodySize]); + + const tableScrollHeight = useMemo(() => { + if (tableHeight) { + return tableHeight - 54; + } + }, [tableHeight]); + const { sizeLimit, accept } = defaultUploadConf; + + const messageInfo = (msg: string, type: 'success' | 'error') => { + notification[type]({ + message: msg, + placement: 'top', + duration: 3, + }); + }; + + const UploadProps: UploadProps = { + name: 'file', + accept: accept, + multiple: true, + showUploadList: false, + onChange(info) { + const { status } = info.file; + if (status === 'uploading') { + setSpinLoading(true); + } else { + setSpinLoading(false); + } + if (status === 'done') { + messageInfo(`${info.file.name} 上传成功.`, 'success'); + } else if (status === 'error') { + messageInfo(`${info.file.name} 上传失败,请重新选择.`, 'error'); + } + }, + beforeUpload: (file) => { + // 单个文件不超过20M + if (file.size > sizeLimit * 1024 * 1024) { + message.error(`文件“${file.name}”大小超过20M`); + setSpinLoading(false); + return false; + } else { + return Promise.resolve(file); + } + }, + customRequest: ({ file, onSuccess, onError, onProgress }) => { + const fileReader = new FileReader(); + + fileReader.addEventListener('load', () => { + const fileBase64 = fileReader.result as string; + + // 调用上传接口 + Manycore.Integration.Upload.uploadFileAsync({ + fileInfos: [{ name: (file as RcFile).name, fileBase64 }], + bucket: UploadFileBucket.order, + }).then((results) => { + const result = results[0]; + if (result.code === 0) { + addAttachments(omit(result, 'code') as Attachment); + onSuccess?.(result); + } else { + onError?.(new Error(result.message)); + } + }); + }); + + fileReader.readAsDataURL(file as RcFile); + }, + }; + + /** + * 添加附件 + * @param attchment + */ + const addAttachments = async (attchment: Attachment) => { + const payload = { + orderId, + orderState: props.orderDetail.orderState, + attachments: [attchment], + }; + try { + await Manycore.Integration.FOP.addOrderAttachmentAsync(payload); + getOrderAttachmentList(); + } catch { + message.error('添加附件失败'); + } + }; + /** + * table 筛选项/分页/排序 变化回调 + */ + const handleTableChange: PickType, 'onChange'> = useCallback( + (page, filters, sort) => { + let start = page.pageSize + (page.current - 2); + const num = page.pageSize; + if (page.current < 2) { + start = 0; + } + let filter: Record = {}; + mapKeys(filters, (value, key) => { + if (isArray(value) && value.length) { + filter[key] = value[0]; + } else { + filter[key] = value; + } + }); + if (!isEmpty(sort)) { + filter = { + ...filter, + sortField: sort.field, + sortRule: + sort.order === 'descend' + ? OrderSortRuleDirect.descending + : OrderSortRuleDirect.ascending, + }; + } + setFilterInfo({ ...filterInfo, ...filter, ...{ start, num } }); + }, + [filterInfo, setFilterInfo], + ); + + return ( +
+
+ + +

+ + + 点击 + 或 拖拽文件到此处上传 + +

+

单个附件大小不超过20M

+

+ 支持 PNG/ JPG/ BMP/ XLS/ XLSX/ DWG/ DXF/ JSON/ RAR/ ZIP 格式 +

+
+
+
+
+ + rowKey="attachRemark" + loading={tableLoading} + columns={columns} + dataSource={attachmentList} + scroll={{ y: tableScrollHeight }} + pagination={{ + showSizeChanger: true, + total: totalCount, + pageSize: filterInfo.num, + }} + onChange={handleTableChange} + toolBarRender={false} + search={false} + /> +
+ +
+ ); +}); + +export default OrderAttachments; diff --git a/src/pages/Detail/OrderAttachmentsRemarks/index.less b/src/pages/Detail/OrderAttachmentsRemarks/index.less new file mode 100644 index 0000000000000000000000000000000000000000..ce6e2200810f267927df8621fdb374fad8e282e4 --- /dev/null +++ b/src/pages/Detail/OrderAttachmentsRemarks/index.less @@ -0,0 +1,130 @@ +.orderAttachmentsRemarks { + :global(.ant-drawer-title) { + display: flex; + color: rgba(0, 0, 0, 0.85); + font-weight: 500; + font-size: 16px; + line-height: 24px; + } + + :global(.ant-drawer-header-title button) { + position: absolute; + right: 0; + } + + :global(.ant-drawer-body) { + display: flex; + flex: 1; + overflow-x: hidden; + } + + :global(.ant-drawer-body) { + padding-top: 0; + } + + :global(.ant-tabs) { + width: 100%; + } + + .closeBtn { + position: absolute; + top: 10px; + right: 20px; + z-index: 10; + text-align: center; + + &:hover { + cursor: pointer; + } + } + + .tabTitle { + display: flex; + align-items: center; + justify-content: center; + } + + .remarkContainer { + :global(.ant-comment) { + margin-bottom: 15px; + } + + :global(.ant-comment-inner) { + padding: 8px 0; + } + + :global(.ant-comment-content-author) { + justify-content: space-between; + } + + :global(.ant-tag) { + color: #1890ff; + } + + :global(.ant-input) { + resize: none; + } + + .userName { + padding-right: 5px; + color: #000; + font-weight: 600; + font-size: 14px; + } + + .remarkContent { + width: 100%; + padding: 5px; + background: #f8f9fb; + } + + .friendlyTime { + color: #000; + } + } + + .attchmentContainer { + .uploadTitle { + font-weight: bold; + font-size: 18px; + + .desc { + padding-left: 15px; + } + } + + .attchmentName { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + :global(.ant-table-wrapper) { + width: 100%; + } + + :global(.ant-table-cell .ant-btn-link) { + padding-left: 0; + } + } +} + +:global(.ant-notification-notice) { + width: 260px; + padding: 5px; +} + +:global(.ant-notification-notice-close) { + top: 10px; + font-size: 12px; +} + +:global(.ant-notification-notice-with-icon .ant-notification-notice-message) { + margin-left: 30px; + font-size: 12px; +} + +:global(.ant-notification-notice-icon) { + margin-top: 3px; + font-size: 18px; +} diff --git a/src/pages/Detail/OrderAttachmentsRemarks/index.tsx b/src/pages/Detail/OrderAttachmentsRemarks/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7d974ddb99e20d614978d147832111c847f8d37b --- /dev/null +++ b/src/pages/Detail/OrderAttachmentsRemarks/index.tsx @@ -0,0 +1,94 @@ +import { OrderDetail } from '@/types'; +import { CloseOutlined } from '@ant-design/icons'; +import { Badge, Drawer, message, Tabs } from 'antd'; +import { memo, useEffect, useState } from 'react'; +import OrderAttachments from './attachment'; + +const { TabPane } = Tabs; + +import styles from './index.less'; +import OrderRemark from './remark'; + +interface OrderOperationHistoryProps { + orderId: string; + visible: boolean; + onClose: () => void; +} + +const OrderAttachmentsRemarks = memo((props: OrderOperationHistoryProps) => { + const [orderDetail, setOrderDetail] = useState({} as OrderDetail); + const [attachmentTotalCount, setAttachmentTotalCount] = useState(0); + const [remarkTotalCount, setRemarkTotalCount] = useState(0); + const { orderId, visible, onClose } = props; + + useEffect(() => { + if (visible) { + getOrder(); + } + }, [visible]); + + const getOrder = async () => { + try { + const data = await Manycore.Integration.FOP.getOrderAsync(orderId); + setOrderDetail(data); + } catch { + message.error('获取订单详情失败'); + } + }; + + const onAttachmentToTalCount = (num: number) => { + setAttachmentTotalCount(num); + }; + const onRemarkToTalCount = (num: number) => { + setRemarkTotalCount(num); + }; + + const TabTitle = memo(({ title, count }: { title: string; count: number }) => { + return ( +
+ {title} + +
+ ); + }); + + return ( + onClose()} + placement="right" + className={styles.orderAttachmentsRemarks} + headerStyle={{ display: 'none' }} + > +
+ onClose()} /> +
+ + } key="attachment"> + + + } + forceRender={true} + key="remark" + > + + + +
+ ); +}); + +export default OrderAttachmentsRemarks; diff --git a/src/pages/Detail/OrderAttachmentsRemarks/remark.tsx b/src/pages/Detail/OrderAttachmentsRemarks/remark.tsx new file mode 100644 index 0000000000000000000000000000000000000000..59034387c6aa90e4b8ccec373cd7fbbd1100a509 --- /dev/null +++ b/src/pages/Detail/OrderAttachmentsRemarks/remark.tsx @@ -0,0 +1,135 @@ +import { COrderStateNameMap } from '@/consts'; +import { GetOrderRemarksResult, OrderDetail } from '@/types'; +import { useLockFn, useRequest, useSize } from 'ahooks'; +import { Avatar, Button, Comment, Input, message, Tag } from 'antd'; +import moment from 'moment'; +import { memo, useEffect, useMemo, useState } from 'react'; + +import styles from './index.less'; + +interface OrderRemarkProps { + orderId: string; + orderDetail: OrderDetail; + onRemarkToTalCount: (count: number) => void; +} +const OrderRemark = memo((props: OrderRemarkProps) => { + const [inputRemark, setInputRemark] = useState(''); + const [orderRemarkList, setOrderRemarkList] = useState([]); + const { orderId, onRemarkToTalCount, orderDetail } = props; + + const { data: user } = useRequest(() => Manycore.User.getUserAsync(), { + onError: () => message.error('获取用户信息失败'), + }); + + useEffect(() => { + getOrderRemarkList(); + }, []); + + const BodySize = useSize(document.querySelector('body')); + const remarkListHeight = useMemo(() => { + if (BodySize) { + // 减去上方元素的高度 + return BodySize.height - 256; + } + }, [BodySize]); + + const getOrderRemarkList = async () => { + try { + const data = await Manycore.Integration.FOP.getOrderRemarksAsync({ orderId }); + setOrderRemarkList(data); + onRemarkToTalCount(data.length); + } catch { + message.error('获备注信息失败'); + } + }; + + const CommentTitle = memo(({ name, state }: { name?: string; state?: string }) => { + return ( + <> + {name} + {state} + + ); + }); + + const addRemark = useLockFn(async () => { + if (!inputRemark) { + message.warning('请输入备注内容'); + return; + } + try { + const option: Manycore.Integration.FOP.AddOrderStateRemarkOption = { + content: inputRemark, + orderId, + orderState: orderDetail?.orderState, + }; + await Manycore.Integration.FOP.addOrderRemarkAsync(option); + getOrderRemarkList(); + setInputRemark(''); + } catch { + message.error('添加备注失败'); + } + }); + + return ( +
+ + } + avatar={ + + + + } + content={ + setInputRemark(e.target.value)} + /> + } + datetime={ + + } + /> +
+ {orderRemarkList.map((it) => { + return ( + + } + avatar={} + content={
{it.content}
} + datetime={ + + {moment(it.createTime).format('YYYY-MM-DD HH:mm:ss')} + + } + /> + ); + })} +
+ +
+ ); +}); + +export default OrderRemark; diff --git a/src/pages/Detail/OrderOperationHistory/index.tsx b/src/pages/Detail/OrderOperationHistory/index.tsx index 1277eb469700dabb24b02c2009224dcac1baa265..24b78f4d3803999d496d1c033676eebfbf3e4f14 100644 --- a/src/pages/Detail/OrderOperationHistory/index.tsx +++ b/src/pages/Detail/OrderOperationHistory/index.tsx @@ -1,9 +1,9 @@ -import { memo, useMemo, useRef } from 'react'; -import moment from 'moment'; +import { OperationHistoryResults } from '@/types'; import { useRequest, useSize } from 'ahooks'; import { Drawer, message, Table } from 'antd'; import { ColumnsType } from 'antd/es/table'; -import { OperationHistoryResults } from '@/types'; +import moment from 'moment'; +import { forwardRef, memo, useImperativeHandle, useMemo, useRef } from 'react'; import styles from './index.less'; @@ -13,95 +13,111 @@ interface OrderOperationHistoryProps { onClose: () => void; } -const OrderOperationHistory = memo((props: OrderOperationHistoryProps) => { - const { orderId, visible, onClose } = props; +export interface IOperationHistoryRef { + /** + * 更新操作历史 + */ + refresh: () => void; +} - const columns: ColumnsType = [ - { - title: '操作账号', - dataIndex: 'operatorName', - }, - { - title: '操作角色', - dataIndex: 'roleName', - }, - { - title: '时间', - dataIndex: 'operateTime', - render: (dom, entity) => moment(entity.operateTime).format('YYYY-MM-DD HH:MM:SS'), - }, - { - title: '操作', - dataIndex: 'operateEvent', - }, - { - title: '结果', - dataIndex: 'operateResult', - }, - ]; +const OrderOperationHistory = memo( + forwardRef( + (props: OrderOperationHistoryProps, ref) => { + const { orderId, visible, onClose } = props; - const { data = [], loading } = useRequest( - () => Manycore.Integration.FOP.getOrderOperateLogAsync(orderId), - { - onError: () => message.error('获取操作历史失败'), - }, - ); + const columns: ColumnsType = [ + { + title: '操作账号', + dataIndex: 'operatorName', + }, + { + title: '操作角色', + dataIndex: 'roleName', + }, + { + title: '时间', + dataIndex: 'operateTime', + render: (dom, entity) => moment(entity.operateTime).format('YYYY-MM-DD HH:MM:SS'), + }, + { + title: '操作', + dataIndex: 'operateEvent', + }, + { + title: '结果', + dataIndex: 'operateResult', + }, + ]; - // 表格高度 - const tableListClassName = 'order-operation-history-container'; - const TableListRef = useRef(null); - const TableListSize = useSize(TableListRef); + const { + data = [], + refresh, + loading, + } = useRequest(() => Manycore.Integration.FOP.getOrderOperateLogAsync(orderId), { + onError: () => message.error('获取操作历史失败'), + }); - const tableHeight = useMemo(() => { - if (TableListSize) { - return TableListSize.height - 46; - } - }, [TableListSize]); + useImperativeHandle(ref, () => ({ + refresh, + })); - const tableScrollHeight = useMemo(() => { - if (tableHeight) { - return tableHeight - 54; - } - }, [tableHeight]); + // 表格高度 + const tableListClassName = 'order-operation-history-container'; + const TableListRef = useRef(null); + const TableListSize = useSize(TableListRef); - return ( - onClose()} - placement="right" - className={styles.orderOperationHistory} - > -
- - rowKey="operateLogId" - loading={loading} - columns={columns} - dataSource={data} - scroll={{ y: tableScrollHeight }} - pagination={{ showSizeChanger: true, showQuickJumper: true }} - /> -
- -
- ); -}); + const tableHeight = useMemo(() => { + if (TableListSize) { + return TableListSize.height - 46; + } + }, [TableListSize]); + + const tableScrollHeight = useMemo(() => { + if (tableHeight) { + return tableHeight - 54; + } + }, [tableHeight]); + + return ( + onClose()} + placement="right" + className={styles.orderOperationHistory} + > +
+ + rowKey="operateLogId" + loading={loading} + columns={columns} + dataSource={data} + scroll={{ y: tableScrollHeight }} + pagination={{ showSizeChanger: true, showQuickJumper: true }} + /> +
+ +
+ ); + }, + ), +); export default OrderOperationHistory; diff --git a/src/pages/Detail/components/EditOrderCustomerInfo.tsx b/src/pages/Detail/components/EditOrderCustomerInfo.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c995769a0a3f49b0b229ebc88dc328f63be89220 --- /dev/null +++ b/src/pages/Detail/components/EditOrderCustomerInfo.tsx @@ -0,0 +1,87 @@ +import CustomerList from '@/pages/Index/CreateOrderModal/customerList'; +import { CustomerDetail } from '@/types'; +import { Button, Form, Modal } from 'antd'; +import { memo, useEffect, useState } from 'react'; + +interface IProps { + /** + * 确认修改客户信息 + */ + onChange?: (info: CustomerDetail | null) => void; +} + +/** + * 编辑客户信息组件 + * @returns + */ +function EditOrderCustomerInfo({ onChange }: IProps) { + return ( + + + + ); +} + +interface IEditOrderCustomerInfoInner { + value?: string; + onChange?: (value: string | undefined) => string; + confirmCb?: (info: CustomerDetail | null) => void; +} + +/** + * 自定义选择客户信息表单组件 + * @returns + */ +function EditOrderCustomerInfoInner({ value, onChange, confirmCb }: IEditOrderCustomerInfoInner) { + // table 显隐 + const [customerTableVisible, setCustomerTableVisible] = useState(false); + // 当前选中的客户id + const [curCustomerId, setCurCustomerId] = useState(value); + // 当前选中的完整客户信息 + const [curCustomer, setCurCustomer] = useState(null); + useEffect(() => { + setCurCustomerId(value); + }, [value]); + return ( +
+ + { + setCurCustomerId(value); + setCustomerTableVisible(false); + }} + okButtonProps={{ + disabled: curCustomerId === value, // 没修改 + }} + onOk={() => { + onChange && onChange(curCustomerId); // 更新选中的客户 + confirmCb && confirmCb(curCustomer); // 当前选中的完成 + setCustomerTableVisible(false); + }} + > + { + // @ts-ignore + setCurCustomerId(v?.customerId); + setCurCustomer(v); + }} + /> + +
+ ); +} + +export default memo(EditOrderCustomerInfo); diff --git a/src/pages/Detail/components/OrderDetailPanel.tsx b/src/pages/Detail/components/OrderDetailPanel.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f68177389bff1ca260ca23d5d304ac2d0547b56c --- /dev/null +++ b/src/pages/Detail/components/OrderDetailPanel.tsx @@ -0,0 +1,242 @@ +import { CustomerDetail, IOrderFields, IStore, OrderDetail } from '@/types'; +import { InfoCircleOutlined } from '@ant-design/icons'; +import { Card, Descriptions, Popover } from 'antd'; +import { cloneDeep } from 'lodash'; +import { useEffect, useMemo, useState } from 'react'; +import { EOrderBlockExtra, EOrderFieldType, hasEditCustomerAuth } from '../helper'; +import EditOrderCustomerInfo from './EditOrderCustomerInfo'; +import OrderField from './OrderField'; +import OrderFieldEdit from './OrderFieldEdit'; +import OrderFieldLabel from './OrderFieldLabel'; + +/** + * 订单表单字段集合 + */ +interface IFieldsBlock { + /** + * 表单区域标题 + */ + title: string; + extra?: EOrderBlockExtra; + /** + * 表单区域内渲染的字段列表 + */ + fields: { + key: string | string[]; + name: string; + type: EOrderFieldType; + isEditable?: boolean; + multi?: boolean; + /** + * 系统字段的枚举值 + */ + enumValue?: Record; + /** + * 渲染内容处理 + */ + render?: (value: any) => any; + /** + * 后端配置的可选值列表 + */ + optionalValue?: string; + /** + * 是否必填 + */ + required?: boolean; + }[]; +} + +interface IProps { + /** + * 表单块标题 + */ + title: string; + /** + * 表单渲染配置 + */ + fields: IFieldsBlock[]; + /** + * 订单数据 + */ + data: OrderDetail; + /** + * 字段配置 + */ + fieldsConfig?: IOrderFields; + /** + * 是否处于编辑模式 + */ + isEditMode: boolean; +} + +/** + * 上传文件提示 + */ +const UPLOAD_TIP = ( + 附件格式} + content={ + <> +
+ · 图片:PNG/JPG/BMP +
+
+ · 表格:XLS/XLSX +
+
+ · CAD:DWG/DXF +
+
+ · 拆单文件:JSON +
+
+ · 其他:RAR/ZIP +
+ + } + > + +
+); + +/** + * 订单详情面板 + * @returns + */ +const OrderDetailPanel = ({ fields, data, fieldsConfig, isEditMode, title }: IProps) => { + const [stores, setStores] = useState([]); + useEffect(() => { + Manycore.Integration.FOP.getStoresAsync().then((ds) => { + setStores(ds); + }); + }, []); + const formConfig = useFormConfig(fields, fieldsConfig); + // 编辑模式下,临时选中的客户信息 + const [tempCustomer, setTempCustomer] = useState({}); + useEffect(() => { + setTempCustomer({}); + }, [isEditMode]); + const formData = isEditMode ? { ...data, ...tempCustomer } : data; + const editCustomerCb = (v: CustomerDetail | null) => { + let info = v || {}; + // 关联门店地址 + if (v!.storeId) { + const store = stores.find((d) => d.storeId === v!.storeId); + // @ts-ignore + info.shippingAddr = store?.shippingAddr; + } + setTempCustomer(v!); + }; + + return ( + + {formConfig.map(({ title, fields, extra }) => { + const fieldComps = fields.map((field) => { + const value = Array.isArray(field.key) // @ts-ignore + ? field.key.reduce((acc, key) => acc?.[key], formData) // @ts-ignore + : formData[field.key]; + const compKey = Array.isArray(field.key) ? field.key.join('-') : field.key; + let titleExtra; + if (field.type === EOrderFieldType.ATTACH && isEditMode) titleExtra = UPLOAD_TIP; + return ( + } + > + {isEditMode && field.isEditable ? ( + + ) : ( + + )} + + ); + }); + let extraComp; + switch (extra) { + case EOrderBlockExtra.CUSTOMER_EDIT: + // 是否具备修改权限 + const auth = hasEditCustomerAuth(fieldsConfig); + extraComp = + auth && isEditMode ? : null; + break; + } + return ( +
+

+ {title} + {extraComp} +

+ + {fieldComps} + +
+ ); + })} +
+ ); +}; + +// 添加自定义字段的表单区域 +const _formBlockTitleAttachCustomFields = '订单信息'; + +/** + * 获取完整的表单渲染配置:包含自定义字段、权限 + * @param fields + * @param fieldsConfig + * @returns + */ +function useFormConfig(fields: IFieldsBlock[], fieldsConfig?: IOrderFields) { + // 渲染配置(包含自定义字段) + const renderWithCustomFields = useMemo(() => { + // 无配置时,所有字段不可见 + if (!fieldsConfig) return []; + const clone = cloneDeep(fields); + // 系统字段配置 + const sysFieldsAuth = new Map(); + fieldsConfig.systemFields.forEach((d) => { + sysFieldsAuth.set(d.key, { + visible: d.isVisible, + edit: d.isEditable, + required: !!d.required, + }); + }); + clone.forEach((item) => { + item.fields = item.fields.filter((f) => { + const auth = sysFieldsAuth.get(f.key as string); + if (f.isEditable === undefined) { + f.isEditable = auth?.edit; + } + f.required = auth?.required; + return auth?.visible; + }); + }); + const formBlock = clone.find((block) => block.title === _formBlockTitleAttachCustomFields); + if (formBlock) { + formBlock.fields.push( + ...fieldsConfig.customFields + .filter((d) => d.isVisible) + .map((d) => ({ + ...d, + key: ['customFields', d.key], + })), + ); + } + return clone; + }, [fields, fieldsConfig]); + + return renderWithCustomFields; +} + +export default OrderDetailPanel; diff --git a/src/pages/Detail/components/OrderField.less b/src/pages/Detail/components/OrderField.less new file mode 100644 index 0000000000000000000000000000000000000000..851584a6c42a1cc892211db7c9b5d5b447b58829 --- /dev/null +++ b/src/pages/Detail/components/OrderField.less @@ -0,0 +1,10 @@ +.attachment { + white-space: nowrap; + :global { + .ant-descriptions-item-content { + flex: 1; + flex-direction: column; + width: 0; + } + } +} diff --git a/src/pages/Detail/components/OrderField.tsx b/src/pages/Detail/components/OrderField.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d6afea2408fa71fe4b4a916371c70ca378fd9093 --- /dev/null +++ b/src/pages/Detail/components/OrderField.tsx @@ -0,0 +1,57 @@ +import Attachment from '@/pages/components/Attachment'; +import { parseOptionalValue } from '@/utils/helper'; +import { EOrderFieldType } from '../helper'; + +interface IOrderFieldProps { + /** + * 来源于订单数据(后端返回) + */ + value: any; + /** + * 字段类型,系统字段来源于代码中的硬编码,自定义字段来源于后端配置 + */ + type: EOrderFieldType; + /** + * 渲染内容处理 + */ + render?: (value: any) => any; + /** + * 可选值列表 + */ + optionalValue?: string; +} + +/** + * 订单表单字段渲染 + * @param props + * @returns + */ +const OrderField = (props: IOrderFieldProps) => { + const { value, type, render, optionalValue } = props; + const renderText = render ? render(value) : value; + // 可选值匹配 + if (optionalValue) { + const options = parseOptionalValue(optionalValue); + return options.find((d: any) => d.value === value)?.label || renderText || '-'; + } + // 附件 + if (type === EOrderFieldType.ATTACH) { + return ; + } + // 备注列表 + if (type === EOrderFieldType.REMARK) { + return ( +
    + {value?.map((item: any, index: number) => ( +
  1. {item.content}
  2. + ))} +
+ ); + } + if (type === EOrderFieldType.BOOL) { + return value ? '是' : '否'; + } + return renderText ?? '-'; +}; + +export default OrderField; diff --git a/src/pages/Detail/components/OrderFieldEdit.tsx b/src/pages/Detail/components/OrderFieldEdit.tsx new file mode 100644 index 0000000000000000000000000000000000000000..23e49f2630e1e6ee5ad4834c510ed7195f821c02 --- /dev/null +++ b/src/pages/Detail/components/OrderFieldEdit.tsx @@ -0,0 +1,121 @@ +import { uploadFieldProps } from '@/pages/Index/CreateOrderModal'; +import { parseOptionalValue } from '@/utils/helper'; +import { UploadOutlined } from '@ant-design/icons'; +import { + ProFormDigit, + ProFormRadio, + ProFormSelect, + ProFormText, + ProFormUploadButton, +} from '@ant-design/pro-form'; +import { EOrderFieldType } from '../helper'; + +interface IProps { + /** + * 值类型 + */ + type: EOrderFieldType; + /** + * 表单字段 + * >string[] 处理自定义字段 + */ + dataKey: string | string[]; + /** + * 具备可选值列表(select渲染) + */ + optionalValue?: string; + /** + * 枚举值 + */ + enumValue?: Record; + /** + * 是否必填 + */ + required?: boolean; + /** + * 多选 + */ + multi?: boolean; +} + +/** + * 订单详情表单编辑渲染 + * @returns + */ +function OrderFieldEdit({ multi, type, dataKey, optionalValue, enumValue, required }: IProps) { + const rules = required ? [{ required: true, message: '必填' }] : []; + // 可选值 or 枚举 + if (optionalValue || type === EOrderFieldType.ENUM) { + const options = optionalValue ? parseOptionalValue(optionalValue) : enumValue; + const mode = multi ? 'multiple' : void 0; + return ( + options} + name={dataKey} + fieldProps={{ size: 'small' }} + /> + ); + } + // 数字 + if (type === EOrderFieldType.INT || type === EOrderFieldType.FLOAT) { + const precision = type === EOrderFieldType.FLOAT ? undefined : 0; + return ( + + ); + } + // 上传 + if (type === EOrderFieldType.ATTACH) { + return ( + } + extra="单个文件最大可上传 50 MB" + fieldProps={uploadFieldProps} + /> + ); + } + if (type === EOrderFieldType.BOOL) { + return ( + + ); + } + // 字符串 or 未知类型 + return ( + + ); +} + +export default OrderFieldEdit; diff --git a/src/pages/Detail/components/OrderFieldLabel.tsx b/src/pages/Detail/components/OrderFieldLabel.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a5233a1934912c87a0efaa20622838132cb79b98 --- /dev/null +++ b/src/pages/Detail/components/OrderFieldLabel.tsx @@ -0,0 +1,21 @@ +import { sliceStrEllipsis } from '@/utils/helper'; +import { Tooltip } from 'antd'; +import { memo, ReactElement } from 'react'; + +/** + * 订单表单字段标签 + * @returns + */ +function OrderFieldLabel({ label, extra }: { label: string; extra?: ReactElement }) { + return ( +
+ + {sliceStrEllipsis(label)} + +   + {extra} +
+ ); +} + +export default memo(OrderFieldLabel); diff --git a/src/pages/Detail/components/OrderFormExtras.tsx b/src/pages/Detail/components/OrderFormExtras.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3db46c927f2253ba4b5d53475e64f82a187734eb --- /dev/null +++ b/src/pages/Detail/components/OrderFormExtras.tsx @@ -0,0 +1,86 @@ +import { HistoryOutlined } from '@ant-design/icons'; +import { Button, Space } from 'antd'; +import { memo, useState } from 'react'; + +interface IProps { + /** + * 是否为编辑模式 + */ + isEditMode: boolean; + /** + * 打开操作历史列表 + */ + onOpenEventHistory: () => void; + /** + * 进入订单编辑 + */ + onEnterFormEdit: () => void; + /** + * 保存表单编辑 + */ + onSaveFormEdit: () => void; + /** + * 取消表单编辑 + */ + onCancelFormEdit: () => void; + /** + * 是否展示编辑按钮(权限) + */ + showEditBtn: boolean; + /** + * 打开附件和备注 + */ + onAttachmentsRemarks: () => void; +} + +/** + * 订单表单额外操作列表 + * @type {import('@ant-design/pro-layout').PageContainerProps + */ +function OrderFormExtras({ + showEditBtn, + isEditMode, + onOpenEventHistory, + onSaveFormEdit, + onCancelFormEdit, + onEnterFormEdit, + onAttachmentsRemarks, +}: IProps) { + const [loading, setLoading] = useState(false); + const handleSaveForm = async () => { + setLoading(true); + try { + await onSaveFormEdit(); + } catch (error) {} + setLoading(false); + }; + if (isEditMode) { + return ( + <> + + + + ); + } + return ( + <> + {showEditBtn && ( + + )} + + + + + + ); +} + +export default memo(OrderFormExtras); diff --git a/src/pages/Detail/helper/auditInfoFormConfig.ts b/src/pages/Detail/helper/auditInfoFormConfig.ts new file mode 100644 index 0000000000000000000000000000000000000000..389cfc4aaac98d800e7ea064bc11ba47540a82d7 --- /dev/null +++ b/src/pages/Detail/helper/auditInfoFormConfig.ts @@ -0,0 +1,42 @@ +import { EOrderFieldType } from './constant'; + +/** + * 审核详情表单配置 + */ +export const auditInfoFormConfig = [ + { + title: '审核信息', + fields: [ + { + key: 'auditOrderNo', + name: '审核单号', + type: EOrderFieldType.STR, + isEditable: false, + }, + { + key: 'auditorName', + name: '审核员', + type: EOrderFieldType.STR, + isEditable: false, + }, + { + key: 'designName', + name: '审核方案', + type: EOrderFieldType.STR, + isEditable: false, + }, + { + key: 'auditRemarks', + name: '审核备注', + type: EOrderFieldType.REMARK, + isEditable: false, + }, + { + key: 'auditAttachments', + name: '审核附件', + type: EOrderFieldType.ATTACH, + isEditable: false, + }, + ], + }, +]; diff --git a/src/pages/Detail/helper/constant.ts b/src/pages/Detail/helper/constant.ts new file mode 100644 index 0000000000000000000000000000000000000000..853b4af4069146b1acde4babf67c2630c9e69280 --- /dev/null +++ b/src/pages/Detail/helper/constant.ts @@ -0,0 +1,61 @@ +/** + * 订单字段类型 + * - 自定义字段:后端接口定义 + * - 系统字段:小程序内置 + */ +export enum EOrderFieldType { + /** + * 字符串 + */ + STR = 0, + /** + * 整数 + */ + INT, + /** + * 浮点数 + */ + FLOAT, + /** + * 布尔值 + */ + BOOL, + /** + * 枚举:目前只存在于系统字段 + */ + ENUM, + /** + * 附件,表单编辑:上传 + */ + ATTACH, + /** + * 备注列表 + */ + REMARK, +} + +/** + * 订单块额外组件 + */ +export enum EOrderBlockExtra { + /** + * 修改客户信息 + */ + CUSTOMER_EDIT, +} + +/** + * 详情页面包屑 + */ +export const detailPageBreadCrumb = { + routes: [ + { + path: '/', + breadcrumbName: '订单管理列表', + }, + { + path: '/detail', + breadcrumbName: '订单详情', + }, + ], +}; diff --git a/src/pages/Detail/helper/index.ts b/src/pages/Detail/helper/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..5a701ebfda2141aa812e2949049638db0c77b919 --- /dev/null +++ b/src/pages/Detail/helper/index.ts @@ -0,0 +1,5 @@ +export * from './auditInfoFormConfig'; +export * from './constant'; +export * from './orderInfoFormConfig'; +export * from './paymentFormConfig'; +export * from './util'; diff --git a/src/pages/Detail/helper/orderInfoFormConfig.ts b/src/pages/Detail/helper/orderInfoFormConfig.ts new file mode 100644 index 0000000000000000000000000000000000000000..221b7fb7cf57723554788ea82f9d4a57b0811780 --- /dev/null +++ b/src/pages/Detail/helper/orderInfoFormConfig.ts @@ -0,0 +1,156 @@ +import { + COrderStateNameMap, + COrderTypeNameMap, + CProductTypeNameMap, + CUserSourceDescribeMap, +} from '@/consts'; +import { OrderState, OrderType, ProductType, UserSource } from '@/types'; +import moment from 'moment'; +import { EOrderBlockExtra, EOrderFieldType } from './constant'; + +/** + * 订单详情表单配置 + */ +export const orderDetailFormConfig = [ + { + title: '订单信息', + fields: [ + { + key: 'orderNo', + name: '订单编号', + type: EOrderFieldType.STR, + isEditable: false, + }, + { + key: 'orderName', + name: '订单名称', + type: EOrderFieldType.STR, + }, + { + key: 'orderType', + name: '订单类型', + type: EOrderFieldType.ENUM, + multi: true, + enumValue: [...COrderTypeNameMap.keys()].map((k) => ({ + label: COrderTypeNameMap.get(k), + value: k, + })), + render: (value: OrderType[]) => + (value || []).map((v) => COrderTypeNameMap.get(v)!).join('、'), + }, + { + key: 'placeTime', + name: '创建时间', + type: EOrderFieldType.STR, + isEditable: false, + render: (value: number) => (value ? moment(value).format('YYYY-MM-DD') : '-'), + }, + { + key: 'orderState', + name: '订单状态', + type: EOrderFieldType.ENUM, + isEditable: false, + enumValue: [...COrderStateNameMap.keys()].map((k) => ({ + label: COrderStateNameMap.get(k), + value: k, + })), + render: (value: OrderState) => COrderStateNameMap.get(value) || '-', + }, + { + key: 'designerName', + name: '设计师名称', + type: EOrderFieldType.STR, + isEditable: false, + }, + { + key: 'attachments', + name: '订单附件', + type: EOrderFieldType.ATTACH, + }, + ], + }, + { + title: '客户信息', + extra: EOrderBlockExtra.CUSTOMER_EDIT, + fields: [ + { + key: 'customerName', + name: '客户姓名', + type: EOrderFieldType.STR, + isEditable: false, + }, + { + key: 'customerPhone', + name: '客户电话', + type: EOrderFieldType.STR, + isEditable: false, + }, + { + key: 'customerAddr', + name: '客户地址', + type: EOrderFieldType.STR, + isEditable: false, + }, + { + key: 'customerSource', + name: '客户来源', + type: EOrderFieldType.ENUM, + isEditable: false, + enumValue: [...CUserSourceDescribeMap.keys()].map((k) => ({ + label: CUserSourceDescribeMap.get(k), + value: k, + })), + render: (value: UserSource) => CUserSourceDescribeMap.get(value) || '-', + }, + { + key: 'planArea', + name: '客户面积', + type: EOrderFieldType.STR, + isEditable: false, + }, + ], + }, + { + title: '门店信息', + fields: [ + { + key: 'storeName', + name: '门店名称', + type: EOrderFieldType.STR, + isEditable: false, + }, + { + key: 'shippingAddr', + name: '门店地址', + type: EOrderFieldType.STR, + isEditable: false, + }, + ], + }, + { + title: '产品信息', + fields: [ + { + key: 'productType', + name: '产品类型', + type: EOrderFieldType.ENUM, + enumValue: [...CProductTypeNameMap.keys()].map((k) => ({ + label: CProductTypeNameMap.get(k), + value: k, + })), + render: (value: ProductType) => CProductTypeNameMap.get(value) || '-', + }, + { + key: 'designName', + name: '设计方案', + type: EOrderFieldType.STR, + isEditable: false, + }, + { + key: 'designAttachments', + name: '设计附件', + type: EOrderFieldType.ATTACH, + }, + ], + }, +]; diff --git a/src/pages/Detail/helper/paymentFormConfig.ts b/src/pages/Detail/helper/paymentFormConfig.ts new file mode 100644 index 0000000000000000000000000000000000000000..f8df741642d753b38a5407fbcd7e81541b2739ee --- /dev/null +++ b/src/pages/Detail/helper/paymentFormConfig.ts @@ -0,0 +1,44 @@ +import Decimal from 'decimal.js'; +import { isNil } from 'lodash'; +import { EOrderFieldType } from './constant'; + +/** + * 付款记录表单配置 + */ +export const paymentFormConfig = [ + { + title: '付款记录', + fields: [ + { + key: 'payAmt', + name: '应付金额', + type: EOrderFieldType.FLOAT, + isEditable: false, + render(value: any) { + return isNil(value) ? '-' : `¥${new Decimal(value).div(100)}`; + }, + }, + { + key: 'actualPayAmt', + name: '实付金额', + type: EOrderFieldType.FLOAT, + isEditable: false, + render(value: any) { + return isNil(value) ? '-' : `¥${new Decimal(value).div(100)}`; + }, + }, + { + key: 'payRecordCreator', + name: '创建人', + isEditable: false, + type: EOrderFieldType.STR, + }, + { + key: 'payCertificates', + name: '付款凭证', + isEditable: false, + type: EOrderFieldType.ATTACH, + }, + ], + }, +]; diff --git a/src/pages/Detail/helper/util.ts b/src/pages/Detail/helper/util.ts new file mode 100644 index 0000000000000000000000000000000000000000..e49ef41f0c007c3dc148249bfd9862da89045b8c --- /dev/null +++ b/src/pages/Detail/helper/util.ts @@ -0,0 +1,42 @@ +import { IOrderFields } from '@/types'; + +/** + * 转换上传组件表单值 + * @param data data + * @param key 上传组件name + */ +export function transformFormUploaderValues(data: Record, key: string) { + if (Array.isArray(data[key])) { + // status === undefined: 后端返回的已上传文件 + // status === 'done': 新上传文件 + data[key] = data[key] + .filter((item: any) => item.status === undefined || item.status === 'done') + .map((item: any) => { + if (item.status) { + return { + name: item.name, + url: item.response.url, + uploadKey: item.response.uploadKey, + }; + } + return item; + }); + } +} + +/** + * 是否具备修改客户信息的权限 + * @param fieldsConf + */ +export function hasEditCustomerAuth(fieldsConf?: IOrderFields) { + // 客户信息的所有系统字段(包含门店) + const keys = [ + 'customerName', + 'customerPhone', + 'customerAddr', + 'customerSource', + 'storeName', + 'shippingAddr', + ]; + return keys.every((k) => fieldsConf?.systemFields.find((d) => d.key === k)?.isEditable === true); +} diff --git a/src/pages/Detail/index.tsx b/src/pages/Detail/index.tsx index fad06c6dcf64ea65f5b7f1cbd9bfc7f1180985dc..4f648f52fadc7a54a4bf3747c40e6186d59d0556 100644 --- a/src/pages/Detail/index.tsx +++ b/src/pages/Detail/index.tsx @@ -1,30 +1,38 @@ -import React, { ReactNode, useCallback, useEffect, useMemo, useState } from 'react'; -import { useRequest, useLocation, useModel, useAccess } from 'umi'; -import { GridContent, PageContainer } from '@ant-design/pro-layout'; -import moment from 'moment'; -import Decimal from 'decimal.js'; -import { isNil } from 'lodash'; -import Attachment from '../components/Attachment'; -import OrderStateChangeModals from '../components/OrderStateChangeModal'; -import OrderStateFlow from './OrderStateFlow'; -import OrderOperationHistory from './OrderOperationHistory'; -import { HistoryOutlined } from '@ant-design/icons'; -import { DetailPageTab, OrderDetail } from '@/types'; -import { Button, Card, Statistic, Descriptions, Space, Badge, message, ConfigProvider } from 'antd'; import { CDefaultInitialState, CDesignDetailUrl, CDetailTabList, COrderStateNameMap, COrderTypeNameMap, - CProductTypeNameMap, CTabFieldsListMap, - CUserSourceDescribeMap, } from '@/consts'; +import { DetailPageTab, OrderDetail } from '@/types'; +import { GridContent, PageContainer } from '@ant-design/pro-layout'; +import { Button, ConfigProvider, Descriptions, message, Space, Statistic } from 'antd'; +import moment from 'moment'; +import React, { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useAccess, useLocation, useModel, useRequest } from 'umi'; +import OrderStateChangeModals from '../components/OrderStateChangeModal'; +import OrderOperationHistory, { IOperationHistoryRef } from './OrderOperationHistory'; +import OrderStateFlow from './OrderStateFlow'; +import { ProForm, ProFormInstance } from '@ant-design/pro-form'; import zhCN from 'antd/lib/locale/zh_CN'; +import OrderDetailPanel from './components/OrderDetailPanel'; +import OrderFormExtras from './components/OrderFormExtras'; +import { + auditInfoFormConfig, + detailPageBreadCrumb, + orderDetailFormConfig, + paymentFormConfig, + transformFormUploaderValues, +} from './helper'; import styles from './style.less'; +import OrderAttachmentsRemarks from './OrderAttachmentsRemarks'; +/** + * 订单详情页面 + */ export default function DetailPage() { const { initialState: { visibleFields } = CDefaultInitialState } = useModel('@@initialState'); const { setOrderDetail, updateOrderStateFlow } = useModel('orderDetail'); @@ -37,6 +45,10 @@ export default function DetailPage() { query: { orderId }, } = useLocation(); const [showActionHistoryDraw, setShowActionHistoryDraw] = useState(false); + const [showAttachmentsRemarksDraw, setShowAttachmentsRemarksDraw] = useState(false); + // 是否处于编辑模式 + const [editMode, setEditMode] = useState(false); + const formRef = useRef(); const detailTabList = useMemo(() => { return CDetailTabList.filter((item) => { @@ -55,6 +67,14 @@ export default function DetailPage() { }); const data: OrderDetail = orderDetail || ({} as OrderDetail); // 防止订单ID错误,结果返回null + // 获取当前订单的字段配置 + const { data: orderFields } = useRequest( + () => Manycore.Integration.FOP.getOrderFieldsAsync({ orderId }), + { + onError: () => message.error('获取订单字段配置失败'), + }, + ); + const orderPlaceTimeStr = useMemo(() => { return data.placeTime ? moment(data.placeTime).format('YYYY-MM-DD') : '-'; }, [data]); @@ -86,13 +106,6 @@ export default function DetailPage() { [data], ); - const renderMoneyField = useCallback( - (field: keyof OrderDetail) => { - return isNil(data[field]) ? '-' : `¥${new Decimal(data[field] as number).div(100)}`; - }, - [data], - ); - const renderAccessDescriptionsItem = useCallback( (field: keyof OrderDetail, label: ReactNode, customContent?: ReactNode) => { if (access[field]) { @@ -105,6 +118,7 @@ export default function DetailPage() { ); const action = useMemo(() => { + if (editMode) return null; const actions = getOrderActions(data); return ( @@ -122,23 +136,7 @@ export default function DetailPage() { ))} ); - }, [data, orderStateChangeCallback, getOrderActions, handleOrderStateChange]); - - const breadcrumb = useMemo( - () => ({ - routes: [ - { - path: '/', - breadcrumbName: '订单管理列表', - }, - { - path: '/detail', - breadcrumbName: '订单详情', - }, - ], - }), - [], - ); + }, [editMode, data, orderStateChangeCallback, getOrderActions, handleOrderStateChange]); const headerContent = useMemo(() => { return ( @@ -169,143 +167,105 @@ export default function DetailPage() { ); }, [renderField]); - const tabBarExtraContent = useMemo(() => { - return ( - - ); - }, []); + const operationHistoryRef = useRef(null); - const orderInfoTabContent = useMemo(() => { - return ( - -

订单信息

- - {renderAccessDescriptionsItem('orderNo', '订单编号')} - {renderAccessDescriptionsItem('orderName', '订单名称')} - {renderAccessDescriptionsItem( - 'orderType', - '订单类型', - data.orderType?.map((type) => COrderTypeNameMap.get(type)).join('、'), - )} - {renderAccessDescriptionsItem('placeTime', '创建时间', orderPlaceTimeStr)} - {renderAccessDescriptionsItem( - 'orderState', - '订单状态', - renderField('orderState', COrderStateNameMap), - )} - {renderAccessDescriptionsItem('designerName', '设计师名称')} - {renderAccessDescriptionsItem('remark', '订单备注')} - + /** + * 进入编辑模式 + */ + const enterEditMode = () => { + setEditMode(true); + formRef.current!.setFieldsValue(data); + }; -

客户信息

- - {renderAccessDescriptionsItem('customerName', '客户姓名')} - {renderAccessDescriptionsItem('customerPhone', '客户电话')} - {renderAccessDescriptionsItem('customerAddr', '客户地址')} - {renderAccessDescriptionsItem( - 'customerSource', - '客户来源', - renderField('customerSource', CUserSourceDescribeMap), - )} - {renderAccessDescriptionsItem('planArea', '客户面积')} - + /** + * 保存编辑后的表单,并更新详情和事件操作列表 + */ + const saveEditForm = async () => { + const values = await formRef.current?.validateFields(); + transformFormUploaderValues(values, 'attachments'); + transformFormUploaderValues(values, 'designAttachments'); + const payload = { + orderId: data.orderId, + eventKey: 'orderEdit', + extraParam: { + fieldKeyValues: values, + }, + }; -

门店信息

- - {renderAccessDescriptionsItem('storeName', '门店名称')} - {renderAccessDescriptionsItem('shippingAddr', '门店地址')} - + // @ts-ignore + await Manycore.Integration.FOP.operateOrderEventAsync(payload) + .then(() => { + message.success('编辑成功'); + }) + .catch((err) => { + message.error('编辑失败'); + throw err; + }); + await refresh(); + operationHistoryRef.current!.refresh(); + setEditMode(false); + }; -

产品信息

- - {renderAccessDescriptionsItem( - 'productType', - '产品类型', - renderField('productType', CProductTypeNameMap), - )} - {renderAccessDescriptionsItem('designName', '设计方案')} - - - - - - -
- ); - }, [data, orderPlaceTimeStr, renderField, renderAccessDescriptionsItem]); + /** + * 取消编辑表单 + */ + const cancelEditFrom = () => { + setEditMode(false); + }; - const auditInfoTabContent = useMemo(() => { - return ( - - - {renderAccessDescriptionsItem('auditOrderNo', '审核单号')} - {renderAccessDescriptionsItem('auditorName', '审核员')} - {renderAccessDescriptionsItem('designName', '审核方案')} - {renderAccessDescriptionsItem( - 'auditRemarks', - '审核备注', - - 0}> -
    - {data.auditRemarks?.map((item, index) => ( -
  1. {item.content}
  2. - ))} -
-
- - -
, - )} - - - - - -
-
- ); - }, [data, renderField, renderAccessDescriptionsItem]); + /** + * 打开操作历史面板 + */ + const openEventHistory = () => { + setShowActionHistoryDraw(true); + }; - const paymentInfoTabContent = useMemo(() => { - return ( - - - {renderAccessDescriptionsItem('payAmt', '应付金额', renderMoneyField('payAmt'))} - {renderAccessDescriptionsItem( - 'actualPayAmt', - '实付金额', - renderMoneyField('actualPayAmt'), - )} - {renderAccessDescriptionsItem('payRecordCreator', '创建人')} - - - - - - - - ); - }, [data, renderField, renderMoneyField, renderAccessDescriptionsItem]); + /** + * 打开附件和备注 + */ + const openAttachmentsRemarks = () => { + setShowAttachmentsRemarksDraw(true); + }; const detailContent = useMemo(() => { const tabContentKeyMap: Map = new Map([ - [DetailPageTab.orderInfo, orderInfoTabContent], - [DetailPageTab.auditInfo, auditInfoTabContent], - [DetailPageTab.paymentInfo, paymentInfoTabContent], + [ + DetailPageTab.orderInfo, + , + ], + [ + DetailPageTab.auditInfo, + , + ], + [ + DetailPageTab.paymentInfo, + , + ], ]); const tabContentKeys = detailTabList.map((item) => item.key); return tabContentKeys .sort((a) => (a === tabActiveKey ? -1 : 1)) .map((key) => {tabContentKeyMap.get(key)}); - }, [ - detailTabList, - tabActiveKey, - orderInfoTabContent, - auditInfoTabContent, - paymentInfoTabContent, - ]); + }, [detailTabList, tabActiveKey, editMode, data, orderFields]); useEffect(() => { // 更新model内的orderDetail @@ -314,6 +274,15 @@ export default function DetailPage() { } }, [loading]); + /** + * 判断是否具备编辑事件 + */ + const showEditBtn = useMemo(() => { + const events = data.events || []; + // @ts-ignore + return !!events.find((d) => d.key === 'orderEdit'); + }, [data]); + return ( + } tabActiveKey={tabActiveKey} onTabChange={(key) => setTabActiveKey(key as DetailPageTab)} >
- {detailContent} + + {detailContent} +
setShowActionHistoryDraw(false)} /> + setShowAttachmentsRemarksDraw(false)} + />
); } diff --git a/src/pages/Detail/style.less b/src/pages/Detail/style.less index dc0f18e5b29d77e85285339aad8997f78eb5b474..e5dd166cb2085ed41b9f0565d4ebec83c4eee808 100644 --- a/src/pages/Detail/style.less +++ b/src/pages/Detail/style.less @@ -27,6 +27,13 @@ .ant-page-header-heading-extra { flex-direction: column; } + .ant-descriptions-item-label { + white-space: nowrap; + } + .ant-form-item { + width: 14vw; + margin-top: -5px; + } } } @@ -57,3 +64,15 @@ } } } + +.options { + :global { + .ant-form-item-control-input { + min-height: 22px; + } + + .ant-form-item { + margin-bottom: 0; + } + } +} diff --git a/src/pages/Index/CreateOrderModal/customerList.tsx b/src/pages/Index/CreateOrderModal/customerList.tsx new file mode 100644 index 0000000000000000000000000000000000000000..bb35e6fd49513caff602213044fed9dac26a33c3 --- /dev/null +++ b/src/pages/Index/CreateOrderModal/customerList.tsx @@ -0,0 +1,133 @@ +import { CCustomerListColumnsStateMap, CCustomerMiniappId, CMerchantBackstageUrl } from '@/consts'; +import { CustomerDetail, TableListPagination, TFilterInfo } from '@/types'; +import { generateColumnSearchProps } from '@/utils'; +import ProTable, { ActionType, ColumnsState, ProColumns } from '@ant-design/pro-table'; +import { Button, TableProps } from 'antd'; +import { each, isNil } from 'lodash'; +import { memo, useCallback, useRef, useState } from 'react'; +import { getCustomerList } from './service'; + +interface CustomerListProps { + selectedCustomer: CustomerDetail | null; + setSelectedCustomer: React.Dispatch>; +} + +const CustomerList = memo((props: CustomerListProps) => { + const tableActionRef = useRef(); + const { selectedCustomer, setSelectedCustomer } = props; + const [filterInfo, setFilterInfo] = useState({}); + + const updateFilterInfo = useCallback( + (newFilterInfo: TFilterInfo) => { + each(newFilterInfo, (value, key) => { + const prev = filterInfo[key]; + const current = { [key]: value } as TFilterInfo; + if (isNil(prev) && !isNil(value)) { + setFilterInfo(current); + return; + } + if (!isNil(prev) && !isNil(value) && prev[0] !== value[0]) { + setFilterInfo(current); + return; + } + if (!isNil(prev) && isNil(value)) { + setFilterInfo(current); + return; + } + }); + }, + [filterInfo], + ); + + // 表格列设置 + const [columnsStateMap, setColumnsStateMap] = useState>( + CCustomerListColumnsStateMap, + ); + + const requestDataSource = useCallback( + (params: TableListPagination) => { + return getCustomerList(params, filterInfo); + }, + [filterInfo], + ); + + const handleCreateCustomer = useCallback(() => { + window.open(`${CMerchantBackstageUrl}?launchMiniapp=${CCustomerMiniappId}`); + }, []); + + const handleTableChange: PickType, 'onChange'> = useCallback( + (_, filters) => { + updateFilterInfo(filters); + tableActionRef?.current?.reload(); + }, + [tableActionRef, updateFilterInfo], + ); + + const columns: ProColumns[] = [ + { + title: '客户姓名', + dataIndex: 'customerName', + filteredValue: filterInfo['customerName'], + ...generateColumnSearchProps('customerName'), + }, + { + title: '客户电话', + dataIndex: 'customerPhone', + filteredValue: filterInfo['customerPhone'], + ...generateColumnSearchProps('customerPhone'), + }, + { + title: '客户地址', + dataIndex: 'customerAddr', + filteredValue: filterInfo['customerAddr'], + ...generateColumnSearchProps('customerAddr'), + }, + { + title: '所属门店', + dataIndex: 'storeName', + }, + { + title: '创建者', + dataIndex: 'creatorName', + }, + ]; + + return ( + + rowKey="customerId" + actionRef={tableActionRef} + headerTitle="关联已有客户订单" + search={false} + columns={columns} + request={requestDataSource} + tableAlertRender={() => false} + tableAlertOptionRender={() => false} + rowSelection={{ + type: 'radio', + selectedRowKeys: selectedCustomer?.customerId ? [selectedCustomer.customerId] : [], + onChange: (_, customers) => setSelectedCustomer(customers[0]), + }} + scroll={{ + y: 350, + }} + pagination={{ + size: 'default', + showQuickJumper: true, + }} + toolbar={{ + actions: [ + , + ], + }} + columnsState={{ + value: columnsStateMap, + onChange: setColumnsStateMap, + }} + onChange={handleTableChange} + /> + ); +}); + +export default CustomerList; diff --git a/src/pages/Index/CreateOrderModal/index.less b/src/pages/Index/CreateOrderModal/index.less new file mode 100644 index 0000000000000000000000000000000000000000..606751d5278b462316bb91bf56219d9c511c71b9 --- /dev/null +++ b/src/pages/Index/CreateOrderModal/index.less @@ -0,0 +1,28 @@ +.createOrderModal { + :global { + .ant-pro-card-body { + padding: 0; + } + .ant-pro-steps-form-step { + margin-top: 10px; + .ant-table-placeholder { + height: 350px; + } + .ant-upload-list { + max-height: 100px; + overflow-y: auto; + } + } + .ant-pro-steps-form-container { + max-height: 600px; + overflow: auto; + } + } + .orderInfoForm { + width: 856px; + margin-top: 30px; + padding: 48px 18% 20px; + border: 1px solid #d9d9d9; + border-radius: 4px; + } +} diff --git a/src/pages/Index/CreateOrderModal/index.tsx b/src/pages/Index/CreateOrderModal/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ba4365763f0000670a21b4b405446ca1c120ec60 --- /dev/null +++ b/src/pages/Index/CreateOrderModal/index.tsx @@ -0,0 +1,326 @@ +import { + CreateOrderOption, + CustomerDetail, + EOrderFieldType, + OrderField, + UploadFileBucket, + UploadFileResult, +} from '@/types'; +import { InfoCircleOutlined, UploadOutlined } from '@ant-design/icons'; +import { + ProFormDigit, + ProFormSelect, + ProFormSwitch, + ProFormText, + ProFormTextArea, + ProFormUploadButton, + StepsForm, +} from '@ant-design/pro-form'; +import { FieldProps } from '@ant-design/pro-form/lib/interface'; +import { Button, message, Modal, Popconfirm, Popover, Space, Upload, UploadProps } from 'antd'; +import { RcFile } from 'antd/lib/upload'; +import { UploadFile } from 'antd/lib/upload/interface'; +import { memo, useCallback, useState } from 'react'; +import CustomerList from './customerList'; + +import { CDefaultInitialState } from '@/consts'; +import { parseOptionalValue } from '@/utils/helper'; +import { useModel } from 'umi'; +import styles from './index.less'; + +interface CreateOrderModalProps { + visible: boolean; + onVisibleChange: (visible: boolean) => void; + onSuccess: () => void; +} + +interface CreateOrderForm { + orderName: string; + remark?: string; + attachments?: Array>; + customFields: Record; +} + +export const UploadFieldLabel = memo(() => ( + + 订单附件 + 附件格式} + content={ + <> +
+ · 图片:PNG/JPG/BMP +
+
+ · 表格:XLS/XLSX +
+
+ · CAD:DWG/DXF +
+
+ · 拆单文件:JSON +
+
+ · 其他:RAR/ZIP +
+ + } + > + +
+
+)); + +export const uploadFieldProps: FieldProps & UploadProps = { + multiple: true, + listType: 'text', + accept: '.png,.jpg,.bmp,.xls,.xlsx,.dwg,.dxf,.json,.rar,.zip', + maxCount: 10, + beforeUpload: (file) => { + // 单个文件不超过50M + if (file.size > 50 * 1024 * 1024) { + message.error(`文件“${file.name}”大小超过50MB`); + return Upload.LIST_IGNORE; + } else { + return Promise.resolve(file); + } + }, + customRequest: ({ file, onSuccess, onError, onProgress }) => { + const fileReader = new FileReader(); + + fileReader.addEventListener('load', () => { + const fileBase64 = fileReader.result as string; + // 模拟进度 + onProgress?.({ percent: 20 }); + + // 调用上传接口 + Manycore.Integration.Upload.uploadFileAsync({ + fileInfos: [{ name: (file as RcFile).name, fileBase64 }], + bucket: UploadFileBucket.order, + }).then((results) => { + const result = results[0]; + if (result.code === 0) { + onSuccess?.(result); + } else { + onError?.(new Error(result.message)); + } + }); + }); + + fileReader.readAsDataURL(file as RcFile); + }, +}; + +const CreateOrderModal = memo((props: CreateOrderModalProps) => { + const { visible, onVisibleChange, onSuccess } = props; + const [selectedCustomer, setSelectedCustomer] = useState(null); + const [currentStep, setCurrentStep] = useState(0); + const [isSubmitting, setIsSubmitting] = useState(false); + const { initialState: { orderFields: { customFields } } = CDefaultInitialState } = + useModel('@@initialState'); + + const handleModalClose = useCallback(() => { + onVisibleChange(false); + setCurrentStep(0); + setSelectedCustomer(null); + setIsSubmitting(false); + }, []); + + const handleFinish = async (values: CreateOrderForm) => { + const { orderName, remark, attachments, customFields } = values; + const payload: CreateOrderOption = { + orderName, + remark, + customFields, + ...selectedCustomer!, + attachments: attachments + ?.filter((item: UploadFile) => item.status === 'done') + ?.map((item: UploadFile) => ({ + name: item.name, + url: item.response.url, + uploadKey: item.response.uploadKey, + })), + }; + + setIsSubmitting(true); + try { + await Manycore.Integration.FOP.createOrderAsync(payload); + } catch (error) { + message.warning('FOP订单创建失败,请检查后重试'); + setIsSubmitting(false); + return false; + } + message.success('创建成功'); + setIsSubmitting(false); + onVisibleChange(false); + onSuccess(); + return true; + }; + + // 渲染自定义字段 + const renderCustomField = useCallback((field: OrderField) => { + const { key, name, optionalValue, isEditable } = field; + // 配置了可选项(优先级更高) + if (optionalValue) { + try { + const options = parseOptionalValue(optionalValue); + return ( + options} + label={name} + name={['customFields', key]} + disabled={!isEditable} + /> + ); + } catch (e) { + console.error(e); + return null; + } + } + + switch (field.type) { + case EOrderFieldType.STR: + return ( + + ); + case EOrderFieldType.INT: + return ( + + ); + case EOrderFieldType.FLOAT: + return ( + + ); + case EOrderFieldType.BOOL: + return ( + + ); + default: + return null; + } + }, []); + + return ( + + current={currentStep} + onCurrentChange={setCurrentStep} + onFinish={handleFinish} + submitter={{ + render: ({ step, onSubmit, onPre }) => { + if (step === 0) { + return ( + + ); + } + + return [ + , + + + , + ]; + }, + }} + stepsFormRender={(dom, submitter) => { + return ( + + {dom} + + ); + }} + > + { + return true; + }} + > + + + + + {/* 渲染自定义字段 */} + {customFields.map((cf) => { + if (cf.isVisible) { + return renderCustomField(cf); + } + })} + + } + title="上传附件(Max: 10)" + icon={} + extra="单个文件最大可上传 50 MB" + fieldProps={uploadFieldProps} + /> + + + ); +}); + +export default CreateOrderModal; diff --git a/src/pages/Index/CreateOrderModal/service.ts b/src/pages/Index/CreateOrderModal/service.ts new file mode 100644 index 0000000000000000000000000000000000000000..0274bcf7f9d6989ed0af953d9ed9e6f47543c3db --- /dev/null +++ b/src/pages/Index/CreateOrderModal/service.ts @@ -0,0 +1,41 @@ +import { CCustomerQueryTypeMap } from '@/consts'; +import { CustomerQueryRange, GetCustomerListOption, TFilterInfo } from '@/types'; +import { each, isNil, omitBy } from 'lodash'; + +/** 获取客户列表 */ +export async function getCustomerList( + params: { + pageSize: number; + current: number; + }, + filter: TFilterInfo, +) { + const { current, pageSize } = params; + let requestParams: GetCustomerListOption = { + start: (current - 1) * pageSize, + pageSize, + queryRange: CustomerQueryRange.myself, + }; + + each(omitBy(filter, isNil), (value, key) => { + if (isNil(value)) return; + requestParams.queryType = CCustomerQueryTypeMap.get(key); + requestParams.keyWord = String(value[0]); + }); + + try { + const { totalCount: total, result: data } = await Manycore.Integration.FOP.getCustomerListAsync( + requestParams, + ); + + return { + data, + total, + success: true, + }; + } catch (error) { + return { + success: false, + }; + } +} diff --git a/src/pages/Index/TableList/index.tsx b/src/pages/Index/TableList/index.tsx index cee5f83d61d3180f5d809d42900c0a2dc2319f69..0bf8d0652e623bc546fdf4ff2c2a078719e95182 100644 --- a/src/pages/Index/TableList/index.tsx +++ b/src/pages/Index/TableList/index.tsx @@ -1,14 +1,3 @@ -import { memo, useCallback, useMemo, useRef, useState } from 'react'; -import { Link, useModel } from 'umi'; -import { useSize } from 'ahooks'; -import ProTable, { ColumnsState, ProColumns } from '@ant-design/pro-table'; -import moment from 'moment'; -import { getOrderList } from './service'; -import { generateColumnSearchProps } from '@/utils'; -import { uniq } from 'lodash'; -import { OrderStateAction, TableListItem, TableListPagination } from '@/types'; -import { QuestionCircleOutlined } from '@ant-design/icons'; -import OrderStateChangeModals from '../../components/OrderStateChangeModal'; import { CColumnsStateMap, CDefaultInitialState, @@ -16,7 +5,13 @@ import { COrderStateNameMap, COrderTypeNameMap, CTableListScrollWidth, + ORDER_CONF_ACCESS_KEY, } from '@/consts'; +import { OrderStateAction, Permission, TableListItem, TableListPagination } from '@/types'; +import { generateColumnSearchProps } from '@/utils'; +import { QuestionCircleOutlined } from '@ant-design/icons'; +import ProTable, { ColumnsState, ProColumns } from '@ant-design/pro-table'; +import { useSize } from 'ahooks'; import { Badge, Button, @@ -29,6 +24,13 @@ import { Tooltip, Typography, } from 'antd'; +import { uniq } from 'lodash'; +import moment from 'moment'; +import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { history, Link, useAccess, useModel } from 'umi'; +import OrderStateChangeModals from '../../components/OrderStateChangeModal'; +import CreateOrderModal from '../CreateOrderModal'; +import { getOrderList } from './service'; import styles from './index.less'; @@ -39,7 +41,8 @@ export interface ITableListProps { const TableList = memo((props: ITableListProps) => { const { className: tableListClassName } = props; - const { initialState: { visibleFields } = CDefaultInitialState } = useModel('@@initialState'); + const { initialState: { orderFields: { systemFields, customFields } } = CDefaultInitialState } = + useModel('@@initialState'); const { tableActionRef, filterInfo, setFilterInfo, updateOrderStatistics } = useModel('orderList'); const { @@ -50,10 +53,16 @@ const TableList = memo((props: ITableListProps) => { handleOrderStateChange, } = useModel('orderStateOperate'); + const access = useAccess(); + // 表格列设置 const [columnsStateMap, setColumnsStateMap] = useState>(CColumnsStateMap); + useEffect(() => { + setCustomFieldsColumnsStateMap(); + }, []); + // 表格高度 const TableListRef = useRef(null); const TableListSize = useSize(TableListRef); @@ -90,6 +99,17 @@ const TableList = memo((props: ITableListProps) => { setBatchOperatingOrders([]); }, [tableActionRef, setBatchOperatingOrders, updateOrderStatistics]); + const setCustomFieldsColumnsStateMap = useCallback(() => { + const customFieldsMap: Record = {}; + // 添加可见的自定义字段 + customFields.forEach((cf) => { + if (cf.isVisible) { + customFieldsMap[cf.key] = { show: false }; + } + }); + setColumnsStateMap({ ...columnsStateMap, ...customFieldsMap }); + }, [customFields]); + const renderTableActions = useCallback( (record: TableListItem) => { const actions = getOrderActions(record); @@ -120,7 +140,7 @@ const TableList = memo((props: ITableListProps) => { 0}> {renderActions(collapsedActions).map((item, index) => ( @@ -138,6 +158,12 @@ const TableList = memo((props: ITableListProps) => { [getOrderActions, handleOrderStateChange, orderStateChangeCallback], ); + // 创建订单 + const [createOrderVisible, setCreateOrderVisible] = useState(false); + const handleCreateOrder = useCallback(() => { + setCreateOrderVisible(true); + }, [setCreateOrderVisible]); + // 批操作 const batchOperateBarVisible = useMemo( () => batchOperatingIds.length > 0, @@ -207,6 +233,34 @@ const TableList = memo((props: ITableListProps) => { orderStateChangeCallback, ]); + /** + * 进入订单配置页 + */ + const onEnterOrderConf = useCallback(() => { + history.push('/config'); + }, []); + + const toolbarActions = useMemo(() => { + const actions = []; + if (access[ORDER_CONF_ACCESS_KEY]) { + actions.push( + , + ); + } + + if (access[Permission.OrderSimpleCreate]) { + actions.push( + , + ); + } + + return actions; + }, [handleCreateOrder, onEnterOrderConf]); + const columns = useMemo[]>(() => { const allColumns: ProColumns[] = [ { @@ -222,6 +276,10 @@ const TableList = memo((props: ITableListProps) => { title: '方案', dataIndex: 'designName', render: (dom, entity) => { + if (!entity.designId) { + return dom; + } + return ( { title: '门店名称', dataIndex: 'storeName', }, + { + title: '创建者', + dataIndex: 'creatorName', + }, { title: '客户电话', dataIndex: 'customerPhone', @@ -314,13 +376,28 @@ const TableList = memo((props: ITableListProps) => { render: (_, record) => renderTableActions(record), }, ]; + // 添加可见的自定义字段 + customFields.forEach((cf) => { + if (cf.isVisible) { + allColumns.push({ + title: cf.name, + dataIndex: cf.key, + render: (_, record) => (record.customFields || {})[cf.key] || '-', + }); + } + }); + + const allVisibleDataIndexArr: string[] = []; + [...systemFields, ...customFields].forEach( + (field) => field.isVisible && allVisibleDataIndexArr.push(field.key), + ); return allColumns.filter((item) => { - const visibleDataIndexArr = visibleFields.concat(['option']); + const visibleDataIndexArr = allVisibleDataIndexArr.concat(['option']); return visibleDataIndexArr.includes(item.dataIndex as string); }); - }, [visibleFields, filterInfo, renderTableActions]); + }, [systemFields, customFields, filterInfo, renderTableActions]); return (
@@ -346,6 +423,9 @@ const TableList = memo((props: ITableListProps) => { size: 'default', showQuickJumper: true, }} + toolbar={{ + actions: toolbarActions, + }} columnsState={{ value: columnsStateMap, onChange: setColumnsStateMap, @@ -365,6 +445,12 @@ const TableList = memo((props: ITableListProps) => {
{renderBatchOperateBarOption()}
+ +