Преглед изворни кода

取消用户管理页面tabbar,添加通知管理图标菜单和页面

wyn пре 5 месеци
родитељ
комит
89534c24e5
3 измењених фајлова са 718 додато и 712 уклоњено
  1. 28 9
      src/Dashboard.tsx
  2. 441 0
      src/components/NotificationManagement.tsx
  3. 249 703
      src/components/UserManagement.tsx

+ 28 - 9
src/Dashboard.tsx

@@ -1,7 +1,7 @@
 import React, { useState } from 'react';
 import { useNavigate } from 'react-router-dom';
 import { useTranslation } from 'react-i18next';
-import { Shield, Settings, Users, Cpu, MapPin, Layers, Bell, User, LogOut, ChevronDown, Activity, Radio, Lock, AlertCircle, CheckCircle, Clock, Menu, Building2, UserCog, BookOpen, Server, Globe, Gauge } from 'lucide-react';
+import { Shield, Settings, Users, Cpu, MapPin, Layers, Bell, User, LogOut, ChevronDown, Activity, Radio, Lock, AlertCircle, CheckCircle, Clock, Menu, Building2, UserCog, BookOpen, Server, Globe, Gauge, MessageSquare } from 'lucide-react';
 import SystemConfig from './components/SystemConfig';
 import UserManagement from './components/UserManagement';
 import HardwareManagement from './components/HardwareManagement';
@@ -9,6 +9,7 @@ import LocationManagement from './components/LocationManagement';
 import IsolationWork from './components/IsolationWork';
 import ProfileSettings from './components/ProfileSettings';
 import CockpitDashboard from './components/CockpitDashboard';
+import NotificationManagement from './components/NotificationManagement';
 import { authApi } from './api';
 import { toast } from 'sonner';
 import { env } from './config/env';
@@ -22,6 +23,7 @@ export default function Dashboard() {
   const [showSystemConfigDropdown, setShowSystemConfigDropdown] = useState(false);
   const [dropdownTimer, setDropdownTimer] = useState<NodeJS.Timeout | null>(null);
   const [showProfileSettings, setShowProfileSettings] = useState(false);
+  const [showNotificationManagement, setShowNotificationManagement] = useState(false);
 
   // 切换语言
   const toggleLanguage = () => {
@@ -66,7 +68,6 @@ export default function Dashboard() {
     ],
     userManagement: [
       { key: 'userList', icon: User },
-      { key: 'notificationManagement', icon: Bell },
     ],
     hardwareManagement: [
       { key: 'cabinet', icon: Cpu },
@@ -147,6 +148,7 @@ export default function Dashboard() {
                                   <button
                                     key={subItem.key}
                                     onClick={() => {
+                                      setShowNotificationManagement(false);
                                       setActiveMenu('systemConfig');
                                       setActiveSubMenu(subItem.key);
                                       setShowSystemConfigDropdown(false);
@@ -178,6 +180,7 @@ export default function Dashboard() {
                     <button
                       key={item.key}
                       onClick={() => {
+                        setShowNotificationManagement(false);
                         setActiveMenu(item.key);
                         setActiveSubMenu(subMenuConfig[item.key]?.[0]?.key || '');
                       }}
@@ -206,11 +209,23 @@ export default function Dashboard() {
                 <span className="text-sm text-gray-700">{i18n.language === 'zh' ? 'EN' : '中文'}</span>
               </button>
               
-              {/* 通知 */}
-              <button className="relative p-2.5 hover:bg-gray-100 rounded-xl transition-colors">
+              {/* 消息通知 */}
+              <button className="relative p-2.5 hover:bg-gray-100 rounded-xl transition-colors" title="消息">
                 <Bell className="w-5 h-5 text-gray-600" />
                 <span className="absolute top-1.5 right-1.5 w-2 h-2 bg-red-500 rounded-full"></span>
               </button>
+              
+              {/* 通知管理 */}
+              <button 
+                className="relative p-2.5 hover:bg-gray-100 rounded-xl transition-colors" 
+                title="通知"
+                onClick={() => {
+                  setShowNotificationManagement(true);
+                  setActiveMenu('');
+                }}
+              >
+                <MessageSquare className="w-5 h-5 text-gray-600" />
+              </button>
 
               {/* 用户菜单 */}
               <div className="relative">
@@ -256,19 +271,21 @@ export default function Dashboard() {
 
       {/* 主内容区 */}
       <div className="px-6 pt-6 pb-6 h-[calc(100vh-88px)] overflow-auto">
-        {/* 二级菜单 Tab - 只在有子菜单且不是系统配置时显示,并且不在个人资料页面时显示 */}
-        {!showProfileSettings && subMenuConfig[activeMenu]?.length > 0 && activeMenu !== 'systemConfig' && (
+        {/* 二级菜单 Tab - 只在有子菜单且不是系统配置时显示,并且不在个人资料页面时显示,用户管理也不显示(因为只有一个用户列表) */}
+        {!showProfileSettings && subMenuConfig[activeMenu]?.length > 0 && activeMenu !== 'systemConfig' && activeMenu !== 'userManagement' && (
           <div className="bg-white rounded-2xl border border-gray-200/50 shadow-sm p-2 mb-6">
             <div className="flex items-center gap-2 overflow-x-auto">
               {subMenuConfig[activeMenu]?.map((item) => {
                 const isActive = activeSubMenu === item.key;
-                const menuKey = activeMenu === 'userManagement' ? 'userManagement' : 
-                               activeMenu === 'hardwareManagement' ? 'hardwareManagement' : 
+                const menuKey = activeMenu === 'hardwareManagement' ? 'hardwareManagement' : 
                                activeMenu === 'isolationWork' ? 'isolationWork' : '';
                 return (
                   <button
                     key={item.key}
-                    onClick={() => setActiveSubMenu(item.key)}
+                    onClick={() => {
+                      setShowNotificationManagement(false);
+                      setActiveSubMenu(item.key);
+                    }}
                     className={`px-4 py-2.5 rounded-xl text-sm transition-all duration-200 whitespace-nowrap ${
                       isActive
                         ? 'bg-gradient-to-r from-blue-500 to-blue-600 text-white shadow-lg shadow-blue-400/40'
@@ -286,6 +303,8 @@ export default function Dashboard() {
         {/* 主内容区域 */}
         {showProfileSettings ? (
           <ProfileSettings onBack={() => setShowProfileSettings(false)} />
+        ) : showNotificationManagement ? (
+          <NotificationManagement />
         ) : activeMenu === 'dashboard' ? (
           <CockpitDashboard />
         ) : activeMenu === 'systemConfig' ? (

+ 441 - 0
src/components/NotificationManagement.tsx

@@ -0,0 +1,441 @@
+import React, { useState } from 'react';
+import { Plus, Search, Edit2, Trash2, MoreVertical, Mail, Phone, MessageSquare, Smartphone } from 'lucide-react';
+
+interface TableRow {
+  id: number;
+  [key: string]: any;
+}
+
+export default function NotificationManagement() {
+  const [searchTerm, setSearchTerm] = useState('');
+  const [showAddModal, setShowAddModal] = useState(false);
+  const [editingItem, setEditingItem] = useState<TableRow | null>(null);
+  const [notificationType, setNotificationType] = useState<string>('email'); // 通知类型:email, sms, message, app
+
+  // 通知管理数据
+  const notificationData: TableRow[] = [
+    {
+      id: 1,
+      title: '系统升级通知',
+      content: '系统将于本周六凌晨2:00-4:00进行升级维护,期间系统将暂停服务',
+      type: '系统通知',
+      level: '重要',
+      sender: '系统管理员',
+      target: '全体用户',
+      status: '已发送',
+      readCount: 156,
+      totalCount: 200,
+      sendTime: '2025-12-01 10:00',
+      createTime: '2025-12-01 09:30'
+    },
+    {
+      id: 2,
+      title: '设备异常告警',
+      content: '1号变压器出现异常,请相关人员立即检处理',
+      type: '告警通知',
+      level: '紧急',
+      sender: '监控系统',
+      target: '技术部、运维部',
+      status: '已发送',
+      readCount: 45,
+      totalCount: 50,
+      sendTime: '2025-12-04 08:30',
+      createTime: '2025-12-04 08:25'
+    },
+    {
+      id: 3,
+      title: '月度安全培训通知',
+      content: '本月安全培训将于12月10日14:00在会议室举行,请全体员工准时参加',
+      type: '培训通知',
+      level: '普通',
+      sender: '安全部',
+      target: '全体用户',
+      status: '定时发送',
+      readCount: 0,
+      totalCount: 200,
+      sendTime: '2025-12-10 09:00',
+      createTime: '2025-12-03 16:00'
+    },
+    {
+      id: 4,
+      title: '隔离作业审批通过',
+      content: '您提交的2号配电柜维护隔离作业已通过审批,可以开始执行',
+      type: '审批通知',
+      level: '普通',
+      sender: '审批系统',
+      target: '张三',
+      status: '已发送',
+      readCount: 1,
+      totalCount: 1,
+      sendTime: '2025-12-03 15:30',
+      createTime: '2025-12-03 15:28'
+    },
+    {
+      id: 5,
+      title: '密码过期提醒',
+      content: '您的密码将于7天后过期,请及时修改密码',
+      type: '安全提醒',
+      level: '普通',
+      sender: '系统管理员',
+      target: '李四、王五',
+      status: '已发送',
+      readCount: 2,
+      totalCount: 2,
+      sendTime: '2025-12-04 09:00',
+      createTime: '2025-12-04 08:55'
+    },
+  ];
+
+  // 表格列配置
+  const columns = [
+    { key: 'title', label: '通知标题', width: '20%' },
+    { key: 'type', label: '类型', width: '10%' },
+    { key: 'level', label: '级别', width: '8%' },
+    { key: 'sender', label: '发送人', width: '10%' },
+    { key: 'target', label: '接收对象', width: '12%' },
+    { key: 'status', label: '状态', width: '10%' },
+    { key: 'readCount', label: '已读/总数', width: '10%' },
+    { key: 'sendTime', label: '发送时间', width: '15%' },
+  ];
+
+  // 过滤数据
+  const filteredData = notificationData.filter((item) =>
+    Object.values(item).some((value) =>
+      String(value).toLowerCase().includes(searchTerm.toLowerCase())
+    )
+  );
+
+  const handleDelete = (id: number) => {
+    if (confirm('确定要删除这条数据吗?')) {
+      console.log('删除:', id);
+    }
+  };
+
+  const handleEdit = (item: TableRow) => {
+    setEditingItem(item);
+    setShowAddModal(true);
+  };
+
+  // 渲染表格内容
+  const renderTable = () => (
+    <>
+      <div className="overflow-x-auto">
+        <table className="w-full">
+          <thead>
+            <tr className="bg-gradient-to-r from-gray-50 to-gray-100/50 border-b border-gray-200">
+              <th className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider" style={{ width: '5%' }}>
+                序号
+              </th>
+              {columns.map((column) => (
+                <th
+                  key={column.key}
+                  className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider"
+                  style={{ width: column.width }}
+                >
+                  {column.label}
+                </th>
+              ))}
+              <th className="px-6 py-4 text-center text-xs text-gray-600 uppercase tracking-wider" style={{ width: '10%' }}>
+                操作
+              </th>
+            </tr>
+          </thead>
+          <tbody className="divide-y divide-gray-100">
+            {filteredData.map((row, index) => (
+              <tr
+                key={row.id}
+                className="hover:bg-blue-50/30 transition-colors"
+              >
+                <td className="px-6 py-4 text-sm text-gray-900">
+                  {index + 1}
+                </td>
+                {columns.map((column) => (
+                  <td key={column.key} className="px-6 py-4 text-sm text-gray-900">
+                    {column.key === 'status' ? (
+                      <span
+                        className={`inline-flex px-3 py-1 rounded-lg text-xs ${
+                          row[column.key] === '已发送'
+                            ? 'bg-green-100 text-green-700'
+                            : row[column.key] === '定时发送'
+                            ? 'bg-blue-100 text-blue-700'
+                            : 'bg-gray-100 text-gray-700'
+                        }`}
+                      >
+                        {row[column.key]}
+                      </span>
+                    ) : column.key === 'level' ? (
+                      <span
+                        className={`inline-flex px-3 py-1 rounded-lg text-xs ${
+                          row[column.key] === '紧急'
+                            ? 'bg-red-100 text-red-700'
+                            : row[column.key] === '重要'
+                            ? 'bg-orange-100 text-orange-700'
+                            : 'bg-blue-100 text-blue-700'
+                        }`}
+                      >
+                        {row[column.key]}
+                      </span>
+                    ) : column.key === 'readCount' ? (
+                      <span className="text-gray-900">
+                        {row.readCount}/{row.totalCount}
+                      </span>
+                    ) : (
+                      row[column.key]
+                    )}
+                  </td>
+                ))}
+                <td className="px-6 py-4">
+                  <div className="flex items-center justify-center gap-2">
+                    <button
+                      onClick={() => handleEdit(row)}
+                      className="p-2 text-blue-600 hover:bg-blue-100 rounded-lg transition-colors"
+                      title="编辑"
+                    >
+                      <Edit2 className="w-4 h-4" />
+                    </button>
+                    <button
+                      onClick={() => handleDelete(row.id)}
+                      className="p-2 text-red-600 hover:bg-red-100 rounded-lg transition-colors"
+                      title="删除"
+                    >
+                      <Trash2 className="w-4 h-4" />
+                    </button>
+                    <button
+                      className="p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors"
+                      title="更多"
+                    >
+                      <MoreVertical className="w-4 h-4" />
+                    </button>
+                  </div>
+                </td>
+              </tr>
+            ))}
+          </tbody>
+        </table>
+      </div>
+
+      {/* 分页 */}
+      <div className="px-6 py-4 bg-gray-50/50 border-t border-gray-200">
+        <div className="flex items-center justify-between">
+          <div className="text-sm text-gray-600">
+            共 <span className="text-blue-600">{filteredData.length}</span> 条记录
+          </div>
+          <div className="flex gap-2">
+            <button className="px-4 py-2 text-sm text-gray-600 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors">
+              上一页
+            </button>
+            <button className="px-4 py-2 text-sm text-white bg-blue-500 rounded-lg hover:bg-blue-600 transition-colors">
+              1
+            </button>
+            <button className="px-4 py-2 text-sm text-gray-600 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors">
+              2
+            </button>
+            <button className="px-4 py-2 text-sm text-gray-600 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors">
+              3
+            </button>
+            <button className="px-4 py-2 text-sm text-gray-600 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors">
+              下一页
+            </button>
+          </div>
+        </div>
+      </div>
+    </>
+  );
+
+  return (
+    <div className="space-y-6">
+      {/* Tab按钮栏 */}
+      <div className="bg-white rounded-2xl border border-gray-200/50 shadow-sm p-2">
+        <div className="flex items-center gap-2">
+          <button
+            onClick={() => setNotificationType('email')}
+            className={`flex items-center gap-2 px-4 py-2.5 rounded-xl text-sm transition-all duration-200 whitespace-nowrap ${
+              notificationType === 'email'
+                ? 'bg-gradient-to-r from-blue-500 to-blue-600 text-white shadow-lg shadow-blue-400/40'
+                : 'text-gray-700 hover:bg-blue-50 hover:text-blue-600'
+            }`}
+          >
+            <Mail className="w-4 h-4" />
+            <span>邮件</span>
+          </button>
+          <button
+            onClick={() => setNotificationType('sms')}
+            className={`flex items-center gap-2 px-4 py-2.5 rounded-xl text-sm transition-all duration-200 whitespace-nowrap ${
+              notificationType === 'sms'
+                ? 'bg-gradient-to-r from-blue-500 to-blue-600 text-white shadow-lg shadow-blue-400/40'
+                : 'text-gray-700 hover:bg-blue-50 hover:text-blue-600'
+            }`}
+          >
+            <Phone className="w-4 h-4" />
+            <span>短信</span>
+          </button>
+          <button
+            onClick={() => setNotificationType('message')}
+            className={`flex items-center gap-2 px-4 py-2.5 rounded-xl text-sm transition-all duration-200 whitespace-nowrap ${
+              notificationType === 'message'
+                ? 'bg-gradient-to-r from-blue-500 to-blue-600 text-white shadow-lg shadow-blue-400/40'
+                : 'text-gray-700 hover:bg-blue-50 hover:text-blue-600'
+            }`}
+          >
+            <MessageSquare className="w-4 h-4" />
+            <span>站内信</span>
+          </button>
+          <button
+            onClick={() => setNotificationType('app')}
+            className={`flex items-center gap-2 px-4 py-2.5 rounded-xl text-sm transition-all duration-200 whitespace-nowrap ${
+              notificationType === 'app'
+                ? 'bg-gradient-to-r from-blue-500 to-blue-600 text-white shadow-lg shadow-blue-400/40'
+                : 'text-gray-700 hover:bg-blue-50 hover:text-blue-600'
+            }`}
+          >
+            <Smartphone className="w-4 h-4" />
+            <span>APP通知</span>
+          </button>
+        </div>
+      </div>
+
+      {/* 表格容器 */}
+      <div className="bg-white rounded-2xl border border-gray-200/50 shadow-sm overflow-hidden">
+        {/* 工具栏 */}
+        <div className="p-4 border-b border-gray-200/50">
+          <div className="flex items-center justify-between">
+            <div className="relative w-80">
+              <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" />
+              <input
+                type="text"
+                placeholder="搜索通知..."
+                value={searchTerm}
+                onChange={(e) => setSearchTerm(e.target.value)}
+                className="w-full h-10 pl-10 pr-4 bg-gray-50 border border-gray-200 rounded-xl outline-none focus:border-blue-400 focus:ring-4 focus:ring-blue-100 transition-all text-sm"
+              />
+            </div>
+
+            <button
+              onClick={() => {
+                setEditingItem(null);
+                setShowAddModal(true);
+              }}
+              className="flex items-center gap-2 px-4 py-2.5 bg-gradient-to-r from-blue-500 to-blue-600 text-white rounded-xl hover:shadow-lg hover:shadow-blue-400/40 transition-all duration-300"
+            >
+              <Plus className="w-4 h-4" strokeWidth={2.5} />
+              <span className="text-sm">新增通知</span>
+            </button>
+          </div>
+        </div>
+
+        {/* 通知内容区域 */}
+        <div>
+          {notificationType === 'email' && renderTable()}
+          {notificationType === 'sms' && renderTable()}
+          {notificationType === 'message' && renderTable()}
+          {notificationType === 'app' && renderTable()}
+        </div>
+      </div>
+
+      {/* 新增/编辑弹窗 */}
+      {showAddModal && (
+        <div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 animate-in fade-in duration-200">
+          <div className="bg-white rounded-2xl shadow-2xl w-full max-w-3xl mx-4 max-h-[90vh] overflow-y-auto animate-in zoom-in duration-200">
+            {/* 弹窗标题 */}
+            <div className="px-6 py-4 border-b border-gray-200 flex items-center justify-between sticky top-0 bg-white z-10">
+              <h3 className="text-lg text-gray-900">
+                {editingItem ? '编辑通知' : '新增通知'}
+              </h3>
+              <button
+                onClick={() => setShowAddModal(false)}
+                className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
+              >
+                <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
+                </svg>
+              </button>
+            </div>
+
+            {/* 弹窗内容 */}
+            <div className="px-6 py-6">
+              <div className="space-y-4">
+                <div>
+                  <label className="block text-sm text-gray-700 mb-2">通知标题 *</label>
+                  <input
+                    type="text"
+                    className="w-full px-4 py-2.5 bg-gray-50 border border-gray-200 rounded-xl outline-none focus:border-blue-400 focus:ring-4 focus:ring-blue-100 transition-all text-sm"
+                    placeholder="请输入通知标题"
+                    defaultValue={editingItem?.title || ''}
+                  />
+                </div>
+                <div className="grid grid-cols-3 gap-4">
+                  <div>
+                    <label className="block text-sm text-gray-700 mb-2">通知类型 *</label>
+                    <select className="w-full px-4 py-2.5 bg-gray-50 border border-gray-200 rounded-xl outline-none focus:border-blue-400 focus:ring-4 focus:ring-blue-100 transition-all text-sm">
+                      <option value="">请选择</option>
+                      <option value="系统通知">系统通知</option>
+                      <option value="告警通知">告警通知</option>
+                      <option value="培训通知">培训通知</option>
+                      <option value="审批通知">审批通知</option>
+                      <option value="安全提醒">安全提醒</option>
+                    </select>
+                  </div>
+                  <div>
+                    <label className="block text-sm text-gray-700 mb-2">优先级 *</label>
+                    <select className="w-full px-4 py-2.5 bg-gray-50 border border-gray-200 rounded-xl outline-none focus:border-blue-400 focus:ring-4 focus:ring-blue-100 transition-all text-sm">
+                      <option value="">请选择</option>
+                      <option value="紧急">紧急</option>
+                      <option value="重要">重要</option>
+                      <option value="普通">普通</option>
+                    </select>
+                  </div>
+                  <div>
+                    <label className="block text-sm text-gray-700 mb-2">发送方式</label>
+                    <select className="w-full px-4 py-2.5 bg-gray-50 border border-gray-200 rounded-xl outline-none focus:border-blue-400 focus:ring-4 focus:ring-blue-100 transition-all text-sm">
+                      <option value="立即发送">立即发送</option>
+                      <option value="定时发送">定时发送</option>
+                    </select>
+                  </div>
+                </div>
+                <div>
+                  <label className="block text-sm text-gray-700 mb-2">接收对象 *</label>
+                  <select className="w-full px-4 py-2.5 bg-gray-50 border border-gray-200 rounded-xl outline-none focus:border-blue-400 focus:ring-4 focus:ring-blue-100 transition-all text-sm">
+                    <option value="">请选择</option>
+                    <option value="全体用户">全体用户</option>
+                    <option value="按部门">按部门</option>
+                    <option value="按角色">按角色</option>
+                    <option value="指定用户">指定用户</option>
+                  </select>
+                </div>
+                <div>
+                  <label className="block text-sm text-gray-700 mb-2">通知内容 *</label>
+                  <textarea
+                    rows={5}
+                    className="w-full px-4 py-2.5 bg-gray-50 border border-gray-200 rounded-xl outline-none focus:border-blue-400 focus:ring-4 focus:ring-blue-100 transition-all text-sm resize-none"
+                    placeholder="请输入通知内容"
+                    defaultValue={editingItem?.content || ''}
+                  ></textarea>
+                </div>
+              </div>
+            </div>
+
+            {/* 弹窗底部 */}
+            <div className="px-6 py-4 bg-gray-50/50 border-t border-gray-200 flex justify-end gap-3 rounded-b-2xl">
+              <button
+                onClick={() => setShowAddModal(false)}
+                className="px-5 py-2.5 text-sm text-gray-600 bg-white border border-gray-200 rounded-xl hover:bg-gray-50 transition-colors"
+              >
+                取消
+              </button>
+              <button
+                onClick={() => {
+                  console.log('保存:', editingItem);
+                  setShowAddModal(false);
+                }}
+                className="px-5 py-2.5 text-sm text-white bg-gradient-to-r from-blue-500 to-blue-600 rounded-xl hover:shadow-lg hover:shadow-blue-400/40 transition-all"
+              >
+                {editingItem ? '保存修改' : '确定'}
+              </button>
+            </div>
+          </div>
+        </div>
+      )}
+    </div>
+  );
+}
+

Разлика између датотеке није приказан због своје велике величине
+ 249 - 703
src/components/UserManagement.tsx


Неке датотеке нису приказане због велике количине промена