From cb44316d3975af52a3a9af824094b844dd7df874 Mon Sep 17 00:00:00 2001
From: longz <longzhaoz@qq.com>
Date: 星期二, 26 五月 2026 11:34:49 +0800
Subject: [PATCH] fix

---
 backend/src/main/java/com/sinata/appointment/service/StaffManageApplicationService.java        |  120 +++++++--
 yuyueMiniProgram/src/pages/review/index.vue                                                    |    3 
 frontend-admin/src/views/StaffGroupsView.vue                                                   |   95 +++++--
 yuyueMiniProgram/src/pages/visitRecord/index.vue                                               |   34 ++
 frontend-admin/src/views/StaffEmployeesView.vue                                                |   24 +
 frontend-admin/src/views/DashboardView.vue                                                     |    7 
 frontend-admin/src/views/ExportView.vue                                                        |   12 
 yuyueMiniProgram/.env.development                                                              |    2 
 frontend-admin/src/views/PassAreasView.vue                                                     |   62 +++-
 frontend-admin/src/views/AppointmentListView.vue                                               |   21 +
 frontend-admin/src/views/UsersView.vue                                                         |   41 ++
 yuyueMiniProgram/src/pages/reviewDetail/index.vue                                              |   13 
 backend/src/main/java/com/sinata/appointment/service/MiniApprovalDetailApplicationService.java |    5 
 frontend-admin/src/views/DeptManageView.vue                                                    |    4 
 frontend-admin/src/views/LoginView.vue                                                         |    2 
 frontend-admin/src/views/RegionLocalUnitsView.vue                                              |   66 +++-
 frontend-admin/src/views/RolesView.vue                                                         |   36 ++
 backend/src/main/java/com/sinata/appointment/config/LocalFileUploadProperties.java             |    4 
 frontend-admin/src/views/VisitorInfoView.vue                                                   |   24 +
 yuyueMiniProgram/.env.production                                                               |    2 
 frontend-admin/src/views/ApprovalTodoView.vue                                                  |   12 
 frontend-admin/src/views/StaffGroupDetailView.vue                                              |   10 
 frontend-admin/src/views/VisitReasonsSettingsView.vue                                          |   84 ++++--
 23 files changed, 513 insertions(+), 170 deletions(-)

diff --git a/backend/src/main/java/com/sinata/appointment/config/LocalFileUploadProperties.java b/backend/src/main/java/com/sinata/appointment/config/LocalFileUploadProperties.java
index bc82bc5..9114f13 100644
--- a/backend/src/main/java/com/sinata/appointment/config/LocalFileUploadProperties.java
+++ b/backend/src/main/java/com/sinata/appointment/config/LocalFileUploadProperties.java
@@ -6,10 +6,10 @@
 @Component
 public class LocalFileUploadProperties {
 
-    @Value("${file-upload-path:D:/tmp/}")
+    @Value("${file-upload-path}")
     private String fileUploadPath;
 
-    @Value("${picture-server-address:http://192.168.110.149:81/}")
+    @Value("${picture-server-address}")
     private String pictureServerAddress;
 
     public String getFileUploadPath() {
diff --git a/backend/src/main/java/com/sinata/appointment/service/MiniApprovalDetailApplicationService.java b/backend/src/main/java/com/sinata/appointment/service/MiniApprovalDetailApplicationService.java
index d5706ac..ac8b3ad 100644
--- a/backend/src/main/java/com/sinata/appointment/service/MiniApprovalDetailApplicationService.java
+++ b/backend/src/main/java/com/sinata/appointment/service/MiniApprovalDetailApplicationService.java
@@ -5,6 +5,7 @@
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.sinata.appointment.config.MiniStpUtil;
 import com.sinata.appointment.constants.AppointmentBizConstants;
+import com.sinata.appointment.constants.AppointmentOrderStatus;
 import com.sinata.appointment.constants.ApprovalNodeCode;
 import com.sinata.appointment.constants.ApprovalTaskStatus;
 import com.sinata.appointment.constants.BizErrorCode;
@@ -148,6 +149,10 @@
             vo.setApprovalStatusText("已查看");
             return;
         }
+        if (AppointmentOrderStatus.CANCELLED.equals(order.getStatus())) {
+            vo.setApprovalStatusText("已取消");
+            return;
+        }
         if (ApprovalTaskStatus.APPROVED.equals(currentUserTask.getTaskStatus())) {
             vo.setApprovalStatusText("已同意");
             return;
diff --git a/backend/src/main/java/com/sinata/appointment/service/StaffManageApplicationService.java b/backend/src/main/java/com/sinata/appointment/service/StaffManageApplicationService.java
index 0cf356b..d32cee1 100644
--- a/backend/src/main/java/com/sinata/appointment/service/StaffManageApplicationService.java
+++ b/backend/src/main/java/com/sinata/appointment/service/StaffManageApplicationService.java
@@ -45,6 +45,7 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -476,11 +477,13 @@
                                 .eq(SysDept::getStatus, 1)
                                 .orderByAsc(SysDept::getSortOrder)
                                 .orderByAsc(SysDept::getId));
-        List<String> deptNames =
+        Map<Long, String> deptPathById = buildDeptPathById(depts);
+        List<String> deptPaths =
                 depts.stream()
-                        .map(SysDept::getDeptName)
+                        .map(SysDept::getId)
+                        .map(deptPathById::get)
                         .filter(StringUtils::hasText)
-                        .map(String::trim)
+                        .distinct()
                         .toList();
         List<String> groupNames = loadActiveGroupNames();
 
@@ -489,8 +492,8 @@
         ex.setRealName("张三");
         ex.setStaffNo("EMP001");
         ex.setMobile("13800138000");
-        if (!deptNames.isEmpty()) {
-            ex.setDept(deptNames.get(0));
+        if (!deptPaths.isEmpty()) {
+            ex.setDept(deptPaths.get(0));
         }
         ex.setPosition("员工");
         if (!groupNames.isEmpty()) {
@@ -510,12 +513,12 @@
                         EasyExcel.writerSheet("员工导入")
                                 .head(StaffImportExcelRow.class)
                                 .registerWriteHandler(
-                                        new StaffTemplateDropdownHandler(deptNames, groupNames))
+                                        new StaffTemplateDropdownHandler(deptPaths, groupNames))
                                 .build();
                 writer.write(demo, dataSheet);
 
                 var dictSheet = EasyExcel.writerSheet("字典").needHead(false).build();
-                writer.write(buildDictRows(deptNames, groupNames), dictSheet);
+                writer.write(buildDictRows(deptPaths, groupNames), dictSheet);
             } finally {
                 writer.finish();
             }
@@ -531,15 +534,22 @@
             vo.getErrors().add("文件为空");
             return vo;
         }
-        Map<String, Long> deptNameToId = new HashMap<>();
+        Map<String, Long> deptPathToId = new HashMap<>();
+        Map<String, List<Long>> deptNameToIds = new HashMap<>();
         List<SysDept> depts =
                 sysDeptMapper.selectList(
                         new LambdaQueryWrapper<SysDept>()
                                 .eq(SysDept::getDelFlag, 0)
                                 .eq(SysDept::getStatus, 1));
+        Map<Long, String> deptPathById = buildDeptPathById(depts);
         for (SysDept d : depts) {
+            String path = deptPathById.get(d.getId());
+            if (StringUtils.hasText(path)) {
+                deptPathToId.put(path.trim(), d.getId());
+            }
             if (StringUtils.hasText(d.getDeptName())) {
-                deptNameToId.put(d.getDeptName().trim(), d.getId());
+                String deptName = d.getDeptName().trim();
+                deptNameToIds.computeIfAbsent(deptName, k -> new ArrayList<>()).add(d.getId());
             }
         }
 
@@ -551,7 +561,7 @@
                         public void invoke(StaffImportExcelRow row, AnalysisContext context) {
                             rowIndex++;
                             try {
-                                handleImportRow(rowIndex, row, deptNameToId);
+                                handleImportRow(rowIndex, row, deptPathToId, deptNameToIds);
                                 vo.setSuccess(vo.getSuccess() + 1);
                             } catch (StopRowException ignored) {
                             } catch (BizException e) {
@@ -574,7 +584,11 @@
         return vo;
     }
 
-    private void handleImportRow(int lineNo, StaffImportExcelRow row, Map<String, Long> deptNameToId) {
+    private void handleImportRow(
+            int lineNo,
+            StaffImportExcelRow row,
+            Map<String, Long> deptPathToId,
+            Map<String, List<Long>> deptNameToIds) {
         String name = row.getRealName() == null ? "" : row.getRealName().trim();
         String staffNo = row.getStaffNo() == null ? "" : row.getStaffNo().trim();
         String mobile = row.getMobile() == null ? "" : row.getMobile().trim();
@@ -595,7 +609,7 @@
             throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "员工姓名、联系方式、所属部门不能为空");
         }
 
-        Long deptId = parseDeptId(dept, deptNameToId);
+        Long deptId = parseDeptId(dept, deptPathToId, deptNameToIds);
         boolean leader = parseDeptLeader(position);
 
         StaffCreateRequest req = new StaffCreateRequest();
@@ -614,11 +628,11 @@
 
     private static final class StopRowException extends RuntimeException {}
 
-    private static List<List<String>> buildDictRows(List<String> deptNames, List<String> groupNames) {
-        int max = Math.max(Math.max(deptNames.size(), groupNames.size()), 2);
+    private static List<List<String>> buildDictRows(List<String> deptPaths, List<String> groupNames) {
+        int max = Math.max(Math.max(deptPaths.size(), groupNames.size()), 2);
         List<List<String>> rows = new ArrayList<>();
         for (int i = 0; i < max; i++) {
-            String dept = i < deptNames.size() ? deptNames.get(i) : "";
+            String dept = i < deptPaths.size() ? deptPaths.get(i) : "";
             String pos = i == 0 ? "部门负责人" : (i == 1 ? "员工" : "");
             String grp = i < groupNames.size() ? groupNames.get(i) : "";
             rows.add(java.util.Arrays.asList(dept, pos, grp));
@@ -638,11 +652,11 @@
     }
 
     private static final class StaffTemplateDropdownHandler implements SheetWriteHandler {
-        private final List<String> deptNames;
+        private final List<String> deptPaths;
         private final List<String> groupNames;
 
-        private StaffTemplateDropdownHandler(List<String> deptNames, List<String> groupNames) {
-            this.deptNames = deptNames != null ? deptNames : List.of();
+        private StaffTemplateDropdownHandler(List<String> deptPaths, List<String> groupNames) {
+            this.deptPaths = deptPaths != null ? deptPaths : List.of();
             this.groupNames = groupNames != null ? groupNames : List.of();
         }
 
@@ -651,10 +665,10 @@
             Sheet sheet = writeSheetHolder.getSheet();
             Workbook wb = writeWorkbookHolder.getWorkbook();
 
-            if (!deptNames.isEmpty()) {
+            if (!deptPaths.isEmpty()) {
                 Name deptRange = wb.createName();
                 deptRange.setNameName("dept_list");
-                deptRange.setRefersToFormula("字典!$A$1:$A$" + deptNames.size());
+                deptRange.setRefersToFormula("字典!$A$1:$A$" + deptPaths.size());
             }
             Name posRange = wb.createName();
             posRange.setNameName("pos_list");
@@ -669,7 +683,7 @@
             int startRow = 1;
             int endRow = 500;
 
-            if (!deptNames.isEmpty()) {
+            if (!deptPaths.isEmpty()) {
                 DataValidationConstraint deptConstraint = helper.createFormulaListConstraint("dept_list");
                 CellRangeAddressList deptAddr = new CellRangeAddressList(startRow, endRow, 3, 3);
                 DataValidation dvDept = helper.createValidation(deptConstraint, deptAddr);
@@ -693,7 +707,8 @@
         }
     }
 
-    private static Long parseDeptId(String dept, Map<String, Long> deptNameToId) {
+    private static Long parseDeptId(
+            String dept, Map<String, Long> deptPathToId, Map<String, List<Long>> deptNameToIds) {
         if (!StringUtils.hasText(dept)) {
             return null;
         }
@@ -702,11 +717,64 @@
             return Long.parseLong(t);
         } catch (Exception ignored) {
         }
-        Long id = deptNameToId.get(t);
-        if (id == null) {
-            throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "所属部门不存在: " + t);
+        Long pathMatchedId = deptPathToId.get(t);
+        if (pathMatchedId != null) {
+            return pathMatchedId;
         }
-        return id;
+        List<Long> nameMatchedIds = deptNameToIds.getOrDefault(t, List.of());
+        if (nameMatchedIds.size() == 1) {
+            return nameMatchedIds.get(0);
+        }
+        if (nameMatchedIds.size() > 1) {
+            throw new BizException(
+                    BizErrorCode.AUTH_INVALID_CREDENTIALS,
+                    "所属部门名称重复,请使用全路径(示例:A公司/财务部): " + t);
+        }
+        throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "所属部门不存在: " + t);
+    }
+
+    private static Map<Long, String> buildDeptPathById(List<SysDept> depts) {
+        if (depts == null || depts.isEmpty()) {
+            return Map.of();
+        }
+        Map<Long, SysDept> byId = new LinkedHashMap<>();
+        for (SysDept dept : depts) {
+            byId.put(dept.getId(), dept);
+        }
+        Map<Long, String> cache = new HashMap<>();
+        for (SysDept dept : depts) {
+            resolveDeptPath(dept.getId(), byId, cache, new HashSet<>());
+        }
+        return cache;
+    }
+
+    private static String resolveDeptPath(
+            Long deptId,
+            Map<Long, SysDept> byId,
+            Map<Long, String> cache,
+            Set<Long> visiting) {
+        if (deptId == null || deptId <= 0) {
+            return "";
+        }
+        String cached = cache.get(deptId);
+        if (cached != null) {
+            return cached;
+        }
+        SysDept current = byId.get(deptId);
+        if (current == null) {
+            return "";
+        }
+        String self = StringUtils.hasText(current.getDeptName()) ? current.getDeptName().trim() : "";
+        Long parentId = current.getParentId();
+        if (parentId == null || parentId <= 0 || parentId.equals(deptId) || !visiting.add(deptId)) {
+            cache.put(deptId, self);
+            return self;
+        }
+        String parentPath = resolveDeptPath(parentId, byId, cache, visiting);
+        visiting.remove(deptId);
+        String path = StringUtils.hasText(parentPath) ? (parentPath + "/" + self) : self;
+        cache.put(deptId, path);
+        return path;
     }
 
     private static boolean parseDeptLeader(String position) {
diff --git a/frontend-admin/src/views/AppointmentListView.vue b/frontend-admin/src/views/AppointmentListView.vue
index 6fe53d6..09b7439 100644
--- a/frontend-admin/src/views/AppointmentListView.vue
+++ b/frontend-admin/src/views/AppointmentListView.vue
@@ -79,7 +79,8 @@
           <el-button v-permission="'appointments:export'" type="primary" plain :loading="exporting" @click="exportExcel">导出</el-button>
       </div>
 
-      <el-table :data="rows" v-loading="loading" stripe border :max-height="tableMaxHeight">
+      <div class="table-wrap">
+        <el-table :data="rows" v-loading="loading" stripe border height="100%">
         <el-table-column label="序号" width="68" type="index" :index="indexMethod" />
         <el-table-column prop="visitTypeText" label="来访类型" width="110" />
         <el-table-column prop="visitedUserName" label="被访人" width="110" show-overflow-tooltip />
@@ -109,7 +110,8 @@
               <el-button v-permission="'appointments:detail'" type="primary" link @click="openDetail(row)">详情</el-button>
           </template>
         </el-table-column>
-      </el-table>
+        </el-table>
+      </div>
 
       <div class="pager-wrap">
         <div class="pager-total">共 {{ total }} 条</div>
@@ -335,7 +337,6 @@
 const size = ref(10);
 const total = ref(0);
 const rows = ref<AppointmentRow[]>([]);
-const tableMaxHeight = computed(() => (rows.value.length > 10 ? 560 : undefined));
 const visitDateRange = ref<string[]>([]);
 
 const deptOptions = ref<DeptOption[]>([]);
@@ -708,6 +709,8 @@
   display: flex;
   flex-direction: column;
   gap: 12px;
+  height: calc(100vh - 170px);
+  min-height: calc(100vh - 170px);
 }
 
 .search-card,
@@ -766,12 +769,23 @@
 
 .table-card {
   padding: 14px 16px 12px;
+  display: flex;
+  flex-direction: column;
+  flex: 1;
+  min-height: 0;
+  overflow: hidden;
 }
 
 .table-toolbar {
   display: flex;
   justify-content: flex-start;
   margin-bottom: 12px;
+  flex: none;
+}
+
+.table-wrap {
+  flex: 1;
+  min-height: 0;
 }
 
 .pager-wrap {
@@ -779,6 +793,7 @@
   justify-content: space-between;
   align-items: center;
   margin-top: 14px;
+  flex: none;
 }
 
 .pager-total {
diff --git a/frontend-admin/src/views/ApprovalTodoView.vue b/frontend-admin/src/views/ApprovalTodoView.vue
index 0843696..c6c7ad7 100644
--- a/frontend-admin/src/views/ApprovalTodoView.vue
+++ b/frontend-admin/src/views/ApprovalTodoView.vue
@@ -24,11 +24,13 @@
       <div class="pager">
       <el-pagination
         background
-        layout="total, prev, pager, next"
+        layout="total, sizes, prev, pager, next"
         :current-page="page"
         :page-size="size"
+        :page-sizes="[10, 20, 50, 100]"
         :total="total"
         @current-change="handlePageChange"
+        @size-change="handleSizeChange"
       />
       </div>
     </div>
@@ -52,7 +54,7 @@
 const loading = ref(false);
 const rows = ref<OpLogRow[]>([]);
 const page = ref(1);
-const size = ref(20);
+const size = ref(10);
 const total = ref(0);
 
 function formatTime(v: unknown) {
@@ -96,6 +98,12 @@
   load();
 }
 
+function handleSizeChange(nextSize: number) {
+  size.value = nextSize;
+  page.value = 1;
+  load();
+}
+
 onMounted(load);
 </script>
 
diff --git a/frontend-admin/src/views/DashboardView.vue b/frontend-admin/src/views/DashboardView.vue
index 8b2a962..1063476 100644
--- a/frontend-admin/src/views/DashboardView.vue
+++ b/frontend-admin/src/views/DashboardView.vue
@@ -257,6 +257,11 @@
 
 function getLineChartOption(): EChartsOption {
   const rows = cappedTrend.value;
+  const today = formatDate(new Date());
+  const inProgressSeries = rows.map((item) => {
+    const value = Number(item.inProgressCount) || 0;
+    return item.statDate > today ? 0 : value;
+  });
   return {
     color: ["#45c36c", "#5269FF", "#ff6a4d", "#ffc24b"],
     tooltip: {
@@ -312,7 +317,7 @@
         smooth: true,
         showSymbol: true,
         symbolSize: 6,
-        data: rows.map(item => Number(item.inProgressCount) || 0),
+        data: inProgressSeries,
         label: { show: true, position: "top", fontSize: 11, fontWeight: 600, color: "#5269FF" },
       },
       {
diff --git a/frontend-admin/src/views/DeptManageView.vue b/frontend-admin/src/views/DeptManageView.vue
index 6afb4e0..2ea82db 100644
--- a/frontend-admin/src/views/DeptManageView.vue
+++ b/frontend-admin/src/views/DeptManageView.vue
@@ -343,4 +343,8 @@
 .tree-sel {
   width: 100%;
 }
+
+:deep(.table-ops .el-button) {
+  font-size: 14px;
+}
 </style>
diff --git a/frontend-admin/src/views/ExportView.vue b/frontend-admin/src/views/ExportView.vue
index cbf1789..e9b8ff1 100644
--- a/frontend-admin/src/views/ExportView.vue
+++ b/frontend-admin/src/views/ExportView.vue
@@ -32,11 +32,13 @@
     <div class="pager">
       <el-pagination
         background
-        layout="total, prev, pager, next"
+        layout="total, sizes, prev, pager, next"
         :current-page="page"
         :page-size="size"
+        :page-sizes="[10, 20, 50, 100]"
         :total="total"
         @current-change="handlePageChange"
+        @size-change="handleSizeChange"
       />
     </div>
     </div>
@@ -63,7 +65,7 @@
 const loading = ref(false);
 const rows = ref<OpLogRow[]>([]);
 const page = ref(1);
-const size = ref(20);
+const size = ref(10);
 const total = ref(0);
 
 function formatTime(v: unknown) {
@@ -107,6 +109,12 @@
   load();
 }
 
+function handleSizeChange(nextSize: number) {
+  size.value = nextSize;
+  page.value = 1;
+  load();
+}
+
 onMounted(load);
 </script>
 
diff --git a/frontend-admin/src/views/LoginView.vue b/frontend-admin/src/views/LoginView.vue
index dc8c9e2..594c7ee 100644
--- a/frontend-admin/src/views/LoginView.vue
+++ b/frontend-admin/src/views/LoginView.vue
@@ -4,7 +4,7 @@
     <div class="bg-glow bg-glow-right"></div>
 
     <el-card class="card" shadow="never">
-      <h2 class="title">运维运营管理平台</h2>
+      <h2 class="title">宇通行管理平台</h2>
       <p class="subtitle">安全、高效地处理预约、审批与接待流程</p>
 
       <el-form :model="form" label-position="top" class="login-form" @submit.prevent="onSubmit">
diff --git a/frontend-admin/src/views/PassAreasView.vue b/frontend-admin/src/views/PassAreasView.vue
index 2924323..1dac9f7 100644
--- a/frontend-admin/src/views/PassAreasView.vue
+++ b/frontend-admin/src/views/PassAreasView.vue
@@ -14,36 +14,40 @@
         <el-button @click="resetSearch">重置</el-button>
       </el-form-item>
       <el-form-item class="push-right">
-        <el-button type="success" @click="openCreate">添加区域</el-button>
+        <el-button type="primary" @click="openCreate">添加区域</el-button>
       </el-form-item>
     </el-form>
 
-    <el-table :data="rows" v-loading="loading" stripe border>
-      <el-table-column label="序号" width="64" type="index" :index="indexMethod" />
-      <el-table-column prop="areaName" label="区域名称" min-width="140" show-overflow-tooltip />
-      <el-table-column prop="areaDesc" label="区域说明" min-width="180" show-overflow-tooltip />
-      <el-table-column label="创建时间" width="170">
-        <template #default="{ row }">{{ formatTime(row.createTime) }}</template>
-      </el-table-column>
-      <el-table-column label="操作" width="220" fixed="right">
-        <template #default="{ row }">
-          <div class="table-ops">
-            <el-button type="primary" link @click="openEdit(row)">编辑</el-button>
-            <el-button v-if="!isSubMode" type="primary" link @click="goSub(row)">子区域管理</el-button>
-            <el-button type="primary" link @click="confirmDelete(row)">删除</el-button>
-          </div>
-        </template>
-      </el-table-column>
-    </el-table>
+    <div class="table-wrap">
+      <el-table :data="rows" v-loading="loading" stripe border height="100%">
+        <el-table-column label="序号" width="64" type="index" :index="indexMethod" />
+        <el-table-column prop="areaName" label="区域名称" min-width="140" show-overflow-tooltip />
+        <el-table-column prop="areaDesc" label="区域说明" min-width="180" show-overflow-tooltip />
+        <el-table-column label="创建时间" width="170">
+          <template #default="{ row }">{{ formatTime(row.createTime) }}</template>
+        </el-table-column>
+        <el-table-column label="操作" width="220" fixed="right">
+          <template #default="{ row }">
+            <div class="table-ops">
+              <el-button type="primary" link @click="openEdit(row)">编辑</el-button>
+              <el-button v-if="!isSubMode" type="primary" link @click="goSub(row)">子区域管理</el-button>
+              <el-button type="primary" link @click="confirmDelete(row)">删除</el-button>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
 
     <el-pagination
-      class="pager"
+      class="main-pager"
       background
-      layout="prev, pager, next"
+      layout="sizes, prev, pager, next"
       :total="total"
       :page-size="size"
+      :page-sizes="[10, 20, 50, 100]"
       v-model:current-page="page"
       @current-change="load"
+      @size-change="onSizeChange"
     />
 
     <el-dialog v-model="dlg" :title="dlgTitle" width="520px" destroy-on-close @closed="onDlgClosed">
@@ -187,6 +191,12 @@
 }
 
 function doSearch() {
+  page.value = 1;
+  load();
+}
+
+function onSizeChange(nextSize: number) {
+  size.value = nextSize;
   page.value = 1;
   load();
 }
@@ -337,6 +347,10 @@
 <style scoped>
 .page {
   padding: 0;
+  display: flex;
+  flex-direction: column;
+  height: calc(100vh - 190px);
+  min-height: 420px;
 }
 .sub-hdr {
   display: flex;
@@ -351,13 +365,19 @@
 .search-form {
   margin-bottom: 12px;
   flex-wrap: wrap;
+  flex: none;
 }
 .push-right {
   margin-left: auto;
 }
-.pager {
+.table-wrap {
+  flex: 1;
+  min-height: 0;
+}
+.main-pager {
   margin-top: 12px;
   justify-content: flex-end;
+  flex: none;
 }
 .fullw {
   width: 100%;
diff --git a/frontend-admin/src/views/RegionLocalUnitsView.vue b/frontend-admin/src/views/RegionLocalUnitsView.vue
index 22306e0..4a37298 100644
--- a/frontend-admin/src/views/RegionLocalUnitsView.vue
+++ b/frontend-admin/src/views/RegionLocalUnitsView.vue
@@ -9,33 +9,37 @@
         <el-button @click="resetSearch">重置</el-button>
       </el-form-item>
       <el-form-item class="push-right">
-        <el-button type="success" @click="openCreate">添加属地单位</el-button>
+        <el-button type="primary" @click="openCreate">添加属地单位</el-button>
       </el-form-item>
     </el-form>
 
-    <el-table :data="rows" v-loading="loading" stripe border>
-      <el-table-column label="序号" width="64" type="index" :index="indexMethod" />
-      <el-table-column prop="unitName" label="单位名称" min-width="140" show-overflow-tooltip />
-      <el-table-column prop="approverSummary" label="审批人员" min-width="160" show-overflow-tooltip />
-      <el-table-column prop="remark" label="备注" min-width="120" show-overflow-tooltip />
-      <el-table-column label="操作" width="140" fixed="right">
-        <template #default="{ row }">
-          <div class="table-ops">
-            <el-button type="primary" link @click="openEdit(row)">编辑</el-button>
-            <el-button type="primary" link @click="confirmDelete(row)">删除</el-button>
-          </div>
-        </template>
-      </el-table-column>
-    </el-table>
+    <div class="table-wrap">
+      <el-table :data="rows" v-loading="loading" stripe border height="100%">
+        <el-table-column label="序号" width="64" type="index" :index="indexMethod" />
+        <el-table-column prop="unitName" label="单位名称" min-width="140" show-overflow-tooltip />
+        <el-table-column prop="approverSummary" label="审批人员" min-width="160" show-overflow-tooltip />
+        <el-table-column prop="remark" label="备注" min-width="120" show-overflow-tooltip />
+        <el-table-column label="操作" width="140" fixed="right">
+          <template #default="{ row }">
+            <div class="table-ops">
+              <el-button type="primary" link @click="openEdit(row)">编辑</el-button>
+              <el-button type="primary" link @click="confirmDelete(row)">删除</el-button>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
 
     <el-pagination
-      class="pager"
+      class="main-pager"
       background
-      layout="prev, pager, next"
+      layout="sizes, prev, pager, next"
       :total="total"
       :page-size="size"
+      :page-sizes="[10, 20, 50, 100]"
       v-model:current-page="page"
       @current-change="load"
+      @size-change="onMainSizeChange"
     />
 
     <el-dialog v-model="dlg" :title="dlgTitle" width="720px" destroy-on-close>
@@ -156,11 +160,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>
@@ -244,6 +250,12 @@
 
 function resetSearch() {
   filters.unitName = "";
+  page.value = 1;
+  load();
+}
+
+function onMainSizeChange(nextSize: number) {
+  size.value = nextSize;
   page.value = 1;
   load();
 }
@@ -476,6 +488,12 @@
   loadPicker();
 }
 
+function onPickerSizeChange(nextSize: number) {
+  pickerSize.value = nextSize;
+  pickerPage.value = 1;
+  loadPicker();
+}
+
 function pickerParams() {
   return {
     page: pickerPage.value,
@@ -547,19 +565,29 @@
 <style scoped>
 .page {
   padding: 0;
+  display: flex;
+  flex-direction: column;
+  height: calc(100vh - 190px);
+  min-height: 420px;
 }
 .search-form {
   margin-bottom: 12px;
   display: flex;
   flex-wrap: wrap;
   align-items: center;
+  flex: none;
 }
 .push-right {
   margin-left: auto;
 }
-.pager {
+.table-wrap {
+  flex: 1;
+  min-height: 0;
+}
+.main-pager {
   margin-top: 12px;
   justify-content: flex-end;
+  flex: none;
 }
 .member-actions {
   display: flex;
diff --git a/frontend-admin/src/views/RolesView.vue b/frontend-admin/src/views/RolesView.vue
index bfcb9ba..22d6c7a 100644
--- a/frontend-admin/src/views/RolesView.vue
+++ b/frontend-admin/src/views/RolesView.vue
@@ -1,5 +1,5 @@
 <template>
-  <div>
+  <div class="page">
     <el-form :inline="true" class="search-form" @submit.prevent="doSearch">
       <el-form-item label="角色名称">
         <el-input v-model="filters.roleName" placeholder="请输入角色名称" clearable style="width: 160px" />
@@ -9,11 +9,12 @@
         <el-button @click="resetSearch">重置</el-button>
       </el-form-item>
       <el-form-item class="push-right">
-        <el-button v-permission="'sys:roles:create'" type="success" @click="goCreate">新增</el-button>
+        <el-button v-permission="'sys:roles:create'" type="primary" @click="goCreate">新增</el-button>
       </el-form-item>
     </el-form>
 
-    <el-table :data="rows" v-loading="loading" stripe>
+    <div class="table-wrap">
+      <el-table :data="rows" v-loading="loading" stripe height="100%">
       <el-table-column label="序号" width="64" type="index" :index="indexMethod" />
       <el-table-column prop="roleName" label="角色名称" min-width="140" show-overflow-tooltip />
       <el-table-column label="关联账号数" width="120" align="center">
@@ -42,16 +43,19 @@
           </div>
         </template>
       </el-table-column>
-    </el-table>
+      </el-table>
+    </div>
 
     <el-pagination
-      class="pager"
+      class="main-pager"
       background
-      layout="prev, pager, next"
+      layout="sizes, prev, pager, next"
       :total="total"
       :page-size="size"
+      :page-sizes="[10, 20, 50, 100]"
       v-model:current-page="page"
       @current-change="load"
+      @size-change="onSizeChange"
     />
   </div>
 </template>
@@ -124,6 +128,12 @@
   load();
 }
 
+function onSizeChange(nextSize: number) {
+  size.value = nextSize;
+  page.value = 1;
+  load();
+}
+
 function goCreate() {
   router.push({ name: "role-create" });
 }
@@ -165,12 +175,24 @@
   flex-wrap: wrap;
   align-items: center;
   gap: 4px 0;
+  flex: none;
 }
 .push-right {
   margin-left: auto;
 }
-.pager {
+.page {
+  display: flex;
+  flex-direction: column;
+  height: calc(100vh - 190px);
+  min-height: 420px;
+}
+.table-wrap {
+  flex: 1;
+  min-height: 0;
+}
+.main-pager {
   margin-top: 12px;
   justify-content: flex-end;
+  flex: none;
 }
 </style>
diff --git a/frontend-admin/src/views/StaffEmployeesView.vue b/frontend-admin/src/views/StaffEmployeesView.vue
index d28c859..a25c362 100644
--- a/frontend-admin/src/views/StaffEmployeesView.vue
+++ b/frontend-admin/src/views/StaffEmployeesView.vue
@@ -103,7 +103,8 @@
           <el-button v-permission="'sys:staff:create'" type="primary" @click="openCreate">添加</el-button>
         </div>
 
-        <el-table :data="rows" v-loading="loading" stripe border class="staff-table">
+        <div class="staff-table-wrap">
+          <el-table :data="rows" v-loading="loading" stripe border class="staff-table" height="100%">
           <el-table-column label="姓名" min-width="130">
             <template #default="{ row }">
               <div class="person-cell">
@@ -150,7 +151,8 @@
               </div>
             </template>
           </el-table-column>
-        </el-table>
+          </el-table>
+        </div>
 
         <div class="pager-wrap">
           <div class="pager-total">共 {{ total }} 条</div>
@@ -159,7 +161,7 @@
             layout="sizes, prev, pager, next"
             :total="total"
             :page-size="size"
-            :page-sizes="[10, 16, 20, 50, 100]"
+            :page-sizes="[10, 20, 50, 100]"
             v-model:current-page="page"
             @current-change="load"
             @size-change="onSizeChange"
@@ -261,7 +263,7 @@
 const avatarUploading = ref(false);
 const rows = ref<StaffRow[]>([]);
 const page = ref(1);
-const size = ref(16);
+const size = ref(10);
 const total = ref(0);
 const deptTree = ref<DeptTree[]>([]);
 const deptOptions = ref<DeptTree[]>([]);
@@ -610,6 +612,7 @@
   display: grid;
   grid-template-columns: 250px minmax(0, 1fr);
   gap: 12px;
+  height: calc(100vh - 170px);
   min-height: calc(100vh - 170px);
 }
 
@@ -631,6 +634,7 @@
   flex-direction: column;
   gap: 12px;
   min-width: 0;
+  min-height: 0;
 }
 
 .panel-title {
@@ -710,6 +714,11 @@
 
 .table-card {
   padding: 14px 16px 12px;
+  display: flex;
+  flex-direction: column;
+  flex: 1;
+  min-height: 0;
+  overflow: hidden;
 }
 
 .table-toolbar {
@@ -718,6 +727,12 @@
   align-items: center;
   gap: 12px;
   margin-bottom: 12px;
+  flex: none;
+}
+
+.staff-table-wrap {
+  flex: 1;
+  min-height: 0;
 }
 
 .toolbar-left {
@@ -748,6 +763,7 @@
   justify-content: space-between;
   align-items: center;
   margin-top: 14px;
+  flex: none;
 }
 
 .pager-total {
diff --git a/frontend-admin/src/views/StaffGroupDetailView.vue b/frontend-admin/src/views/StaffGroupDetailView.vue
index 83f80cf..b8c5061 100644
--- a/frontend-admin/src/views/StaffGroupDetailView.vue
+++ b/frontend-admin/src/views/StaffGroupDetailView.vue
@@ -113,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>
@@ -330,6 +332,12 @@
   loadPicker();
 }
 
+function onPickerSizeChange(nextSize: number) {
+  pickerSize.value = nextSize;
+  pickerPage.value = 1;
+  loadPicker();
+}
+
 function clearPickerSelection() {
   pickedMap.value = new Map();
   pickerTableRef.value?.clearSelection?.();
diff --git a/frontend-admin/src/views/StaffGroupsView.vue b/frontend-admin/src/views/StaffGroupsView.vue
index 2676df4..7a42879 100644
--- a/frontend-admin/src/views/StaffGroupsView.vue
+++ b/frontend-admin/src/views/StaffGroupsView.vue
@@ -12,38 +12,42 @@
         <el-button @click="resetSearch">重置</el-button>
       </el-form-item>
       <el-form-item class="push-right">
-        <el-button type="success" @click="openCreate">新建分组</el-button>
+        <el-button type="primary" @click="openCreate">新建分组</el-button>
       </el-form-item>
     </el-form>
 
-    <el-table :data="rows" v-loading="loading" stripe>
-      <el-table-column label="序号" width="64" type="index" :index="indexMethod" />
-      <el-table-column prop="groupName" label="分组名称" min-width="140" show-overflow-tooltip />
-      <el-table-column prop="leaderName" label="负责人" width="110" show-overflow-tooltip />
-      <el-table-column prop="leaderMobileMasked" label="联系电话" width="140" />
-      <el-table-column prop="memberCount" label="绑定人员数量" width="120" />
-      <el-table-column label="创建时间" width="170">
-        <template #default="{ row }">{{ formatTime(row.createTime) }}</template>
-      </el-table-column>
-      <el-table-column label="操作" width="240" fixed="right">
-        <template #default="{ row }">
-          <div class="table-ops">
-            <el-button type="primary" link @click="goDetail(row)">详情</el-button>
-            <el-button type="primary" link @click="openEdit(row)">编辑</el-button>
-            <el-button type="primary" link @click="confirmDelete(row)">删除</el-button>
-          </div>
-        </template>
-      </el-table-column>
-    </el-table>
+    <div class="table-wrap">
+      <el-table :data="rows" v-loading="loading" stripe height="100%">
+        <el-table-column label="序号" width="64" type="index" :index="indexMethod" />
+        <el-table-column prop="groupName" label="分组名称" min-width="140" show-overflow-tooltip />
+        <el-table-column prop="leaderName" label="负责人" width="110" show-overflow-tooltip />
+        <el-table-column prop="leaderMobileMasked" label="联系电话" width="140" />
+        <el-table-column prop="memberCount" label="绑定人员数量" width="120" />
+        <el-table-column label="创建时间" width="170">
+          <template #default="{ row }">{{ formatTime(row.createTime) }}</template>
+        </el-table-column>
+        <el-table-column label="操作" width="240" fixed="right">
+          <template #default="{ row }">
+            <div class="table-ops">
+              <el-button type="primary" link @click="goDetail(row)">详情</el-button>
+              <el-button type="primary" link @click="openEdit(row)">编辑</el-button>
+              <el-button type="primary" link @click="confirmDelete(row)">删除</el-button>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
 
     <el-pagination
-      class="pager"
+      class="main-pager"
       background
-      layout="prev, pager, next"
+      layout="sizes, prev, pager, next"
       :total="total"
       :page-size="size"
+      :page-sizes="[10, 20, 50, 100]"
       v-model:current-page="page"
       @current-change="load"
+      @size-change="onMainSizeChange"
     />
 
     <el-dialog v-model="dlg" :title="dlgTitle" width="860px" destroy-on-close class="group-dialog">
@@ -60,7 +64,12 @@
             <template #label>
               <span class="req-label">负责人</span>
             </template>
-            <el-select v-model="form.leaderMpUserId" placeholder="请选择负责人" class="fullw">
+            <el-select
+              v-model="form.leaderMpUserId"
+              placeholder="请选择负责人"
+              class="fullw"
+              @visible-change="onLeaderVisibleChange"
+            >
               <el-option
                 v-for="m in memberList"
                 :key="m.id"
@@ -172,11 +181,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>
@@ -273,6 +284,12 @@
 function resetSearch() {
   filters.groupName = "";
   filters.leaderMobile = "";
+  page.value = 1;
+  load();
+}
+
+function onMainSizeChange(nextSize: number) {
+  size.value = nextSize;
   page.value = 1;
   load();
 }
@@ -411,6 +428,16 @@
   membersMap.value.delete(id);
 }
 
+function onLeaderVisibleChange(visible: boolean) {
+  if (!visible) {
+    return;
+  }
+  if (memberList.value.length > 0) {
+    return;
+  }
+  ElMessage.warning("请先选择人员,再从已有人员列表中选择负责人");
+}
+
 async function submitForm() {
   if (!form.groupName.trim()) {
     ElMessage.warning("请填写分组名称");
@@ -524,6 +551,12 @@
   loadPicker();
 }
 
+function onPickerSizeChange(nextSize: number) {
+  pickerSize.value = nextSize;
+  pickerPage.value = 1;
+  loadPicker();
+}
+
 function pickerParams() {
   return {
     page: pickerPage.value,
@@ -608,13 +641,25 @@
 <style scoped>
 .search-form {
   margin-bottom: 12px;
+  flex: none;
 }
 .push-right {
   margin-left: auto;
 }
-.pager {
+.page {
+  display: flex;
+  flex-direction: column;
+  height: calc(100vh - 190px);
+  min-height: 420px;
+}
+.table-wrap {
+  flex: 1;
+  min-height: 0;
+}
+.main-pager {
   margin-top: 12px;
   justify-content: flex-end;
+  flex: none;
 }
 .member-actions {
   display: flex;
diff --git a/frontend-admin/src/views/UsersView.vue b/frontend-admin/src/views/UsersView.vue
index 632597f..b518c0b 100644
--- a/frontend-admin/src/views/UsersView.vue
+++ b/frontend-admin/src/views/UsersView.vue
@@ -1,5 +1,5 @@
 <template>
-  <div>
+  <div class="page">
     <h2 class="page-title">平台管理员</h2>
     <el-form :inline="true" class="search-form" @submit.prevent="doSearch">
       <el-form-item label="姓名">
@@ -34,11 +34,12 @@
         <el-button @click="resetSearch">重置</el-button>
       </el-form-item>
       <el-form-item class="push-right">
-        <el-button v-permission="'sys:users:create'" type="success" @click="openCreate">新增管理员</el-button>
+        <el-button v-permission="'sys:users:create'" type="primary" @click="openCreate">新增管理员</el-button>
       </el-form-item>
     </el-form>
 
-    <el-table :data="rows" v-loading="loading" stripe>
+    <div class="table-wrap">
+      <el-table :data="rows" v-loading="loading" stripe height="100%">
       <el-table-column label="序号" width="64" :index="indexMethod" type="index" />
       <el-table-column prop="realName" label="姓名" width="100" show-overflow-tooltip />
       <el-table-column prop="username" label="登录账号" width="120" show-overflow-tooltip />
@@ -107,16 +108,19 @@
           </div>
         </template>
       </el-table-column>
-    </el-table>
+      </el-table>
+    </div>
 
     <el-pagination
-      class="pager"
+      class="main-pager"
       background
-      layout="prev, pager, next"
+      layout="sizes, prev, pager, next"
       :total="total"
       :page-size="size"
+      :page-sizes="[10, 20, 50, 100]"
       v-model:current-page="page"
       @current-change="load"
+      @size-change="onSizeChange"
     />
 
     <!-- 新增 -->
@@ -162,7 +166,7 @@
             <el-option
               v-for="r in roles"
               :key="r.id"
-              :label="`${r.roleName} (${r.roleCode})`"
+              :label="r.roleName"
               :value="r.id"
             />
           </el-select>
@@ -217,7 +221,7 @@
             <el-option
               v-for="r in roles"
               :key="r.id"
-              :label="`${r.roleName} (${r.roleCode})`"
+              :label="r.roleName"
               :value="r.id"
             />
           </el-select>
@@ -389,6 +393,12 @@
   filters.mobile = "";
   filters.roleId = undefined;
   filters.status = undefined;
+  page.value = 1;
+  load();
+}
+
+function onSizeChange(nextSize: number) {
+  size.value = nextSize;
   page.value = 1;
   load();
 }
@@ -659,19 +669,32 @@
   font-weight: 700;
 }
 
+.page {
+  display: flex;
+  flex-direction: column;
+  height: calc(100vh - 190px);
+  min-height: 420px;
+}
+
 .search-form {
   margin-bottom: 12px;
   display: flex;
   flex-wrap: wrap;
   align-items: center;
   gap: 4px 0;
+  flex: none;
 }
 .push-right {
   margin-left: auto;
 }
-.pager {
+.table-wrap {
+  flex: 1;
+  min-height: 0;
+}
+.main-pager {
   margin-top: 12px;
   justify-content: flex-end;
+  flex: none;
 }
 
 .avatar-upload-row {
diff --git a/frontend-admin/src/views/VisitReasonsSettingsView.vue b/frontend-admin/src/views/VisitReasonsSettingsView.vue
index 3a13545..ab1d91d 100644
--- a/frontend-admin/src/views/VisitReasonsSettingsView.vue
+++ b/frontend-admin/src/views/VisitReasonsSettingsView.vue
@@ -5,46 +5,50 @@
       <el-button v-permission="'config:visit-reasons:create'" type="primary" @click="openCreate">新增</el-button>
     </div>
 
-    <el-table :data="rows" v-loading="loading" stripe border>
-      <el-table-column label="序号" width="72" type="index" :index="indexMethod" />
-      <el-table-column prop="reasonName" label="来访事由" min-width="220" show-overflow-tooltip />
-      <el-table-column label="创建时间" width="180">
-        <template #default="{ row }">{{ formatTime(row.createTime) }}</template>
-      </el-table-column>
-      <el-table-column label="操作" width="160" fixed="right">
-        <template #default="{ row }">
-          <div class="table-ops">
-            <el-button
-              v-if="!isOtherReason(row)"
-              v-permission="'config:visit-reasons:update'"
-              type="primary"
-              link
-              @click="openEdit(row)"
-            >
-              编辑
-            </el-button>
-            <el-button
-              v-if="!isOtherReason(row)"
-              v-permission="'config:visit-reasons:delete'"
-              type="primary"
-              link
-              @click="confirmDelete(row)"
-            >
-              删除
-            </el-button>
-          </div>
-        </template>
-      </el-table-column>
-    </el-table>
+    <div class="table-wrap">
+      <el-table :data="rows" v-loading="loading" stripe border height="100%">
+        <el-table-column label="序号" width="72" type="index" :index="indexMethod" />
+        <el-table-column prop="reasonName" label="来访事由" min-width="220" show-overflow-tooltip />
+        <el-table-column label="创建时间" width="180">
+          <template #default="{ row }">{{ formatTime(row.createTime) }}</template>
+        </el-table-column>
+        <el-table-column label="操作" width="160" fixed="right">
+          <template #default="{ row }">
+            <div class="table-ops">
+              <el-button
+                v-if="!isOtherReason(row)"
+                v-permission="'config:visit-reasons:update'"
+                type="primary"
+                link
+                @click="openEdit(row)"
+              >
+                编辑
+              </el-button>
+              <el-button
+                v-if="!isOtherReason(row)"
+                v-permission="'config:visit-reasons:delete'"
+                type="primary"
+                link
+                @click="confirmDelete(row)"
+              >
+                删除
+              </el-button>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
 
     <el-pagination
       class="pager"
       background
-      layout="prev, pager, next"
+      layout="sizes, prev, pager, next"
       :total="total"
       :page-size="size"
+      :page-sizes="[10, 20, 50, 100]"
       v-model:current-page="page"
       @current-change="load"
+      @size-change="onSizeChange"
     />
 
     <el-dialog v-model="dlg" :title="dlgTitle" width="460px" destroy-on-close>
@@ -133,6 +137,12 @@
   dlg.value = true;
 }
 
+function onSizeChange(nextSize: number) {
+  size.value = nextSize;
+  page.value = 1;
+  load();
+}
+
 function openEdit(row: Row) {
   if (isOtherReason(row)) {
     return;
@@ -200,12 +210,17 @@
 <style scoped>
 .page {
   padding: 0;
+  display: flex;
+  flex-direction: column;
+  height: calc(100vh - 190px);
+  min-height: 420px;
 }
 .header {
   display: flex;
   justify-content: space-between;
   align-items: center;
   margin-bottom: 12px;
+  flex: none;
 }
 .section-title {
   font-size: 16px;
@@ -215,8 +230,13 @@
   border-left: 3px solid #409eff;
   color: #303133;
 }
+.table-wrap {
+  flex: 1;
+  min-height: 0;
+}
 .pager {
   margin-top: 12px;
   justify-content: flex-end;
+  flex: none;
 }
 </style>
diff --git a/frontend-admin/src/views/VisitorInfoView.vue b/frontend-admin/src/views/VisitorInfoView.vue
index f586186..4e857cc 100644
--- a/frontend-admin/src/views/VisitorInfoView.vue
+++ b/frontend-admin/src/views/VisitorInfoView.vue
@@ -48,7 +48,8 @@
           导出 Excel
         </el-button>
       </div>
-      <el-table :data="rows" v-loading="loading" stripe border>
+      <div class="table-wrap">
+        <el-table :data="rows" v-loading="loading" stripe border height="100%">
         <el-table-column label="序号" width="68" type="index" :index="indexMethod" />
         <el-table-column prop="userName" label="用户" min-width="120" show-overflow-tooltip />
         <el-table-column prop="gender" label="性别" width="88" align="center" />
@@ -87,7 +88,8 @@
             </div>
           </template>
         </el-table-column>
-      </el-table>
+        </el-table>
+      </div>
 
       <div class="pager-wrap">
         <div class="pager-total">共 {{ total }} 条</div>
@@ -96,7 +98,7 @@
           layout="sizes, prev, pager, next"
           :total="total"
           :page-size="size"
-          :page-sizes="[10, 16, 20, 50, 100]"
+          :page-sizes="[10, 20, 50, 100]"
           v-model:current-page="page"
           @current-change="load"
           @size-change="onSizeChange"
@@ -174,7 +176,7 @@
 const auth = useAuthStore();
 const rows = ref<VisitorRow[]>([]);
 const page = ref(1);
-const size = ref(16);
+const size = ref(10);
 const total = ref(0);
 const registerDateRange = ref<string[]>([]);
 const dlg = ref(false);
@@ -414,6 +416,8 @@
   display: flex;
   flex-direction: column;
   gap: 12px;
+  height: calc(100vh - 170px);
+  min-height: calc(100vh - 170px);
 }
 
 .search-card,
@@ -471,12 +475,23 @@
 
 .table-card {
   padding: 14px 16px 12px;
+  display: flex;
+  flex-direction: column;
+  flex: 1;
+  min-height: 0;
+  overflow: hidden;
 }
 
 .table-toolbar {
   display: flex;
   justify-content: flex-end;
   margin-bottom: 12px;
+  flex: none;
+}
+
+.table-wrap {
+  flex: 1;
+  min-height: 0;
 }
 
 .pager-wrap {
@@ -484,6 +499,7 @@
   justify-content: space-between;
   align-items: center;
   margin-top: 14px;
+  flex: none;
 }
 
 .pager-total {
diff --git a/yuyueMiniProgram/.env.development b/yuyueMiniProgram/.env.development
index 458d440..3358710 100644
--- a/yuyueMiniProgram/.env.development
+++ b/yuyueMiniProgram/.env.development
@@ -2,7 +2,7 @@
 # 在开发模式下使用 (npm run dev)
 
 # API 基础 URL - 开发环境
-VITE_API_BASE_URL=http://221.182.45.100:8880
+VITE_API_BASE_URL=https://yts.hongyu.com
 
 # 环境名称
 VITE_ENV_NAME=development
diff --git a/yuyueMiniProgram/.env.production b/yuyueMiniProgram/.env.production
index c69dc2a..97b8511 100644
--- a/yuyueMiniProgram/.env.production
+++ b/yuyueMiniProgram/.env.production
@@ -2,7 +2,7 @@
 # 在生产构建时使用 (npm run build)
 
 # API 基础 URL - 生产环境
-VITE_API_BASE_URL=http://221.182.45.100:8880
+VITE_API_BASE_URL=https://yts.hongyu.com
 
 # 环境名称
 VITE_ENV_NAME=production
diff --git a/yuyueMiniProgram/src/pages/review/index.vue b/yuyueMiniProgram/src/pages/review/index.vue
index d561c10..dd3ea61 100644
--- a/yuyueMiniProgram/src/pages/review/index.vue
+++ b/yuyueMiniProgram/src/pages/review/index.vue
@@ -294,9 +294,10 @@
     .sort((left, right) => NODE_CODE_ORDER[left.nodeCode] - NODE_CODE_ORDER[right.nodeCode])
   const rejectedAnchorIndex = findRejectedAnchorIndex(rows)
   return rows.map((node, index) => {
+    const hiddenAfterRejected = rejectedAnchorIndex >= 0 && index > rejectedAnchorIndex
     return {
       title: stripBracketSuffix(node.nodeName) || NODE_CODE_TITLE[node.nodeCode] || node.nodeCode,
-      time: formatFlowTime(findNodeActionUser(node)?.actionTime),
+      time: hiddenAfterRejected ? '' : formatFlowTime(findNodeActionUser(node)?.actionTime),
       status: mapApprovalStepStatus(node, rejectedAnchorIndex, index),
     } satisfies ApprovalStepItem
   })
diff --git a/yuyueMiniProgram/src/pages/reviewDetail/index.vue b/yuyueMiniProgram/src/pages/reviewDetail/index.vue
index 5682bc5..fb9e0d9 100644
--- a/yuyueMiniProgram/src/pages/reviewDetail/index.vue
+++ b/yuyueMiniProgram/src/pages/reviewDetail/index.vue
@@ -919,7 +919,8 @@
 
     <!-- 状态图标(右上角绝对定位) -->
     <view class="absolute right-[24rpx] z-20 pointer-events-none" :style="{ top: (statusBarHeight + navBarHeight) - 5 + 'px' }">
-      <image v-if="overallStatus === 'passReview'" src="/static/review/passReview.png" class="w-[156rpx] h-[156rpx]" mode="aspectFit" />
+      <image v-if="overallStatus === 'underReview'" src="/static/review/underReview.png" class="w-[156rpx] h-[156rpx]" mode="aspectFit" />
+      <image v-else-if="overallStatus === 'passReview'" src="/static/review/passReview.png" class="w-[156rpx] h-[156rpx]" mode="aspectFit" />
       <image v-else-if="overallStatus === 'refueReview'" src="/static/review/refueReview.png" class="w-[156rpx] h-[156rpx]" mode="aspectFit" />
       <image v-else-if="overallStatus === 'cancelReview'" src="/static/review/cancelReview.png" class="w-[156rpx] h-[156rpx]" mode="aspectFit" />
     </view>
@@ -1070,7 +1071,7 @@
         </template>
 
         <!-- 其他属地单位领导审批 专属字段 -->
-        <template v-if="currentReviewNode === '其他属地单位领导审批'">
+        <template v-if="currentReviewNode === '其他属地单位领导审批' && canApprove">
           <view class="flex flex-col pt-[8rpx] pb-[16rpx]">
             <view class="flex items-center h-[96rpx] border-b1 relative">
               <view class="flex items-center shrink-0">
@@ -1133,11 +1134,11 @@
                   <view class="flex items-center">
                     <text class="text-[28rpx] font-medium" :class="['completed', 'rejected'].includes(item.status) ? 'text-[#B3B3B3]' : 'text-[#333333]'">{{ item.title }}</text>
                     <!-- 或签/会签/成签标签 -->
-                    <view v-if="item.signType === 'or'" class="ml-[12rpx] w-[64rpx] h-[32rpx] rounded-[16rpx] bg-[#F0F6FF] flex items-center justify-center box-border">
-                      <text class="text-[#0068FF] text-[20rpx]">或签</text>
+                    <view v-if="item.signType === 'or'" class="ml-[6rpx] w-[78rpx] h-[32rpx] rounded-[16rpx] bg-[#F0F6FF] flex items-center justify-center box-border">
+                      <text class="text-[#0068FF] text-[26rpx]">或签</text>
                     </view>
-                    <view v-if="item.signType === 'and'" class="ml-[12rpx] w-[64rpx] h-[32rpx] rounded-[16rpx] bg-[#F0F6FF] flex items-center justify-center box-border">
-                      <text class="text-[#0068FF] text-[20rpx]">会签</text>
+                    <view v-if="item.signType === 'and'" class="ml-[6rpx] w-[78rpx] h-[32rpx] rounded-[16rpx] bg-[#F0F6FF] flex items-center justify-center box-border">
+                      <text class="text-[#0068FF] text-[26rpx]">会签</text>
                     </view>
                     <view v-if="!item.signType && item.title === '部门负责人审批'" class="ml-[12rpx] w-[64rpx] h-[32rpx] rounded-[16rpx] bg-[#F0F6FF] flex items-center justify-center box-border">
                       <text class="text-[#0068FF] text-[20rpx]">成签</text>
diff --git a/yuyueMiniProgram/src/pages/visitRecord/index.vue b/yuyueMiniProgram/src/pages/visitRecord/index.vue
index 2487399..b0e02a6 100644
--- a/yuyueMiniProgram/src/pages/visitRecord/index.vue
+++ b/yuyueMiniProgram/src/pages/visitRecord/index.vue
@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { onLoad } from '@dcloudio/uni-app'
+import { onLoad, onPullDownRefresh } from '@dcloudio/uni-app'
 import { fetchVisitRecords } from '@/api/appointment'
 import type { MiniVisitRecordItem } from '@/types/appointment'
 import { normalizeVisitReasonText } from '@/utils/visitReason'
@@ -38,6 +38,7 @@
 const dateValue = ref('')
 const showDatePicker = ref(false)
 const loading = ref(false)
+const refreshing = ref(false)
 const records = ref<VisitRecordCard[]>([])
 const pageTitle = ref(props.title || '来访记录')
 
@@ -178,6 +179,19 @@
   void loadRecords()
 }
 
+async function handlePullRefresh() {
+  if (refreshing.value) {
+    return
+  }
+  refreshing.value = true
+  try {
+    await loadRecords()
+  }
+  finally {
+    refreshing.value = false
+  }
+}
+
 updateDateColumns()
 
 onLoad((options) => {
@@ -188,6 +202,15 @@
 
 onShow(() => {
   void loadRecords()
+})
+
+onPullDownRefresh(async () => {
+  try {
+    await loadRecords()
+  }
+  finally {
+    uni.stopPullDownRefresh()
+  }
 })
 </script>
 
@@ -247,7 +270,14 @@
     <view :style="{ height: (statusBarHeight + navBarHeight + uni.upx2px(144)) + 'px' }" class="shrink-0" />
 
     <view class="relative z-10 px-[24rpx] flex-1 flex flex-col min-h-0 box-border pt-[16rpx]">
-      <scroll-view scroll-y class="flex-1 min-h-0" :show-scrollbar="false">
+      <scroll-view
+        scroll-y
+        class="flex-1 min-h-0"
+        :show-scrollbar="false"
+        refresher-enabled
+        :refresher-triggered="refreshing"
+        @refresherrefresh="handlePullRefresh"
+      >
         <view class="pb-[48rpx]">
           <view v-for="item in records" :key="item.id" class="bg-white rounded-[8rpx] shadow-[0_4rpx_8rpx_rgba(6,47,108,0.1608)] mb-[24rpx] px-[40rpx]">
             <view class="flex items-center h-[96rpx]">

--
Gitblit v1.7.1