UserDetailPage.tsx 11.6 KB
import { useEffect, useState } from 'react'
import { useNavigate, useLocation, useParams } from 'react-router-dom'
import { message } from 'antd'
import { useAppDispatch } from '../../store/hooks'
import { openTab } from '../../store/slices/tabsSlice'
import { getStaffs, getPermissionGroups, createUser, updateUser } from '../../api/usr'
import type { StaffVO, PermissionGroupVO, UserListItemVO, UserCreateReq, UserUpdateReq } from '../../api/usr'

// REQ-USR-001: 增加用户
// REQ-USR-002: 修改用户
export default function UserDetailPage() {
  const { id } = useParams()
  const navigate = useNavigate()
  const location = useLocation()
  const dispatch = useAppDispatch()

  const isNew = !id
  const rowData = location.state as UserListItemVO | null

  const [staffs, setStaffs] = useState<StaffVO[]>([])
  const [permGroups, setPermGroups] = useState<PermissionGroupVO[]>([])
  const [selectedPermIds, setSelectedPermIds] = useState<string[]>([])
  const [submitting, setSubmitting] = useState(false)
  const [activePermTab, setActivePermTab] = useState(0)

  const [userCode, setUserCode] = useState('')
  const [username, setUsername] = useState('')
  const [userType, setUserType] = useState<'普通用户' | '超级管理员'>('普通用户')
  const [language, setLanguage] = useState<'中文' | '英文' | '繁体'>('中文')
  const [canEditDoc, setCanEditDoc] = useState(false)
  const [isDisabled, setIsDisabled] = useState(false)
  const [employeeId, setEmployeeId] = useState<string | null>(null)

  useEffect(() => {
    if (!isNew && !rowData) {
      navigate('/usr/users', { replace: true })
      return
    }
    dispatch(openTab({ id: 'userdetail', title: '用户信息单据', path: location.pathname, closable: true }))
    getStaffs().then(setStaffs).catch(() => {})
    getPermissionGroups().then(setPermGroups).catch(() => {})
    if (!isNew && rowData) {
      setUserType(rowData.sUserType as '普通用户' | '超级管理员')
      setLanguage(rowData.sLanguage as '中文' | '英文' | '繁体')
      setCanEditDoc(rowData.bCanEditDoc === 1)
      setIsDisabled(rowData.bIsDisabled === 1)
    }
  }, [])  // eslint-disable-line react-hooks/exhaustive-deps

  async function handleSave() {
    if (isNew) {
      if (!userCode.trim()) { message.error('请输入用户号'); return }
      if (!username.trim()) { message.error('请输入用户名'); return }
    }
    setSubmitting(true)
    try {
      if (isNew) {
        const req: UserCreateReq = { userCode, username, userType, language, canEditDoc, employeeId, permGroupIds: selectedPermIds }
        await createUser(req)
        message.success('新增用户成功')
      } else {
        const req: UserUpdateReq = { userType, language, canEditDoc, isDisabled, employeeId, permGroupIds: selectedPermIds }
        await updateUser(id!, req)
        message.success('修改用户成功')
      }
      navigate('/usr/users')
    } catch (e: unknown) {
      if (e instanceof Error) message.error(e.message)
    } finally {
      setSubmitting(false)
    }
  }

  const PERM_TABS = ['权限组', '客户查看权限', '供应商查看权限', '人员查看权限', '工序查看权限', '司机查看权限']

  const fieldBg = 'var(--color-field-bg-edit)'
  const readonlyBg = 'var(--color-form-bg-readonly)'
  const inputStyle: React.CSSProperties = { flex: 1, height: 28, border: '1px solid #d5d8de', borderRadius: 2, padding: '0 10px', background: fieldBg, minWidth: 0, fontSize: 13, fontFamily: 'inherit' }
  const roStyle: React.CSSProperties = { flex: 1, height: 28, border: '1px solid #d5d8de', borderRadius: 2, padding: '0 10px', background: readonlyBg, color: '#444', display: 'flex', alignItems: 'center', fontSize: 13, minWidth: 0, overflow: 'hidden' }
  const selectStyle: React.CSSProperties = { ...inputStyle, padding: '0 8px' }
  const cellStyle: React.CSSProperties = { display: 'flex', alignItems: 'center', gap: 6, padding: '8px 10px' }
  const lblStyle: React.CSSProperties = { minWidth: 88, color: '#333', fontSize: 13, textAlign: 'right', flexShrink: 0 }
  const reqLblStyle: React.CSSProperties = { ...lblStyle, color: 'var(--color-field-label-req)' }

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
      {/* Dark toolbar */}
      <div style={{ background: 'var(--color-toolbar-bg)', color: 'var(--color-toolbar-text)', display: 'flex', alignItems: 'center', gap: 6, padding: '0 8px', height: 38, flexShrink: 0 }}>
        {[
          { label: '⊕ 新增', onClick: () => navigate('/usr/users/new'), disabled: false },
          { label: '✎ 修改', onClick: undefined, disabled: true },
          { label: '🗑 删除', onClick: undefined, disabled: true, title: '功能待实现' },
          { label: '💾 保存', onClick: handleSave, disabled: submitting },
          { label: '✕ 取消', onClick: () => navigate('/usr/users'), disabled: false },
          { label: '作废', onClick: undefined, disabled: true, title: '功能待实现' },
          { label: '重置密码', onClick: undefined, disabled: true, title: '功能待实现' },
        ].map(btn => (
          <button
            key={btn.label}
            onClick={btn.onClick}
            disabled={btn.disabled}
            title={btn.title}
            style={{ display: 'inline-flex', alignItems: 'center', gap: 6, padding: '6px 12px', color: btn.disabled ? '#666' : 'var(--color-toolbar-text)', cursor: btn.disabled ? 'not-allowed' : 'pointer', fontSize: 13, borderRadius: 2, border: 'none', background: 'transparent', fontFamily: 'inherit', opacity: btn.disabled ? 0.5 : 1 }}
          >
            {btn.label}
          </button>
        ))}
        <span style={{ flex: 1 }} />
        <span style={{ padding: '6px 8px', color: '#cfd2d8', cursor: 'pointer' }}>⚙</span>
      </div>

      {/* 3-column form grid */}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', background: '#fff', padding: '10px 14px', borderBottom: '1px solid var(--color-table-border)', flexShrink: 0 }}>
        {/* Row 1: 创建时间 / 制单人 / 员工名 */}
        <div style={cellStyle}>
          <span style={lblStyle}>创建时间:</span>
          <div style={roStyle}>{isNew ? '' : rowData?.tCreateDate}</div>
        </div>
        <div style={cellStyle}>
          <span style={lblStyle}>制单人:</span>
          <div style={roStyle}>{isNew ? '保存后自动生成' : rowData?.sCreatorUsername}</div>
        </div>
        <div style={cellStyle}>
          <span style={reqLblStyle}>*员工名:</span>
          <select value={employeeId ?? ''} onChange={e => setEmployeeId(e.target.value || null)} style={{ ...selectStyle, backgroundImage: 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' width=\'10\' height=\'10\' viewBox=\'0 0 10 10\'><path d=\'M2 3l3 4 3-4z\' fill=\'%23888\'/></svg>")', backgroundRepeat: 'no-repeat', backgroundPosition: 'right 8px center', paddingRight: 24 }}>
            <option value="">— 不关联 —</option>
            {staffs.map(s => <option key={s.sId} value={s.sId}>{s.sStaffName}</option>)}
          </select>
        </div>
        {/* Row 2: 用户名 / 类型 / 语言 */}
        <div style={cellStyle}>
          <span style={isNew ? reqLblStyle : lblStyle}>{isNew ? '*用户名:' : '用户名:'}</span>
          {isNew
            ? <input type="text" value={username} onChange={e => setUsername(e.target.value)} placeholder="请输入用户名" style={inputStyle} />
            : <div style={roStyle}>{rowData?.sUsername}</div>}
        </div>
        <div style={cellStyle}>
          <span style={reqLblStyle}>*类型:</span>
          <select value={userType} onChange={e => setUserType(e.target.value as typeof userType)} style={selectStyle}>
            <option value="普通用户">普通用户</option>
            <option value="超级管理员">超级管理员</option>
          </select>
        </div>
        <div style={cellStyle}>
          <span style={reqLblStyle}>*语言:</span>
          <select value={language} onChange={e => setLanguage(e.target.value as typeof language)} style={selectStyle}>
            <option value="中文">中文</option>
            <option value="英文">English</option>
            <option value="繁体">繁體</option>
          </select>
        </div>
        {/* Row 3: 用户号 / (empty) / 单据修改权限 */}
        <div style={cellStyle}>
          <span style={isNew ? reqLblStyle : lblStyle}>{isNew ? '*用户号:' : '用户号:'}</span>
          {isNew
            ? <input type="text" value={userCode} onChange={e => setUserCode(e.target.value)} placeholder="请输入用户号" style={inputStyle} />
            : <div style={roStyle}>{rowData?.sUserCode}</div>}
        </div>
        <div style={cellStyle} />
        <div style={cellStyle}>
          <span style={lblStyle}>单据修改权限:</span>
          <input type="checkbox" aria-hidden="true" checked={canEditDoc} onChange={e => setCanEditDoc(e.target.checked)} style={{ width: 14, height: 14 }} />
        </div>
      </div>

      {/* Permission tabs row */}
      <div style={{ display: 'flex', background: '#fff', borderBottom: '1px solid var(--color-table-border)', padding: '0 6px', flexShrink: 0 }}>
        {PERM_TABS.map((tab, i) => (
          <div
            key={tab}
            onClick={() => setActivePermTab(i)}
            style={{ padding: '11px 18px', fontSize: 14, color: i === activePermTab ? 'var(--color-tab-active)' : '#444', cursor: 'pointer', borderBottom: i === activePermTab ? `2px solid var(--color-tab-active)` : 'none', marginRight: 4 }}
          >
            {tab}
          </div>
        ))}
      </div>

      {/* Permission list
          KNOWN LIMITATION (edit mode): UserListItemVO does not include permGroupIds (backend list API
          does not return them). In edit mode, selectedPermIds starts empty — saving will overwrite
          existing permissions with whatever the user selects here. A future backend GET /usr/users/:id
          endpoint should return permGroupIds so they can be pre-populated. */}
      {!isNew && activePermTab === 0 && (
        <div style={{ background: '#fffbe6', borderBottom: '1px solid #ffe58f', padding: '8px 14px', fontSize: 12, color: '#ad6800' }}>
          ⚠ 编辑模式下权限组未预加载(后端列表接口不返回 permGroupIds)。保存将以当前勾选为准,请重新勾选所需权限组。
        </div>
      )}
      <div style={{ flex: 1, background: '#fff', overflow: 'auto' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 14, padding: '10px 14px', borderBottom: '1px solid var(--color-table-border)', background: 'var(--color-table-header-bg)', fontWeight: 500, color: '#222', fontSize: 13 }}>
          <span style={{ width: 14, height: 14, border: '1px solid #b8bcc3', display: 'inline-block', flexShrink: 0 }} />
          <span>权限分类</span>
        </div>
        {activePermTab === 0
          ? permGroups.map(pg => (
              <div key={pg.sId} style={{ display: 'flex', alignItems: 'center', gap: 14, padding: '10px 14px', borderBottom: '1px solid var(--color-table-border)', fontSize: 13, color: '#333' }}>
                <input
                  type="checkbox"
                  checked={selectedPermIds.includes(pg.sId)}
                  onChange={e => setSelectedPermIds(prev => e.target.checked ? [...prev, pg.sId] : prev.filter(x => x !== pg.sId))}
                  style={{ width: 14, height: 14, flexShrink: 0 }}
                />
                <span>{pg.sGroupName}</span>
              </div>
            ))
          : <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: 100, color: '#aaa' }}>功能待实现</div>}
      </div>
    </div>
  )
}