Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 91 additions & 27 deletions ui/post-cfg/components/select/CatSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ComboBox } from '@fluentui/react'
import { Checkbox, Label, Stack } from '@fluentui/react'
import { Component } from 'react'
import { PostCat } from '@/model/post-cat'

Expand All @@ -7,43 +7,107 @@ type Props = {
selectedCatIds: number[]
onChange: (categoryIds: number[]) => void
}

type State = { selectedCatIds: number[] }

type TreeNode = {
cat: PostCat
depth: number
}

export class CatSelect extends Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = { selectedCatIds: this.props.selectedCatIds }
}

override componentDidUpdate(prevProps: Props) {
if (prevProps.selectedCatIds !== this.props.selectedCatIds)
this.setState({ selectedCatIds: this.props.selectedCatIds })
}

render() {
const opts = this.props.userCats.map(cat => ({
data: cat.categoryId,
key: cat.categoryId,
text: cat.title,
}))
const nodes = this.buildTreeNodes()

return (
<ComboBox
label="分类"
placeholder="点击选择"
options={opts}
selectedKey={this.state.selectedCatIds}
multiSelect
useComboBoxAsMenuWidth
onChange={(_, opt, __, val) => {
if (opt !== undefined) {
if (opt.selected !== true || val === undefined) {
const selectedCatIds = this.state.selectedCatIds.filter(x => x !== opt.data)
this.setState({ selectedCatIds })
this.props.onChange(selectedCatIds)
} else {
this.state.selectedCatIds.push(opt.data as number)
this.setState(this.state)
this.props.onChange(this.state.selectedCatIds)
}
}
}}
/>
<Stack tokens={{ childrenGap: 8 }}>
<Label styles={{ root: { paddingTop: 0 } }}>分类</Label>
<Stack tokens={{ childrenGap: 6 }}>
{nodes.map(({ cat, depth }) => {
const isChecked = this.state.selectedCatIds.includes(cat.categoryId)
return (
<Stack
key={cat.categoryId}
horizontal
verticalAlign="center"
styles={{ root: { paddingLeft: depth * 16 } }}
>
<Checkbox
label={cat.title}
checked={isChecked}
onChange={(_, checked) => this.onCheckboxChange(cat.categoryId, checked === true)}
/>
</Stack>
)
})}
</Stack>
</Stack>
)
}

private onCheckboxChange(categoryId: number, checked: boolean) {
const selectedCatIds = checked
? this.state.selectedCatIds.includes(categoryId)
? this.state.selectedCatIds
: [...this.state.selectedCatIds, categoryId]
: this.state.selectedCatIds.filter(x => x !== categoryId)

this.setState({ selectedCatIds })
this.props.onChange(selectedCatIds)
}

private buildTreeNodes(): TreeNode[] {
const allCats = this.props.userCats.map(cat => Object.assign(new PostCat(), cat))
const nodeMap = new Map<number, PostCat>()
allCats.forEach(cat => {
cat.children = []
nodeMap.set(cat.categoryId, cat)
})

const roots: PostCat[] = []
allCats.forEach(cat => {
if (cat.parentId == null) {
roots.push(cat)
return
}

const parent = nodeMap.get(cat.parentId)
if (parent == null) {
roots.push(cat)
return
}

cat.parent = parent
parent.children?.push(cat)
})

const sortCats = (cats: PostCat[]) =>
cats.sort((x, y) => {
const order1 = x.order ?? Number.MAX_SAFE_INTEGER
const order2 = y.order ?? Number.MAX_SAFE_INTEGER
if (order1 !== order2) return order1 - order2
return x.title.localeCompare(y.title)
})

const result: TreeNode[] = []
const walk = (cats: PostCat[], depth: number) => {
for (const cat of sortCats(cats)) {
result.push({ cat, depth })
if (cat.children != null && cat.children.length > 0) walk(cat.children, depth + 1)
}
}

walk(sortCats(roots), 0)
return result
}
}
Loading