837390164@qq.com 23 godzin temu
rodzic
commit
997296ed91
4 zmienionych plików z 913 dodań i 74 usunięć
  1. 5 2
      src/App.tsx
  2. 26 8
      src/api/system.ts
  3. 479 0
      src/pages/System/RoleManage.tsx
  4. 403 64
      src/pages/System/Users.tsx

+ 5 - 2
src/App.tsx

@@ -64,6 +64,7 @@ import ProfitCostStructure from './pages/Profit/CostStructure';
 import SystemUsers from './pages/System/Users';
 import SystemUserApply from './pages/System/UserApply';
 import SystemRoles from './pages/System/Roles';
+import SystemRoleManage from './pages/System/RoleManage';
 import SystemMenus from './pages/System/Menus';
 import SystemInterfaces from './pages/System/Interfaces';
 import SystemInterfaceLogs from './pages/System/InterfaceLogs';
@@ -157,8 +158,8 @@ const menuTitleMap: Record<string, string> = {
   'basic-data-drg-catalog': 'DRGs目录信息表',
   'basic-data-segmentation-rules': 'ADRG细分规则表',
   'system-user': '用户管理',
-  'system-user-apply': '用户申请审核',
   'system-role': '角色权限',
+  'system-role-manage': '角色管理',
   'system-menu': '菜单配置',
   'system-hospital': '医疗机构管理',
   'system-api': '接口服务配置',
@@ -254,7 +255,7 @@ const menuItems: MenuProps['items'] = [
     label: '用户管理',
     children: [
       { key: 'system-user', label: '用户管理' },
-      { key: 'system-user-apply', label: '用户申请审核' },
+      { key: 'system-role-manage', label: '角色管理' },
       { key: 'system-role', label: '角色权限' },
       { key: 'system-menu', label: '菜单配置' },
     ],
@@ -519,6 +520,8 @@ function App() {
         return <SystemUsers />;
       case 'system-user-apply':
         return <SystemUserApply />;
+      case 'system-role-manage':
+        return <SystemRoleManage />;
       case 'system-role':
         return <SystemRoles />;
       case 'system-menu':

+ 26 - 8
src/api/system.ts

@@ -9,18 +9,16 @@ import type { ApiResponse, Pagination, PageResult } from './basicData';
 
 // ========== 用户管理 (01030101-01030111) ==========
 
-/** 用户列表项 */
+/** 用户列表项 - 01030101接口返回,只包含用户基本信息 */
 export interface UserItem {
   userDr: number;           // 用户ID
   userCode: string;         // 用户编码
   userName: string;         // 用户姓名
-  sexDr?: number;           // 性别ID
+  sexID?: number;           // 性别ID
   sexDesc?: string;         // 性别描述
-  hospDr?: number;          // 医院ID
-  hospDesc?: string;        // 医院名称
   mobile?: string;          // 手机号
-  credTypeDr?: number;      // 证件类型ID
-  ceadTypeDesc?: string;    // 证件类型描述
+  credTypeID?: number;      // 证件类型ID
+  credTypeDesc?: string;    // 证件类型描述
   credNo?: string;          // 证件号
   image?: string;           // 头像
   introduce?: string;       // 简介
@@ -53,7 +51,7 @@ export interface UserAuditLogItem {
   hospitalDr?: number;      // 医院ID
   hospDesc?: string;        // 医院名称
   mobile?: string;          // 手机号
-  credTypeDr?: number;      // 证件类型ID
+  credTypeID?: number;      // 证件类型ID (后端返回)
   credTypeDesc?: string;    // 证件类型描述
   credNo?: string;          // 证件号
   image?: string;           // 头像
@@ -103,7 +101,7 @@ export interface SaveUserParams {
   nickname?: string;        // 昵称
   image?: string;           // 头像
   introduce?: string;       // 简介
-  hospID?: string;          // 所属医院ID(必填
+  hospID?: string;          // 所属医院ID(可选,现在通过角色管理
   hospDesc?: string;        // 医院名称
   startDate: string;        // 启用日期(必填)
   stopDate?: string;        // 停用日期
@@ -324,3 +322,23 @@ export const getUserDetail = (
 ): Promise<ApiResponse<{ rows: UserLogonLocItem[] }>> => {
   return invoke('01030111', [params]);
 };
+
+// ========== 角色管理 API 函数 ==========
+
+/** 角色下拉选项项 */
+export interface GroupOptionItem {
+  id: number;               // 角色ID
+  code: string;             // 角色编码
+  descripts: string;        // 角色名称
+}
+
+/** 查询角色下拉列表 (01010057)
+ * 注意:该接口返回格式为 result: [] 而非 result: { rows: [] }
+ */
+export const queryGroupOptions = (
+  params: {
+    active?: string;         // 状态 Y/N,默认Y
+  } = {}
+): Promise<ApiResponse<GroupOptionItem[]>> => {
+  return invoke('01010057', [params]);
+};

+ 479 - 0
src/pages/System/RoleManage.tsx

@@ -0,0 +1,479 @@
+import React, { useState, useEffect } from 'react';
+import {
+  Card,
+  Table,
+  Button,
+  Input,
+  Modal,
+  message,
+  Space,
+  Row,
+  Col,
+  Form,
+  Switch,
+  Select
+} from 'antd';
+import {
+  PlusOutlined,
+  SearchOutlined,
+  ReloadOutlined,
+  EditOutlined,
+  DeleteOutlined
+} from '@ant-design/icons';
+import type { ColumnsType } from 'antd/es/table';
+import CustomPagination from '../../components/CustomPagination';
+import { invoke } from '../../api/request';
+import type { ApiResponse, PageResult } from '../../api/basicData';
+
+const { Option } = Select;
+
+// 分页参数
+interface PaginationParams {
+  current: number;
+  pageSize: number;
+  total: number;
+}
+
+// 角色项
+interface RoleItem {
+  id: number;
+  code: string;
+  descripts: string;
+  enDesc?: string;
+  mainInterface?: string;
+  operCodeTable?: string;
+  sendMsgToAllUser?: string;
+  safeClassificat?: string;
+  columnEdit?: string;
+  layoutEdit?: string;
+  defaultMenuType?: string;
+  tokenOverTime?: number;
+  startDate?: string;
+  endDate?: string;
+}
+
+// 保存角色参数
+interface SaveRoleParams {
+  id?: number;
+  code: string;
+  descripts: string;
+  enDesc?: string;
+  mainInterface?: string;
+  operCodeTable?: string;
+  sendMsgToAllUser?: string;
+  safeClassificat?: string;
+  columnEdit?: string;
+  layoutEdit?: string;
+  defaultMenuType?: string;
+  tokenOverTime?: number;
+  startDate?: string;
+  endDate?: string;
+}
+
+// 查询角色列表 (01010032)
+const queryRoles = (
+  params: {
+    code?: string;
+    descripts?: string;
+  },
+  pagination: { pageSize: number; currentPage: number }
+): Promise<ApiResponse<PageResult<RoleItem>>> => {
+  return invoke('01010032', [params], undefined, pagination);
+};
+
+// 保存角色 (01010031)
+const saveRole = (params: SaveRoleParams): Promise<ApiResponse> => {
+  return invoke('01010031', [params]);
+};
+
+const RoleManage: React.FC = () => {
+  const [form] = Form.useForm();
+  const [data, setData] = useState<RoleItem[]>([]);
+  const [loading, setLoading] = useState(false);
+  const [pagination, setPagination] = useState<PaginationParams>({
+    current: 1,
+    pageSize: 10,
+    total: 0
+  });
+  const [modalVisible, setModalVisible] = useState(false);
+  const [modalTitle, setModalTitle] = useState('新增角色');
+  const [editingRecord, setEditingRecord] = useState<RoleItem | null>(null);
+
+  // 查询条件
+  const [searchCode, setSearchCode] = useState('');
+  const [searchDesc, setSearchDesc] = useState('');
+
+  // 表格列定义
+  const columns: ColumnsType<RoleItem> = [
+    {
+      title: '序号',
+      key: 'index',
+      width: 60,
+      render: (_, __, index) => (pagination.current - 1) * pagination.pageSize + index + 1
+    },
+    {
+      title: '角色编码',
+      dataIndex: 'code',
+      key: 'code',
+      width: 120
+    },
+    {
+      title: '角色名称',
+      dataIndex: 'descripts',
+      key: 'descripts',
+      width: 150
+    },
+    {
+      title: '英文名称',
+      dataIndex: 'enDesc',
+      key: 'enDesc',
+      width: 150,
+      render: (text: string) => text || '-'
+    },
+    {
+      title: '安全级别',
+      dataIndex: 'safeClassificat',
+      key: 'safeClassificat',
+      width: 100,
+      render: (text: string) => text || '-'
+    },
+    {
+      title: '操作',
+      key: 'action',
+      fixed: 'right',
+      width: 150,
+      render: (_, record) => (
+        <Space size="small">
+          <Button
+            type="link"
+            size="small"
+            icon={<EditOutlined />}
+            onClick={() => handleEdit(record)}
+          >
+            编辑
+          </Button>
+        </Space>
+      )
+    }
+  ];
+
+  // 查询角色列表
+  const fetchData = async (page = pagination.current, size = pagination.pageSize) => {
+    setLoading(true);
+    try {
+      const res = await queryRoles(
+        {
+          code: searchCode || undefined,
+          descripts: searchDesc || undefined
+        },
+        { pageSize: size, currentPage: page }
+      );
+
+      if (String(res.errorCode) === '0' && res.result) {
+        setData(res.result.rows || []);
+        setPagination(prev => ({
+          ...prev,
+          total: res.result?.total || 0
+        }));
+      } else {
+        message.error(res.errorMessage || '查询失败');
+      }
+    } catch (error) {
+      console.error('查询角色失败:', error);
+      message.error('查询角色失败');
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  // 初始化加载
+  useEffect(() => {
+    fetchData(1, pagination.pageSize);
+  }, []);
+
+  // 搜索
+  const handleSearch = () => {
+    setPagination(prev => ({ ...prev, current: 1 }));
+    fetchData(1, pagination.pageSize);
+  };
+
+  // 重置
+  const handleReset = () => {
+    setSearchCode('');
+    setSearchDesc('');
+    setPagination(prev => ({ ...prev, current: 1 }));
+    fetchData(1, pagination.pageSize);
+  };
+
+  // 新增
+  const handleAdd = () => {
+    setEditingRecord(null);
+    setModalTitle('新增角色');
+    form.resetFields();
+    setModalVisible(true);
+  };
+
+  // 编辑
+  const handleEdit = (record: RoleItem) => {
+    setEditingRecord(record);
+    setModalTitle('编辑角色');
+    form.resetFields();
+    setModalVisible(true);
+    
+    setTimeout(() => {
+      form.setFieldsValue({
+        id: record.id,
+        code: record.code,
+        descripts: record.descripts,
+        enDesc: record.enDesc,
+        mainInterface: record.mainInterface,
+        operCodeTable: record.operCodeTable === 'Y',
+        sendMsgToAllUser: record.sendMsgToAllUser === 'Y',
+        safeClassificat: record.safeClassificat,
+        columnEdit: record.columnEdit === 'Y',
+        layoutEdit: record.layoutEdit === 'Y',
+        defaultMenuType: record.defaultMenuType,
+        tokenOverTime: record.tokenOverTime,
+        startDate: record.startDate,
+        endDate: record.endDate
+      });
+    }, 0);
+  };
+
+  // 保存角色
+  const handleSave = async () => {
+    try {
+      const values = await form.validateFields();
+
+      const params: SaveRoleParams = {
+        id: editingRecord?.id,
+        code: values.code,
+        descripts: values.descripts,
+        enDesc: values.enDesc,
+        mainInterface: values.mainInterface,
+        operCodeTable: values.operCodeTable ? 'Y' : 'N',
+        sendMsgToAllUser: values.sendMsgToAllUser ? 'Y' : 'N',
+        safeClassificat: values.safeClassificat,
+        columnEdit: values.columnEdit ? 'Y' : 'N',
+        layoutEdit: values.layoutEdit ? 'Y' : 'N',
+        defaultMenuType: values.defaultMenuType,
+        tokenOverTime: values.tokenOverTime,
+        startDate: values.startDate,
+        endDate: values.endDate
+      };
+
+      const res = await saveRole(params);
+
+      if (String(res.errorCode) === '0') {
+        message.success(editingRecord ? '修改成功' : '新增成功');
+        setModalVisible(false);
+        fetchData(pagination.current, pagination.pageSize);
+      } else {
+        message.error(res.errorMessage || (editingRecord ? '修改失败' : '新增失败'));
+      }
+    } catch (error) {
+      console.error('保存角色失败:', error);
+      message.error('保存角色失败');
+    }
+  };
+
+  return (
+    <div style={{ padding: 16 }}>
+      {/* 查询条件 */}
+      <Card size="small" style={{ marginBottom: 16 }}>
+        <Row gutter={16} align="middle">
+          <Col>
+            <Input
+              placeholder="角色编码"
+              value={searchCode}
+              onChange={e => setSearchCode(e.target.value)}
+              style={{ width: 140 }}
+              allowClear
+            />
+          </Col>
+          <Col>
+            <Input
+              placeholder="角色名称"
+              value={searchDesc}
+              onChange={e => setSearchDesc(e.target.value)}
+              style={{ width: 140 }}
+              allowClear
+            />
+          </Col>
+          <Col>
+            <Space>
+              <Button type="primary" icon={<SearchOutlined />} onClick={handleSearch}>查询</Button>
+              <Button icon={<ReloadOutlined />} onClick={handleReset}>重置</Button>
+            </Space>
+          </Col>
+        </Row>
+      </Card>
+
+      {/* 工具栏 + 表格 */}
+      <Card size="small">
+        <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 12 }}>
+          <Button type="primary" icon={<PlusOutlined />} onClick={handleAdd}>新增角色</Button>
+          <span>共 {pagination.total} 条记录</span>
+        </div>
+        <Table
+          columns={columns}
+          dataSource={data}
+          rowKey="id"
+          loading={loading}
+          scroll={{ x: 800 }}
+          size="small"
+          pagination={false}
+        />
+        <div style={{ marginTop: 12, display: 'flex', justifyContent: 'flex-end' }}>
+          <CustomPagination
+            current={pagination.current}
+            pageSize={pagination.pageSize}
+            total={pagination.total}
+            onChange={(page, size) => {
+              setPagination(prev => ({ ...prev, current: page, pageSize: size }));
+              fetchData(page, size);
+            }}
+          />
+        </div>
+      </Card>
+
+      {/* 新增/编辑弹窗 */}
+      <Modal
+        title={modalTitle}
+        open={modalVisible}
+        onOk={handleSave}
+        onCancel={() => setModalVisible(false)}
+        okText="确定"
+        cancelText="取消"
+        width={600}
+      >
+        <Form
+          form={form}
+          layout="vertical"
+        >
+          <Row gutter={16}>
+            <Col span={12}>
+              <Form.Item
+                name="code"
+                label="角色编码"
+                rules={[{ required: true, message: '请输入角色编码' }]}
+              >
+                <Input placeholder="请输入角色编码" maxLength={50} disabled={!!editingRecord} />
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item
+                name="descripts"
+                label="角色名称"
+                rules={[{ required: true, message: '请输入角色名称' }]}
+              >
+                <Input placeholder="请输入角色名称" maxLength={50} />
+              </Form.Item>
+            </Col>
+          </Row>
+          
+          <Row gutter={16}>
+            <Col span={12}>
+              <Form.Item
+                name="enDesc"
+                label="英文名称"
+              >
+                <Input placeholder="请输入英文名称" maxLength={50} />
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item
+                name="safeClassificat"
+                label="安全级别"
+              >
+                <Input placeholder="请输入安全级别" maxLength={10} />
+              </Form.Item>
+            </Col>
+          </Row>
+
+          <Row gutter={16}>
+            <Col span={12}>
+              <Form.Item
+                name="operCodeTable"
+                label="操作码表"
+                valuePropName="checked"
+              >
+                <Switch checkedChildren="是" unCheckedChildren="否" />
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item
+                name="sendMsgToAllUser"
+                label="发送消息给所有用户"
+                valuePropName="checked"
+              >
+                <Switch checkedChildren="是" unCheckedChildren="否" />
+              </Form.Item>
+            </Col>
+          </Row>
+
+          <Row gutter={16}>
+            <Col span={12}>
+              <Form.Item
+                name="columnEdit"
+                label="列编辑"
+                valuePropName="checked"
+              >
+                <Switch checkedChildren="是" unCheckedChildren="否" />
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item
+                name="layoutEdit"
+                label="布局编辑"
+                valuePropName="checked"
+              >
+                <Switch checkedChildren="是" unCheckedChildren="否" />
+              </Form.Item>
+            </Col>
+          </Row>
+
+          <Row gutter={16}>
+            <Col span={12}>
+              <Form.Item
+                name="tokenOverTime"
+                label="Token超时时间(分钟)"
+              >
+                <Input type="number" placeholder="请输入Token超时时间" />
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item
+                name="defaultMenuType"
+                label="默认菜单类型"
+              >
+                <Input placeholder="请输入默认菜单类型" maxLength={10} />
+              </Form.Item>
+            </Col>
+          </Row>
+
+          <Row gutter={16}>
+            <Col span={12}>
+              <Form.Item
+                name="startDate"
+                label="开始日期"
+              >
+                <Input placeholder="YYYY-MM-DD" maxLength={10} />
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item
+                name="endDate"
+                label="结束日期"
+              >
+                <Input placeholder="YYYY-MM-DD" maxLength={10} />
+              </Form.Item>
+            </Col>
+          </Row>
+        </Form>
+      </Modal>
+    </div>
+  );
+};
+
+export default RoleManage;

+ 403 - 64
src/pages/System/Users.tsx

@@ -14,7 +14,8 @@ import {
   Form,
   Tag,
   Popconfirm,
-  Tabs
+  Tabs,
+  Switch
 } from 'antd';
 import {
   PlusOutlined,
@@ -33,10 +34,12 @@ import {
   getUserDetail,
   saveUserLogonLoc,
   deleteUserLogonLoc,
+  queryGroupOptions,
   type UserItem,
   type UserLogonLocItem,
   type SaveUserParams,
-  type SaveUserLogonLocParams
+  type SaveUserLogonLocParams,
+  type GroupOptionItem
 } from '../../api/system';
 import { queryHospitals, type HospitalItem } from '../../api/hospital';
 import CustomPagination from '../../components/CustomPagination';
@@ -65,17 +68,10 @@ const credTypeOptions = [
   { value: '4', label: '其他' },
 ];
 
-// 角色组选项(临时,后续从接口获取)
-const groupOptions = [
-  { value: '1', label: '系统管理员' },
-  { value: '2', label: '医院管理员' },
-  { value: '3', label: '医生' },
-  { value: '4', label: '护士' },
-];
-
 const Users: React.FC = () => {
   const [modalForm] = Form.useForm();
   const [roleForm] = Form.useForm();
+  const [editRoleForm] = Form.useForm();
   const [data, setData] = useState<UserItem[]>([]);
   const [loading, setLoading] = useState(false);
   const [pagination, setPagination] = useState<PaginationParams>({
@@ -97,6 +93,12 @@ const Users: React.FC = () => {
   const [roleModalVisible, setRoleModalVisible] = useState(false);
   const [editingRole, setEditingRole] = useState<UserLogonLocItem | null>(null);
 
+  // 新增/编辑弹窗中的角色管理
+  const [editUserRoles, setEditUserRoles] = useState<UserLogonLocItem[]>([]);
+  const [editRoleLoading, setEditRoleLoading] = useState(false);
+  const [editRoleModalVisible, setEditRoleModalVisible] = useState(false);
+  const [editingEditRole, setEditingEditRole] = useState<UserLogonLocItem | null>(null);
+
   // 查询条件
   const [searchCode, setSearchCode] = useState('');
   const [searchName, setSearchName] = useState('');
@@ -107,6 +109,10 @@ const Users: React.FC = () => {
   const [hospitals, setHospitals] = useState<HospitalItem[]>([]);
   const [hospitalLoading, setHospitalLoading] = useState(false);
 
+  // 角色列表
+  const [groups, setGroups] = useState<GroupOptionItem[]>([]);
+  const [groupLoading, setGroupLoading] = useState(false);
+
   // 加载医院列表
   const fetchHospitals = async () => {
     setHospitalLoading(true);
@@ -122,6 +128,22 @@ const Users: React.FC = () => {
     }
   };
 
+  // 加载角色列表
+  const fetchGroups = async () => {
+    setGroupLoading(true);
+    try {
+      const res = await queryGroupOptions({ active: 'Y' });
+      if (String(res.errorCode) === '0' && res.result) {
+        // 接口返回的是 result: [] 数组格式
+        setGroups(res.result || []);
+      }
+    } catch (error) {
+      console.error('加载角色列表失败:', error);
+    } finally {
+      setGroupLoading(false);
+    }
+  };
+
   // 表格列定义
   const columns: ColumnsType<UserItem> = [
     {
@@ -148,12 +170,6 @@ const Users: React.FC = () => {
       key: 'sexDesc',
       width: 80
     },
-    {
-      title: '所属医院',
-      dataIndex: 'hospDesc',
-      key: 'hospDesc',
-      width: 180
-    },
     {
       title: '手机号',
       dataIndex: 'mobile',
@@ -162,8 +178,8 @@ const Users: React.FC = () => {
     },
     {
       title: '证件类型',
-      dataIndex: 'ceadTypeDesc',
-      key: 'ceadTypeDesc',
+      dataIndex: 'credTypeDesc',
+      key: 'credTypeDesc',
       width: 100
     },
     {
@@ -282,6 +298,7 @@ const Users: React.FC = () => {
     setEditingRecord(null);
     setModalTitle('新增用户');
     modalForm.resetFields();
+    setEditUserRoles([]); // 清空角色列表
     setModalVisible(true);
     setTimeout(() => {
       modalForm.setFieldsValue({ 
@@ -292,26 +309,39 @@ const Users: React.FC = () => {
   };
 
   // 编辑
-  const handleEdit = (record: UserItem) => {
+  const handleEdit = async (record: UserItem) => {
     setEditingRecord(record);
     setModalTitle('编辑用户');
     modalForm.resetFields();
     setModalVisible(true);
     
+    // 加载用户角色信息
+    setEditUserRoles([]);
+    setEditRoleLoading(true);
+    try {
+      const res = await getUserDetail({ userID: record.userDr });
+      if (String(res.errorCode) === '0' && res.result) {
+        setEditUserRoles(res.result.rows || []);
+      }
+    } catch (error) {
+      console.error('加载用户角色失败:', error);
+    } finally {
+      setEditRoleLoading(false);
+    }
+    
     setTimeout(() => {
       modalForm.setFieldsValue({
         userDr: record.userDr,
         userName: record.userName,
         mobile: record.mobile,
-        sexID: record.sexDr ? String(record.sexDr) : '1',
-        credTypeID: record.credTypeDr ? String(record.credTypeDr) : '',
+        sexID: record.sexID ? String(record.sexID) : '1',
+        credTypeID: record.credTypeID ? String(record.credTypeID) : '',
         credNo: record.credNo,
         birthDate: record.birthDate ? dayjs(record.birthDate) : null,
         workMobile: record.workMobile,
         mail: record.mail,
         nickname: record.nickname,
         introduce: record.introduce,
-        hospID: record.hospDr ? String(record.hospDr) : '',
         startDate: record.startDate ? dayjs(record.startDate) : dayjs(),
         stopDate: record.stopDate ? dayjs(record.stopDate) : null,
         statusFlag: record.statusFlag || 'Y',
@@ -358,8 +388,6 @@ const Users: React.FC = () => {
         mail: values.mail,
         nickname: values.nickname,
         introduce: values.introduce,
-        hospID: values.hospID,
-        hospDesc: hospitals.find(h => String(h.hospitalID) === values.hospID)?.descripts,
         startDate: values.startDate ? values.startDate.format('YYYY-MM-DD') : dayjs().format('YYYY-MM-DD'),
         stopDate: values.stopDate ? values.stopDate.format('YYYY-MM-DD') : '',
         statusFlag: values.statusFlag,
@@ -370,6 +398,13 @@ const Users: React.FC = () => {
       const res = await saveUser(params);
 
       if (String(res.errorCode) === '0') {
+        const savedUserDr = res.rowIDArr?.[0] || editingRecord?.userDr;
+        
+        // 保存用户角色信息
+        if (savedUserDr && editUserRoles.length > 0) {
+          await handleSaveUserRoles(savedUserDr);
+        }
+        
         message.success(editingRecord ? '修改成功' : '新增成功');
         setModalVisible(false);
         fetchData(pagination.current, pagination.pageSize);
@@ -382,18 +417,173 @@ const Users: React.FC = () => {
     }
   };
 
+  // 保存用户角色信息
+  const handleSaveUserRoles = async (userDr: number) => {
+    try {
+      for (const role of editUserRoles) {
+        if (!role.userLogonLocID) {
+          // 新增角色
+          const params: SaveUserLogonLocParams = {
+            userID: userDr,
+            hospID: String(role.hospID),
+            groupID: String(role.groupID),
+            isDefault: role.isDefault ? 'Y' : 'N'
+          };
+          await saveUserLogonLoc(params);
+        }
+      }
+    } catch (error) {
+      console.error('保存用户角色失败:', error);
+    }
+  };
+
+  // 在编辑弹窗中打开添加角色弹窗
+  const handleAddRoleInEdit = async () => {
+    // 加载角色列表
+    if (groups.length === 0) {
+      await fetchGroups();
+    }
+    setEditingEditRole(null);
+    editRoleForm.resetFields();
+    setEditRoleModalVisible(true);
+    setTimeout(() => {
+      editRoleForm.setFieldsValue({ isDefault: false });
+    }, 0);
+  };
+
+  // 在编辑弹窗中编辑角色
+  const handleEditRoleInEdit = async (role: UserLogonLocItem) => {
+    // 加载角色列表
+    if (groups.length === 0) {
+      await fetchGroups();
+    }
+    setEditingEditRole(role);
+    editRoleForm.resetFields();
+    setEditRoleModalVisible(true);
+    setTimeout(() => {
+      editRoleForm.setFieldsValue({
+        hospID: String(role.hospID),
+        groupID: String(role.groupID),
+        isDefault: role.isDefault === 'Y'
+      });
+    }, 0);
+  };
+
+  // 在编辑弹窗中保存角色
+  const handleSaveRoleInEdit = async () => {
+    try {
+      const values = await editRoleForm.validateFields();
+      const isDefault = values.isDefault ? 'Y' : 'N';
+      
+      if (editingEditRole?.userLogonLocID) {
+        // 编辑现有角色 - 调用API
+        // 如果设置为默认,先将其他角色设为非默认
+        if (isDefault === 'Y' && editingRecord) {
+          for (const role of editUserRoles) {
+            if (role.userLogonLocID && role.userLogonLocID !== editingEditRole.userLogonLocID && role.isDefault === 'Y') {
+              await saveUserLogonLoc({
+                userLogonLocID: role.userLogonLocID,
+                userID: editingRecord.userDr,
+                hospID: String(role.hospID),
+                groupID: String(role.groupID),
+                isDefault: 'N'
+              });
+            }
+          }
+        }
+        
+        const params: SaveUserLogonLocParams = {
+          userLogonLocID: editingEditRole.userLogonLocID,
+          userID: editingRecord?.userDr || 0,
+          hospID: values.hospID,
+          groupID: values.groupID,
+          isDefault: isDefault
+        };
+        const res = await saveUserLogonLoc(params);
+        if (String(res.errorCode) === '0') {
+          // 刷新角色列表
+          if (editingRecord) {
+            const detailRes = await getUserDetail({ userID: editingRecord.userDr });
+            if (String(detailRes.errorCode) === '0' && detailRes.result) {
+              setEditUserRoles(detailRes.result.rows || []);
+            }
+          }
+        }
+      } else {
+        // 新增角色到本地列表
+        const newRole: UserLogonLocItem = {
+          userLogonLocID: undefined,
+          hospID: Number(values.hospID),
+          hospDesc: hospitals.find(h => String(h.hospitalID) === values.hospID)?.descripts || '',
+          hospCode: '',
+          groupID: Number(values.groupID),
+          groupDesc: groups.find(g => String(g.id) === values.groupID)?.descripts || '',
+          isDefault: isDefault
+        };
+        
+        // 如果设置为默认,将其他本地角色设为非默认
+        if (isDefault === 'Y') {
+          setEditUserRoles(prev => {
+            const updated: UserLogonLocItem[] = prev.map(r => ({ ...r, isDefault: 'N' as const }));
+            updated.push(newRole);
+            return updated;
+          });
+        } else {
+          setEditUserRoles(prev => [...prev, newRole]);
+        }
+      }
+      
+      setEditRoleModalVisible(false);
+      message.success(editingEditRole ? '修改成功' : '添加成功');
+    } catch (error) {
+      console.error('保存角色失败:', error);
+      message.error('保存角色失败');
+    }
+  };
+
+  // 在编辑弹窗中删除角色
+  const handleDeleteRoleInEdit = async (role: UserLogonLocItem) => {
+    if (role.userLogonLocID) {
+      // 已有ID的角色,需要调用API删除
+      try {
+        const res = await deleteUserLogonLoc({ userLogonLocID: role.userLogonLocID });
+        if (String(res.errorCode) === '0') {
+          setEditUserRoles(prev => prev.filter(r => r.userLogonLocID !== role.userLogonLocID));
+          message.success('删除成功');
+        } else {
+          message.error(res.errorMessage || '删除失败');
+        }
+      } catch (error) {
+        console.error('删除角色失败:', error);
+        message.error('删除角色失败');
+      }
+    } else {
+      // 本地新增的角色,直接从列表移除
+      setEditUserRoles(prev => prev.filter(r => !(r.hospID === role.hospID && r.groupID === role.groupID)));
+      message.success('删除成功');
+    }
+  };
+
   // 打开添加角色弹窗
-  const handleAddRole = () => {
+  const handleAddRole = async () => {
+    // 加载角色列表
+    if (groups.length === 0) {
+      await fetchGroups();
+    }
     setEditingRole(null);
     roleForm.resetFields();
     setRoleModalVisible(true);
     setTimeout(() => {
-      roleForm.setFieldsValue({ isDefault: 'N' });
+      roleForm.setFieldsValue({ isDefault: false });
     }, 0);
   };
 
   // 编辑角色
-  const handleEditRole = (role: UserLogonLocItem) => {
+  const handleEditRole = async (role: UserLogonLocItem) => {
+    // 加载角色列表
+    if (groups.length === 0) {
+      await fetchGroups();
+    }
     setEditingRole(role);
     roleForm.resetFields();
     setRoleModalVisible(true);
@@ -401,7 +591,7 @@ const Users: React.FC = () => {
       roleForm.setFieldsValue({
         hospID: String(role.hospID),
         groupID: String(role.groupID),
-        isDefault: role.isDefault || 'N'
+        isDefault: role.isDefault === 'Y'
       });
     }, 0);
   };
@@ -411,12 +601,29 @@ const Users: React.FC = () => {
     if (!detailRecord) return;
     try {
       const values = await roleForm.validateFields();
+      const isDefault = values.isDefault ? 'Y' : 'N';
+      
+      // 如果设置为默认,先将其他角色设为非默认
+      if (isDefault === 'Y' && userRoles.length > 0) {
+        for (const role of userRoles) {
+          if (role.userLogonLocID && role.userLogonLocID !== editingRole?.userLogonLocID && role.isDefault === 'Y') {
+            await saveUserLogonLoc({
+              userLogonLocID: role.userLogonLocID,
+              userID: detailRecord.userDr,
+              hospID: String(role.hospID),
+              groupID: String(role.groupID),
+              isDefault: 'N'
+            });
+          }
+        }
+      }
+      
       const params: SaveUserLogonLocParams = {
         userLogonLocID: editingRole?.userLogonLocID,
         userID: detailRecord.userDr,
         hospID: values.hospID,
         groupID: values.groupID,
-        isDefault: values.isDefault
+        isDefault: isDefault
       };
 
       const res = await saveUserLogonLoc(params);
@@ -600,7 +807,7 @@ const Users: React.FC = () => {
           dataSource={data}
           rowKey="userDr"
           loading={loading}
-          scroll={{ x: 1400 }}
+          scroll={{ x: 1200 }}
           size="small"
           pagination={false}
         />
@@ -740,26 +947,6 @@ const Users: React.FC = () => {
           </Row>
 
           <Row gutter={16}>
-            <Col span={12}>
-              <Form.Item
-                name="hospID"
-                label="所属医院"
-                rules={[{ required: true, message: '请选择所属医院' }]}
-              >
-                <Select 
-                  placeholder="请选择所属医院"
-                  loading={hospitalLoading}
-                  showSearch
-                  filterOption={(input, option) =>
-                    String(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
-                  }
-                  options={hospitals.map(h => ({
-                    value: String(h.hospitalID),
-                    label: h.descripts
-                  }))}
-                />
-              </Form.Item>
-            </Col>
             <Col span={12}>
               <Form.Item
                 name="startDate"
@@ -769,9 +956,6 @@ const Users: React.FC = () => {
                 <DatePicker style={{ width: '100%' }} placeholder="请选择启用日期" />
               </Form.Item>
             </Col>
-          </Row>
-
-          <Row gutter={16}>
             <Col span={12}>
               <Form.Item
                 name="stopDate"
@@ -788,6 +972,104 @@ const Users: React.FC = () => {
           >
             <Input.TextArea placeholder="请输入简介" maxLength={200} rows={3} />
           </Form.Item>
+
+          {/* 角色管理区域 */}
+          <div style={{ marginTop: 24, borderTop: '1px solid #f0f0f0', paddingTop: 16 }}>
+            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
+              <h4 style={{ margin: 0, fontWeight: 'bold' }}>医院角色信息</h4>
+              <Button 
+                type="primary" 
+                icon={<PlusOutlined />} 
+                size="small"
+                onClick={handleAddRoleInEdit}
+              >
+                添加角色
+              </Button>
+            </div>
+            <Table
+              columns={[
+                {
+                  title: '医院编码',
+                  dataIndex: 'hospCode',
+                  key: 'hospCode',
+                  width: 100,
+                  render: (text: string, record: UserLogonLocItem) => {
+                    if (text) return text;
+                    // 从医院列表查找编码
+                    const hosp = hospitals.find(h => Number(h.hospitalID) === record.hospID);
+                    return hosp?.code || '-';
+                  }
+                },
+                {
+                  title: '医院名称',
+                  dataIndex: 'hospDesc',
+                  key: 'hospDesc',
+                  width: 180,
+                  render: (text: string, record: UserLogonLocItem) => {
+                    if (text) return text;
+                    // 从医院列表查找名称
+                    const hosp = hospitals.find(h => Number(h.hospitalID) === record.hospID);
+                    return hosp?.descripts || '-';
+                  }
+                },
+                {
+                  title: '角色名称',
+                  dataIndex: 'groupDesc',
+                  key: 'groupDesc',
+                  width: 120,
+                  render: (text: string, record: UserLogonLocItem) => {
+                    if (text) return text;
+                    // 从角色选项查找名称
+                    const group = groups.find(g => g.id === record.groupID);
+                    return group?.descripts || '-';
+                  }
+                },
+                {
+                  title: '是否默认',
+                  dataIndex: 'isDefault',
+                  key: 'isDefault',
+                  width: 100,
+                  render: (isDefault: string) => (
+                    isDefault === 'Y' ? <Tag color="blue">默认</Tag> : '-'
+                  )
+                },
+                {
+                  title: '操作',
+                  key: 'action',
+                  width: 120,
+                  render: (_, record) => (
+                    <Space size="small">
+                      <Button
+                        type="link"
+                        size="small"
+                        icon={<EditOutlined />}
+                        onClick={() => handleEditRoleInEdit(record)}
+                      >
+                        编辑
+                      </Button>
+                      <Popconfirm
+                        title="确认删除"
+                        description="确定要删除该角色权限吗?"
+                        onConfirm={() => handleDeleteRoleInEdit(record)}
+                        okText="确定"
+                        cancelText="取消"
+                      >
+                        <Button type="link" size="small" danger icon={<DeleteOutlined />}>
+                          删除
+                        </Button>
+                      </Popconfirm>
+                    </Space>
+                  )
+                }
+              ]}
+              dataSource={editUserRoles}
+              rowKey={(record) => record.userLogonLocID || `${record.hospID}-${record.groupID}`}
+              loading={editRoleLoading}
+              size="small"
+              pagination={false}
+              scroll={{ y: 200 }}
+            />
+          </div>
         </Form>
       </Modal>
 
@@ -823,9 +1105,6 @@ const Users: React.FC = () => {
                 </Col>
               </Row>
               <Row gutter={[16, 16]}>
-                <Col span={12}>
-                  <p><strong>所属医院:</strong>{detailRecord.hospDesc || '-'}</p>
-                </Col>
                 <Col span={12}>
                   <p><strong>状态:</strong>
                     <Tag color={detailRecord.statusFlag === 'Y' ? 'green' : 'red'}>
@@ -833,10 +1112,13 @@ const Users: React.FC = () => {
                     </Tag>
                   </p>
                 </Col>
+                <Col span={12}>
+                  <p><strong>创建人:</strong>{detailRecord.creatUserDesc || '-'}</p>
+                </Col>
               </Row>
               <Row gutter={[16, 16]}>
                 <Col span={12}>
-                  <p><strong>证件类型:</strong>{detailRecord.ceadTypeDesc || '-'}</p>
+                  <p><strong>证件类型:</strong>{detailRecord.credTypeDesc || '-'}</p>
                 </Col>
                 <Col span={12}>
                   <p><strong>证件号:</strong>{detailRecord.credNo || '-'}</p>
@@ -930,20 +1212,77 @@ const Users: React.FC = () => {
           >
             <Select 
               placeholder="请选择角色"
-              options={groupOptions}
+              loading={groupLoading}
+              options={groups.map(g => ({
+                value: String(g.id),
+                label: g.descripts
+              }))}
             />
           </Form.Item>
           <Form.Item
             name="isDefault"
             label="是否默认"
-            rules={[{ required: true, message: '请选择是否默认' }]}
+            valuePropName="checked"
+          >
+            <Switch 
+              checkedChildren="是"
+              unCheckedChildren="否"
+            />
+          </Form.Item>
+        </Form>
+      </Modal>
+
+      {/* 新增/编辑弹窗中的角色分配弹窗 */}
+      <Modal
+        title={editingEditRole ? '编辑角色' : '添加角色'}
+        open={editRoleModalVisible}
+        onOk={handleSaveRoleInEdit}
+        onCancel={() => setEditRoleModalVisible(false)}
+        okText="确定"
+        cancelText="取消"
+        width={500}
+      >
+        <Form form={editRoleForm} layout="vertical">
+          <Form.Item
+            name="hospID"
+            label="医院"
+            rules={[{ required: true, message: '请选择医院' }]}
           >
             <Select 
-              placeholder="请选择是否默认"
-              options={[
-                { value: 'Y', label: '是' },
-                { value: 'N', label: '否' }
-              ]}
+              placeholder="请选择医院"
+              loading={hospitalLoading}
+              showSearch
+              filterOption={(input, option) =>
+                String(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
+              }
+              options={hospitals.map(h => ({
+                value: String(h.hospitalID),
+                label: h.descripts
+              }))}
+            />
+          </Form.Item>
+          <Form.Item
+            name="groupID"
+            label="角色"
+            rules={[{ required: true, message: '请选择角色' }]}
+          >
+            <Select 
+              placeholder="请选择角色"
+              loading={groupLoading}
+              options={groups.map(g => ({
+                value: String(g.id),
+                label: g.descripts
+              }))}
+            />
+          </Form.Item>
+          <Form.Item
+            name="isDefault"
+            label="是否默认"
+            valuePropName="checked"
+          >
+            <Switch 
+              checkedChildren="是"
+              unCheckedChildren="否"
             />
           </Form.Item>
         </Form>