UserList.tsx 6.99 KB
import { useCallback, useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { message } from 'antd'
import { listUsers } from '@/api/usr'
import { BizError } from '@/api/request'
import type { MatchType, QueryField, UserListItem } from '@/api/types'
import { useAppDispatch } from '@/store'
import { openTab } from '@/store/tabs'
import { IconExport, IconPlus, IconRefresh, IconSearch } from '@/components/icons'

const QUERY_FIELDS: { value: QueryField; label: string }[] = [
  { value: 'username', label: '用户名' },
  { value: 'staff', label: '员工名' },
  { value: 'userno', label: '用户号' },
  { value: 'department', label: '部门' },
  { value: 'usertype', label: '用户类型' },
  { value: 'language', label: '语言' },
  { value: 'deleted', label: '是否作废' },
]

const MATCH_TYPES: { value: MatchType; label: string }[] = [
  { value: 'contains', label: '包含' },
  { value: 'equals', label: '等于' },
  { value: 'startsWith', label: '开头是' },
  { value: 'endsWith', label: '结尾是' },
]

const fmt = (s: string | null | undefined) => s ?? ''

export default function UserList() {
  const navigate = useNavigate()
  const dispatch = useAppDispatch()
  const [data, setData] = useState<UserListItem[]>([])
  const [total, setTotal] = useState(0)
  const [pageNum, setPageNum] = useState(1)
  const [pageSize] = useState(20)
  const [loading, setLoading] = useState(false)
  const [selectedId, setSelectedId] = useState<number | null>(null)

  const [scopeAll] = useState(true)
  const [queryField, setQueryField] = useState<QueryField>('username')
  const [matchType, setMatchType] = useState<MatchType>('contains')
  const [queryValue, setQueryValue] = useState('')

  const load = useCallback(async (page: number) => {
    setLoading(true)
    try {
      const params: Parameters<typeof listUsers>[0] = { pageNum: page, pageSize }
      if (queryValue.trim()) {
        params.queryField = queryField
        params.matchType = matchType
        params.queryValue = queryValue.trim()
      }
      const res = await listUsers(params)
      setData(res.list)
      setTotal(res.total)
      setPageNum(res.pageNum)
    } catch (err) {
      const msg = err instanceof BizError ? err.message : '加载用户列表失败'
      message.error(msg)
    } finally {
      setLoading(false)
    }
  }, [pageSize, queryField, matchType, queryValue])

  // eslint-disable-next-line react-hooks/set-state-in-effect -- intentional reload when filter callback identity changes
  useEffect(() => { load(1) }, [load])

  const handleAdd = () => {
    dispatch(openTab({ key: 'userdetail', title: '用户信息单据', path: '/users/new' }))
    navigate('/users/new')
  }

  const handleRowDouble = (u: UserListItem) => {
    dispatch(openTab({ key: 'userdetail', title: '用户信息单据', path: `/users/${u.iIncrement}` }))
    navigate(`/users/${u.iIncrement}`)
  }

  const handleClear = () => {
    setQueryValue('')
    setQueryField('username')
    setMatchType('contains')
  }

  const totalPages = Math.max(1, Math.ceil(total / pageSize))

  return (
    <div className="userlist-screen">
      <div className="toolbar">
        <span className="tb-btn" onClick={() => load(pageNum)}><IconRefresh />刷新</span>
        <span className="tb-btn" onClick={handleAdd}><IconPlus />新增</span>
        <span className="tb-btn" onClick={() => message.info('导出 Excel - 未实现')}><IconExport />导出Excel</span>
        <span className="spacer" />
        <span className="gear">⚙</span>
      </div>
      <div className="filterbar">
        <select value={scopeAll ? 'all' : 'all'} disabled>
          <option value="all">全部用户</option>
        </select>
        <select value={queryField} onChange={e => setQueryField(e.target.value as QueryField)}>
          {QUERY_FIELDS.map(f => <option key={f.value} value={f.value}>{f.label}</option>)}
        </select>
        <select value={matchType} onChange={e => setMatchType(e.target.value as MatchType)}>
          {MATCH_TYPES.map(m => <option key={m.value} value={m.value}>{m.label}</option>)}
        </select>
        <input
          type="text"
          value={queryValue}
          onChange={e => setQueryValue(e.target.value)}
          onKeyDown={e => { if (e.key === 'Enter') load(1) }}
        />
        <span className="down">▾</span>
        <button className="btn" onClick={() => load(1)} disabled={loading}>
          <IconSearch />搜索
        </button>
        <button className="btn ghost" onClick={handleClear}>⊗ 清空</button>
      </div>
      <div className="table-shell">
        <table className="grid-table">
          <thead>
            <tr>
              <th style={{ width: 36 }}></th>
              <th style={{ width: 60 }}>序号</th>
              <th>用户名</th>
              <th>员工名</th>
              <th>用户号</th>
              <th>部门</th>
              <th>用户类型</th>
              <th>语言</th>
              <th>作</th>
              <th>登录日期</th>
              <th>制单人</th>
              <th>制单日期</th>
            </tr>
          </thead>
          <tbody>
            {data.length === 0 && !loading && (
              <tr><td colSpan={12} style={{ textAlign: 'center', color: '#888', padding: 24 }}>无数据</td></tr>
            )}
            {data.map((u, i) => (
              <tr
                key={u.iIncrement}
                className={selectedId === u.iIncrement ? 'selected' : ''}
                onClick={() => setSelectedId(u.iIncrement)}
                onDoubleClick={() => handleRowDouble(u)}
              >
                <td className="radio-cell">
                  <span className={`radio-dot ${selectedId === u.iIncrement ? 'checked' : ''}`}></span>
                </td>
                <td>{(pageNum - 1) * pageSize + i + 1}</td>
                <td>{u.sUserName}</td>
                <td>{fmt(u.sStaffName)}</td>
                <td>{u.sUserNo}</td>
                <td>{fmt(u.sDepartment)}</td>
                <td>{u.sUserType}</td>
                <td>{u.sLanguage === 'zh' ? '中文' : u.sLanguage === 'en' ? '英文' : u.sLanguage}</td>
                <td><input className="cb" type="checkbox" readOnly checked={u.bDeleted} /></td>
                <td>{fmt(u.tLastLoginDate)}</td>
                <td>{fmt(u.sCreatedBy)}</td>
                <td>{fmt(u.tCreateDate)}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
      <div className="pager">
        <span>当前显示 共 {total} 条记录</span>
        <span
          className={`pgbtn ${pageNum <= 1 ? 'disabled' : ''}`}
          onClick={() => pageNum > 1 && load(pageNum - 1)}
        >‹</span>
        <span className="pgcur">{pageNum}</span>
        <span
          className={`pgbtn ${pageNum >= totalPages ? 'disabled' : ''}`}
          onClick={() => pageNum < totalPages && load(pageNum + 1)}
        >›</span>
        <select value={pageSize} disabled>
          <option value={pageSize}>{pageSize} 条/页</option>
        </select>
      </div>
    </div>
  )
}