Pārlūkot izejas kodu

fix: EmployeeSelector 独立选中ID + row-click切换,彻底修复跨页选中丢失

alphah 2 nedēļas atpakaļ
vecāks
revīzija
ce1b50b004

+ 67 - 146
frontend/src/views/module_payment/institution/components/EmployeeSelector.vue

@@ -2,7 +2,7 @@
   <el-drawer
     title="按员工选择"
     v-model="visibleProxy"
-    :direction="'rtl'"
+    direction="rtl"
     size="500px"
     @close="handleClose"
   >
@@ -10,216 +10,137 @@
       <div class="employee-selector__search">
         <el-form :model="searchForm" inline>
           <el-form-item label="姓名">
-            <el-input v-model="searchForm.name" placeholder="输入员工姓名" style="width: 120px" />
+            <el-input v-model="searchForm.name" placeholder="输入员工姓名" style="width:120px" />
           </el-form-item>
           <el-form-item label="手机号">
-            <el-input v-model="searchForm.phone" placeholder="输入员工手机号" style="width: 120px" />
+            <el-input v-model="searchForm.phone" placeholder="输入手机号" style="width:120px" />
           </el-form-item>
         </el-form>
         <div class="employee-selector__actions">
-          <el-button type="default" @click="handleReset">重置</el-button>
+          <el-button @click="handleReset">重置</el-button>
           <el-button type="primary" @click="handleSearch">查询</el-button>
         </div>
       </div>
 
       <div class="employee-selector__tabs">
-        <el-tabs v-model="activeTab" @tab-change="handleTabChange">
+        <el-tabs v-model="activeTab">
           <el-tab-pane label="全部" name="all" />
-          <el-tab-pane label="已选 ({{ internalSelectedIds.length }}人)" name="selected" />
+          <el-tab-pane label="已选 ({{ selectedIds.length }}人)" name="selected" />
         </el-tabs>
       </div>
 
-      <div class="employee-selector__table">
-        <el-table
-          ref="tableRef"
-          :data="displayEmployees"
-          row-key="id"
-          border
-          :max-height="300"
-          @selection-change="handleSelectionChange"
-        >
-          <el-table-column type="selection" width="50" :reserve-selection="true" />
-          <el-table-column label="员工姓名" prop="name" />
-          <el-table-column label="手机号" prop="phone" width="140" />
-        </el-table>
-      </div>
+      <el-table
+        ref="tableRef"
+        :data="displayEmployees"
+        row-key="id"
+        border
+        :max-height="300"
+        @row-click="handleRowClick"
+      >
+        <el-table-column width="50">
+          <template #default="scope">
+            <el-checkbox
+              :model-value="selectedIds.includes(scope.row.id)"
+              @click.stop="toggleRow(scope.row.id)"
+            />
+          </template>
+        </el-table-column>
+        <el-table-column label="员工姓名" prop="name" />
+        <el-table-column label="手机号" prop="phone" width="140" />
+      </el-table>
 
       <div class="employee-selector__pagination">
         <el-pagination
           v-model:current-page="pagination.page_no"
           v-model:page-size="pagination.page_size"
           :total="pagination.total"
-          :page-sizes="[10, 20, 50]"
-          layout="prev, pager, next, jumper, ->, total, sizes"
+          :page-sizes="[10,20,50]"
+          layout="prev,pager,next,jumper,->,total,sizes"
           @current-change="fetchEmployees"
         />
       </div>
 
       <div class="employee-selector__footer">
         <el-button @click="handleClose">取消</el-button>
-        <el-button type="primary" @click="handleConfirm">保存</el-button>
+        <el-button type="primary" @click="handleConfirm">保存 ({{ selectedIds.length }})</el-button>
       </div>
     </div>
   </el-drawer>
 </template>
 
 <script setup lang="ts">
-import { ref, computed, watch, nextTick } from "vue";
+import { ref, computed, watch } from "vue";
 import EmployeeAPI from "@/api/module_payment/employee";
 
-interface Employee {
-  id: string;
-  name: string;
-  phone: string;
-}
-
-interface Props {
-  visible: boolean;
-  selectedIds: string[];
-  enterpriseId: string;
-}
+interface Row { id: string; name: string; phone: string }
+interface Props { visible: boolean; selectedIds: string[]; enterpriseId: string }
 
 const props = defineProps<Props>();
+const emit = defineEmits<{ (e: "update:visible", value: boolean): void; (e: "confirm", ids: string[]): void }>();
 
-const emit = defineEmits<{
-  (e: "update:visible", value: boolean): void;
-  (e: "confirm", ids: string[]): void;
-}>();
-
-const visibleProxy = computed({
-  get: () => props.visible,
-  set: (v: boolean) => emit("update:visible", v),
-});
+const visibleProxy = computed({ get: () => props.visible, set: (v) => emit("update:visible", v) });
 
 const searchForm = ref({ name: "", phone: "" });
 const activeTab = ref("all");
 const pagination = ref({ page_no: 1, page_size: 10, total: 0 });
-
-const allEmployees = ref<Employee[]>([]);
+const allEmployees = ref<Row[]>([]);
 const tableRef = ref();
 
-// 追踪已选 ID 集合(跨页持久)
-const internalSelectedIds = ref<string[]>([]);
+/** 独立维护选中 ID(跨页持久) */
+const selectedIds = ref<string[]>([]);
 
-watch(
-  () => props.visible,
-  (v) => {
-    if (v) {
-      internalSelectedIds.value = [...props.selectedIds];
-      fetchEmployees();
-    }
+watch(() => props.visible, (v) => {
+  if (v) {
+    activeTab.value = "all";
+    selectedIds.value = [...props.selectedIds];
+    fetchEmployees();
   }
-);
+});
 
 async function fetchEmployees() {
+  const params: Record<string, unknown> = {
+    page_no: pagination.value.page_no, page_size: pagination.value.page_size,
+    enterprise_id: props.enterpriseId,
+  };
+  if (searchForm.value.name) params.employee_name = searchForm.value.name;
+  if (searchForm.value.phone) params.employee_mobile = searchForm.value.phone;
   try {
-    const params: Record<string, unknown> = {
-      page_no: pagination.value.page_no,
-      page_size: pagination.value.page_size,
-      enterprise_id: props.enterpriseId,
-    };
-    if (searchForm.value.name) params.employee_name = searchForm.value.name;
-    if (searchForm.value.phone) params.employee_mobile = searchForm.value.phone;
-
     const res = await EmployeeAPI.listEmployee(params);
     const data = res?.data?.data || res?.data;
     const list = data?.items || data?.list || [];
-    allEmployees.value = (list).map((item: any) => ({
+    allEmployees.value = list.map((item: any) => ({
       id: item.employee_id || item.id,
       name: item.employee_name || "-",
       phone: item.employee_mobile || "-",
     }));
     pagination.value.total = data?.total || 0;
-
-    // 重新勾选已选条目
-    nextTick(() => {
-      if (tableRef.value) {
-        allEmployees.value.forEach((row) => {
-          if (internalSelectedIds.value.includes(row.id)) {
-            tableRef.value.toggleRowSelection(row, true);
-          }
-        });
-      }
-    });
-  } catch (e) {
-    console.error("获取员工列表失败", e);
-  }
+  } catch (e) { console.error(e); }
 }
 
-function handleSelectionChange(rows: Employee[]) {
-  // 只更新当前页的选中状态
-  const currentIds = rows.map((r) => r.id);
-  const otherIds = internalSelectedIds.value.filter(
-    (id) => !allEmployees.value.some((e) => e.id === id)
-  );
-  internalSelectedIds.value = [...new Set([...otherIds, ...currentIds])];
+function toggleRow(id: string) {
+  const idx = selectedIds.value.indexOf(id);
+  if (idx >= 0) selectedIds.value.splice(idx, 1);
+  else selectedIds.value.push(id);
 }
 
+function handleRowClick(row: Row) { toggleRow(row.id); }
+
 const displayEmployees = computed(() => {
-  if (activeTab.value === "selected") {
-    return allEmployees.value.filter((e) => internalSelectedIds.value.includes(e.id));
-  }
+  if (activeTab.value === "selected") return allEmployees.value.filter((e) => selectedIds.value.includes(e.id));
   return allEmployees.value;
 });
 
-function handleClose() {
-  emit("update:visible", false);
-}
-
-function handleReset() {
-  searchForm.value = { name: "", phone: "" };
-  pagination.value.page_no = 1;
-  fetchEmployees();
-}
-
-function handleSearch() {
-  pagination.value.page_no = 1;
-  fetchEmployees();
-}
-
-function handleTabChange() {
-  // 不重置
-}
-
-function handleConfirm() {
-  emit("confirm", internalSelectedIds.value);
-  emit("update:visible", false);
-}
+function handleClose() { emit("update:visible", false); }
+function handleReset() { searchForm.value = { name: "", phone: "" }; pagination.value.page_no = 1; fetchEmployees(); }
+function handleSearch() { pagination.value.page_no = 1; fetchEmployees(); }
+function handleConfirm() { emit("confirm", [...selectedIds.value]); emit("update:visible", false); }
 </script>
 
 <style scoped>
-.employee-selector {
-  display: flex;
-  flex-direction: column;
-  gap: 16px;
-  height: 100%;
-}
-.employee-selector__search {
-  display: flex;
-  justify-content: space-between;
-  align-items: flex-start;
-}
-.employee-selector__actions {
-  display: flex;
-  gap: 8px;
-}
-.employee-selector__tabs {
-  background: #f5f7fa;
-  padding: 8px;
-}
-.employee-selector__table {
-  flex: 1;
-  overflow: hidden;
-}
-.employee-selector__pagination {
-  padding: 8px 0;
-  border-top: 1px solid #ebeef5;
-}
-.employee-selector__footer {
-  display: flex;
-  justify-content: flex-end;
-  gap: 8px;
-  padding-top: 16px;
-  border-top: 1px solid #ebeef5;
-}
+.employee-selector { display: flex; flex-direction: column; gap: 16px; height: 100%; }
+.employee-selector__search { display: flex; justify-content: space-between; align-items: flex-start; }
+.employee-selector__actions { display: flex; gap: 8px; }
+.employee-selector__tabs { background: #f5f7fa; padding: 8px; }
+.employee-selector__pagination { padding: 8px 0; border-top: 1px solid #ebeef5; }
+.employee-selector__footer { display: flex; justify-content: flex-end; gap: 8px; padding-top: 16px; border-top: 1px solid #ebeef5; }
 </style>