From e92cd56bb56b111c1afe9922191f9897215ff6b0 Mon Sep 17 00:00:00 2001
From: puzhibing <393733352@qq.com>
Date: 星期五, 28 八月 2026 17:55:27 +0800
Subject: [PATCH] 提交数据库和服务器部署说明文档

---
 frontend-admin/src/views/StaffGroupDetailView.vue |  103 +++++++++++++++++++++++++++++++++++++++++++--------
 1 files changed, 87 insertions(+), 16 deletions(-)

diff --git a/frontend-admin/src/views/StaffGroupDetailView.vue b/frontend-admin/src/views/StaffGroupDetailView.vue
index c745d28..f2933ed 100644
--- a/frontend-admin/src/views/StaffGroupDetailView.vue
+++ b/frontend-admin/src/views/StaffGroupDetailView.vue
@@ -15,22 +15,44 @@
     </el-card>
 
     <el-card shadow="never">
-      <el-table :data="detail.members || []" v-loading="loading" stripe border>
+      <el-form :inline="true" class="member-search" @submit.prevent>
+        <el-form-item label="员工姓名">
+          <el-input v-model="memberFilters.realName" placeholder="请输入员工姓名" clearable style="width: 160px" />
+        </el-form-item>
+        <el-form-item label="联系电话">
+          <el-input v-model="memberFilters.mobile" placeholder="请输入联系电话" clearable style="width: 160px" />
+        </el-form-item>
+        <el-form-item label="所属部门">
+          <el-tree-select
+            v-model="memberFilters.deptId"
+            :data="deptOptions"
+            :props="{ value: 'id', label: 'label', children: 'children' }"
+            check-strictly
+            :render-after-expand="false"
+            placeholder="全部"
+            clearable
+            style="width: 240px"
+          />
+        </el-form-item>
+      </el-form>
+
+      <el-table :data="filteredMembers" v-loading="loading" stripe border>
         <el-table-column label="序号" width="64" type="index" />
         <el-table-column prop="realName" label="姓名" width="120" show-overflow-tooltip />
         <el-table-column prop="staffNo" label="编号" width="140" show-overflow-tooltip />
+        <el-table-column prop="mobileMasked" label="联系电话" width="140" />
         <el-table-column prop="deptName" label="所属部门" min-width="160" show-overflow-tooltip />
         <el-table-column label="操作" width="100" fixed="right">
           <template #default="{ row }">
             <div class="table-ops">
               <el-tooltip v-if="row.id === detail.leaderMpUserId" content="不能移除负责人" placement="top">
                 <span class="btn-wrap">
-                  <el-button type="danger" link disabled>删除</el-button>
+                  <el-button type="primary" link disabled>删除</el-button>
                 </span>
               </el-tooltip>
               <el-button
                 v-else
-                type="danger"
+                type="primary"
                 link
                 :loading="removingId === row.id"
                 @click="confirmRemoveMember(row)"
@@ -44,14 +66,14 @@
     </el-card>
 
     <el-dialog v-model="picker" title="添加成员" width="960px" destroy-on-close>
-      <p class="picker-hint">仅展示未加入本分组的员工;勾选后点击「追加成员」加入分组。</p>
+      <p class="picker-hint">已在本分组的成员会默认勾选;勾选后点击「追加成员」加入分组。</p>
       <div class="picker-toolbar">
         <el-form :inline="true" @submit.prevent="pickerSearch">
           <el-form-item label="姓名">
-            <el-input v-model="pickerFilters.realName" placeholder="模糊" clearable style="width: 140px" />
+            <el-input v-model="pickerFilters.realName" placeholder="请输入姓名" clearable style="width: 140px" />
           </el-form-item>
           <el-form-item label="联系电话">
-            <el-input v-model="pickerFilters.mobile" placeholder="模糊" clearable style="width: 140px" />
+            <el-input v-model="pickerFilters.mobile" placeholder="请输入联系电话" clearable style="width: 140px" />
           </el-form-item>
           <el-form-item label="部门">
             <el-tree-select
@@ -75,12 +97,13 @@
       <el-table
         ref="pickerTableRef"
         :data="pickerRows"
+        row-key="id"
         v-loading="pickerLoading"
         stripe
         border
         @selection-change="onPickerSelectionChange"
       >
-        <el-table-column type="selection" width="48" />
+        <el-table-column type="selection" width="48" :reserve-selection="true" />
         <el-table-column prop="realName" label="姓名" width="120" show-overflow-tooltip />
         <el-table-column prop="staffNo" label="编号" width="140" show-overflow-tooltip />
         <el-table-column prop="mobileMasked" label="联系电话" width="140" />
@@ -90,11 +113,13 @@
       <el-pagination
         class="pager"
         background
-        layout="prev, pager, next"
+        layout="sizes, prev, pager, next"
         :total="pickerTotal"
         :page-size="pickerSize"
+        :page-sizes="[10, 20, 50, 100]"
         v-model:current-page="pickerPage"
         @current-change="loadPicker"
+        @size-change="onPickerSizeChange"
       />
 
       <template #footer>
@@ -109,13 +134,14 @@
 </template>
 
 <script setup lang="ts">
-import { nextTick, onMounted, reactive, ref } from "vue";
+import { computed, nextTick, onMounted, reactive, ref } from "vue";
 import { ElMessage, ElMessageBox } from "element-plus";
 import { useRoute } from "vue-router";
 import http from "../api/http";
+import { normalizeFormData } from "../utils/formHelper";
 
 type DeptTree = { id: number; label: string; children?: DeptTree[] };
-type StaffRow = { id: number; uuid: string; realName?: string; staffNo?: string; mobileMasked: string; deptId?: number; deptName?: string };
+type StaffRow = { id: number; uuid: string; realName?: string; staffNo?: string; mobileMasked?: string; deptId?: number; deptName?: string };
 
 const route = useRoute();
 
@@ -135,7 +161,7 @@
   leaderMobile: "",
   memberCount: 0,
   createTime: "",
-  members: [] as { id: number; uuid: string; realName: string; staffNo?: string; deptId?: number; deptName?: string }[],
+  members: [] as { id: number; uuid: string; realName: string; staffNo?: string; mobileMasked?: string; deptId?: number; deptName?: string }[],
 });
 
 function formatTime(s: string) {
@@ -189,9 +215,28 @@
   mobile: "",
   deptId: undefined as number | undefined,
 });
+const memberFilters = reactive({
+  realName: "",
+  mobile: "",
+  deptId: undefined as number | undefined,
+});
 
 const pickedMap = ref(new Map<number, StaffRow>());
+const restoringPickerSelection = ref(false);
 const deptOptions = ref<DeptTree[]>([]);
+
+const filteredMembers = computed(() => {
+  const keywordName = memberFilters.realName.trim().toLowerCase();
+  const keywordMobile = memberFilters.mobile.trim();
+  const deptId = memberFilters.deptId;
+  return (detail.value.members || []).filter((m) => {
+    const okName =
+      !keywordName || (m.realName || "").toLowerCase().includes(keywordName);
+    const okMobile = !keywordMobile || (m.mobileMasked || "").includes(keywordMobile);
+    const okDept = deptId == null || m.deptId === deptId;
+    return okName && okMobile && okDept;
+  });
+});
 
 function mapDeptSelect(nodes: Record<string, unknown>[]): DeptTree[] {
   return nodes.map((n) => ({
@@ -208,7 +253,19 @@
 
 function openAddMembers() {
   picker.value = true;
-  pickedMap.value = new Map();
+  const map = new Map<number, StaffRow>();
+  for (const m of detail.value.members || []) {
+    map.set(m.id, {
+      id: m.id,
+      uuid: m.uuid,
+      realName: m.realName,
+      staffNo: m.staffNo,
+      mobileMasked: "",
+      deptId: m.deptId,
+      deptName: m.deptName,
+    });
+  }
+  pickedMap.value = map;
   pickerPage.value = 1;
   loadPicker();
 }
@@ -221,7 +278,6 @@
     realName: pickerFilters.realName?.trim() || undefined,
     mobile: pickerFilters.mobile?.trim() || undefined,
     status: 1,
-    excludeStaffGroupId: id,
   };
 }
 
@@ -232,19 +288,26 @@
     pickerRows.value = (data.result || []) as StaffRow[];
     pickerTotal.value = Number(data.total) || 0;
     await nextTick();
+    restoringPickerSelection.value = true;
+    pickerTableRef.value?.clearSelection?.();
     for (const r of pickerRows.value) {
       if (pickedMap.value.has(r.id)) {
         pickerTableRef.value?.toggleRowSelection?.(r, true);
       }
     }
+    restoringPickerSelection.value = false;
   } catch (e: unknown) {
     ElMessage.error(e instanceof Error ? e.message : "加载失败");
   } finally {
+    restoringPickerSelection.value = false;
     pickerLoading.value = false;
   }
 }
 
 function onPickerSelectionChange(cur: StaffRow[]) {
+  if (restoringPickerSelection.value) {
+    return;
+  }
   const idsOnPage = new Set(pickerRows.value.map((x) => x.id));
   const selectedOnPage = new Set(cur.map((x) => x.id));
   for (const rid of idsOnPage) {
@@ -270,6 +333,12 @@
   loadPicker();
 }
 
+function onPickerSizeChange(nextSize: number) {
+  pickerSize.value = nextSize;
+  pickerPage.value = 1;
+  loadPicker();
+}
+
 function clearPickerSelection() {
   pickedMap.value = new Map();
   pickerTableRef.value?.clearSelection?.();
@@ -281,14 +350,16 @@
 }
 
 async function submitAppendMembers() {
-  const memberIds = Array.from(pickedMap.value.keys());
+  const existingIds = new Set((detail.value.members || []).map((m) => m.id));
+  const memberIds = Array.from(pickedMap.value.keys()).filter((id) => !existingIds.has(id));
   if (memberIds.length === 0) {
-    ElMessage.warning("请至少选择一名待追加的成员");
+    ElMessage.warning("没有可追加的新成员");
     return;
   }
   saving.value = true;
   try {
-    await http.post(`/sys/staff-groups/${id}/members`, { uuid, memberIds });
+    const payload = normalizeFormData({ uuid, memberIds });
+    await http.post(`/sys/staff-groups/${id}/members`, payload);
     ElMessage.success("已追加成员");
     picker.value = false;
     pickedMap.value = new Map();

--
Gitblit v1.7.1