feat(耗材管理): 增强扫码入库功能并优化UI样式

添加入库原因、操作人和备注字段到扫码入库功能
重构入库/出库弹窗的UI布局和交互设计
优化扫码弹窗的视觉体验和信息展示
This commit is contained in:
zhang1106
2026-04-16 16:54:10 +08:00
parent 01a05719dc
commit 7df6c73d7b
+290 -98
View File
@@ -65,6 +65,8 @@ import {
DownOutlined, DownOutlined,
CheckSquareOutlined, CheckSquareOutlined,
SettingOutlined, SettingOutlined,
InboxOutlined,
UserOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import axios from 'axios'; import axios from 'axios';
import * as XLSX from 'xlsx'; import * as XLSX from 'xlsx';
@@ -163,6 +165,9 @@ function ConsumableManagement() {
const [scanChecking, setScanChecking] = useState(false); const [scanChecking, setScanChecking] = useState(false);
const [scannedSnList, setScannedSnList] = useState([]); const [scannedSnList, setScannedSnList] = useState([]);
const [selectedScanInConsumable, setSelectedScanInConsumable] = useState(null); const [selectedScanInConsumable, setSelectedScanInConsumable] = useState(null);
const [scanInOperator, setScanInOperator] = useState('');
const [scanInReason, setScanInReason] = useState('');
const [scanInNotes, setScanInNotes] = useState('');
const scanInputRef = React.useRef(null); const scanInputRef = React.useRef(null);
const [quickOutModalVisible, setQuickOutModalVisible] = useState(false); const [quickOutModalVisible, setQuickOutModalVisible] = useState(false);
const [quickOutConsumable, setQuickOutConsumable] = useState(null); const [quickOutConsumable, setQuickOutConsumable] = useState(null);
@@ -1066,6 +1071,9 @@ function ConsumableManagement() {
setScannedSnList([]); setScannedSnList([]);
setSelectedScanInConsumable(null); setSelectedScanInConsumable(null);
setScanChecking(false); setScanChecking(false);
setScanInOperator('');
setScanInReason('');
setScanInNotes('');
}, []); }, []);
const addToPendingOut = (consumable, snList = []) => { const addToPendingOut = (consumable, snList = []) => {
@@ -1253,8 +1261,9 @@ function ConsumableManagement() {
consumableId, consumableId,
type: 'in', type: 'in',
quantity: scannedSnList.length, quantity: scannedSnList.length,
operator: '系统管理员', operator: scanInOperator || '系统管理员',
reason: '扫码入库', reason: scanInReason || '扫码入库',
notes: scanInNotes || '',
snList: scannedSnList, snList: scannedSnList,
}); });
message.success({ message.success({
@@ -1268,7 +1277,7 @@ function ConsumableManagement() {
console.error('入库失败:', error); console.error('入库失败:', error);
} }
}, },
[scannedSnList, handleScanCancel, fetchConsumables] [scannedSnList, scanInOperator, scanInReason, scanInNotes, handleScanCancel, fetchConsumables]
); );
const searchDevices = useCallback(async keyword => { const searchDevices = useCallback(async keyword => {
@@ -3920,68 +3929,135 @@ function ConsumableManagement() {
{/* 入库/出库弹窗 */} {/* 入库/出库弹窗 */}
<Modal <Modal
title={ title={null}
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}> open={stockModalVisible}
closeIcon={<CloseButton />}
onCancel={handleStockCancel}
footer={null}
width={560}
style={{ top: 80 }}
bodyStyle={{ padding: 0 }}
>
{/* 标题栏 */}
<div <div
style={{ style={{
width: '36px', padding: '20px 24px 16px',
height: '36px', background: stockType === 'in'
borderRadius: designTokens.borderRadius.md, ? 'linear-gradient(135deg, #52c41a15 0%, #52c41a08 100%)'
background: : 'linear-gradient(135deg, #ff4d4f15 0%, #ff4d4f08 100%)',
stockType === 'in' borderBottom: `1px solid ${designTokens.colors.neutral[200]}`,
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '14px' }}>
<div
style={{
width: '48px',
height: '48px',
borderRadius: designTokens.borderRadius.lg,
background: stockType === 'in'
? designTokens.colors.success.gradient ? designTokens.colors.success.gradient
: designTokens.colors.error.gradient, : designTokens.colors.error.gradient,
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
color: '#fff', color: '#fff',
boxShadow: stockType === 'in'
? `0 4px 16px ${designTokens.colors.success.main}50`
: `0 4px 16px ${designTokens.colors.error.main}50`,
}} }}
> >
{stockType === 'in' ? <ArrowDownOutlined /> : <ArrowUpOutlined />} {stockType === 'in' ? <ArrowDownOutlined style={{ fontSize: '22px' }} /> : <ArrowUpOutlined style={{ fontSize: '22px' }} />}
</div> </div>
<span style={{ fontSize: '18px', fontWeight: 600 }}> <div>
<div style={{ fontSize: '20px', fontWeight: 600, color: designTokens.colors.text.primary }}>
{stockType === 'in' ? '耗材入库' : '耗材出库'} {stockType === 'in' ? '耗材入库' : '耗材出库'}
</span>
</div> </div>
} <div style={{ fontSize: '13px', color: designTokens.colors.neutral[500], marginTop: '2px' }}>
open={stockModalVisible} {stockType === 'in' ? '填写入库信息,增加库存数量' : '填写出库信息,减少库存数量'}
closeIcon={<CloseButton />} </div>
onCancel={handleStockCancel} </div>
footer={null} </div>
width={500} </div>
>
<div style={{ padding: '20px 24px 24px' }}>
<Form <Form
form={stockForm} form={stockForm}
layout="vertical" layout="vertical"
onFinish={handleStockSubmit} onFinish={handleStockSubmit}
style={{ marginTop: '16px' }}
> >
<Form.Item name="consumableId" label="耗材ID"> {/* 耗材信息卡片 */}
<Input disabled style={{ ...inputStyles.form, ...inputStyles.disabled }} /> <div
</Form.Item> style={{
<Form.Item name="consumableName" label="耗材名称"> background: `linear-gradient(135deg, ${designTokens.colors.primary.main}08 0%, ${designTokens.colors.primary.main}03 100%)`,
<Input disabled style={{ ...inputStyles.form, ...inputStyles.disabled }} /> border: `1px solid ${designTokens.colors.primary.main}20`,
</Form.Item> borderRadius: designTokens.borderRadius.md,
padding: '16px',
{stockType === 'out' && stockRecord?.snList && stockRecord.snList.length > 0 && ( marginBottom: '20px',
<Form.Item }}
label={ >
<span> <div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '12px' }}>
选择SN序列号 <InboxOutlined style={{ color: designTokens.colors.primary.main, fontSize: '16px' }} />
<Text type="secondary" style={{ fontSize: '12px', marginLeft: '8px' }}> <span style={{ fontSize: '13px', fontWeight: 600, color: designTokens.colors.text.secondary }}>
(已选 {selectedSnList.length} ) 耗材信息
</Text>
</span> </span>
} </div>
<Row gutter={16}>
<Col span={12}>
<div style={{ marginBottom: '8px' }}>
<div style={{ fontSize: '12px', color: designTokens.colors.neutral[500], marginBottom: '2px' }}>耗材ID</div>
<div style={{ fontSize: '14px', fontWeight: 500, color: designTokens.colors.text.primary, fontFamily: 'monospace' }}>
<Form.Item name="consumableId" style={{ margin: 0 }}>
<Input disabled style={{ ...inputStyles.form, ...inputStyles.disabled, background: 'transparent', border: 'none', padding: 0 }} />
</Form.Item>
</div>
</div>
</Col>
<Col span={12}>
<div style={{ marginBottom: '8px' }}>
<div style={{ fontSize: '12px', color: designTokens.colors.neutral[500], marginBottom: '2px' }}>耗材名称</div>
<div style={{ fontSize: '14px', fontWeight: 500, color: designTokens.colors.text.primary }}>
<Form.Item name="consumableName" style={{ margin: 0 }}>
<Input disabled style={{ ...inputStyles.form, ...inputStyles.disabled, background: 'transparent', border: 'none', padding: 0 }} />
</Form.Item>
</div>
</div>
</Col>
</Row>
</div>
{/* SN序列号选择区(出库) */}
{stockType === 'out' && stockRecord?.snList && stockRecord.snList.length > 0 && (
<div
style={{
background: designTokens.colors.neutral[50],
border: `1px solid ${designTokens.colors.neutral[200]}`,
borderRadius: designTokens.borderRadius.md,
padding: '16px',
marginBottom: '20px',
}}
> >
<Space direction="vertical" style={{ width: '100%' }} size="small"> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '12px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<BarcodeOutlined style={{ color: designTokens.colors.neutral[600], fontSize: '14px' }} />
<span style={{ fontSize: '13px', fontWeight: 600, color: designTokens.colors.text.secondary }}>
SN序列号选择
</span>
</div>
<Badge
count={`已选 ${selectedSnList.length}`}
style={{
backgroundColor: selectedSnList.length > 0 ? designTokens.colors.success.main : designTokens.colors.neutral[400],
fontSize: '11px',
}}
/>
</div>
<Input <Input
placeholder="搜索SN序列号..." placeholder="搜索SN序列号..."
prefix={<SearchOutlined />} prefix={<SearchOutlined />}
allowClear allowClear
value={snSearchKeyword} value={snSearchKeyword}
onChange={e => setSnSearchKeyword(e.target.value)} onChange={e => setSnSearchKeyword(e.target.value)}
style={{ ...inputStyles.search, marginBottom: '8px' }} style={{ ...inputStyles.search, marginBottom: '12px' }}
/> />
<Space size="small" style={{ marginBottom: '8px' }}> <Space size="small" style={{ marginBottom: '8px' }}>
<Button <Button
@@ -4012,8 +4088,9 @@ function ConsumableManagement() {
</Space> </Space>
<div <div
style={{ style={{
maxHeight: '150px', maxHeight: '140px',
overflowY: 'auto', overflowY: 'auto',
background: '#fff',
border: `1px solid ${designTokens.colors.neutral[200]}`, border: `1px solid ${designTokens.colors.neutral[200]}`,
borderRadius: designTokens.borderRadius.sm, borderRadius: designTokens.borderRadius.sm,
padding: '8px', padding: '8px',
@@ -4026,10 +4103,7 @@ function ConsumableManagement() {
); );
if (filteredSnList.length === 0) { if (filteredSnList.length === 0) {
return ( return (
<Text <Text type="secondary" style={{ display: 'block', textAlign: 'center', padding: '16px 0' }}>
type="secondary"
style={{ display: 'block', textAlign: 'center', padding: '16px 0' }}
>
无匹配的 SN 无匹配的 SN
</Text> </Text>
); );
@@ -4055,31 +4129,59 @@ function ConsumableManagement() {
)); ));
})()} })()}
</div> </div>
<Text type="secondary" style={{ fontSize: '12px' }}> <Text type="secondary" style={{ fontSize: '11px', marginTop: '8px', display: 'block' }}>
{snSearchKeyword {snSearchKeyword
? `过滤结果: ${stockRecord.snList.filter(sn => sn.toLowerCase().includes(snSearchKeyword.toLowerCase())).length} 个SN` ? `过滤结果: ${stockRecord.snList.filter(sn => sn.toLowerCase().includes(snSearchKeyword.toLowerCase())).length} 个SN`
: `${stockRecord.snList.length} 个SN`} : `${stockRecord.snList.length} 个SN`}
点击SN进行选择
</Text> </Text>
</Space> </div>
</Form.Item>
)} )}
{/* 入库SN输入区 */}
{stockType === 'in' && ( {stockType === 'in' && (
<Form.Item name="snList" label="入库SN序列号(可选)"> <div style={{ marginBottom: '20px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px' }}>
<BarcodeOutlined style={{ color: designTokens.colors.neutral[600], fontSize: '14px' }} />
<span style={{ fontSize: '13px', fontWeight: 500, color: designTokens.colors.text.secondary }}>
入库SN序列号
</span>
<Text type="secondary" style={{ fontSize: '12px' }}>可选</Text>
</div>
<Form.Item name="snList" style={{ margin: 0 }}>
<Select <Select
mode="tags" mode="tags"
style={selectStyles.base} style={{ ...selectStyles.base, maxHeight: '120px', overflowY: 'auto' }}
placeholder={inputPlaceholders.snList} placeholder="输入SN后按回车添加,支持批量粘贴"
tokenSeparators={[',', '\n']} tokenSeparators={[',', '\n']}
maxTagCount="responsive"
/> />
</Form.Item> </Form.Item>
</div>
)} )}
{/* 操作信息区 */}
<div
style={{
background: designTokens.colors.neutral[50],
borderRadius: designTokens.borderRadius.md,
padding: '16px',
marginBottom: '20px',
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '16px' }}>
<InfoCircleOutlined style={{ color: designTokens.colors.neutral[600], fontSize: '14px' }} />
<span style={{ fontSize: '13px', fontWeight: 600, color: designTokens.colors.text.secondary }}>
操作信息
</span>
</div>
<Row gutter={12}>
<Col span={12}>
<Form.Item <Form.Item
name="quantity" name="quantity"
label="数量" label="数量"
rules={[inputValidationRules.required('请输入数量')]} rules={[inputValidationRules.required('请输入数量')]}
style={{ marginBottom: '16px' }}
> >
<InputNumber <InputNumber
min={1} min={1}
@@ -4088,53 +4190,104 @@ function ConsumableManagement() {
? stockRecord.snList.length ? stockRecord.snList.length
: undefined : undefined
} }
style={inputNumberStyles.base} style={{ ...inputNumberStyles.base, width: '100%' }}
placeholder="请输入数量" placeholder="请输入数量"
/> />
</Form.Item> </Form.Item>
<Form.Item name="operator" label="操作人"> </Col>
<Input placeholder={inputPlaceholders.operator} style={inputStyles.form} /> <Col span={12}>
<Form.Item name="operator" label="操作人" style={{ marginBottom: '16px' }}>
<Input prefix={<UserOutlined style={{ color: designTokens.colors.neutral[400] }} />} placeholder={inputPlaceholders.operator} style={inputStyles.form} />
</Form.Item> </Form.Item>
<Form.Item name="reason" label={stockType === 'in' ? '入库原因' : '出库原因'}> </Col>
</Row>
<Form.Item name="reason" label={stockType === 'in' ? '入库原因' : '出库原因'} style={{ marginBottom: '16px' }}>
<Input <Input
placeholder={stockType === 'in' ? '如: 采购入库' : '如: 部门领用、报损出库'} prefix={<FileTextOutlined style={{ color: designTokens.colors.neutral[400] }} />}
placeholder={stockType === 'in' ? '如: 采购入库、退还入库' : '如: 部门领用、报损出库'}
style={inputStyles.form} style={inputStyles.form}
/> />
</Form.Item> </Form.Item>
<Form.Item name="notes" label="备注"> <Form.Item name="notes" label="备注">
<TextArea rows={2} placeholder={inputPlaceholders.notes} style={textAreaStyles.base} /> <TextArea rows={2} placeholder="请输入备注信息(可选)" style={textAreaStyles.base} />
</Form.Item> </Form.Item>
<Form.Item> </div>
<Space>
{/* 操作按钮 */}
<Form.Item style={{ marginBottom: 0 }}>
<div style={{ display: 'flex', gap: '12px', justifyContent: 'flex-end' }}>
<Button
onClick={handleStockCancel}
style={{
height: '44px',
borderRadius: designTokens.borderRadius.md,
paddingLeft: '24px',
paddingRight: '24px',
}}
>
取消
</Button>
<Button <Button
type="primary" type="primary"
htmlType="submit" htmlType="submit"
style={{ style={{
background: background: stockType === 'in'
stockType === 'in'
? designTokens.colors.success.gradient ? designTokens.colors.success.gradient
: designTokens.colors.error.gradient, : designTokens.colors.error.gradient,
border: 'none', border: 'none',
borderRadius: designTokens.borderRadius.sm, borderRadius: designTokens.borderRadius.md,
height: '44px',
paddingLeft: '32px',
paddingRight: '32px',
fontSize: '15px',
fontWeight: 500,
boxShadow: stockType === 'in'
? `0 4px 12px ${designTokens.colors.success.main}40`
: `0 4px 12px ${designTokens.colors.error.main}40`,
}} }}
> >
<Space>
{stockType === 'in' ? <ArrowDownOutlined /> : <ArrowUpOutlined />}
{stockType === 'in' ? '确认入库' : '确认出库'} {stockType === 'in' ? '确认入库' : '确认出库'}
</Button>
<Button onClick={handleStockCancel}>取消</Button>
</Space> </Space>
</Button>
</div>
</Form.Item> </Form.Item>
</Form> </Form>
</div>
</Modal> </Modal>
{/* 扫码弹窗 - 支持入库/出库/添加SN三种模式 */} {/* 扫码弹窗 - 支持入库/出库/添加SN三种模式 */}
<Modal <Modal
title={ title={null}
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}> open={scanModalVisible}
closeIcon={<CloseButton />}
onCancel={handleScanCancel}
footer={null}
width={560}
style={{ top: 80 }}
bodyStyle={{ padding: 0 }}
>
{/* 标题栏 */}
<div <div
style={{ style={{
width: '40px', padding: '20px 24px 16px',
height: '40px', background: scanMode === 'in'
borderRadius: designTokens.borderRadius.md, ? 'linear-gradient(135deg, #52c41a15 0%, #52c41a08 100%)'
: scanMode === 'out'
? 'linear-gradient(135deg, #ff4d4f15 0%, #ff4d4f08 100%)'
: 'linear-gradient(135deg, #1890ff15 0%, #1890ff08 100%)',
borderBottom: `1px solid ${designTokens.colors.neutral[200]}`,
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '14px' }}>
<div
style={{
width: '48px',
height: '48px',
borderRadius: designTokens.borderRadius.lg,
background: scanMode === 'in' background: scanMode === 'in'
? designTokens.colors.success.gradient ? designTokens.colors.success.gradient
: scanMode === 'out' : scanMode === 'out'
@@ -4145,57 +4298,50 @@ function ConsumableManagement() {
justifyContent: 'center', justifyContent: 'center',
color: '#fff', color: '#fff',
boxShadow: scanMode === 'in' boxShadow: scanMode === 'in'
? `0 4px 12px ${designTokens.colors.success.main}40` ? `0 4px 16px ${designTokens.colors.success.main}50`
: scanMode === 'out' : scanMode === 'out'
? `0 4px 12px ${designTokens.colors.error.main}40` ? `0 4px 16px ${designTokens.colors.error.main}50`
: `0 4px 12px ${designTokens.colors.primary.main}40`, : `0 4px 16px ${designTokens.colors.primary.main}50`,
}} }}
> >
{scanMode === 'in' ? <ArrowDownOutlined style={{ fontSize: '20px' }} /> : {scanMode === 'in' ? <ArrowDownOutlined style={{ fontSize: '22px' }} /> :
scanMode === 'out' ? <ArrowUpOutlined style={{ fontSize: '20px' }} /> : scanMode === 'out' ? <ArrowUpOutlined style={{ fontSize: '22px' }} /> :
<BarcodeOutlined style={{ fontSize: '20px' }} />} <BarcodeOutlined style={{ fontSize: '22px' }} />}
</div> </div>
<div> <div>
<span style={{ fontSize: '18px', fontWeight: 600, color: designTokens.colors.text.primary }}> <div style={{ fontSize: '20px', fontWeight: 600, color: designTokens.colors.text.primary }}>
{scanMode === 'in' ? '扫码入库' : scanMode === 'out' ? '扫码出库' : '扫码添加SN'} {scanMode === 'in' ? '扫码入库' : scanMode === 'out' ? '扫码出库' : '扫码添加SN'}
</span> </div>
<div style={{ fontSize: '12px', color: designTokens.colors.neutral[500], marginTop: '2px' }}> <div style={{ fontSize: '13px', color: designTokens.colors.neutral[500], marginTop: '2px' }}>
{scanMode === 'in' ? '扫描 SN 序列号快速入库' : {scanMode === 'in' ? '扫描 SN 序列号快速入库' :
scanMode === 'out' ? '扫描 SN 序列号快速出库' : scanMode === 'out' ? '扫描 SN 序列号快速出库' :
'扫描 SN 序列号添加到表单'} '扫描 SN 序列号添加到表单'}
</div> </div>
</div> </div>
</div> </div>
} </div>
open={scanModalVisible}
closeIcon={<CloseButton />} <div style={{ padding: '20px 24px 24px' }}>
onCancel={handleScanCancel}
footer={null}
width={560}
bodyStyle={{ padding: '0 24px 24px' }}
style={{ top: 60 }}
>
{/* 统计卡片区 - 根据模式显示不同内容 */} {/* 统计卡片区 - 根据模式显示不同内容 */}
<div style={{ marginBottom: '24px', marginTop: '-8px' }}> <div style={{ marginBottom: '20px' }}>
{scanMode === 'out' ? ( {scanMode === 'out' ? (
/* 出库模式:显示待出库列表 */
<div <div
style={{ style={{
background: `linear-gradient(135deg, ${designTokens.colors.error.main}10 0%, ${designTokens.colors.error.main}05 100%)`, background: `linear-gradient(135deg, ${designTokens.colors.error.main}10 0%, ${designTokens.colors.error.main}05 100%)`,
border: `1px solid ${designTokens.colors.error.main}30`, border: `1px solid ${designTokens.colors.error.main}30`,
borderRadius: designTokens.borderRadius.md, borderRadius: designTokens.borderRadius.md,
padding: '20px', padding: '16px',
textAlign: 'center', textAlign: 'center',
}} }}
> >
<div style={{ fontSize: '32px', fontWeight: 700, color: designTokens.colors.error.main, lineHeight: 1 }}> <div style={{ fontSize: '28px', fontWeight: 700, color: designTokens.colors.error.main, lineHeight: 1 }}>
{pendingOutItems.length} {pendingOutItems.length}
</div> </div>
<div style={{ fontSize: '13px', color: designTokens.colors.neutral[600], marginTop: '6px' }}> <div style={{ fontSize: '12px', color: designTokens.colors.neutral[600], marginTop: '4px' }}>
待出库耗材项 待出库耗材项
</div> </div>
{pendingOutItems.length > 0 && ( {pendingOutItems.length > 0 && (
<div style={{ marginTop: '12px', paddingTop: '12px', borderTop: `1px dashed ${designTokens.colors.error.main}30` }}> <div style={{ marginTop: '10px', paddingTop: '10px', borderTop: `1px dashed ${designTokens.colors.error.main}30` }}>
<Text type="secondary" style={{ fontSize: '12px' }}> <Text type="secondary" style={{ fontSize: '12px' }}>
{pendingOutItems.reduce((sum, item) => sum + item.quantity, 0)} 件耗材 {pendingOutItems.reduce((sum, item) => sum + item.quantity, 0)} 件耗材
</Text> </Text>
@@ -4203,7 +4349,6 @@ function ConsumableManagement() {
)} )}
</div> </div>
) : ( ) : (
/* 入库/添加SN模式:显示已扫描数量 */
<Row gutter={12}> <Row gutter={12}>
<Col span={12}> <Col span={12}>
<div <div
@@ -4211,11 +4356,11 @@ function ConsumableManagement() {
background: `linear-gradient(135deg, ${designTokens.colors.success.main}15 0%, ${designTokens.colors.success.main}05 100%)`, background: `linear-gradient(135deg, ${designTokens.colors.success.main}15 0%, ${designTokens.colors.success.main}05 100%)`,
border: `1px solid ${designTokens.colors.success.main}30`, border: `1px solid ${designTokens.colors.success.main}30`,
borderRadius: designTokens.borderRadius.md, borderRadius: designTokens.borderRadius.md,
padding: '16px', padding: '14px',
textAlign: 'center', textAlign: 'center',
}} }}
> >
<div style={{ fontSize: '28px', fontWeight: 700, color: designTokens.colors.success.main, lineHeight: 1 }}> <div style={{ fontSize: '26px', fontWeight: 700, color: designTokens.colors.success.main, lineHeight: 1 }}>
{(scanMode === 'in' ? scannedSnList : snList).length} {(scanMode === 'in' ? scannedSnList : snList).length}
</div> </div>
<div style={{ fontSize: '12px', color: designTokens.colors.neutral[500], marginTop: '4px' }}> <div style={{ fontSize: '12px', color: designTokens.colors.neutral[500], marginTop: '4px' }}>
@@ -4229,11 +4374,11 @@ function ConsumableManagement() {
background: `linear-gradient(135deg, ${designTokens.colors.primary.main}15 0%, ${designTokens.colors.primary.main}05 100%)`, background: `linear-gradient(135deg, ${designTokens.colors.primary.main}15 0%, ${designTokens.colors.primary.main}05 100%)`,
border: `1px solid ${designTokens.colors.primary.main}30`, border: `1px solid ${designTokens.colors.primary.main}30`,
borderRadius: designTokens.borderRadius.md, borderRadius: designTokens.borderRadius.md,
padding: '16px', padding: '14px',
textAlign: 'center', textAlign: 'center',
}} }}
> >
<div style={{ fontSize: '28px', fontWeight: 700, color: designTokens.colors.primary.main, lineHeight: 1 }}> <div style={{ fontSize: '26px', fontWeight: 700, color: designTokens.colors.primary.main, lineHeight: 1 }}>
{allConsumablesForScan.length} {allConsumablesForScan.length}
</div> </div>
<div style={{ fontSize: '12px', color: designTokens.colors.neutral[500], marginTop: '4px' }}> <div style={{ fontSize: '12px', color: designTokens.colors.neutral[500], marginTop: '4px' }}>
@@ -4434,6 +4579,52 @@ function ConsumableManagement() {
</div> </div>
)} )}
{/* 入库模式:入库原因、操作人、备注 */}
{scanMode === 'in' && (
<div style={{ marginBottom: '16px' }}>
<Row gutter={12}>
<Col span={12}>
<div style={{ marginBottom: '12px' }}>
<div style={{ fontSize: '13px', fontWeight: 500, color: designTokens.colors.neutral[600], marginBottom: '4px' }}>
入库原因
</div>
<Input
placeholder="如: 采购入库、退还入库"
value={scanInReason}
onChange={e => setScanInReason(e.target.value)}
style={{ borderRadius: designTokens.borderRadius.sm }}
/>
</div>
</Col>
<Col span={12}>
<div style={{ marginBottom: '12px' }}>
<div style={{ fontSize: '13px', fontWeight: 500, color: designTokens.colors.neutral[600], marginBottom: '4px' }}>
操作人
</div>
<Input
placeholder="请输入操作人"
value={scanInOperator}
onChange={e => setScanInOperator(e.target.value)}
style={{ borderRadius: designTokens.borderRadius.sm }}
/>
</div>
</Col>
</Row>
<div>
<div style={{ fontSize: '13px', fontWeight: 500, color: designTokens.colors.neutral[600], marginBottom: '4px' }}>
备注
</div>
<Input.TextArea
placeholder="请输入备注信息(可选)"
value={scanInNotes}
onChange={e => setScanInNotes(e.target.value)}
rows={2}
style={{ borderRadius: designTokens.borderRadius.sm }}
/>
</div>
</div>
)}
{/* 入库/添加SN模式:SN列表网格 */} {/* 入库/添加SN模式:SN列表网格 */}
{scanMode !== 'out' && (scanMode === 'in' ? scannedSnList : snList).length > 0 && ( {scanMode !== 'out' && (scanMode === 'in' ? scannedSnList : snList).length > 0 && (
<motion.div <motion.div
@@ -4683,6 +4874,7 @@ function ConsumableManagement() {
'扫描 SN 序列号自动添加到表单中'} '扫描 SN 序列号自动添加到表单中'}
</Text> </Text>
</div> </div>
</div>
</Modal> </Modal>
{/* 扫码快速出库到设备弹窗 */} {/* 扫码快速出库到设备弹窗 */}