💄 麻烦的新闻来源过滤
This commit is contained in:
parent
83b87074ec
commit
9c2117da36
@ -244,14 +244,59 @@
|
|||||||
|
|
||||||
.timer-select-options {
|
.timer-select-options {
|
||||||
@apply rounded-xl py-1 overflow-hidden drop-shadow absolute inset-x-0 top-[30px];
|
@apply rounded-xl py-1 overflow-hidden drop-shadow absolute inset-x-0 top-[30px];
|
||||||
background: linear-gradient(180deg,rgb(244, 247, 252) 0%, rgb(217, 232, 255) 100%);
|
background: linear-gradient(180deg, rgb(244, 247, 252) 0%, rgb(217, 232, 255) 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.timer-select-option-item {
|
.timer-select-option-item {
|
||||||
@apply py-1.5 px-4 cursor-pointer text-gray-800 hover:text-blue-500;
|
@apply py-1.5 px-4 cursor-pointer text-gray-800 hover:text-blue-500;
|
||||||
&.selected{
|
&.selected {
|
||||||
@apply text-blue-500;
|
@apply text-blue-500;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-select-container {
|
||||||
|
.select-value {
|
||||||
|
@apply text-blue-500 px-4 cursor-pointer h-[31px];
|
||||||
|
}
|
||||||
|
.options-list-container {
|
||||||
|
@apply overflow-auto py-1 drop-shadow absolute top-[30px];
|
||||||
|
border-radius: 0 0 0.75rem 0.75rem;
|
||||||
|
background: linear-gradient(180deg, rgb(244, 247, 252) 0%, rgb(217, 232, 255) 100%);
|
||||||
|
max-height: calc(100vh - var(--app-header-header) - 300px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-list {
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-option-item {
|
||||||
|
@apply py-1.5 px-4 cursor-pointer text-gray-800 hover:text-blue-500 hover:bg-[#d9eaff];
|
||||||
|
&.selected {
|
||||||
|
@apply text-blue-500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-select-child-container {
|
||||||
|
.ant-popover-inner {
|
||||||
|
@apply p-0 overflow-hidden drop-shadow shadow-none; //
|
||||||
|
background: linear-gradient(180deg, rgb(235, 242, 253) 0%, rgb(244, 247, 252) 100%);
|
||||||
|
border-radius: 0 0.75rem 0.75rem 0;
|
||||||
|
transform: translate(16px, -7px);
|
||||||
|
//backdrop-filter: 0 3px 1px 5px rgb(0, 0, 0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-options-list {
|
||||||
|
@apply py-1.5 top-0;
|
||||||
|
max-height: 150px;
|
||||||
|
|
||||||
|
.select-option-item {
|
||||||
|
@apply py-1.5 px-4 cursor-pointer text-gray-800 hover:text-blue-500 hover:bg-[#d9eaff];
|
||||||
|
&.selected {
|
||||||
|
@apply text-blue-500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
151
src/components/form/tag-select.tsx
Normal file
151
src/components/form/tag-select.tsx
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
import React, {useMemo, useState} from "react";
|
||||||
|
import {Checkbox, Popover} from "antd";
|
||||||
|
import {useBoolean} from "ahooks";
|
||||||
|
import {CaretUpOutlined} from "@ant-design/icons";
|
||||||
|
|
||||||
|
const TagSelect = (props: {
|
||||||
|
options: OptionItem[];
|
||||||
|
onChange: (values: Id[][]) => void;
|
||||||
|
className?: string;
|
||||||
|
}) => {
|
||||||
|
const [selectValues, _setSelectValues] = React.useState<Id[][]>([])
|
||||||
|
const [checkedAll, _setCheckedAll] = React.useState<boolean>(false)
|
||||||
|
const [visible, {set}] = useBoolean(false);
|
||||||
|
const allValues = useMemo(()=>{
|
||||||
|
const values:Id[][] = []
|
||||||
|
props.options.forEach(item=>{
|
||||||
|
if(item && item.children?.length > 0){
|
||||||
|
// eslint-disable-next-line no-unsafe-optional-chaining
|
||||||
|
values.push(...(item.children?.map(c => [item.value, c.value])))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return values
|
||||||
|
},[props.options])
|
||||||
|
const handleAllChanged = (checked: boolean) => {
|
||||||
|
_setCheckedAll(checked)
|
||||||
|
const values:Id[][] = []
|
||||||
|
if (checked){
|
||||||
|
// set(false)
|
||||||
|
values.push(...allValues)
|
||||||
|
}
|
||||||
|
_setSelectValues(values)
|
||||||
|
}
|
||||||
|
const handleLevel1Change = (checked: boolean, item: OptionItem) => {
|
||||||
|
console.log('handleLevel1Change', checked, item)
|
||||||
|
if (checked) {
|
||||||
|
const values = selectValues.filter(s => s[0] != item.value)
|
||||||
|
const checkedIds: Id[][] = [];
|
||||||
|
item.children?.forEach(c => {
|
||||||
|
checkedIds.push([item.value, c.value])
|
||||||
|
})
|
||||||
|
const _values = [...values, ...checkedIds];
|
||||||
|
_setCheckedAll(_values.length == allValues.length)
|
||||||
|
_setSelectValues(_values)
|
||||||
|
} else {
|
||||||
|
_setCheckedAll(false)
|
||||||
|
const values = selectValues.filter(s => s[0] != item.value)
|
||||||
|
_setSelectValues([...values])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const level1Checked = (item: OptionItem) => {
|
||||||
|
// 完全没有选中
|
||||||
|
if (selectValues.findIndex(s => s[0] == item.value) == -1) return -1
|
||||||
|
const myList = selectValues.filter(s => s[0] == item.value)
|
||||||
|
// 只有1个但是全选或者选中元素和子元素个数相等
|
||||||
|
if (myList.length == item.children?.length) return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const handleLevel2Change = (checked: boolean, item: OptionItem, parent: OptionItem) => {
|
||||||
|
// 获取一级选项的选中状态
|
||||||
|
const parentList = selectValues.filter(s => s[0] == parent.value)
|
||||||
|
_setSelectValues((prev) => {
|
||||||
|
let values = [...prev]
|
||||||
|
if (checked) {
|
||||||
|
values = [...prev,[parent.value, item.value]]
|
||||||
|
} else {
|
||||||
|
_setCheckedAll(false)
|
||||||
|
if(parentList.length == 0){ // && parentList[0].length == 1
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
values = values.filter(s => s.length == 1 || s[1] != item.value)
|
||||||
|
}
|
||||||
|
_setCheckedAll(values.length == allValues.length)
|
||||||
|
return values
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const level2Checked = (item: OptionItem, parent: OptionItem) => {
|
||||||
|
// 获取一级选项的选中状态
|
||||||
|
const parentList = selectValues.filter(s => s[0] == parent.value)
|
||||||
|
// 没有找到父级
|
||||||
|
if (parentList.length == 0) return false;
|
||||||
|
if (parentList.length == 1) {
|
||||||
|
// 只有一个 且长度为0 说明是全选
|
||||||
|
if (parentList[0].length == 1) return true;
|
||||||
|
return parentList[0][1] == item.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parentList.findIndex(s => s[1] == item.value) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (<div className={`tag-select-container z-10 select-none relative group ${props.className}`}>
|
||||||
|
<div className="select-value w-[120px] flex justify-center items-center"
|
||||||
|
onMouseEnter={() => set(!visible)}
|
||||||
|
>
|
||||||
|
<span>{checkedAll ? '全部来源' : '来源'}</span>
|
||||||
|
<CaretUpOutlined className={`ml-2 arrow-icon ${visible ? 'rotate-0' : 'rotate-180'}`}/>
|
||||||
|
</div>
|
||||||
|
<div className={`options-list-container absolute ${visible ? 'block' : 'hidden'}`}>
|
||||||
|
<ul className="options-list">
|
||||||
|
<li className="select-option-item relative">
|
||||||
|
<div className="option-value whitespace-nowrap flex justify-between">
|
||||||
|
<span className="text-center flex-1"
|
||||||
|
onClick={() => handleAllChanged(!checkedAll)}>全部来源</span>
|
||||||
|
<Checkbox className="ml-6" checked={checkedAll}
|
||||||
|
onChange={e => handleAllChanged(e.target.checked)}/>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{props.options.filter(s => s.value != 999999).map((option) => {
|
||||||
|
const checkStatus = level1Checked(option)
|
||||||
|
return (<li key={option.value} className="select-option-item relative">
|
||||||
|
{(option.children && option.children.length > 0) ?
|
||||||
|
<Popover placement="rightTop" trigger={['hover']}
|
||||||
|
rootClassName="tag-select-child-container" arrow={false}
|
||||||
|
content={option.children && <ul className="sub-options-list">
|
||||||
|
{option.children.map((subOption) => {
|
||||||
|
const myCheckStatus = level2Checked(subOption, option)
|
||||||
|
return (<li key={subOption.value}
|
||||||
|
className="sub-option-item select-option-item whitespace-nowrap">
|
||||||
|
<div
|
||||||
|
className="option-value whitespace-nowrap flex justify-between">
|
||||||
|
<span
|
||||||
|
onClick={() => {
|
||||||
|
handleLevel2Change(!myCheckStatus, subOption, option)
|
||||||
|
}}>{subOption.label}</span>
|
||||||
|
<Checkbox className="ml-6" checked={myCheckStatus}
|
||||||
|
onChange={e => handleLevel2Change(e.target.checked, subOption, option)}/>
|
||||||
|
</div>
|
||||||
|
</li>)
|
||||||
|
})}
|
||||||
|
</ul>}>
|
||||||
|
<div className="option-value whitespace-nowrap flex justify-between">
|
||||||
|
<span className="text-center flex-1">{option.label}</span>
|
||||||
|
<Checkbox className="ml-6"
|
||||||
|
checked={checkStatus == 1}
|
||||||
|
indeterminate={checkStatus == 0}
|
||||||
|
onChange={e => {
|
||||||
|
handleLevel1Change(e.target.checked, option)
|
||||||
|
}}/>
|
||||||
|
</div>
|
||||||
|
</Popover> : <div className="option-value whitespace-nowrap flex justify-between">
|
||||||
|
<span>{option.label}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
</li>)
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default TagSelect
|
@ -1,5 +1,5 @@
|
|||||||
import {Cascader} from "antd";
|
import {Cascader} from "antd";
|
||||||
import React, {useEffect, useMemo} from "react";
|
import React, {useEffect} from "react";
|
||||||
|
|
||||||
|
|
||||||
const prevSelectValues: Id[][] = [];
|
const prevSelectValues: Id[][] = [];
|
||||||
@ -71,9 +71,6 @@ export default function ArticleCascader(props: {
|
|||||||
// 清除上一次的选中值
|
// 清除上一次的选中值
|
||||||
prevSelectValues.length = 0;
|
prevSelectValues.length = 0;
|
||||||
}, [])
|
}, [])
|
||||||
// const allOptionValue = useMemo(() => {
|
|
||||||
// return getAllValue(props.options)
|
|
||||||
// }, [props.options])
|
|
||||||
|
|
||||||
const setSelectValues = (value: Id[][]) => {
|
const setSelectValues = (value: Id[][]) => {
|
||||||
_setSelectValues(value)
|
_setSelectValues(value)
|
||||||
@ -81,33 +78,6 @@ export default function ArticleCascader(props: {
|
|||||||
props.onChange?.(value)
|
props.onChange?.(value)
|
||||||
}
|
}
|
||||||
const handleChange = (values: Id[][]) => {
|
const handleChange = (values: Id[][]) => {
|
||||||
// const fullValues = buildValues(props.options, values)
|
|
||||||
// const diffValue = getValuesDiff(fullValues, prevSelectValues);
|
|
||||||
// const isIncrease = fullValues.length > prevSelectValues.length;
|
|
||||||
// prevSelectValues.length = 0;
|
|
||||||
//
|
|
||||||
// if(values.length == 0){
|
|
||||||
// setSelectValues([])
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// // 判断操作的是否是全部
|
|
||||||
// if(diffValue?.length == 1 && diffValue[0] == -1){
|
|
||||||
// if(isIncrease) prevSelectValues.push(...allOptionValue);
|
|
||||||
// setSelectValues(isIncrease ? [...allOptionValue] : [])
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// // if(fullValues.length != allOptionValue.length){
|
|
||||||
// // setSelectValues(fullValues.filter(s=>s.length == 1 && s[0] != -1))
|
|
||||||
// // }else{
|
|
||||||
// //
|
|
||||||
// // }
|
|
||||||
//
|
|
||||||
// if(fullValues.filter(s=>s.length > 1 || s[0] != -1).length == allOptionValue.length - 1){
|
|
||||||
// prevSelectValues.push(...allOptionValue);
|
|
||||||
// setSelectValues( [...allOptionValue])
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// prevSelectValues.push(...fullValues);
|
|
||||||
setSelectValues(values.filter(s=>s.length > 1 || s[0] != -1))
|
setSelectValues(values.filter(s=>s.length > 1 || s[0] != -1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import ArticleCascader from "@/pages/news/components/article-cascader.tsx";
|
|||||||
import React, {useState} from "react";
|
import React, {useState} from "react";
|
||||||
import {useSetState} from "ahooks";
|
import {useSetState} from "ahooks";
|
||||||
import useArticleTags from "@/hooks/useArticleTags.ts";
|
import useArticleTags from "@/hooks/useArticleTags.ts";
|
||||||
|
import TagSelect from "@/components/form/tag-select.tsx";
|
||||||
|
|
||||||
export default function EditSearchForm(props: {
|
export default function EditSearchForm(props: {
|
||||||
onSubmit: (values: ApiArticleSearchParams) => void;
|
onSubmit: (values: ApiArticleSearchParams) => void;
|
||||||
@ -35,6 +36,7 @@ export default function EditSearchForm(props: {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="search-form-input flex gap-2 items-center">
|
<div className="search-form-input flex gap-2 items-center">
|
||||||
<Input
|
<Input
|
||||||
@ -42,16 +44,17 @@ export default function EditSearchForm(props: {
|
|||||||
setParams({title: e.target.value})
|
setParams({title: e.target.value})
|
||||||
}}
|
}}
|
||||||
allowClear
|
allowClear
|
||||||
type="text" className="rounded px-3 w-[250px]"
|
type="text" className="rounded-3xl px-3 w-[270px]"
|
||||||
suffix={<SearchOutlined/>}
|
prefix={<SearchOutlined/>}
|
||||||
placeholder="请输入你先搜索的关键词"
|
placeholder="请输入新闻标题关键词进行搜索"
|
||||||
|
onPressEnter={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<span className="ml-5 text-sm">来源</span>
|
<span className="ml-5 text-sm">来源</span>
|
||||||
<ArticleCascader
|
<ArticleCascader
|
||||||
options={articleTags}
|
options={articleTags}
|
||||||
onChange={setTags}
|
onChange={setTags}
|
||||||
/>
|
/>
|
||||||
<Button type="primary" onClick={handleSubmit}>搜索</Button>
|
<TagSelect onChange={setTags} options={articleTags}/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user