feat: 懒搜索

fix:删除、退出团队
test
bigfengyu 5 years ago
parent 1705c3ea51
commit af43b6f09b

@ -28,7 +28,8 @@
"@material-ui/styles": "^4.2.0",
"@types/json5": "^0.0.30",
"animate.css": "3.7.2",
"bootstrap": "^4.3.1",
"awesome-debounce-promise": "^2.1.0",
"buc-client": "^3.12.5",
"chart.js": "^2.8.0",
"classnames": "^2.2.6",
"clipboard-copy": "^3.1.0",

@ -1,4 +1,3 @@
// bootstrap v4
// GitHub
// TODO 2.x codesandbox.io

@ -1,5 +1,6 @@
import React, { Component } from 'react'
import { PropTypes, Link, StoreStateRouterLocationURI, connect } from '../../family'
import AwesomeDebouncePromise from 'awesome-debounce-promise'
class Highlight extends Component<any, any> {
static replace = (clip: any, seed: any) => {
@ -115,28 +116,41 @@ class DropdownMenuBase extends Component<any, any> {
const DropdownMenu = connect((state: any) => ({ router: state.router }))(DropdownMenuBase)
interface IState {
seed: string
result: string
}
// TODO 2.2 自动隐藏,高阶组件
class RepositorySearcher extends Component<any, any> {
class RepositorySearcher extends Component<any, IState> {
constructor(props: any) {
super(props)
this.state = { seed: '' }
this.state = { seed: '', result: '' }
}
render() {
const { repository } = this.props
const { seed, result } = this.state
const debouncedInput = AwesomeDebouncePromise((val: string) => this.setState({ result: val }), 500)
return (
<div className="RepositorySearcher dropdown">
<input
value={this.state.seed}
onChange={e => { this.setState({ seed: e.target.value }) }}
value={seed}
onChange={e => {
const val = e.target.value
this.setState({ seed: val })
debouncedInput(val)
}}
className="dropdown-input form-control"
placeholder="工作区搜索"
/>
{this.state.seed && <DropdownMenu repository={repository} seed={this.state.seed} onSelect={this.clearSeed} />}
{this.state.result && <DropdownMenu repository={repository} seed={result} onSelect={this.clearSeed} />}
</div>
)
}
clearSeed = () => {
this.setState({ seed: '' })
this.setState({ seed: '', result: '' })
}
}

@ -1,14 +1,11 @@
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { contextTypes, childContextTypes, getChildContext, CreateButton, OrganizationsTypeDropdown, SearchGroup, OrganizationListWithSpin, PaginationWithLocation, mapDispatchToProps } from './OrganizationListParts'
import { CreateButton, OrganizationsTypeDropdown, SearchGroup, OrganizationListWithSpin, PaginationWithLocation, mapDispatchToProps } from './OrganizationListParts'
import './Organization.css'
import { RootState } from 'actions/types'
// 所有团队
class JoinedOrganizationList extends Component<any, any> {
static contextTypes = contextTypes
static childContextTypes = childContextTypes
getChildContext = getChildContext
render() {
const { location, match, organizations } = this.props
return (

@ -5,7 +5,7 @@ import OrganizationForm from './OrganizationForm'
import { GoOrganization } from 'react-icons/go'
import { useSelector } from 'react-redux'
import { RootState, Organization } from '../../actions/types'
import { handleDelete, handleExit, handleJoin } from './OrganizationListParts'
import { useHandleDelete, useHandleExit, useHandleJoin } from './OrganizationListParts'
function avatar(user: any) {
return `https://work.alibaba-inc.com/photo/${user.empId}.220x220.jpg`
@ -22,41 +22,97 @@ function OrganizationBlock(props: Props) {
const owned = organization.owner && organization.owner.id === auth.id
const joined = organization.members!.find(user => user.id === auth.id)
const selfHelpJoin = false // DONE 2.1 不允许自助加入团队
const handleDelete = useHandleDelete()
const handleExit = useHandleExit()
const handleJoin = useHandleJoin()
return (
<section className="Organization card">
<div className="card-block">
<div className="header clearfix">
<span className="title">
<GoOrganization className="mr6 color-9" />
<Link to={`/organization/repository?organization=${organization.id}`} >{organization.name}</Link>
<Link
to={`/organization/repository?organization=${organization.id}`}
>
{organization.name}
</Link>
</span>
<span className="toolbar">
{owned || joined ? ( // 拥有或已加入
<span className="fake-link operation mr5" onClick={() => setUpdate(true)}></span>
<span
className="fake-link operation mr5"
onClick={() => setUpdate(true)}
>
</span>
) : null}
<OrganizationForm organization={organization} open={update} onClose={() => setUpdate(false)} />
<OrganizationForm
organization={organization}
open={update}
onClose={() => setUpdate(false)}
/>
{owned ? ( // 拥有
<Link to="" onClick={e => handleDelete(e, organization)} className="operation mr5"></Link>
<Link
to=""
onClick={e => {
e.preventDefault()
handleDelete(organization)
}}
className="operation mr5"
>
</Link>
) : null}
{!owned && joined ? ( // 不拥有,已加入
<Link to="" onClick={e => handleExit(e, organization)} className="operation mr5">退</Link>
<Link
to=""
onClick={e => {
e.preventDefault()
handleExit(organization)
}}
className="operation mr5"
>
退
</Link>
) : null}
{!owned && !joined && selfHelpJoin ? ( // 不拥有,未加入
<Link to="" onClick={e => handleJoin(e, organization)} className="operation mr5"></Link>
<Link
to=""
onClick={e => {
e.preventDefault()
handleJoin(organization)
}}
className="operation mr5"
>
</Link>
) : null}
</span>
</div>
<div className="body">
<div className="desc">{organization.description}</div>
<div className="members">
<Popover content={`${organization.owner!.fullname} ${organization.owner!.id}`}>
<img alt={organization.owner!.fullname} src={avatar(organization.owner)} className="avatar owner" />
<Popover
content={`${organization.owner!.fullname} ${
organization.owner!.id
}`}
>
<img
alt={organization.owner!.fullname}
src={avatar(organization.owner)}
className="avatar owner"
/>
</Popover>
{organization.members!.map(user =>
{organization.members!.map(user => (
<Popover key={user.id} content={`${user.fullname} ${user.id}`}>
<img alt={user.fullname} title={user.fullname} src={avatar(user)} className="avatar" />
<img
alt={user.fullname}
title={user.fullname}
src={avatar(user)}
className="avatar"
/>
</Popover>
)}
))}
</div>
</div>
</div>

@ -1,9 +1,19 @@
import React, { Component, useState, useEffect } from 'react'
import { PropTypes, push, replace, URI, StoreStateRouterLocationURI } from '../../family'
import React, { useState, useEffect } from 'react'
import {
PropTypes,
push,
replace,
URI,
StoreStateRouterLocationURI
} from '../../family'
import { Spin, Pagination } from '../utils'
import OrganizationList from './OrganizationList'
import OrganizationForm from './OrganizationForm'
import { addOrganization, deleteOrganization, updateOrganization } from '../../actions/organization'
import {
addOrganization,
deleteOrganization,
updateOrganization
} from '../../actions/organization'
import { useDispatch, useSelector } from 'react-redux'
import { Select, MenuItem, TextField, Button } from '@material-ui/core'
import { RootState } from 'actions/types'
@ -23,21 +33,45 @@ export const childContextTypes = {
}
export function getChildContext(this: any) {
const { history, location, match, onAddOrganization, onDeleteOrganization, onUpdateOrganization, auth } = this.props
return { history, location, match, onAddOrganization, onDeleteOrganization, onUpdateOrganization, auth }
const {
history,
location,
match,
onAddOrganization,
onDeleteOrganization,
onUpdateOrganization,
auth,
} = this.props
return {
history,
location,
match,
onAddOrganization,
onDeleteOrganization,
onUpdateOrganization,
auth,
}
}
export const mapDispatchToProps = ({
export const mapDispatchToProps = {
onAddOrganization: addOrganization,
onDeleteOrganization: deleteOrganization,
onUpdateOrganization: updateOrganization,
})
}
export function CreateButton() {
const [open, setOpen] = useState(false)
return (
<span className="float-right ml10">
<Button className="OrganizationCreateButton" variant="contained" color="primary" onClick={() => setOpen(true)}> </Button>
<Button
className="OrganizationCreateButton"
variant="contained"
color="primary"
onClick={() => setOpen(true)}
>
{' '}
{' '}
</Button>
<OrganizationForm open={open} onClose={() => setOpen(false)} />
</span>
)
@ -91,62 +125,83 @@ export function SearchGroup(props: { name: string }) {
// DONE 把控制钱从 Dialog 移动到 Component 中!
// DONE 2.1 通常应该用 replaceLocation
// DONE 2.1 删除确认
export function handleDelete(this: any, e: any, organization: any) {
e.preventDefault()
const message = `团队被删除后不可恢复!\n确认继续删除『#${organization.id} ${organization.name}』吗?`
if (!window.confirm(message)) { return }
const { onDeleteOrganization } = this.context
onDeleteOrganization(organization.id, () => {
this.replaceLocation()
})
export function useHandleDelete() {
const replaceLocation = useReplaceLocation()
const dispatch = useDispatch()
return (organization: any) => {
const message = `团队被删除后不可恢复!\n确认继续删除『#${organization.id} ${organization.name}』吗?`
if (!window.confirm(message)) {
return
}
dispatch(
deleteOrganization(organization.id, () => {
replaceLocation()
})
)
}
}
// DONE 重构表之间的对应关系,分为多张表。否则每次更新成员都会导致当前团队排到第一位!待测试。
export function replaceLocation(this: any) {
const { router } = this.props
const uri = StoreStateRouterLocationURI(router)
replace(uri.href())
export function useReplaceLocation() {
const router = useSelector((state: RootState) => state.router)
const dispatch = useDispatch()
return () => {
const uri = StoreStateRouterLocationURI(router)
dispatch(replace(uri.href()))
}
}
export function handleJoin(this: any, e: any, organization: any) {
e.preventDefault()
const { auth, onUpdateOrganization } = this.context
const next = {
id: organization.id,
memberIds: [...organization.members.map((user: any) => user.id), auth.id],
export function useHandleJoin() {
const auth = useSelector((state: RootState) => state.auth)
const replaceLocation = useReplaceLocation()
const dispatch = useDispatch()
return (organization: any) => {
const next = {
id: organization.id,
memberIds: [...organization.members.map((user: any) => user.id), auth.id],
}
dispatch(
updateOrganization(next, () => {
replaceLocation()
})
)
}
onUpdateOrganization(next, () => {
this.replaceLocation()
})
}
export function handleExit(this: any, e: any, organization: any) {
e.preventDefault()
const message = `确认继续退出『#${organization.id} ${organization.name}』吗?`
if (!window.confirm(message)) { return }
const { auth, onUpdateOrganization } = this.context
const next = {
id: organization.id,
memberIds: organization.members.filter((user: any) => user.id !== auth.id).map((user: any) => user.id),
export function useHandleExit() {
const auth = useSelector((state: RootState) => state.auth)
const replaceLocation = useReplaceLocation()
const dispatch = useDispatch()
return (organization: any) => {
const message = `确认继续退出『#${organization.id} ${organization.name}』吗?`
if (!window.confirm(message)) {
return
}
const next = {
id: organization.id,
memberIds: organization.members
.filter((user: any) => user.id !== auth.id)
.map((user: any) => user.id),
}
dispatch(
updateOrganization(next, () => {
replaceLocation()
})
)
}
onUpdateOrganization(next, () => {
this.replaceLocation()
})
}
export const OrganizationListWithSpin = ({ name, organizations }: any) => (
organizations.fetching
? <Spin />
: <OrganizationList name={name} organizations={organizations.data} />
)
export const OrganizationListWithSpin = ({ name, organizations }: any) =>
organizations.fetching ? (
<Spin />
) : (
<OrganizationList name={name} organizations={organizations.data} />
)
export class PaginationWithLocation extends Component<any, any> {
static contextTypes = {
location: PropTypes.object,
}
render() {
const { calculated } = this.props
const { location } = this.context
return <Pagination location={location} calculated={calculated} />
}
export function PaginationWithLocation(props: any) {
const router = useSelector((state: RootState) => state.router)
const { calculated } = props
const { location } = router
return <Pagination location={location} calculated={calculated} />
}

Loading…
Cancel
Save