feat(supply-chain): 查询框直接支持批量筛选
This commit is contained in:
parent
c691669101
commit
211aff2578
@ -273,7 +273,7 @@ describe('限价管理', () => {
|
||||
expect(screen.getByRole('button', { name: '筛选运营负责人' })).toBeInTheDocument()
|
||||
expect((screen.getByRole('columnheader', { name: /^组合编码/ }) as HTMLElement).style.top).toBe('0px')
|
||||
fireEvent.click(screen.getByRole('button', { name: '筛选组合编码', exact: true }))
|
||||
expect(await screen.findByRole('button', { name: '批量查询' })).toBeInTheDocument()
|
||||
expect(await screen.findByRole('textbox', { name: '搜索组合编码' })).toBeInTheDocument()
|
||||
const columnLabels = screen.getAllByRole('columnheader').map((cell) => (cell.textContent ?? '').replace(/[↕↑↓▾]/g, '').trim())
|
||||
expect(columnLabels.indexOf('运营负责人')).toBe(columnLabels.indexOf('组合编码') + 1)
|
||||
expect(screen.queryByRole('button', { name: '导出明细' })).not.toBeInTheDocument()
|
||||
@ -304,11 +304,9 @@ describe('限价管理', () => {
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '筛选条码', exact: true }))
|
||||
const dialog = await screen.findByRole('dialog', { name: '筛选条码' })
|
||||
fireEvent.click(within(dialog).getByRole('button', { name: '批量查询' }))
|
||||
fireEvent.change(within(dialog).getByRole('textbox', { name: '批量查询条码' }), {
|
||||
target: { value: 'BAR-001\nBAR-002,BAR-003' },
|
||||
})
|
||||
fireEvent.click(within(dialog).getByRole('button', { name: '查询' }))
|
||||
const search = within(dialog).getByRole('textbox', { name: '搜索条码' })
|
||||
fireEvent.change(search, { target: { value: 'BAR-001 BAR-002 BAR-003' } })
|
||||
fireEvent.keyDown(search, { key: 'Enter' })
|
||||
|
||||
await waitFor(() => {
|
||||
const latestProductRequest = (global.fetch as jest.Mock).mock.calls
|
||||
|
||||
@ -31,8 +31,6 @@ export function FilterOptionPanel({ field, selected, onChange, contextFilters, o
|
||||
const [search, setSearch] = useState('')
|
||||
const [options, setOptions] = useState<string[]>(field.fixedOptions ?? [])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [batchOpen, setBatchOpen] = useState(false)
|
||||
const [batchText, setBatchText] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
if (field.fixedOptions) return
|
||||
@ -92,13 +90,12 @@ export function FilterOptionPanel({ field, selected, onChange, contextFilters, o
|
||||
)
|
||||
}
|
||||
|
||||
const applyBatch = () => {
|
||||
const values = parseBatchFilterValues(batchText)
|
||||
const applyBatch = (raw: string) => {
|
||||
const values = parseBatchFilterValues(raw)
|
||||
.map((value) => value === '空值' ? PRODUCT_ARCHIVE_EMPTY_FILTER_VALUE : value)
|
||||
if (!values.length) return
|
||||
onChange([...new Set([...selected, ...values])].slice(0, 20000))
|
||||
setBatchText('')
|
||||
setBatchOpen(false)
|
||||
setSearch('')
|
||||
}
|
||||
|
||||
return (
|
||||
@ -106,25 +103,21 @@ export function FilterOptionPanel({ field, selected, onChange, contextFilters, o
|
||||
<input
|
||||
className={styles.filterSearch}
|
||||
value={search}
|
||||
placeholder={`搜索${field.label}`}
|
||||
placeholder={`搜索${field.label},多个值用空格分隔`}
|
||||
aria-label={`搜索${field.label}`}
|
||||
onChange={(event) => setSearch(event.target.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key !== 'Enter') return
|
||||
event.preventDefault()
|
||||
applyBatch(search)
|
||||
}}
|
||||
onPaste={(event) => {
|
||||
const text = event.clipboardData.getData('text')
|
||||
if (parseBatchFilterValues(text).length <= 1) return
|
||||
event.preventDefault()
|
||||
applyBatch(text)
|
||||
}}
|
||||
/>
|
||||
{batchOpen && (
|
||||
<div className={styles.batchFilterBox}>
|
||||
<textarea
|
||||
className={styles.batchFilterTextarea}
|
||||
value={batchText}
|
||||
placeholder={`批量输入${field.label},支持换行、逗号或顿号分隔`}
|
||||
aria-label={`批量查询${field.label}`}
|
||||
onChange={(event) => setBatchText(event.target.value)}
|
||||
/>
|
||||
<div className={styles.batchFilterActions}>
|
||||
<button type="button" className={styles.linkButton} onClick={() => setBatchOpen(false)}>取消</button>
|
||||
<button type="button" className={styles.batchFilterApply} onClick={applyBatch}>查询</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.filterOptions} role="listbox" aria-label={field.label}>
|
||||
{displayOptions.length === 0 && (
|
||||
<div className={styles.filterEmpty}>{loading ? '加载中…' : '没有可选值'}</div>
|
||||
@ -143,10 +136,7 @@ export function FilterOptionPanel({ field, selected, onChange, contextFilters, o
|
||||
))}
|
||||
</div>
|
||||
<div className={styles.filterFooter}>
|
||||
<div className={styles.filterFooterActions}>
|
||||
<button type="button" className={styles.linkButton} onClick={() => onChange([])}>清空</button>
|
||||
<button type="button" className={styles.linkButton} onClick={() => setBatchOpen((open) => !open)}>批量查询</button>
|
||||
</div>
|
||||
{onCollapse && (
|
||||
<button type="button" className={styles.linkButton} onClick={onCollapse}>收起</button>
|
||||
)}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { parseBatchFilterValues } from './limit-filter-batch'
|
||||
|
||||
describe('parseBatchFilterValues', () => {
|
||||
it('parses pasted values separated by newlines, commas, semicolons and tabs', () => {
|
||||
expect(parseBatchFilterValues('CODE-A\nCODE-B,CODE-C; CODE-D、CODE-E\tCODE-A')).toEqual([
|
||||
it('parses pasted values separated by spaces, newlines, commas and semicolons', () => {
|
||||
expect(parseBatchFilterValues('CODE-A CODE-B\nCODE-C,CODE-D; CODE-E、CODE-A')).toEqual([
|
||||
'CODE-A',
|
||||
'CODE-B',
|
||||
'CODE-C',
|
||||
@ -11,7 +11,7 @@ describe('parseBatchFilterValues', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('keeps spaces inside a single value and ignores empty entries', () => {
|
||||
it('collapses repeated spaces and ignores empty entries', () => {
|
||||
expect(parseBatchFilterValues(' 供应商A ,, \n供应商B ')).toEqual(['供应商A', '供应商B'])
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
const BATCH_SEPARATOR = /[\n\r,,;;、\t]+/
|
||||
const BATCH_SEPARATOR = /[\s,,;;、]+/
|
||||
|
||||
/** 批量筛选支持换行、逗号、分号、顿号和制表符分隔,结果去重并保持输入顺序。 */
|
||||
export function parseBatchFilterValues(raw: string) {
|
||||
|
||||
@ -43,12 +43,6 @@
|
||||
.filterDropdown{position:absolute;top:calc(100% + 5px);left:0;z-index:35;display:flex;width:236px;max-height:320px;flex-direction:column;border:1px solid #e4e4e7;border-radius:6px;background:#fff;box-shadow:0 14px 36px rgba(24,24,27,.14);padding:6px}
|
||||
.filterSearch{height:28px;flex:0 0 auto;margin-bottom:5px;border:1px solid #e4e4e7;border-radius:4px;padding:0 8px;color:#27272a;font-size:12px;outline:0}
|
||||
.filterSearch:focus{border-color:#a1a1aa}
|
||||
.batchFilterBox{display:flex;flex:0 0 auto;flex-direction:column;gap:6px;border:1px solid #e4e4e7;border-radius:5px;background:#fafafa;margin-bottom:6px;padding:6px}
|
||||
.batchFilterTextarea{box-sizing:border-box;width:100%;height:58px;resize:vertical;border:1px solid #d4d4d8;border-radius:4px;background:#fff;padding:6px 8px;color:#27272a;font:12px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;outline:0}
|
||||
.batchFilterTextarea:focus{border-color:#a1a1aa}
|
||||
.batchFilterActions{display:flex;align-items:center;justify-content:flex-end;gap:8px}
|
||||
.batchFilterApply{height:26px;border:1px solid #27272a;border-radius:4px;background:#27272a;padding:0 10px;color:#fff;font-size:11px;cursor:pointer}
|
||||
.batchFilterApply:hover{background:#3f3f46}
|
||||
.filterOptions{min-height:0;flex:1;overflow-y:auto}
|
||||
.filterOption{display:flex;min-height:28px;align-items:center;gap:8px;border-radius:4px;padding:0 7px;color:#3f3f46;font-size:12px;cursor:pointer}
|
||||
.filterOption:hover{background:#f4f4f5}
|
||||
@ -56,7 +50,6 @@
|
||||
.filterOptionLabel{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.filterEmpty{padding:10px 7px;color:#a1a1aa;font-size:11px}
|
||||
.filterFooter{display:flex;flex:0 0 auto;align-items:center;justify-content:space-between;gap:8px;border-top:1px solid #f0f0f1;margin-top:5px;padding-top:6px}
|
||||
.filterFooterActions{display:flex;align-items:center;gap:4px}
|
||||
.linkButton{border:0;background:transparent;color:#52525b;font-size:11px;cursor:pointer;padding:2px 4px}
|
||||
.linkButton:hover{color:#18181b;text-decoration:underline}
|
||||
.tableScroll{position:relative;min-height:0;flex:1;overflow:auto;background:#fff}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user