登录 个人 与 零星采购

This commit is contained in:
孙景学
2025-07-09 14:01:45 +08:00
parent 56da66ee21
commit b46b35cd4b
35 changed files with 1054 additions and 343 deletions

View File

@ -0,0 +1,10 @@
.summary-sticky-cell {
position: sticky !important;
left: 0 !important;
z-index: 3 !important;
background: #fff !important;
min-width: 180px;
max-width: 180px;
border-right: 1px solid #f0f0f0;
}

View File

@ -1,7 +1,8 @@
import React, { useEffect, useState } from 'react';
import { Modal, Table, Button, Select, Spin, message } from 'antd';
import { reviewInfo, update } from '../services';
import { reviewInfo, groupByList, update } from '../services';
import { connect } from 'umi';
import './GroupLeaderModal.less'
interface GroupLeaderModalProps {
visible: boolean;
view: boolean;
@ -11,7 +12,6 @@ interface GroupLeaderModalProps {
dispatch: any;
}
// 只读备注弹窗
const RemarkViewModal: React.FC<{
visible: boolean;
onCancel: () => void;
@ -52,12 +52,11 @@ const GroupLeaderModal: React.FC<GroupLeaderModalProps> = ({
const [loading, setLoading] = useState(false);
const [suppliers, setSuppliers] = useState<any[]>([]);
const [items, setItems] = useState<any[]>([]);
// groupSummaryResult: { [supplierId]: 0|1 }
const [supplierReviewerMap, setSupplierReviewerMap] = useState<Record<string, string[]>>({});
const [groupSummaryResult, setGroupSummaryResult] = useState<{ [k: string]: '0' | '1' | undefined }>({});
// 查看备注弹窗
const [remarkModal, setRemarkModal] = useState({ open: false, remark: '', file: undefined as any });
// 拉取数据
// 适配新结构
useEffect(() => {
if (visible && record?.id) {
setLoading(true);
@ -65,17 +64,38 @@ const GroupLeaderModal: React.FC<GroupLeaderModalProps> = ({
.then((res: any) => {
const supplierList = res?.data || [];
setSuppliers(supplierList);
// 所有项,取第一家公司
const allItems = (supplierList[0]?.coscoAccessItemList || []);
// 非summary项
setItems(allItems.filter((item: any) => item.itemType !== 'summary'));
// summary 行初始化
// ============ 1. 收集所有评审项itemType==='item' ===========
const allItemsSet = new Set<string>();
const allItemsArr: any[] = [];
supplierList.forEach((sup: any) => {
(sup.coscoAccessUserItemList || []).forEach((u: any) => {
if (u.itemType === 'item' && !allItemsSet.has(u.itemName)) {
allItemsSet.add(u.itemName);
allItemsArr.push({ id: u.itemId, itemName: u.itemName });
}
});
});
setItems(allItemsArr);
// ============ 2. 组员收集 =============
const tempReviewerMap: Record<string, string[]> = {};
supplierList.forEach((sup: any) => {
const reviewerSet = new Set<string>();
(sup.coscoAccessUserItemList || []).forEach((u: any) => {
if (u.itemType === 'item' && u.reviewBy) {
reviewerSet.add(u.reviewBy);
}
});
tempReviewerMap[sup.supplierId] = Array.from(reviewerSet);
});
setSupplierReviewerMap(tempReviewerMap);
// ============= 3. 汇总行初始化 ============
const summaryMap: { [k: string]: '0' | '1' | undefined } = {};
supplierList.forEach((sup: any) => {
// summary 行
const summaryItem = (sup.coscoAccessItemList || []).find((i: any) => i.itemType === 'summary');
// summaryMap[sup.supplierId] = summaryItem?.reviewResult;coscoAccessUserItemList
summaryMap[sup.supplierId] = summaryItem.coscoAccessUserItemList[0]?.reviewResult;
const summary = (sup.coscoAccessUserItemList || []).find((u: any) => u.itemType === 'summary');
summaryMap[sup.supplierId] = summary?.reviewResult;
});
setGroupSummaryResult(summaryMap);
})
@ -83,6 +103,7 @@ const GroupLeaderModal: React.FC<GroupLeaderModalProps> = ({
} else if (!visible) {
setSuppliers([]);
setItems([]);
setSupplierReviewerMap({});
setGroupSummaryResult({});
}
}, [visible, record]);
@ -97,29 +118,22 @@ const GroupLeaderModal: React.FC<GroupLeaderModalProps> = ({
// 提交
const handleSubmit = () => {
// 只收集 summary 行参数
let summaryParams: any[] = [];
summaryParams = suppliers.map((sup: any) => {
// 找到summary项
const summaryItem = (sup.coscoAccessItemList || []).find((i: any) => i.itemType === 'summary');
let summaryParams: any[] = suppliers.map((sup: any) => {
const summaryItem = (sup.coscoAccessUserItemList || []).find((i: any) => i.itemType === 'summary');
if (!summaryItem) return null;
return {
id: summaryItem.coscoAccessUserItemList[0].id,
id: summaryItem.id,
reviewResult: groupSummaryResult[sup.supplierId],
remark: '', // 可拓展
remark: '',
coscoAccessTtemAttachments: undefined,
accessWorkId: record?.id
}
}).filter(Boolean);
let accessWorkId = ''
if(record?.id) {
accessWorkId = record?.id
}
let accessWorkId = record?.id || '';
for (let index = 0; index < summaryParams.length; index++) {
if(summaryParams[index].reviewResult === null) {
message.warning('有未评审项');
return
if(summaryParams[index].reviewResult === null || summaryParams[index].reviewResult === undefined) {
message.warning('有未评审项');
return
}
}
update({ coscoAccessUserItemList: summaryParams, accessWorkId }).then((res: any) => {
@ -140,27 +154,15 @@ const GroupLeaderModal: React.FC<GroupLeaderModalProps> = ({
fixed: 'left',
},
...suppliers.map(sup => {
// 当前公司所有人员所有非summary项取 coscoAccessUserItemList 多个)
const reviewerSet = new Set<string>();
(sup.coscoAccessItemList || []).forEach((item: any) => {
if (item.itemType !== 'summary' && Array.isArray(item.coscoAccessUserItemList)) {
item.coscoAccessUserItemList.forEach((u: any) => {
reviewerSet.add(u.reviewBy);
});
}
});
const reviewers = Array.from(reviewerSet);
const reviewers = supplierReviewerMap[sup.supplierId] || [];
return {
title: (
<div>
{/* <div style={{ fontWeight: 600 }}>{sup.supplierName}</div> */}
<a
onClick={() => {
dispatch({
type: 'globalModal/show',
payload: {
id: sup.supplierId,
},
payload: { id: sup.supplierId },
});
}}
>
@ -175,10 +177,10 @@ const GroupLeaderModal: React.FC<GroupLeaderModalProps> = ({
width: 100,
align: 'center',
render: (_: any, row: any) => {
// 在sup.coscoAccessItemList里找该itemName下该人员
const item = (sup.coscoAccessItemList || []).find((it: any) => it.itemName === row.itemName && it.itemType !== 'summary');
if (!item) return null;
const userItem = (item.coscoAccessUserItemList || []).find((u: any) => u.reviewBy === reviewBy);
// itemName+reviewBy精确查找
const userItem = (sup.coscoAccessUserItemList || []).find(
(u: any) => u.itemType === 'item' && u.itemName === row.itemName && u.reviewBy === reviewBy
);
if (!userItem) return null;
return (
<div>
@ -207,18 +209,9 @@ const GroupLeaderModal: React.FC<GroupLeaderModalProps> = ({
itemName: item.itemName
};
suppliers.forEach(sup => {
// 每个人员一格
const reviewerSet = new Set<string>();
(sup.coscoAccessItemList || []).forEach((it: any) => {
if (it.itemType !== 'summary' && Array.isArray(it.coscoAccessUserItemList)) {
it.coscoAccessUserItemList.forEach((u: any) => {
reviewerSet.add(u.reviewBy);
});
}
});
const reviewers = Array.from(reviewerSet);
const reviewers = supplierReviewerMap[sup.supplierId] || [];
reviewers.forEach((reviewBy: string) => {
row[`${sup.supplierId}_${reviewBy}`] = null; // 具体显示用render
row[`${sup.supplierId}_${reviewBy}`] = null;
});
});
return row;
@ -227,25 +220,22 @@ const GroupLeaderModal: React.FC<GroupLeaderModalProps> = ({
// summary 行
const summaryRow = (
<Table.Summary.Row>
<Table.Summary.Cell index={-1}></Table.Summary.Cell>
<Table.Summary.Cell
index={-1}
className="summary-sticky-cell"
>
</Table.Summary.Cell>
{suppliers.map((sup, index) => {
// 统计reviewer个数
const reviewerSet = new Set<string>();
(sup.coscoAccessItemList || []).forEach((item: any) => {
if (item.itemType !== 'summary' && Array.isArray(item.coscoAccessUserItemList)) {
item.coscoAccessUserItemList.forEach((u: any) => {
reviewerSet.add(u.reviewBy);
});
}
});
const colSpan = reviewerSet.size || 1;
const reviewers = supplierReviewerMap[sup.supplierId] || [];
const colSpan = reviewers.length || 1;
return (
<Table.Summary.Cell index={index} key={sup.supplierId} colSpan={colSpan} align="center">
{view && (
<span style={{color: groupSummaryResult[sup.supplierId] === '0'? '#52c41a': '#f5222d' }}> {groupSummaryResult[sup.supplierId] === '0'? '合格': '不合格'}</span>
)}
{ !view && (
<Select
<Select
style={{ width: 120 }}
value={groupSummaryResult[sup.supplierId]}
placeholder="请选择"

View File

@ -1,9 +1,10 @@
import React, { useEffect, useState } from 'react';
import { Modal, Table, Button, Radio, Input, Upload, message, Spin } from 'antd';
import { reviewInfo, uploadFile, update } from '../services';
import { reviewInfo, groupByList, uploadFile, update } from '../services';
import type { ColumnsType } from 'antd/es/table';
import { connect } from 'umi';
//主体参数接口
// 参数类型
interface ResultModalProps {
visible: boolean;
view: boolean;
@ -15,7 +16,6 @@ interface ResultModalProps {
interface RowData {
key: string;
itemName: string;
// 其他如有
}
type CellValue = {
reviewResult?: '0' | '1';
@ -53,8 +53,6 @@ const RemarkViewModal: React.FC<{
</Modal>
);
//主体
const ResultModal: React.FC<ResultModalProps> = ({
visible,
view,
@ -63,15 +61,12 @@ const ResultModal: React.FC<ResultModalProps> = ({
onSubmit,
dispatch,
}) => {
// type: 'teamMembers' | 'groupLeader'
// 供应商、评审项、单元格数据
const [suppliers, setSuppliers] = useState<any[]>([]);
const [items, setItems] = useState<any[]>([]);
const [userItemMatrix, setUserItemMatrix] = useState<any[][]>([]);
// userItemId为唯一key
const [cellData, setCellData] = useState<{ [userItemId: string]: CellValue }>({});
const [loading, setLoading] = useState(false);
// 查看备注弹窗
// 查看备注弹窗
const [remarkModal, setRemarkModal] = useState({ open: false, remark: '', file: undefined as any });
// 备注弹窗
const [remarksModalVisible, setRemarksModalVisible] = useState(false);
@ -83,30 +78,33 @@ const ResultModal: React.FC<ResultModalProps> = ({
useEffect(() => {
if (visible && record?.id) {
setLoading(true);
reviewInfo({ id: record.id })
.then((res: any) => {
const supplierList = res?.data || [];
// 拉取所有评审项groupByList和所有供应商评审数据reviewInfo
Promise.all([
groupByList({ accessWorkId: record.id }),
reviewInfo({ id: record.id }),
])
.then(([groupRes, reviewRes]: any) => {
// 1. 评审项
const itemList = (groupRes?.data || []).filter((it: any) => it.itemType !== 'summary');
setItems(itemList);
// 2. 供应商reviewInfo每个供应商有coscoAccessUserItemList平铺
const supplierList = reviewRes?.data || [];
setSuppliers(supplierList);
// 用第一家供应商取所有“非summary”的评审项
const baseItems = (supplierList[0]?.coscoAccessItemList || []).filter(
(item: any) => item.itemType !== 'summary'
);
setItems(baseItems);
// 构建userItem二维矩阵[row][col]=userItem/null
// userItemMatrix[row][col]为对应格的userItem
// 3. 构建表格的 userItemMatrix每行item每列供应商单元格为对应userItem/null
const matrix: any[][] = [];
const newCellData: { [userItemId: string]: CellValue } = {};
baseItems.forEach((item: any, rowIdx: number) => {
itemList.forEach((item: any, rowIdx: number) => {
matrix[rowIdx] = [];
supplierList.forEach((sup: any, colIdx: number) => {
const cur = (sup.coscoAccessItemList || []).find((it: any) => it.itemName === item.itemName && it.itemType !== 'summary');
const userItem =
cur?.coscoAccessUserItemList?.length > 0
? cur.coscoAccessUserItemList[0]
: null;
matrix[rowIdx][colIdx] = userItem;
// 在当前供应商的 coscoAccessUserItemList 里找itemName完全一致的userItem
const userItem = (sup.coscoAccessUserItemList || []).find(
(ui: any) => ui.itemName === item.itemName && ui.itemType !== 'summary'
);
matrix[rowIdx][colIdx] = userItem || null;
// 初始化cellData
if (userItem) {
@ -224,7 +222,7 @@ const ResultModal: React.FC<ResultModalProps> = ({
}
for (let index = 0; index < result.length; index++) {
if(result[index].reviewResult === null) {
if(result[index].reviewResult === null || result[index].reviewResult === undefined) {
message.warning('有未评审项');
return
}
@ -273,17 +271,14 @@ const ResultModal: React.FC<ResultModalProps> = ({
const v = cellData[userItem.id] || {};
return (
<>
{view && (
<>
<span style={{color: v.reviewResult === '0'? '#52c41a': '#f5222d' }}> {v.reviewResult === '0'? '合格': '不合格'}</span>
<Button type="link" size="small" onClick={() =>
setRemarkModal({ open: true, remark: v.remark || '', file: v.file })
}></Button>
</>
setRemarkModal({ open: true, remark: v.remark || '', file: v.file })
}></Button>
</>
)}
{ !view && (
<div>
<Radio.Group
@ -312,7 +307,6 @@ const ResultModal: React.FC<ResultModalProps> = ({
)}
</div>
)}
</>
);
}
@ -327,66 +321,66 @@ const ResultModal: React.FC<ResultModalProps> = ({
return (
<>
<Modal
title="评审结果"
visible={visible}
onCancel={onCancel}
footer={[
<Button key="cancel" onClick={onCancel}>
{view ? '关闭' : '取消'}
</Button>,
!view && (
<Button key="submit" type="primary" onClick={handleSubmit}>
</Button>
)
]}
width={1000}
bodyStyle={{ maxHeight: '60vh', overflowY: 'auto' }}
centered
destroyOnClose
>
<Spin spinning={loading}>
<Table
dataSource={tableData}
columns={columns}
pagination={false}
bordered
scroll={{ x: 300 * suppliers.length + 200 }}
/>
</Spin>
<Modal
title="备注/上传附件"
visible={remarksModalVisible}
onCancel={() => setRemarksModalVisible(false)}
title="评审结果"
visible={visible}
onCancel={onCancel}
footer={[
<Button key="cancel" onClick={() => setRemarksModalVisible(false)}>
<Button key="cancel" onClick={onCancel}>
{view ? '关闭' : '取消'}
</Button>,
<Button key="submit" type="primary" onClick={handleSubmitRemarks}>
</Button>
!view && (
<Button key="submit" type="primary" onClick={handleSubmit}>
</Button>
)
]}
width={1000}
bodyStyle={{ maxHeight: '60vh', overflowY: 'auto' }}
centered
destroyOnClose
>
<Input.TextArea
rows={4}
placeholder="请输入备注"
value={remarks}
onChange={e => setRemarks(e.target.value)}
/>
<Upload {...uploadProps}>
<Button style={{ marginTop: 12 }}></Button>
</Upload>
<Spin spinning={loading}>
<Table
dataSource={tableData}
columns={columns}
pagination={false}
bordered
scroll={{ x: 300 * suppliers.length + 200 }}
/>
</Spin>
<Modal
title="备注/上传附件"
visible={remarksModalVisible}
onCancel={() => setRemarksModalVisible(false)}
footer={[
<Button key="cancel" onClick={() => setRemarksModalVisible(false)}>
</Button>,
<Button key="submit" type="primary" onClick={handleSubmitRemarks}>
</Button>
]}
destroyOnClose
>
<Input.TextArea
rows={4}
placeholder="请输入备注"
value={remarks}
onChange={e => setRemarks(e.target.value)}
/>
<Upload {...uploadProps}>
<Button style={{ marginTop: 12 }}></Button>
</Upload>
</Modal>
</Modal>
</Modal>
<RemarkViewModal
visible={remarkModal.open}
onCancel={() => setRemarkModal({ open: false, remark: '', file: undefined })}
remark={remarkModal.remark}
file={remarkModal.file}
/>
</>
<RemarkViewModal
visible={remarkModal.open}
onCancel={() => setRemarkModal({ open: false, remark: '', file: undefined })}
remark={remarkModal.remark}
file={remarkModal.file}
/>
</>
);
};

View File

@ -17,6 +17,13 @@ export const getPage = (data: getPageData) => request.post('/coscoAccessWork/get
userId?: string;
}
export const reviewInfo = (params: reviewInfoData) => request.get(`/coscoAccessWork/reviewInfo`, { params });
/**
* 评审项
*/
interface groupByListoData {
accessWorkId: string;
}
export const groupByList = (params: groupByListoData) => request.get(`/coscoAccessItem/groupByList`, { params });
/**
* 上传文件