Dashboard.tsx 9.05 KB
import { useNavigate } from 'react-router-dom'
import { message } from 'antd'
import { useAppDispatch } from '@/store'
import { openTab } from '@/store/tabs'
import { IconAi } from '@/components/icons'

interface KpiRow {
  role?: string
  item: string
  desc: string
  today: string
  total: string
  red?: boolean
  sub?: string
  roleSpan?: number
  subSpan?: number
}

const kpiRows: KpiRow[] = [
  { role: '核价人员', item: '01/04【新增】新报价单', desc: '报价单明细', today: '-', total: '-', sub: '估价管理流程', roleSpan: 4, subSpan: 5 },
  { item: '02/04 审核后报价单->客户确认价格', desc: '报价单明细', today: '16', total: '16', red: true },
  { item: '03/04 客户不认可->二次确认', desc: '报价单明细', today: '-', total: '-' },
  { item: '04/04 报价单->销售订单', desc: '销售订单明细', today: '1', total: '1', red: true },
  { role: '销售人员', item: '04/04 报价单->销售订单(标签)', desc: '销售订单明细(标签)', today: '0', total: '0', red: true },
  { role: '印前', item: '1/2 新增设计申请单', desc: '设计申请明细', today: '-', total: '-', sub: '设计制作流程', roleSpan: 2, subSpan: 2 },
  { item: '2/2 设计申请->设计制作', desc: '根据设计申请单进行设计制作,当日16:00前审核的为今日任务,16:00后(含)顺延至次日', today: '11', total: '11', red: true },
  { role: '客服部', item: '1/1 研发申请->文件制作', desc: '根据研发申请单,制作电子文件,当日16:00前下达的为今日任务,16:00后(含)顺延至次日', today: '0', total: '12', red: true, sub: '新品研发流程', subSpan: 5 },
  { role: '客服部', item: '1/5 新增研发申请单', desc: '研发申请明细', today: '-', total: '-' },
  { role: '技术研发部', item: '2/5 研发申请>>研发工单', desc: '及时开立研发工单,当日16:00前审核的为今日任务,16:00后(含)顺延至次日', today: '4', total: '4', red: true, roleSpan: 2 },
  { item: '3/5 研发工单>>完工处理', desc: '计划人员在交货日期前确认工单完工', today: '7', total: '7', red: true },
  { role: '客服部', item: '4/5 研发工单->客户确认', desc: '工单完工后需在7天内和客户确认样品', today: '-', total: '2703' },
  { role: '技术研发部', item: '5/5 客户确认->工艺卡', desc: '根据客户已经确认的研发工单,生成产品工艺卡。当日16:00前确认的为今日任务,16:00后(含)顺延至次日', today: '0', total: '1632', red: true },
  { role: '车间主管', item: '1/3 工单(测试部门数)->车间反馈', desc: '车间主管在工单完工前对测试材料进行数据反馈', today: '10', total: '115', red: true, sub: '材料测试流程', subSpan: 3 },
  { item: '2/3 车间反馈->车间补充(多部门)', desc: '补充新材料测试信息,车间反馈次日16:00前的为当日任务,16:00后(含)顺延一日', today: '8', total: '8', red: true, roleSpan: 2 },
  { role: '技术研发部', item: '2/3 车间反馈->工程部反馈(单部门)', desc: '工程部对新材料的测试结果进行反馈,车间反馈次日16:00前的为当日任务,16:00后(含)顺延一日', today: '23', total: '23', red: true },
]

const kpiHeads = ['导航类型', '角色', 'KPI待处理事项(当前行双击进入)', 'KPI内容描述及处理结果(点击蓝色查看明细)', '今日未处理', '未清总数', '子流程']

const navTreeRoles = [
  ['所有部门', 37428, true], ['核价人员', 17], ['销售人员', 0], ['印前', 11], ['客服部', 30127],
  ['技术研发部', 47], ['车间主管', 316], ['工艺部', 6], ['物控部', 728], ['生产计划部', 225],
  ['版房', 120], ['生产车间', 596], ['工艺技术部', 0], ['品质管理部', 589], ['储运部', 3496],
  ['通用', 0], ['外发组', 867], ['材料仓管', 0], ['机修组', 42], ['应收', 30],
  ['出纳', 211], ['应付', 0], ['客服', 0],
] as const

const navTreeFlows = [
  ['估价管理流程', 17], ['设计制作流程', 11], ['新品研发流程', 11],
  ['材料测试流程', 51], ['订单下达流程', 30118],
] as const

export default function Dashboard() {
  const navigate = useNavigate()
  const dispatch = useAppDispatch()

  const goUserList = () => {
    dispatch(openTab({ key: 'userlist', title: '用户列表', path: '/users' }))
    navigate('/users')
  }

  const total = kpiRows.length

  return (
    <section style={{ height: '100%', overflow: 'auto' }}>
      <div className="main-wrap">
        <div style={{ display: 'flex', flexDirection: 'column', gap: 10, minHeight: 0 }}>
          <div className="panel kpi-head">
            <span className="title">KPI监控</span>
            <span className="stat">今日未处理:<b>37428</b></span>
            <span className="sep">|</span>
            <span className="stat blue">未清总数:<b>56433</b></span>
            <button className="ai-btn" onClick={() => message.info('AI 助手 - 未实现')}>
              <IconAi />
              小ai同学,请帮我安排今日工作
            </button>
          </div>
          <div className="three-col">
            <div className="left-nav nav-tree">
              <div className="group"><span className="arrow"></span><span className="ico">📁</span>按角色</div>
              {navTreeRoles.map(([name, count, active]) => (
                <div key={name as string} className={`item${active ? ' active' : ''}`}>
                  <span className="ico">📄</span>{name} ({count})
                </div>
              ))}
              <div className="group"><span className="arrow"></span><span className="ico">📁</span>按流程</div>
              {navTreeFlows.map(([name, count]) => (
                <div key={name as string} className="item">
                  <span className="ico">📄</span>{name} ({count})
                </div>
              ))}
            </div>
            <div className="center">
              <div className="panel" style={{ overflow: 'auto' }}>
                <div
                  className="kpi-body"
                  style={{ gridTemplateRows: `38px repeat(${total}, minmax(38px, auto))` }}
                >
                  {kpiHeads.map(h => (
                    <div key={h} className="h">{h}</div>
                  ))}
                  <div className="center" style={{ gridColumn: '1', gridRow: `2 / span ${total}` }}>按角色</div>
                  {kpiRows.map((row, i) => {
                    const altClass = i % 2 === 1 ? 'row-alt' : ''
                    const curRow = i + 2
                    const cells: React.ReactNode[] = []
                    if (row.role) {
                      cells.push(
                        <div
                          key={`role-${i}`}
                          className={`center ${altClass}`}
                          style={{ gridColumn: '2', gridRow: `${curRow} / span ${row.roleSpan || 1}` }}
                        >
                          {row.role}
                        </div>,
                      )
                    }
                    cells.push(
                      <div key={`item-${i}`} className={`link ${altClass}`} style={{ gridColumn: '3', gridRow: `${curRow}` }}>{row.item}</div>,
                      <div key={`desc-${i}`} className={`link ${altClass}`} style={{ gridColumn: '4', gridRow: `${curRow}` }}>{row.desc}</div>,
                      <div key={`today-${i}`} className={`num ${row.red ? 'num-red' : ''} ${altClass}`} style={{ gridColumn: '5', gridRow: `${curRow}` }}>{row.today}</div>,
                      <div key={`total-${i}`} className={`num ${row.red ? 'num-red' : ''} ${altClass}`} style={{ gridColumn: '6', gridRow: `${curRow}` }}>{row.total}</div>,
                    )
                    if (row.sub && row.subSpan) {
                      cells.push(
                        <div key={`sub-${i}`} className="subproc" style={{ gridColumn: '7', gridRow: `${curRow} / span ${row.subSpan}` }}>
                          {row.sub}
                        </div>,
                      )
                    }
                    return cells
                  })}
                </div>
              </div>
            </div>
          </div>
        </div>
        <div className="panel common-ops" style={{ height: 'fit-content' }}>
          <div className="h">常用操作</div>
          <a onClick={goUserList}>用户列表</a>
          <a onClick={() => message.info('系统功能模块设置 - 未实现')}>系统功能模块设置</a>
        </div>
      </div>
      <footer className="foot">
        <span style={{ verticalAlign: 'middle' }}>🛠</span>
        ©Copyright Antler Software <span className="pipe">|</span> 印刷智慧工厂 <span className="pipe">|</span> 印刷MES <span className="pipe">|</span> 印刷ERP <span className="pipe">|</span> 印刷电商平台 <span className="pipe">|</span> 文件智能处理 <span className="pipe">|</span> 印前自动化 <span className="pipe">|</span> 400-880-6237
        <span className="police">
          <svg viewBox="0 0 24 24" fill="#3a6cb6"><path d="M12 2l9 4v6c0 5-4 9-9 10-5-1-9-5-9-10V6z" /></svg>
          沪ICP备14034791号-1
        </span>
      </footer>
    </section>
  )
}