Browse Source

- 文件夹管理初步完成
- 中心信息基本完成
- 中心成员完成一半

Huanyi 1 day ago
parent
commit
62b492f23d

+ 2 - 2
src/api/document/folder/index.ts

@@ -1,6 +1,6 @@
 import request from '@/utils/request';
 import { AxiosPromise } from 'axios';
-import { FolderVO, FolderForm, FolderQuery, ProjectVO, ProjectQuery } from '@/api/document/folder/types';
+import { FolderVO, FolderForm, FolderQuery, ProjectVO, ProjectQuery, FolderListVO } from '@/api/document/folder/types';
 
 /**
  * 查询文件夹管理列表
@@ -8,7 +8,7 @@ import { FolderVO, FolderForm, FolderQuery, ProjectVO, ProjectQuery } from '@/ap
  * @returns {*}
  */
 
-export const listFolder = (query?: FolderQuery): AxiosPromise<FolderVO[]> => {
+export const listFolder = (query?: FolderQuery): AxiosPromise<FolderListVO[]> => {
   return request({
     url: '/document/folder/list',
     method: 'get',

+ 30 - 0
src/api/document/folder/types.ts

@@ -77,6 +77,11 @@ export interface FolderForm extends BaseEntity {
    */
   note?: string;
 
+  /**
+   * 限制层级
+   */
+  restrictionLevel?: number;
+
 }
 
 export interface FolderQuery extends PageQuery {
@@ -206,3 +211,28 @@ export interface ProjectQuery extends PageQuery {
    */
   params?: any;
 }
+
+/**
+ * 文件夹树形列表VO
+ */
+export interface FolderListVO {
+  /**
+   * 序号
+   */
+  id: string | number;
+
+  /**
+   * 名称
+   */
+  name: string;
+
+  /**
+   * 类型
+   */
+  type: number;
+
+  /**
+   * 子节点
+   */
+  children?: FolderListVO[];
+}

+ 52 - 1
src/api/project/management/index.ts

@@ -1,6 +1,6 @@
 import request from '@/utils/request';
 import { AxiosPromise } from 'axios';
-import { ManagementVO, ManagementForm, ManagementQuery, ProjectMemberVO, ProjectMemberQuery, UserNotInProjectQuery, UserVO, InviteMemberForm } from '@/api/project/management/types';
+import { ManagementVO, ManagementForm, ManagementQuery, ProjectMemberVO, ProjectMemberQuery, UserNotInProjectQuery, UserVO, InviteMemberForm, CenterInfoVO, CenterInfoQuery, MemberNotInCenterVO, MemberNotInCenterQuery, InviteCenterMemberForm, CenterMemberVO, CenterMemberQuery } from '@/api/project/management/types';
 
 /**
  * 查询项目管理列表
@@ -125,3 +125,54 @@ export const removeProjectMember = (projectId: string | number, userId: string |
     params: { projectId, userId }
   });
 };
+
+/**
+ * 查询中心信息列表
+ * @param query
+ * @returns {*}
+ */
+export const queryCenterInfo = (query: CenterInfoQuery): AxiosPromise<CenterInfoVO[]> => {
+  return request({
+    url: '/project/management/queryCenterInfo',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询未加入中心的成员列表
+ * @param query
+ * @returns {*}
+ */
+export const queryMemberNotInCenter = (query: MemberNotInCenterQuery): AxiosPromise<MemberNotInCenterVO[]> => {
+  return request({
+    url: '/project/management/queryMemberNotInCenter',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 邀请成员加入中心
+ * @param data
+ */
+export const inviteCenterMember = (data: InviteCenterMemberForm) => {
+  return request({
+    url: '/project/management/queryCenterInfoInviteMember',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 查询中心成员列表
+ * @param query
+ * @returns {*}
+ */
+export const queryCenterMember = (query: CenterMemberQuery): AxiosPromise<CenterMemberVO[]> => {
+  return request({
+    url: '/project/management/queryCenterMember',
+    method: 'get',
+    params: query
+  });
+};

+ 170 - 0
src/api/project/management/types.ts

@@ -346,3 +346,173 @@ export interface InviteMemberForm {
    */
   users: InviteUserItem[];
 }
+
+/**
+ * 中心信息VO
+ */
+export interface CenterInfoVO {
+  /**
+   * 序号
+   */
+  id: string | number;
+
+  /**
+   * 中心名称
+   */
+  name: string;
+
+  /**
+   * 状态
+   */
+  status: number;
+
+  /**
+   * 创建时间
+   */
+  createTime: string;
+
+  /**
+   * 更新时间
+   */
+  updateTime: string;
+
+  /**
+   * 创建人
+   */
+  createBy: string;
+
+  /**
+   * 更新人
+   */
+  updateBy: string;
+}
+
+/**
+ * 中心信息查询参数
+ */
+export interface CenterInfoQuery extends PageQuery {
+  /**
+   * 项目ID
+   */
+  projectId: string | number;
+
+  /**
+   * 中心名称(模糊查询)
+   */
+  name?: string;
+}
+
+/**
+ * 未加入中心的成员VO
+ */
+export interface MemberNotInCenterVO {
+  /**
+   * 用户ID
+   */
+  id: string | number;
+
+  /**
+   * 姓名
+   */
+  name: string;
+
+  /**
+   * 部门
+   */
+  dept: string;
+
+  /**
+   * 手机号
+   */
+  phoneNumber: string;
+}
+
+/**
+ * 未加入中心的成员查询参数
+ */
+export interface MemberNotInCenterQuery extends PageQuery {
+  /**
+   * 项目ID
+   */
+  projectId: string | number;
+
+  /**
+   * 中心ID
+   */
+  folderId: string | number;
+
+  /**
+   * 姓名(模糊查询)
+   */
+  name?: string;
+}
+
+/**
+ * 邀请成员加入中心表单
+ */
+export interface InviteCenterMemberForm {
+  /**
+   * 用户ID列表
+   */
+  userIds: (string | number)[];
+
+  /**
+   * 中心ID
+   */
+  folderId: string | number;
+}
+
+/**
+ * 中心成员VO
+ */
+export interface CenterMemberVO {
+  /**
+   * 用户ID
+   */
+  id: string | number;
+
+  /**
+   * 姓名
+   */
+  name: string;
+
+  /**
+   * 手机号
+   */
+  phoneNumber: string;
+
+  /**
+   * 部门
+   */
+  dept: string;
+
+  /**
+   * 中心列表
+   */
+  centers: string;
+
+  /**
+   * 时间
+   */
+  time: string;
+}
+
+/**
+ * 中心成员查询参数
+ */
+export interface CenterMemberQuery extends PageQuery {
+  /**
+   * 项目ID
+   */
+  projectId: string | number;
+
+  /**
+   * 姓名(模糊查询)
+   */
+  name?: string;
+
+  /**
+   * 中心(模糊查询)
+   */
+  center?: string;
+}

+ 405 - 12
src/views/document/folder/document.vue

@@ -3,27 +3,102 @@
     <el-card shadow="never">
       <template #header>
         <div class="flex justify-between items-center">
-          <span class="text-lg font-bold">文档管理 - 测试页面</span>
+          <span class="text-lg font-bold">文档管理</span>
           <el-button type="primary" @click="handleBack">返回项目列表</el-button>
         </div>
       </template>
 
-      <el-empty description="这是文档管理测试页面">
-        <template #extra>
-          <el-descriptions :column="1" border>
-            <el-descriptions-item label="项目ID">{{ projectId }}</el-descriptions-item>
-            <el-descriptions-item label="页面状态">测试中</el-descriptions-item>
-            <el-descriptions-item label="说明">
-              这里将展示项目的文档管理功能
-            </el-descriptions-item>
-          </el-descriptions>
-        </template>
-      </el-empty>
+      <div class="content-wrapper">
+        <div class="tree-container">
+          <div class="tree-header">
+            <el-button v-hasPermi="['document:folder:add']" type="primary" icon="Plus" size="small" @click="handleAddFolder">新建文件夹</el-button>
+          </div>
+          <el-scrollbar class="tree-scrollbar">
+            <el-tree
+              v-loading="loading"
+              :data="treeData"
+              :props="treeProps"
+              node-key="id"
+              default-expand-all
+              :expand-on-click-node="false"
+            >
+              <template #default="{ node, data }">
+                <span class="custom-tree-node">
+                  <el-icon>
+                    <Folder v-if="data.type === 0" />
+                    <Location v-else-if="data.type === 1" />
+                    <OfficeBuilding v-else-if="data.type === 2" />
+                    <Document v-else />
+                  </el-icon>
+                  <span class="node-label">{{ node.label }}</span>
+                  <span class="node-actions">
+                    <el-dropdown trigger="click" @command="handleCommand($event, data)">
+                      <span class="el-dropdown-link">
+                        <el-icon><MoreFilled /></el-icon>
+                      </span>
+                      <template #dropdown>
+                        <el-dropdown-menu>
+                          <el-dropdown-item command="add" v-hasPermi="['document:folder:add']">添加子节点</el-dropdown-item>
+                          <el-dropdown-item command="edit" v-hasPermi="['document:folder:edit']">编辑</el-dropdown-item>
+                          <el-dropdown-item command="delete" v-hasPermi="['document:folder:remove']">删除</el-dropdown-item>
+                        </el-dropdown-menu>
+                      </template>
+                    </el-dropdown>
+                  </span>
+                </span>
+              </template>
+            </el-tree>
+          </el-scrollbar>
+        </div>
+        <div class="content-container">
+          <el-empty description="文档内容展示区域">
+          </el-empty>
+        </div>
+      </div>
     </el-card>
+
+    <!-- 添加文件夹对话框 -->
+    <el-dialog v-model="dialog.visible" :title="dialog.title" width="600px" append-to-body>
+      <el-form ref="folderFormRef" :model="form" :rules="rules" label-width="100px">
+        <el-form-item label="名称" prop="name">
+          <el-input v-model="form.name" placeholder="请输入名称" clearable />
+        </el-form-item>
+        <el-form-item label="类型" prop="type">
+          <el-select v-model="form.type" placeholder="请选择类型" style="width: 100%;">
+            <el-option 
+              v-for="typeOption in getAvailableTypes()" 
+              :key="typeOption.value" 
+              :label="typeOption.label" 
+              :value="typeOption.value" 
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="限制层级" prop="restrictionLevel">
+          <el-input-number v-model="form.restrictionLevel" :min="-1" style="width: 100%;" placeholder="请输入限制层级" />
+        </el-form-item>
+        <el-form-item label="备注" prop="note">
+          <el-input v-model="form.note" type="textarea" :rows="4" placeholder="请输入备注" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup lang="ts">
+import { ref, reactive, onMounted, getCurrentInstance } from 'vue';
+import { listFolder, addFolder, delFolder, getFolder, updateFolder } from '@/api/document/folder';
+import { FolderListVO, FolderForm } from '@/api/document/folder/types';
+import { Folder, Document, Edit, Delete, Plus, MoreFilled, Location, OfficeBuilding } from '@element-plus/icons-vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import type { FormInstance } from 'element-plus';
+import type { ComponentInternalInstance } from 'vue';
+
 interface Props {
   projectId?: number | string;
 }
@@ -34,9 +109,250 @@ const emit = defineEmits<{
   back: [];
 }>();
 
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+// 数据定义
+const loading = ref(false);
+const buttonLoading = ref(false);
+const treeData = ref<FolderListVO[]>([]);
+const folderFormRef = ref<FormInstance>();
+
+// 对话框
+const dialog = reactive({
+  visible: false,
+  title: '',
+  isEdit: false
+});
+
+// 当前操作的节点
+const currentNode = ref<FolderListVO | null>(null);
+
+// 表单初始数据
+const initFormData: FolderForm = {
+  id: undefined,
+  projectId: undefined,
+  parentId: undefined,
+  type: 0,
+  name: '',
+  status: 0,
+  note: '',
+  restrictionLevel: -1
+};
+
+// 表单数据
+const form = ref<FolderForm>({ ...initFormData });
+
+// 表单验证规则
+const rules = {
+  name: [
+    { required: true, message: '请输入名称', trigger: 'blur' }
+  ],
+  type: [
+    { required: true, message: '请选择类型', trigger: 'change' }
+  ]
+};
+
+// 树形组件配置
+const treeProps = {
+  children: 'children',
+  label: 'name'
+};
+
+// 获取文件夹列表
+const getList = async () => {
+  if (!props.projectId) {
+    ElMessage.warning('项目ID不存在');
+    return;
+  }
+  
+  loading.value = true;
+  try {
+    const res = await listFolder({ projectId: props.projectId } as any);
+    treeData.value = res.data || [];
+  } catch (error) {
+    ElMessage.error('获取文件夹列表失败');
+    console.error(error);
+  } finally {
+    loading.value = false;
+  }
+};
+
+// 返回项目列表
 const handleBack = () => {
   emit('back');
 };
+
+// 表单重置
+const reset = () => {
+  form.value = { ...initFormData };
+  folderFormRef.value?.resetFields();
+};
+
+// 取消按钮
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+// 新建文件夹(顶级)
+const handleAddFolder = () => {
+  reset();
+  currentNode.value = null;
+  form.value.projectId = props.projectId;
+  form.value.parentId = undefined;
+  dialog.visible = true;
+  dialog.title = '新增文件夹';
+  dialog.isEdit = false;
+};
+
+// 新增子节点
+const handleAddChild = (data: FolderListVO) => {
+  reset();
+  currentNode.value = data;
+  form.value.projectId = props.projectId;
+  form.value.parentId = data.id;
+  dialog.visible = true;
+  dialog.title = '新增子节点';
+  dialog.isEdit = false;
+};
+
+// 获取可选类型(根据父节点类型限制)
+const getAvailableTypes = () => {
+  if (!currentNode.value) {
+    // 顶级节点,可以选择所有类型
+    return [
+      { label: '文件夹', value: 0 },
+      { label: '国家', value: 1 },
+      { label: '中心', value: 2 }
+    ];
+  }
+  
+  const parentType = currentNode.value.type;
+  
+  if (parentType === 1 || parentType === 2) {
+    // 父节点是国家或中心,子节点只能是中心或文件夹
+    return [
+      { label: '文件夹', value: 0 },
+      { label: '中心', value: 2 }
+    ];
+  } else {
+    // 父节点是文件夹,子节点只能是文件夹
+    return [
+      { label: '文件夹', value: 0 }
+    ];
+  }
+};
+
+// 下拉菜单命令处理
+const handleCommand = (command: string, data: FolderListVO) => {
+  if (command === 'add') {
+    handleAddChild(data);
+  } else if (command === 'edit') {
+    handleEdit(data);
+  } else if (command === 'delete') {
+    handleDelete(data);
+  }
+};
+
+// 提交表单
+const submitForm = () => {
+  folderFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      // 如果是编辑,显示确认对话框
+      if (dialog.isEdit) {
+        try {
+          const typeLabel = form.value.type === 0 ? '文件夹' : form.value.type === 1 ? '国家' : '中心';
+          const confirmMessage = `
+            <div style="text-align: left;">
+              <p><strong>名称:</strong>${form.value.name}</p>
+              <p><strong>类型:</strong>${typeLabel}</p>
+              <p><strong>限制层级:</strong>${form.value.restrictionLevel}</p>
+              <p><strong>备注:</strong>${form.value.note || '无'}</p>
+            </div>
+          `;
+          await ElMessageBox.confirm(confirmMessage, '确认修改信息', {
+            confirmButtonText: '确认',
+            cancelButtonText: '取消',
+            type: 'warning',
+            dangerouslyUseHTMLString: true
+          });
+        } catch {
+          return; // 用户取消
+        }
+      }
+      
+      buttonLoading.value = true;
+      try {
+        if (dialog.isEdit) {
+          await updateFolder(form.value);
+          proxy?.$modal.msgSuccess('修改成功');
+        } else {
+          await addFolder(form.value);
+          proxy?.$modal.msgSuccess('新增成功');
+        }
+        dialog.visible = false;
+        await getList();
+      } catch (error) {
+        console.error(dialog.isEdit ? '修改失败' : '新增失败', error);
+      } finally {
+        buttonLoading.value = false;
+      }
+    }
+  });
+};
+
+// 编辑
+const handleEdit = async (data: FolderListVO) => {
+  reset();
+  loading.value = true;
+  try {
+    const res = await getFolder(data.id);
+    Object.assign(form.value, res.data);
+    currentNode.value = null; // 编辑时不限制类型
+    dialog.visible = true;
+    dialog.title = '修改文件夹';
+    dialog.isEdit = true;
+  } catch (error) {
+    ElMessage.error('获取文件夹信息失败');
+    console.error(error);
+  } finally {
+    loading.value = false;
+  }
+};
+
+// 删除
+const handleDelete = async (data: FolderListVO) => {
+  // 检查是否有子节点
+  if (data.children && data.children.length > 0) {
+    ElMessage.warning('该文件夹下存在子节点,无法删除');
+    return;
+  }
+  
+  try {
+    await ElMessageBox.confirm(`确认删除 "${data.name}" 吗?`, '提示', {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning'
+    });
+    
+    loading.value = true;
+    await delFolder(data.id);
+    ElMessage.success('删除成功');
+    await getList();
+  } catch (error: any) {
+    // 用户取消删除或删除失败
+    if (error !== 'cancel') {
+      console.error('删除失败:', error);
+    }
+  } finally {
+    loading.value = false;
+  }
+};
+
+// 初始化
+onMounted(() => {
+  getList();
+});
 </script>
 
 <style scoped lang="scss">
@@ -59,4 +375,81 @@ const handleBack = () => {
 .font-bold {
   font-weight: 700;
 }
+
+.content-wrapper {
+  display: flex;
+  height: calc(100vh - 250px);
+  min-height: 500px;
+}
+
+.tree-container {
+  width: 300px;
+  border-right: 1px solid #e4e7ed;
+  display: flex;
+  flex-direction: column;
+}
+
+.tree-header {
+  padding: 10px;
+  border-bottom: 1px solid #e4e7ed;
+}
+
+.tree-scrollbar {
+  flex: 1;
+  overflow: hidden;
+  
+  :deep(.el-scrollbar__view) {
+    padding: 10px;
+  }
+}
+
+.custom-tree-node {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  font-size: 14px;
+  padding-right: 8px;
+  
+  .el-icon {
+    margin-right: 8px;
+    font-size: 16px;
+  }
+  
+  .node-label {
+    flex: 1;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+  
+  .node-actions {
+    display: none;
+  }
+  
+  &:hover .node-actions {
+    display: inline-flex;
+    gap: 4px;
+  }
+}
+
+.el-dropdown-link {
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  font-size: 16px;
+  
+  &:hover {
+    color: var(--el-color-primary);
+  }
+}
+
+.content-container {
+  flex: 1;
+  padding: 20px;
+  overflow: auto;
+}
+
+.detail-content {
+  max-width: 800px;
+}
 </style>

+ 493 - 23
src/views/project/management/detail/pages/centerInfo.vue

@@ -1,22 +1,433 @@
 <template>
   <div class="center-info-page">
-    <h3>{{ t('project.management.detail.menu.centerInfo') }}</h3>
-    <el-divider />
-    <div class="info-section">
-      <p><strong>{{ t('project.management.detail.content.projectId') }}:</strong> {{ projectId }}</p>
-      <p class="info-tip">{{ t('project.management.detail.content.centerInfoTip') }}</p>
+    <div class="header-section">
+      <h3>{{ t('project.management.detail.menu.centerInfo') }}</h3>
     </div>
+    <el-divider />
+
+    <!-- 搜索表单 -->
+    <el-form :model="queryParams" :inline="true" class="search-form">
+      <el-form-item label="中心名称">
+        <el-input
+          v-model="queryParams.name"
+          placeholder="请输入中心名称"
+          clearable
+          style="width: 240px"
+          @keyup.enter="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <!-- 数据表格 -->
+    <el-card shadow="never">
+      <el-table
+        v-loading="loading"
+        border
+        :data="centerList"
+        style="width: 100%"
+      >
+        <el-table-column label="序号" align="center" prop="id" width="80" />
+        <el-table-column label="中心名称" align="center" prop="name" min-width="150" show-overflow-tooltip />
+        <el-table-column label="状态" align="center" prop="status" width="100">
+          <template #default="scope">
+            <el-tag v-if="scope.row.status === 0" type="success">正常</el-tag>
+            <el-tag v-else type="danger">禁用</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="创建人" align="center" prop="createBy" width="120" show-overflow-tooltip />
+        <el-table-column label="创建时间" align="center" prop="createTime" width="180" />
+        <el-table-column label="更新人" align="center" prop="updateBy" width="120" show-overflow-tooltip />
+        <el-table-column label="更新时间" align="center" prop="updateTime" width="180" />
+        <el-table-column label="操作" align="center" width="120" fixed="right">
+          <template #default="scope">
+            <el-button
+              v-hasPermi="['project:management:queryCenterInfoInviteMember']"
+              type="primary"
+              size="small"
+              link
+              @click="handleInviteMember(scope.row)"
+            >
+              邀请成员
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination
+        v-show="total > 0"
+        :total="total"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        @pagination="getList"
+      />
+    </el-card>
+
+    <!-- 邀请成员对话框 -->
+    <el-dialog
+      v-model="inviteDialogVisible"
+      title="邀请成员"
+      width="700px"
+      @close="handleInviteDialogClose"
+    >
+      <el-form label-width="100px">
+        <el-form-item label="模糊搜索">
+          <el-select
+            v-model="searchKeyword"
+            filterable
+            remote
+            reserve-keyword
+            placeholder="请输入成员昵称"
+            :remote-method="searchMembers"
+            :loading="searchLoading"
+            @change="handleSelectMember"
+            style="width: 100%"
+          >
+            <el-option
+              v-for="member in memberOptions"
+              :key="member.id"
+              :label="`${member.name} / ${member.dept} --- ${member.phoneNumber}`"
+              :value="member.id"
+            />
+            <template #footer>
+              <div v-if="memberTotal > memberQueryParams.pageSize" class="select-pagination">
+                <el-button
+                  text
+                  :disabled="memberQueryParams.pageNum === 1"
+                  @click="loadPrevPage"
+                >
+                  上一页
+                </el-button>
+                <span>{{ memberQueryParams.pageNum }} / {{ Math.ceil(memberTotal / memberQueryParams.pageSize) }}</span>
+                <el-button
+                  text
+                  :disabled="memberQueryParams.pageNum * memberQueryParams.pageSize >= memberTotal"
+                  @click="loadNextPage"
+                >
+                  下一页
+                </el-button>
+              </div>
+            </template>
+          </el-select>
+        </el-form-item>
+
+        <!-- 已选择的成员列表 -->
+        <el-form-item label="已选成员" v-if="selectedMembers.length > 0">
+          <div class="selected-members-list">
+            <div
+              v-for="(member, index) in selectedMembers"
+              :key="member.id"
+              class="member-card"
+            >
+              <div class="member-info">
+                {{ member.name }} / {{ member.dept }} / {{ member.phoneNumber }}
+              </div>
+              <el-button
+                type="danger"
+                size="small"
+                text
+                @click="removeSelectedMember(index)"
+                class="remove-btn"
+              >
+                移除
+              </el-button>
+            </div>
+          </div>
+        </el-form-item>
+      </el-form>
+
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="inviteDialogVisible = false">取消</el-button>
+          <el-button
+            type="primary"
+            @click="handleConfirmInvite"
+            :disabled="selectedMembers.length === 0"
+          >
+            确认
+          </el-button>
+        </span>
+      </template>
+    </el-dialog>
+
+    <!-- 确认邀请对话框 -->
+    <el-dialog
+      v-model="confirmDialogVisible"
+      title="确认邀请"
+      width="500px"
+    >
+      <div class="confirm-content">
+        <p style="margin-bottom: 15px; font-weight: bold;">
+          确认邀请以下成员进入 {{ currentCenter?.name }} 中?
+        </p>
+        <div class="confirm-member-list">
+          <div
+            v-for="member in selectedMembers"
+            :key="member.id"
+            class="confirm-member-item"
+          >
+            <span>{{ member.name }} / {{ member.dept }} / {{ member.phoneNumber }}</span>
+          </div>
+        </div>
+      </div>
+
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="confirmDialogVisible = false">取消</el-button>
+          <el-button
+            type="primary"
+            @click="confirmInvite"
+            :loading="inviteLoading"
+          >
+            确认
+          </el-button>
+        </span>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup lang="ts">
-import { inject } from 'vue';
+import { inject, ref, onMounted } from 'vue';
 import { useI18n } from 'vue-i18n';
+import { ElMessage } from 'element-plus';
+import { queryCenterInfo, queryMemberNotInCenter, inviteCenterMember } from '@/api/project/management';
+import { CenterInfoVO, CenterInfoQuery, MemberNotInCenterVO, MemberNotInCenterQuery, InviteCenterMemberForm } from '@/api/project/management/types';
 
 const { t } = useI18n();
 
 // 接收从父组件传递的项目ID
 const projectId = inject<any>('projectId');
+
+// 列表数据
+const centerList = ref<CenterInfoVO[]>([]);
+const loading = ref(true);
+const total = ref(0);
+
+// 查询参数
+const queryParams = ref<CenterInfoQuery>({
+  pageNum: 1,
+  pageSize: 10,
+  projectId: projectId?.value || 0,
+  name: ''
+});
+
+/** 查询中心信息列表 */
+const getList = async () => {
+  loading.value = true;
+  try {
+    queryParams.value.projectId = projectId?.value || 0;
+    const res = await queryCenterInfo(queryParams.value);
+    centerList.value = res.rows;
+    total.value = res.total;
+  } catch (error) {
+    console.error('Failed to fetch center info:', error);
+  } finally {
+    loading.value = false;
+  }
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryParams.value = {
+    pageNum: 1,
+    pageSize: 10,
+    projectId: projectId?.value || 0,
+    name: ''
+  };
+  getList();
+};
+
+// ========== 邀请成员相关 ==========
+const inviteDialogVisible = ref(false);
+const confirmDialogVisible = ref(false);
+const currentCenter = ref<CenterInfoVO | null>(null);
+
+// 搜索相关
+const searchKeyword = ref('');
+const searchLoading = ref(false);
+const memberOptions = ref<MemberNotInCenterVO[]>([]);
+const memberTotal = ref(0);
+
+// 成员查询参数
+const memberQueryParams = ref<MemberNotInCenterQuery>({
+  pageNum: 1,
+  pageSize: 5,
+  projectId: projectId?.value || 0,
+  folderId: 0,
+  name: ''
+});
+
+// 已选择的成员
+const selectedMembers = ref<MemberNotInCenterVO[]>([]);
+
+// 邀请加载状态
+const inviteLoading = ref(false);
+
+// 防抖定时器
+let searchTimer: NodeJS.Timeout | null = null;
+
+/** 打开邀请成员对话框 */
+const handleInviteMember = (center: CenterInfoVO) => {
+  currentCenter.value = center;
+  memberQueryParams.value.folderId = center.id;
+  inviteDialogVisible.value = true;
+};
+
+/** 实际搜索成员的函数 */
+const doSearchMembers = async () => {
+  searchLoading.value = true;
+  try {
+    const res = await queryMemberNotInCenter(memberQueryParams.value);
+    memberOptions.value = res.rows || [];
+    memberTotal.value = res.total || 0;
+  } catch (error) {
+    console.error('Failed to search members:', error);
+    ElMessage.error('搜索成员失败');
+  } finally {
+    searchLoading.value = false;
+  }
+};
+
+/** 搜索成员(带防抖) */
+const searchMembers = async (query: string) => {
+  if (!query || query.trim() === '') {
+    memberOptions.value = [];
+    memberTotal.value = 0;
+    return;
+  }
+
+  // 清除之前的定时器
+  if (searchTimer) {
+    clearTimeout(searchTimer);
+  }
+
+  // 设置防抖,停止输入后300ms才发起请求
+  searchTimer = setTimeout(async () => {
+    memberQueryParams.value.name = query;
+    memberQueryParams.value.pageNum = 1;
+    memberQueryParams.value.projectId = projectId?.value || 0;
+    await doSearchMembers();
+  }, 300);
+};
+
+/** 加载上一页 */
+const loadPrevPage = async () => {
+  if (memberQueryParams.value.pageNum > 1) {
+    memberQueryParams.value.pageNum--;
+    await doSearchMembers();
+  }
+};
+
+/** 加载下一页 */
+const loadNextPage = async () => {
+  const maxPage = Math.ceil(memberTotal.value / memberQueryParams.value.pageSize);
+  if (memberQueryParams.value.pageNum < maxPage) {
+    memberQueryParams.value.pageNum++;
+    await doSearchMembers();
+  }
+};
+
+/** 选择成员 */
+const handleSelectMember = (memberId: string | number) => {
+  const member = memberOptions.value.find(m => m.id === memberId);
+  if (member) {
+    // 检查是否已经选择过该成员
+    const isAlreadySelected = selectedMembers.value.some(m => m.id === memberId);
+    if (!isAlreadySelected) {
+      selectedMembers.value.push(member);
+    } else {
+      ElMessage.warning('该成员已选择');
+    }
+  }
+  // 清空搜索框
+  searchKeyword.value = '';
+};
+
+/** 移除已选择的成员 */
+const removeSelectedMember = (index: number) => {
+  selectedMembers.value.splice(index, 1);
+};
+
+/** 点击确认按钮 */
+const handleConfirmInvite = () => {
+  if (selectedMembers.value.length === 0) {
+    ElMessage.warning('请至少选择一位成员');
+    return;
+  }
+  // 打开确认对话框
+  confirmDialogVisible.value = true;
+};
+
+/** 确认邀请 */
+const confirmInvite = async () => {
+  if (!currentCenter.value) return;
+
+  inviteLoading.value = true;
+  try {
+    const inviteData: InviteCenterMemberForm = {
+      folderId: currentCenter.value.id,
+      userIds: selectedMembers.value.map(m => m.id)
+    };
+
+    await inviteCenterMember(inviteData);
+    ElMessage.success('邀请成功');
+
+    // 关闭所有对话框
+    confirmDialogVisible.value = false;
+    inviteDialogVisible.value = false;
+
+    // 重置表单
+    resetInviteForm();
+
+    // 刷新列表
+    await getList();
+  } catch (error) {
+    console.error('Failed to invite members:', error);
+    ElMessage.error('邀请失败');
+  } finally {
+    inviteLoading.value = false;
+  }
+};
+
+/** 关闭邀请对话框 */
+const handleInviteDialogClose = () => {
+  resetInviteForm();
+};
+
+/** 重置邀请表单 */
+const resetInviteForm = () => {
+  searchKeyword.value = '';
+  selectedMembers.value = [];
+  memberOptions.value = [];
+  memberTotal.value = 0;
+  memberQueryParams.value = {
+    pageNum: 1,
+    pageSize: 5,
+    projectId: projectId?.value || 0,
+    folderId: 0,
+    name: ''
+  };
+  currentCenter.value = null;
+  if (searchTimer) {
+    clearTimeout(searchTimer);
+    searchTimer = null;
+  }
+};
+
+// 组件挂载时加载数据
+onMounted(() => {
+  getList();
+});
 </script>
 
 <style scoped>
@@ -24,32 +435,91 @@ const projectId = inject<any>('projectId');
   height: 100%;
 }
 
-.info-section {
-  padding: 16px 0;
+.header-section {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 10px;
 }
 
-.info-section p {
-  line-height: 2;
-  font-size: 14px;
-  color: #606266;
-  margin: 8px 0;
+h3 {
+  font-size: 18px;
+  font-weight: 600;
+  color: #303133;
+  margin: 0;
+}
+
+.search-form {
+  margin-bottom: 16px;
+}
+
+/* 邀请成员对话框样式 */
+.selected-members-list {
+  width: 100%;
+  max-height: 400px;
+  overflow-y: auto;
+}
+
+.member-card {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 8px;
+  margin-bottom: 8px;
+  padding: 8px 12px;
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
+  background-color: #fff;
 }
 
-.info-section strong {
+.member-info {
+  flex: 1;
+  font-size: 13px;
   color: #303133;
-  margin-right: 8px;
+  line-height: 1.5;
 }
 
-h3 {
-  font-size: 18px;
-  font-weight: 600;
+.remove-btn {
+  flex-shrink: 0;
+}
+
+/* 确认对话框样式 */
+.confirm-content {
+  padding: 10px 0;
+}
+
+.confirm-member-list {
+  max-height: 300px;
+  overflow-y: auto;
+  border: 1px solid #e4e7ed;
+  border-radius: 4px;
+  padding: 12px;
+  background-color: #f5f7fa;
+}
+
+.confirm-member-item {
+  padding: 8px 0;
+  border-bottom: 1px solid #e4e7ed;
+  font-size: 14px;
   color: #303133;
-  margin: 0;
 }
 
-.info-tip {
-  color: #909399;
-  font-style: italic;
-  margin-top: 16px;
+.confirm-member-item:last-child {
+  border-bottom: none;
+}
+
+/* 下拉选择分页样式 */
+.select-pagination {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 8px 12px;
+  border-top: 1px solid #e4e7ed;
+  background-color: #f5f7fa;
+}
+
+.select-pagination span {
+  font-size: 12px;
+  color: #606266;
 }
 </style>

+ 116 - 24
src/views/project/management/detail/pages/centerMember.vue

@@ -1,22 +1,125 @@
 <template>
   <div class="center-member-page">
-    <h3>{{ t('project.management.detail.menu.centerMember') }}</h3>
-    <el-divider />
-    <div class="info-section">
-      <p><strong>{{ t('project.management.detail.content.projectId') }}:</strong> {{ projectId }}</p>
-      <p class="info-tip">{{ t('project.management.detail.content.centerMemberTip') }}</p>
+    <div class="header-section">
+      <h3>{{ t('project.management.detail.menu.centerMember') }}</h3>
     </div>
+    <el-divider />
+
+    <!-- 搜索表单 -->
+    <el-form :model="queryParams" :inline="true" class="search-form">
+      <el-form-item label="成员昵称">
+        <el-input
+          v-model="queryParams.name"
+          placeholder="请输入成员昵称"
+          clearable
+          style="width: 200px"
+          @keyup.enter="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="中心">
+        <el-input
+          v-model="queryParams.center"
+          placeholder="请输入中心名称"
+          clearable
+          style="width: 200px"
+          @keyup.enter="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <!-- 数据表格 -->
+    <el-card shadow="never">
+      <el-table
+        v-loading="loading"
+        border
+        :data="memberList"
+        style="width: 100%"
+      >
+        <el-table-column label="序号" align="center" prop="id" width="80" />
+        <el-table-column label="姓名" align="center" prop="name" width="120" />
+        <el-table-column label="手机号" align="center" prop="phoneNumber" width="150" />
+        <el-table-column label="部门" align="center" prop="dept" width="200" show-overflow-tooltip />
+        <el-table-column label="中心" align="center" prop="centers" min-width="200" show-overflow-tooltip />
+        <el-table-column label="时间" align="center" prop="time" width="180" />
+      </el-table>
+
+      <pagination
+        v-show="total > 0"
+        :total="total"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        @pagination="getList"
+      />
+    </el-card>
   </div>
 </template>
 
 <script setup lang="ts">
-import { inject } from 'vue';
+import { inject, ref, onMounted } from 'vue';
 import { useI18n } from 'vue-i18n';
+import { queryCenterMember } from '@/api/project/management';
+import { CenterMemberVO, CenterMemberQuery } from '@/api/project/management/types';
 
 const { t } = useI18n();
 
 // 接收从父组件传递的项目ID
 const projectId = inject<any>('projectId');
+
+// 列表数据
+const memberList = ref<CenterMemberVO[]>([]);
+const loading = ref(true);
+const total = ref(0);
+
+// 查询参数
+const queryParams = ref<CenterMemberQuery>({
+  pageNum: 1,
+  pageSize: 10,
+  projectId: projectId?.value || 0,
+  name: '',
+  center: ''
+});
+
+/** 查询中心成员列表 */
+const getList = async () => {
+  loading.value = true;
+  try {
+    queryParams.value.projectId = projectId?.value || 0;
+    const res = await queryCenterMember(queryParams.value);
+    memberList.value = res.rows;
+    total.value = res.total;
+  } catch (error) {
+    console.error('Failed to fetch center members:', error);
+  } finally {
+    loading.value = false;
+  }
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryParams.value = {
+    pageNum: 1,
+    pageSize: 10,
+    projectId: projectId?.value || 0,
+    name: '',
+    center: ''
+  };
+  getList();
+};
+
+// 组件挂载时加载数据
+onMounted(() => {
+  getList();
+});
 </script>
 
 <style scoped>
@@ -24,20 +127,11 @@ const projectId = inject<any>('projectId');
   height: 100%;
 }
 
-.info-section {
-  padding: 16px 0;
-}
-
-.info-section p {
-  line-height: 2;
-  font-size: 14px;
-  color: #606266;
-  margin: 8px 0;
-}
-
-.info-section strong {
-  color: #303133;
-  margin-right: 8px;
+.header-section {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 10px;
 }
 
 h3 {
@@ -47,9 +141,7 @@ h3 {
   margin: 0;
 }
 
-.info-tip {
-  color: #909399;
-  font-style: italic;
-  margin-top: 16px;
+.search-form {
+  margin-bottom: 16px;
 }
 </style>

+ 3 - 468
src/views/project/management/detail/pages/projectMember.vue

@@ -2,13 +2,6 @@
   <div class="project-member-page">
     <div class="header-section">
       <h3>{{ t('project.management.detail.menu.projectMember') }}</h3>
-      <el-button
-        v-hasPermi="['project:management:queryProjectMemberInviteMember']"
-        type="primary"
-        @click="openInviteDialog"
-      >
-        {{ t('project.management.member.inviteMember') }}
-      </el-button>
     </div>
     <el-divider />
 
@@ -22,22 +15,8 @@
         <el-table-column type="index" :label="t('project.management.table.id')" width="60" align="center" />
         <el-table-column :label="t('project.management.member.name')" align="center" prop="name" width="150" />
         <el-table-column :label="t('project.management.member.phoneNumber')" align="center" prop="phoneNumber" width="150" />
-        <el-table-column :label="t('project.management.member.dept')" align="center" prop="dept" width="200" />
-        <el-table-column :label="t('project.management.member.note')" align="center" prop="note" show-overflow-tooltip />
+        <el-table-column :label="t('project.management.member.dept')" align="center" prop="dept" min-width="200" show-overflow-tooltip />
         <el-table-column :label="t('project.management.member.time')" align="center" prop="time" width="180" />
-        <el-table-column :label="t('project.management.member.operation')" align="center" width="120" fixed="right">
-          <template #default="scope">
-            <el-button
-              v-hasPermi="['project:management:queryProjectMemberRemove']"
-              type="danger"
-              size="small"
-              link
-              @click="handleRemoveMember(scope.row)"
-            >
-              {{ t('project.management.member.remove') }}
-            </el-button>
-          </template>
-        </el-table-column>
       </el-table>
 
       <pagination
@@ -48,171 +27,14 @@
         @pagination="getList"
       />
     </el-card>
-
-    <!-- 邀请成员对话框 -->
-    <el-dialog
-      v-model="inviteDialogVisible"
-      :title="t('project.management.member.inviteDialogTitle')"
-      width="700px"
-      @close="handleInviteDialogClose"
-    >
-      <el-form :model="inviteForm" label-width="130px">
-        <el-form-item :label="t('project.management.member.userNickname')">
-          <el-select
-            v-model="searchKeyword"
-            filterable
-            remote
-            reserve-keyword
-            :placeholder="t('project.management.member.userNicknamePlaceholder')"
-            :remote-method="searchUsers"
-            :loading="searchLoading"
-            @change="handleSelectUser"
-            style="width: 100%"
-          >
-            <el-option
-              v-for="user in userOptions"
-              :key="user.id"
-              :label="`${user.name} - ${user.deptName} - ${user.phoneNumber}`"
-              :value="user.id"
-            />
-            <template #footer>
-              <div v-if="userTotal > userQueryParams.pageSize" class="select-pagination">
-                <el-button
-                  text
-                  :disabled="userQueryParams.pageNum === 1"
-                  @click="loadPrevPage"
-                >
-                  {{ t('project.management.member.previousPage') }}
-                </el-button>
-                <span>{{ userQueryParams.pageNum }} / {{ Math.ceil(userTotal / userQueryParams.pageSize) }}</span>
-                <el-button
-                  text
-                  :disabled="userQueryParams.pageNum * userQueryParams.pageSize >= userTotal"
-                  @click="loadNextPage"
-                >
-                  {{ t('project.management.member.nextPage') }}
-                </el-button>
-              </div>
-            </template>
-          </el-select>
-        </el-form-item>
-
-        <!-- 已选择的用户列表 -->
-        <el-form-item :label="t('project.management.member.selectedUsers')" v-if="selectedUsers.length > 0">
-          <div class="selected-users-list">
-            <div
-              v-for="(user, index) in selectedUsers"
-              :key="user.id"
-              class="user-card"
-            >
-              <div class="user-info-left">
-                <div class="info-value">{{ user.name }} / {{ user.deptName }}</div>
-                <div class="info-value">{{ user.phoneNumber }}</div>
-              </div>
-              <div class="user-note-right">
-                <el-input
-                  v-model="user.note"
-                  type="textarea"
-                  :rows="2"
-                  :placeholder="t('project.management.member.notePlaceholder')"
-                  maxlength="50"
-                  show-word-limit
-                />
-              </div>
-              <el-button
-                type="danger"
-                size="small"
-                text
-                @click="removeSelectedUser(index)"
-                class="remove-btn"
-              >
-                {{ t('project.management.member.remove') }}
-              </el-button>
-            </div>
-          </div>
-        </el-form-item>
-      </el-form>
-
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="inviteDialogVisible = false">{{ t('project.management.member.cancel') }}</el-button>
-          <el-button
-            type="primary"
-            @click="handleInvite"
-            :disabled="selectedUsers.length === 0"
-          >
-            {{ t('project.management.member.invite') }}
-          </el-button>
-        </span>
-      </template>
-    </el-dialog>
-
-    <!-- 确认邀请对话框 -->
-    <el-dialog
-      v-model="confirmDialogVisible"
-      :title="t('project.management.member.confirmInviteTitle')"
-      width="500px"
-    >
-      <div class="confirm-content">
-        <p style="margin-bottom: 15px; font-weight: bold;">{{ t('project.management.member.confirmInviteMessage') }}</p>
-        <div class="confirm-user-list">
-          <div
-            v-for="user in selectedUsers"
-            :key="user.id"
-            class="confirm-user-item"
-          >
-            <span>{{ user.name }} - {{ user.deptName }}</span>
-            <span v-if="user.note" class="user-note">({{ t('project.management.member.note') }}: {{ user.note }})</span>
-          </div>
-        </div>
-      </div>
-
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="confirmDialogVisible = false">{{ t('project.management.member.cancel') }}</el-button>
-          <el-button
-            type="primary"
-            @click="confirmInvite"
-            :loading="inviteLoading"
-          >
-            {{ t('project.management.member.confirm') }}
-          </el-button>
-        </span>
-      </template>
-    </el-dialog>
-
-    <!-- 移除成员确认对话框 -->
-    <el-dialog
-      v-model="removeDialogVisible"
-      :title="t('project.management.member.removeMemberTitle')"
-      width="400px"
-    >
-      <div style="padding: 20px 0; text-align: center; font-size: 14px;">
-        {{ t('project.management.member.confirmRemoveMessage', { dept: removingMember?.dept, name: removingMember?.name }) }}
-      </div>
-
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="removeDialogVisible = false">{{ t('project.management.member.cancel') }}</el-button>
-          <el-button
-            type="danger"
-            @click="confirmRemoveMember"
-            :loading="removeLoading"
-          >
-            {{ t('project.management.member.confirm') }}
-          </el-button>
-        </span>
-      </template>
-    </el-dialog>
   </div>
 </template>
 
 <script setup lang="ts">
 import { inject, ref, onMounted } from 'vue';
 import { useI18n } from 'vue-i18n';
-import { ElMessage } from 'element-plus';
-import { queryProjectMember, listOnNameNotJoinProject, inviteProjectMember, removeProjectMember } from '@/api/project/management';
-import { ProjectMemberVO, ProjectMemberQuery, UserVO, UserNotInProjectQuery, InviteMemberForm } from '@/api/project/management/types';
+import { queryProjectMember } from '@/api/project/management';
+import { ProjectMemberVO, ProjectMemberQuery } from '@/api/project/management/types';
 
 const { t } = useI18n();
 
@@ -246,204 +68,6 @@ const getList = async () => {
   }
 };
 
-// ========== 邀请成员相关 ==========
-// 对话框状态
-const inviteDialogVisible = ref(false);
-const confirmDialogVisible = ref(false);
-
-// 搜索相关
-const searchKeyword = ref('');
-const searchLoading = ref(false);
-const userOptions = ref<UserVO[]>([]);
-const userTotal = ref(0);
-
-// 用户查询参数
-const userQueryParams = ref<UserNotInProjectQuery>({
-  pageNum: 1,
-  pageSize: 5,
-  id: projectId?.value || 0,
-  name: ''
-});
-
-// 已选择的用户列表(包含用户信息和备注)
-interface SelectedUser extends UserVO {
-  note?: string;
-}
-const selectedUsers = ref<SelectedUser[]>([]);
-
-// 邀请表单
-const inviteForm = ref({});
-
-// 邀请加载状态
-const inviteLoading = ref(false);
-
-// ========== 移除成员相关 ==========
-// 移除对话框状态
-const removeDialogVisible = ref(false);
-const removeLoading = ref(false);
-const removingMember = ref<ProjectMemberVO | null>(null);
-
-/** 打开邀请对话框 */
-const openInviteDialog = () => {
-  inviteDialogVisible.value = true;
-};
-
-/** 搜索用户 */
-const searchUsers = async (query: string) => {
-  if (!query || query.trim() === '') {
-    userOptions.value = [];
-    userTotal.value = 0;
-    return;
-  }
-
-  searchLoading.value = true;
-  userQueryParams.value.name = query;
-  userQueryParams.value.pageNum = 1;
-  userQueryParams.value.id = projectId?.value || 0;
-
-  try {
-    const res = await listOnNameNotJoinProject(userQueryParams.value);
-    userOptions.value = res.rows || [];
-    userTotal.value = res.total || 0;
-  } catch (error) {
-    console.error('Failed to search users:', error);
-    ElMessage.error(t('project.management.member.searchUserFailed'));
-  } finally {
-    searchLoading.value = false;
-  }
-};
-
-/** 加载上一页 */
-const loadPrevPage = async () => {
-  if (userQueryParams.value.pageNum > 1) {
-    userQueryParams.value.pageNum--;
-    await searchUsers(userQueryParams.value.name || '');
-  }
-};
-
-/** 加载下一页 */
-const loadNextPage = async () => {
-  const maxPage = Math.ceil(userTotal.value / userQueryParams.value.pageSize);
-  if (userQueryParams.value.pageNum < maxPage) {
-    userQueryParams.value.pageNum++;
-    await searchUsers(userQueryParams.value.name || '');
-  }
-};
-
-/** 选择用户 */
-const handleSelectUser = (userId: string | number) => {
-  const user = userOptions.value.find(u => u.id === userId);
-  if (user) {
-    // 检查是否已经选择过该用户
-    const isAlreadySelected = selectedUsers.value.some(u => u.id === userId);
-    if (!isAlreadySelected) {
-      selectedUsers.value.push({
-        ...user,
-        note: ''
-      });
-    } else {
-      ElMessage.warning(t('project.management.member.userAlreadySelected'));
-    }
-  }
-  // 清空搜索框
-  searchKeyword.value = '';
-};
-
-/** 移除已选择的用户 */
-const removeSelectedUser = (index: number) => {
-  selectedUsers.value.splice(index, 1);
-};
-
-/** 点击邀请按钮 */
-const handleInvite = () => {
-  if (selectedUsers.value.length === 0) {
-    ElMessage.warning(t('project.management.member.selectAtLeastOneUser'));
-    return;
-  }
-  // 打开确认对话框
-  confirmDialogVisible.value = true;
-};
-
-/** 确认邀请 */
-const confirmInvite = async () => {
-  inviteLoading.value = true;
-  try {
-    const inviteData: InviteMemberForm = {
-      projectId: projectId?.value || 0,
-      users: selectedUsers.value.map(user => ({
-        id: user.id,
-        note: user.note || ''
-      }))
-    };
-
-    await inviteProjectMember(inviteData);
-    ElMessage.success(t('project.management.member.inviteSuccess'));
-
-    // 关闭所有对话框
-    confirmDialogVisible.value = false;
-    inviteDialogVisible.value = false;
-
-    // 重新初始化
-    resetInviteForm();
-
-    // 刷新成员列表
-    await getList();
-  } catch (error) {
-    console.error('Failed to invite members:', error);
-    ElMessage.error(t('project.management.member.inviteFailed'));
-  } finally {
-    inviteLoading.value = false;
-  }
-};
-
-/** 关闭邀请对话框 */
-const handleInviteDialogClose = () => {
-  resetInviteForm();
-};
-
-/** 重置邀请表单 */
-const resetInviteForm = () => {
-  searchKeyword.value = '';
-  selectedUsers.value = [];
-  userOptions.value = [];
-  userTotal.value = 0;
-  userQueryParams.value = {
-    pageNum: 1,
-    pageSize: 5,
-    id: projectId?.value || 0,
-    name: ''
-  };
-};
-
-/** 点击移除成员 */
-const handleRemoveMember = (member: ProjectMemberVO) => {
-  removingMember.value = member;
-  removeDialogVisible.value = true;
-};
-
-/** 确认移除成员 */
-const confirmRemoveMember = async () => {
-  if (!removingMember.value) return;
-
-  removeLoading.value = true;
-  try {
-    await removeProjectMember(projectId?.value || 0, removingMember.value.id);
-    ElMessage.success(t('project.management.member.removeSuccess'));
-    
-    // 关闭对话框
-    removeDialogVisible.value = false;
-    removingMember.value = null;
-    
-    // 刷新成员列表
-    await getList();
-  } catch (error) {
-    console.error('Failed to remove member:', error);
-    ElMessage.error(t('project.management.member.removeFailed'));
-  } finally {
-    removeLoading.value = false;
-  }
-};
-
 // 组件挂载时加载数据
 onMounted(() => {
   getList();
@@ -491,93 +115,4 @@ h3 {
   margin-top: 16px;
 }
 
-/* 邀请成员对话框样式 */
-.selected-users-list {
-  width: 100%;
-  max-height: 400px;
-  overflow-y: auto;
-}
-
-.user-card {
-  display: flex;
-  align-items: flex-start;
-  gap: 8px;
-  margin-bottom: 8px;
-  padding: 8px 12px;
-  border: 1px solid #dcdfe6;
-  border-radius: 4px;
-  background-color: #fff;
-}
-
-.user-info-left {
-  flex-shrink: 0;
-  width: 120px;
-  display: flex;
-  flex-direction: column;
-  gap: 4px;
-  padding-top: 6px;
-}
-
-.info-value {
-  font-size: 13px;
-  color: #303133;
-  line-height: 1.3;
-}
-
-.user-note-right {
-  flex: 3;
-  min-width: 0;
-}
-
-.remove-btn {
-  flex-shrink: 0;
-  margin-left: 4px;
-  align-self: center;
-}
-
-/* 确认对话框样式 */
-.confirm-content {
-  padding: 10px 0;
-}
-
-.confirm-user-list {
-  max-height: 300px;
-  overflow-y: auto;
-  border: 1px solid #e4e7ed;
-  border-radius: 4px;
-  padding: 12px;
-  background-color: #f5f7fa;
-}
-
-.confirm-user-item {
-  padding: 8px 0;
-  border-bottom: 1px solid #e4e7ed;
-  font-size: 14px;
-  color: #303133;
-}
-
-.confirm-user-item:last-child {
-  border-bottom: none;
-}
-
-.user-note {
-  color: #909399;
-  font-size: 12px;
-  margin-left: 8px;
-}
-
-/* 下拉选择分页样式 */
-.select-pagination {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  padding: 8px 12px;
-  border-top: 1px solid #e4e7ed;
-  background-color: #f5f7fa;
-}
-
-.select-pagination span {
-  font-size: 12px;
-  color: #606266;
-}
 </style>