Преглед на файлове

工资条状态改数字枚举 + 员工状态 tab 直用字典码 + 考勤明细补机构筛选:salarySlip/myPayslip 把工资条状态从 PUBLISHED/VIEWED 等英文码改为后端 PayrollPayslipStatusEnum 数字码(1待发布 2已发布 3已查看 4已确认 5申诉中 6已更正 7已关闭),列显隐缓存 bump 到 v2 清掉旧列残留;员工档案员工状态 tab 改为「一个 hr_user_status 字典项一个 tab」、value 直用字典数字状态码(去掉按文案猜映射的 active/probation 英文 key,字典里多出来的状态排在后面照常渲染),顺序按 在职(在岗)→试用期→已离职→待岗→退休、末尾固定全部;考勤明细页新增「所属机构」筛选(登录缓存 orgVO,默认当前机构,部门下拉按该机构子树过滤且不含机构自身),日期区间默认本月 1 号到今天,去掉操作列;所属机构列数据源确认缺口:/main/user/batch 实测只回 id/name/jobNumber/deptName 不返回 groupName,列保留、值留空,adaptEmployee 改为读 groupName(后端补字段后自动生效),缺口已转后端清单;薪资版本列表补审计字段 createUserName/createTime;薪资统计页删掉金额说明 note 条

lihaiting преди 4 дни
родител
ревизия
60b0af11ca

+ 2 - 0
src/api/hr/attendance.js

@@ -1926,6 +1926,7 @@ export const getDailyAttendanceDetailPage = async (params = {}) => {
  * 每日考勤结果返回模型 → 页面行模型。
  * ★ 接口不返回姓名/部门(只回 userId):姓名、工号、部门走 loadEmployeeBriefs 主数据批量接口补齐,
  *   查不到就留空;打卡地址、工作地址接口完全没有对应字段,恒留空,缺口转后端改造清单。
+ * ★ 所属机构:/main/user/batch 实测不返回 groupName(2026-10-30),brief.company 恒空 → 列留空。
  */
 export function adaptDailyAttendance(raw = {}, brief = null) {
   const records = Array.isArray(raw.punchRecords) ? raw.punchRecords : [];
@@ -1936,6 +1937,7 @@ export function adaptDailyAttendance(raw = {}, brief = null) {
     name: brief?.name || '',
     employeeNo: brief?.employeeNo || '',
     department: brief?.department || '',
+    organization: brief?.company || '',
     attendanceDate: String(raw.attendanceDate || '').slice(0, 10),
     statusList,
     statusText: statusList.map((item) => exceptionLabel(item)).join('、'),

+ 4 - 0
src/api/organization/index.js

@@ -329,6 +329,10 @@ function adaptBrief(raw = {}) {
     name: raw.name || "",
     employeeNo: raw.jobNumber || "",
     department: raw.deptName || "",
+    /* 所属机构:读 groupName。2026-10-30 实测 /main/user/batch 只回
+       {id,name,jobNumber,deptName},不返回 groupName → 此字段恒空(缺口已转后端清单),
+       后端补上字段后此处自动生效。 */
+    company: raw.groupName || "",
   };
 }
 

+ 6 - 0
src/api/salary/employeeSalary.js

@@ -217,6 +217,9 @@ function adaptSalaryProfile(raw = {}) {
       raw.changeSource ||
       '—',
     templateVersionId: raw.templateVersionId,
+    /* 审计字段:2026-10-30 用户确认接口已返回 createUserName / createTime,列表直取(未返回显 —) */
+    createUserName: raw.createUserName || '',
+    createTime: raw.createTime ? String(raw.createTime).slice(0, 16).replace('T', ' ') : '',
     fieldPolicy: policy,
     baseSalary: value('baseSalary'),
     performanceBase: value('performanceBase'),
@@ -275,6 +278,9 @@ function adaptSalaryProfileOverview(raw = {}) {
       '—',
     templateVersionId: raw.templateVersionId,
     taxProfileId: raw.taxProfileId,
+    /* 审计字段:2026-10-30 用户确认 /page 已返回 createUserName / createTime,列表直取(未返回显 —) */
+    createUserName: raw.createUserName || '',
+    createTime: raw.createTime ? String(raw.createTime).slice(0, 16).replace('T', ' ') : '',
     fieldPolicy: policy,
     baseSalary: value('baseSalary'),
     performanceBase: value('performanceBase'),

+ 8 - 8
src/api/salary/myPayslip.js

@@ -29,15 +29,15 @@ import {
   isIdempotentKeyExpired
 } from './salarySlip';
 
-/** 旧后端兜底文案:与 PayrollConstants 的 7 个码值一致,正常不再使用 */
+/** 兜底文案:PayrollPayslipStatusEnum 数字码(2026-10-30 后端确认),正常优先 payslipStatusLabel */
 const FALLBACK_STATUS_LABELS = {
-  WAIT_PUBLISH: '待发布',
-  PUBLISHED: '已发布',
-  VIEWED: '已查看',
-  CONFIRMED: '已确认',
-  APPEALED: '申诉中',
-  CORRECTED: '已更正',
-  CLOSED: '已关闭'
+  1: '待发布',
+  2: '已发布',
+  3: '已查看',
+  4: '已确认',
+  5: '申诉中',
+  6: '已更正',
+  7: '已关闭'
 };
 
 /** 状态码 → 文案:优先后端下发的 payslipStatusLabel,其次兜底表,最后原样显示 */

+ 11 - 7
src/api/salary/salarySlip.js

@@ -126,14 +126,18 @@ function requireIdempotentKey(value, scene) {
 
 /* ---------------- 枚举(取值以后端为准) ---------------- */
 
-/** 工资条状态 */
+/**
+ * 工资条状态 —— 2026-10-30 后端确认枚举 PayrollPayslipStatusEnum(数字码):
+ * 1 待发布 / 2 已发布 / 3 已查看 / 4 已确认 / 5 申诉中 / 6 已更正 / 7 已关闭
+ */
 export const PAYSLIP_STATUS_OPTIONS = [
-  { value: 'PUBLISHED', label: '已发布', tagType: 'primary' },
-  { value: 'VIEWED', label: '已查看', tagType: 'info' },
-  { value: 'CONFIRMED', label: '已确认', tagType: 'success' },
-  { value: 'APPEALED', label: '已申诉', tagType: 'warning' },
-  { value: 'CORRECTED', label: '已更正', tagType: 'warning' },
-  { value: 'CLOSED', label: '已关闭', tagType: 'info' }
+  { value: '1', label: '待发布', tagType: 'info' },
+  { value: '2', label: '已发布', tagType: 'primary' },
+  { value: '3', label: '已查看', tagType: 'info' },
+  { value: '4', label: '已确认', tagType: 'success' },
+  { value: '5', label: '申诉中', tagType: 'warning' },
+  { value: '6', label: '已更正', tagType: 'warning' },
+  { value: '7', label: '已关闭', tagType: 'info' }
 ];
 
 export const PAYSLIP_STATUS_LABELS = PAYSLIP_STATUS_OPTIONS.reduce(

+ 129 - 55
src/views/attendance/daily/index.vue

@@ -1,17 +1,24 @@
 <template>
   <div class="ele-body">
     <el-card shadow="never" class="daily-card">
-      <!-- 筛选区:姓名 / 所属部门 / 日期(与设计图一致) -->
+      <!-- 筛选区:所属机构 / 所属部门 / 姓名 / 日期 -->
       <el-form inline :model="filter" class="daily-filter" @submit.native.prevent>
-        <el-form-item label="姓名">
-          <el-input
-            v-model="filter.employeeKeyword"
-            placeholder="姓名模糊/工号精确"
+        <el-form-item label="所属机构">
+          <el-select
+            v-model="filter.orgId"
+            placeholder="请选择"
             clearable
-            style="width: 200px"
-            @keyup.enter.native="search"
-            @clear="search"
-          />
+            filterable
+            style="width: 220px"
+            @change="onOrgChange"
+          >
+            <el-option
+              v-for="item in filterOrgOptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
         </el-form-item>
         <el-form-item label="所属部门">
           <el-select
@@ -23,13 +30,23 @@
             @change="search"
           >
             <el-option
-              v-for="item in filterOrgOptions"
+              v-for="item in filterDeptOptions"
               :key="item.value"
               :label="item.label"
               :value="item.value"
             />
           </el-select>
         </el-form-item>
+        <el-form-item label="姓名">
+          <el-input
+            v-model="filter.employeeKeyword"
+            placeholder="姓名模糊/工号精确"
+            clearable
+            style="width: 200px"
+            @keyup.enter.native="search"
+            @clear="search"
+          />
+        </el-form-item>
         <el-form-item label="日期">
           <el-date-picker
             v-model="filter.dateRange"
@@ -56,7 +73,7 @@
         :need-page="true"
         :page-size="20"
         :page-sizes="[10, 20, 50]"
-        :cache-key="$tableCacheKey('hr-attendance-daily')"
+        :cache-key="$tableCacheKey('hr-attendance-daily-v3')"
       >
         <template v-slot:toolbar>
           <!-- 后端暂无导入接口:点击如实提示,缺口已转后端改造清单 -->
@@ -65,7 +82,10 @@
         </template>
 
         <template slot="name" slot-scope="{ row }">
-          <span>{{ row.name }}</span>
+          <!-- 点姓名看详情(替代原操作列「查看」入口) -->
+          <el-link type="primary" :underline="false" @click="viewRow(row)">
+            {{ row.name || '—' }}
+          </el-link>
         </template>
 
         <template slot="statusText" slot-scope="{ row }">
@@ -86,10 +106,6 @@
           </template>
         </template>
 
-        <template slot="action" slot-scope="{ row }">
-          <el-link type="primary" :underline="false" @click="viewRow(row)">查看</el-link>
-        </template>
-
         <template slot="empty">
           <el-empty description="暂无数据" />
         </template>
@@ -148,8 +164,15 @@
 </template>
 
 <script>
+import * as XLSX from 'xlsx';
 import { loadEmployeeBriefs } from '@/api/organization';
 import { getCurrentOrgId, getOrgOptions } from '@/utils/current-org';
+import { loadOrganizationList } from '@/utils/org-list-cache';
+import {
+  buildOrganizationTree,
+  flattenOrganizationTree,
+  collectDescendantIds,
+} from '@/utils/organization-tree';
 import {
   getDailyAttendanceDetailPage,
   adaptDailyAttendance,
@@ -161,18 +184,34 @@ const CALCULATION_STATUS_LABEL = {
   LOCKED: '月结锁定',
 };
 
+/* 日期筛选默认当月:本月 1 号 ~ 今天(2026-10-30 用户指定默认查当月) */
+function currentMonthRange() {
+  const now = new Date();
+  const pad = (value) => String(value).padStart(2, '0');
+  const start = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-01`;
+  const end = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
+  return [start, end];
+}
+
 export default {
   name: 'AttendanceDaily',
   data() {
     return {
       filter: {
+        /* 所属机构:登录缓存 orgVO,默认当前机构 */
+        orgId: getCurrentOrgId() || '',
+        deptId: '',
         employeeKeyword: '',
-        deptId: getCurrentOrgId() || '',
-        dateRange: null,
+        dateRange: currentMonthRange(),
       },
       filterOrgOptions: [],
+      /* 组织树全量部门(扁平,带 level 缩进);按选中机构子树过滤见 filterDeptOptions */
+      allDeptOptions: [],
+      orgTreeNodes: [],
       columns: [
-        { type: 'index', label: '序号', width: 60, align: 'center', headerAlign: 'center' },
+        { type: 'index', label: '序号', width: 60, align: 'center', headerAlign: 'center', fixed: 'left' },
+        /* /hr/attendance/daily 与 /main/user/batch 均未返回机构字段:列保留、值留空,缺口转后端清单 */
+        { prop: 'organization', label: '所属机构', minWidth: 180 },
         { prop: 'name', label: '姓名', minWidth: 100, slot: 'name', fixed: 'left' },
         { prop: 'department', label: '所属部门', minWidth: 140 },
         { prop: 'attendanceDate', label: '日期', minWidth: 110, sortable: 'custom' },
@@ -185,7 +224,6 @@ export default {
         { prop: 'actualOut', label: '下班打卡时间', minWidth: 160 },
         /* 接口未返回工作地址:列保留、值留空,缺口转后端改造清单 */
         { prop: 'workAddress', label: '工作地址', minWidth: 140 },
-        { prop: 'action', label: '操作', width: 80, slot: 'action', fixed: 'right' },
       ],
       /* 当前页数据快照:本地 excel 导出用(后端暂无导出接口) */
       currentRows: [],
@@ -195,33 +233,74 @@ export default {
   },
   created() {
     this.loadFilterOrgOptions();
+    this.loadDeptOptions();
+  },
+  computed: {
+    /* 部门选项:选中机构时只列该机构子树下的部门(不含机构自身);
+       机构不在组织树里时展示全量部门,保证部门筛选仍可用 */
+    filterDeptOptions() {
+      if (!this.filter.orgId) return this.allDeptOptions;
+      const node = this.findOrgNode(this.orgTreeNodes, this.filter.orgId);
+      if (!node) return this.allDeptOptions;
+      const ids = new Set(collectDescendantIds(node));
+      ids.delete(String(this.filter.orgId));
+      return this.allDeptOptions.filter((item) => ids.has(String(item.value)));
+    },
   },
   methods: {
     search() {
       this.$refs.table?.reload?.({ page: 1 });
     },
+    /* 切换机构:清掉已选部门(部门选项随机构子树变化),重查 */
+    onOrgChange() {
+      this.filter.deptId = '';
+      this.search();
+    },
     reset() {
       this.filter = {
+        orgId: getCurrentOrgId() || '',
+        deptId: '',
         employeeKeyword: '',
-        deptId: getCurrentOrgId() || '',
-        dateRange: null,
+        dateRange: currentMonthRange(),
       };
       this.search();
     },
-    /* 筛选「所属部门」选项:登录缓存机构列表,与其他页面口径一致 */
+    /* 筛选「所属机构」选项:登录缓存机构列表,与其他页面口径一致 */
     loadFilterOrgOptions() {
       this.filterOrgOptions = getOrgOptions().map((item) => ({
         value: item.id,
         label: item.name || '当前机构',
       }));
     },
+    /* 部门选项:组织树扁平化(带层级缩进),失败不阻塞列表查询 */
+    async loadDeptOptions() {
+      try {
+        const list = await loadOrganizationList();
+        this.orgTreeNodes = buildOrganizationTree(list || []);
+        this.allDeptOptions = flattenOrganizationTree(this.orgTreeNodes).map((item) => ({
+          value: item.id,
+          label: `${' '.repeat(item.level || 0)}${item.name || ''}`,
+        }));
+      } catch (error) {
+        console.error('[考勤明细] 组织列表加载失败,部门筛选暂不可用:', error);
+      }
+    },
+    findOrgNode(nodes, orgId) {
+      for (const node of nodes) {
+        if (String(node.id) === String(orgId)) return node;
+        const hit = this.findOrgNode(node.children || [], orgId);
+        if (hit) return hit;
+      }
+      return null;
+    },
     async fetchPage({ page, limit }) {
-      // GET /hr/attendance/daily
+      // GET /hr/attendance/daily —— 接口只有一个组织参数 deptId:
+      // 选了部门传部门 id,否则传所属机构 id(机构也是组织树节点,服务端按数据权限校验)
       const data = await getDailyAttendanceDetailPage({
         pageNum: page,
         size: limit,
         employeeKeyword: this.filter.employeeKeyword || undefined,
-        deptId: this.filter.deptId || undefined,
+        deptId: this.filter.deptId || this.filter.orgId || undefined,
         dateStart: (this.filter.dateRange || [])[0] || undefined,
         dateEnd: (this.filter.dateRange || [])[1] || undefined,
       });
@@ -251,44 +330,39 @@ export default {
     onImport() {
       this.$message.info('导入考勤明细的后端接口暂未提供,请后端补充导入接口');
     },
-    /* 后端暂无导出接口:先导出当前页已加载数据(CSV),全量导出待后端补接口 */
+    /* 后端暂无导出接口:先导出当前页已加载数据(xlsx,SheetJS 本地生成,与员工档案导入模板同款);
+       全量导出待后端补接口。注意 SheetJS 社区版只支持写 .xlsx,写不了老格式 .xls(BIFF8)。 */
     onExport() {
       if (!this.currentRows.length) {
         this.$message.warning('暂无考勤明细可导出');
         return;
       }
       const header = [
-        '序号', '姓名', '所属部门', '日期', '出勤情况', '打卡类型',
+        '序号', '姓名', '所属机构', '所属部门', '日期', '出勤情况', '打卡类型',
         '打卡地址', '打卡情况', '上班打卡时间', '下班打卡时间', '工作地址',
       ];
-      const lines = this.currentRows.map((row, index) =>
-        [
-          index + 1,
-          row.name,
-          row.department,
-          row.attendanceDate,
-          row.statusText,
-          row.punchTypes,
-          row.punchAddress,
-          row.punchStatuses.map((item) => item.statusText).join(' '),
-          row.actualIn,
-          row.actualOut,
-          row.workAddress,
-        ]
-          .map((cell) => `"${String(cell ?? '').replace(/"/g, '""')}"`)
-          .join(','),
-      );
-      const blob = new Blob(['\ufeff' + [header.join(','), ...lines].join('\n')], {
-        type: 'text/csv;charset=utf-8;',
-      });
-      const url = URL.createObjectURL(blob);
-      const link = document.createElement('a');
-      link.href = url;
-      link.download = `考勤明细-${new Date().toISOString().slice(0, 10)}.csv`;
-      document.body.appendChild(link);
-      link.click();
-      document.body.removeChild(link);
-      URL.revokeObjectURL(url);
+      const body = this.currentRows.map((row, index) => [
+        index + 1,
+        row.name,
+        row.organization,
+        row.department,
+        row.attendanceDate,
+        row.statusText,
+        row.punchTypes,
+        row.punchAddress,
+        row.punchStatuses.map((item) => item.statusText).join(' '),
+        row.actualIn,
+        row.actualOut,
+        row.workAddress,
+      ]);
+      const sheet = XLSX.utils.aoa_to_sheet([header, ...body], { cellDates: true });
+      /* 中文按 2 字宽估列宽,表头不被截断;日期/时间列给宽一点 */
+      sheet['!cols'] = header.map((name, index) => ({
+        wch: index === 4 || index >= 9 ? Math.max(20, name.length * 2) : Math.max(10, name.length * 2),
+      }));
+      const book = XLSX.utils.book_new();
+      XLSX.utils.book_append_sheet(book, sheet, '考勤明细');
+      XLSX.writeFile(book, `考勤明细-${new Date().toISOString().slice(0, 10)}.xlsx`);
     },
   },
 };

+ 16 - 25
src/views/hrManagement/employeeArchive/index.vue

@@ -77,8 +77,8 @@
           </el-form>
           <!-- 员工状态 tab(2026-10-28 用户指定按订单页样式):独立方块 tab,位于搜索区下方、
                表格工具栏(灰色框)之外,不在表头区域内。
-               顺序固定 在职 → 试用期 → 已离职 → 待岗 → 退休,默认「在职」;
-               选项完全来自 hr_user_status 字典,字典里没有的状态不渲染(不编造状态码)。 -->
+               一个 hr_user_status 字典项一个 tab,value = 字典数字状态码(2026-10-30 起),
+               按 在职(在岗)→试用期→已离职→待岗→退休 排序,末尾固定「全部」。 -->
           <div v-if="statusTabs.length" class="status-tab-bar">
             <button v-for="tab in statusTabs" :key="tab.value" type="button" class="status-tab"
               :class="{ active: String(appliedFilters.status) === String(tab.value) }"
@@ -317,34 +317,25 @@ export default {
       return this.orgById[String(this.selectedOrganizationId)]?.name || '全部员工';
     },
     /**
-     * 员工状态 tab:顺序 在职 → 试用期 → 已离职 → 待岗 → 退休 → 全部(2026-10-28 用户指定)。
-     * 前五项一律来自 hr_user_status 字典(value 就是接口 status 参数),字典里没有的状态**不渲染**——
-     * 例如字典目前只有 1 正式在职 / 6 试用期 / 7 待岗 / 8 已离职,没有「退休」,
-     * 需要后端在字典里加「退休」项后这个 tab 才会出现(前端不编状态码)。
+     * 员工状态 tab:一个 hr_user_status 字典项对应一个 tab,value 直接用字典里的数字状态码
+     * (就是接口 status 参数,2026-10-30 用户拍板:不再按文案猜映射、不再有内部英文 key)。
+     * 顺序按 在职(在岗) → 试用期 → 已离职 → 待岗 → 退休 排;字典里多出来的状态排在后面照常渲染。
      * 最后一项「全部」是前端固定项(value 空串 → 不传 status,查全量)。
      */
     statusTabs() {
-      const defs = [
-        { key: 'active', test: (label) => label.includes('在职') && !label.includes('离职') },
-        { key: 'probation', test: (label) => label.includes('试用') },
-        { key: 'resigned', test: (label) => label.includes('离职') },
-        { key: 'idle', test: (label) => label.includes('待岗') },
-        { key: 'retired', test: (label) => label.includes('退休') },
-      ];
-      const options = this.filterStatusOptions || [];
-      const missing = [];
-      const tabs = defs.reduce((list, def) => {
-        const hit = options.find((item) => def.test(String(item.label || '')));
-        if (hit) list.push({ key: def.key, label: hit.label, value: String(hit.value) });
-        else missing.push(def.key);
-        return list;
-      }, []);
-      if (missing.length) {
-        console.warn('[员工档案] 状态 tab 在 hr_user_status 字典中缺少对应项:', missing.join(', '));
-      }
+      const preferred = ['在职', '在岗', '试用', '离职', '待岗', '退休'];
+      const orderOf = (label) => {
+        const text = String(label || '');
+        const index = preferred.findIndex((word) => text.includes(word));
+        return index === -1 ? preferred.length : index;
+      };
+      const ordered = [...(this.filterStatusOptions || [])].sort(
+        (a, b) => orderOf(a.label) - orderOf(b.label)
+      );
+      const tabs = ordered.map((item) => ({ label: item.label, value: String(item.value) }));
       /* 末尾固定追加「全部」(2026-10-28 用户要求):value 为空串 → 不带 status 参数,
          查全量员工(含离职,见 buildBaseParams 的 isQueryLZ) */
-      tabs.push({ key: 'all', label: '全部', value: '' });
+      tabs.push({ label: '全部', value: '' });
       return tabs;
     },
     /** 默认状态(tab 第一项 = 在职):进入页面 / 点重置都回到它 */

+ 17 - 31
src/views/salary/myPayslip/index.vue

@@ -8,19 +8,13 @@
           <span class="title-bar"></span>
           <span class="title-text">我的工资条</span>
         </div>
-        <div class="page-desc">
-          列表不返回工资金额与银行卡号,金额在「查看详情」中查看;
-          确认工资条与提出异议也在详情中操作。
-        </div>
       </div>
 
-      <!-- 契约提示:金额不在列表返回,不显示金额列 -->
-      <el-alert type="info" :closable="false" show-icon class="contract-alert"
-        title="列表仅展示月份、状态、发布时间、首次查看时间与确认时间;工资金额与银行卡号在「查看详情」里展示" />
-
-      <!-- 表格(服务端分页:POST /hr/ess/payslips/page,仅返回当前有效版本) -->
+      <!-- 表格(服务端分页:POST /hr/ess/payslips/page,仅返回当前有效版本)。
+           cacheKeyUrl 带 -v2:2026-10-30 列结构多次调整后,旧 localStorage 列缓存合并出空白列,
+           换键作废旧列配置。 -->
       <ele-pro-table ref="table" :columns="columns" :datasource="datasource" row-key="id" :need-page="true"
-        :cache-key="$tableCacheKey(cacheKeyUrl)" @columns-change="handleColumnChange">
+        :cache-key="$tableCacheKey(cacheKeyUrl)">
         <template v-slot:payslipStatus="{ row }">
           <el-tag size="mini" :type="statusTagType(row.payslipStatus)" effect="light">
             {{ row.payslipStatusLabel }}
@@ -217,16 +211,17 @@ export default {
   },
   computed: {
     cacheKeyUrl() {
-      return 'salary-my-payslip';
+      /* -v2:作废旧列缓存(旧缓存里残留已删列,合并出空白列头) */
+      return 'salary-my-payslip-v2';
     },
     /** 确认入口:仅未确认、未申诉的发布态可确认(已确认/已申诉后端会拒绝,不放行误导用户) */
     canConfirm() {
-      return ['PUBLISHED', 'VIEWED'].includes(this.current?.payslipStatus);
+      return ['2', '3'].includes(this.current?.payslipStatus);
     },
     /** 异议入口:与确认同一前提,且已有异议说明的不再重复入口 */
     canAppeal() {
       return (
-        ['PUBLISHED', 'VIEWED'].includes(this.current?.payslipStatus) &&
+        ['2', '3'].includes(this.current?.payslipStatus) &&
         !this.detail?.appealReason
       );
     }
@@ -244,14 +239,16 @@ export default {
       })}`;
     },
     statusTagType(status) {
+      /* PayrollPayslipStatusEnum 数字码(2026-10-30 后端确认):1待发布 2已发布 3已查看
+         4已确认 5申诉中 6已更正 7已关闭 */
       const types = {
-        WAIT_PUBLISH: 'info',
-        PUBLISHED: 'primary',
-        VIEWED: 'info',
-        CONFIRMED: 'success',
-        APPEALED: 'warning',
-        CORRECTED: 'warning',
-        CLOSED: 'info'
+        1: 'info',
+        2: 'primary',
+        3: 'info',
+        4: 'success',
+        5: 'warning',
+        6: 'warning',
+        7: 'info'
       };
       return types[status] || 'info';
     },
@@ -419,17 +416,6 @@ export default {
       color: #303133;
     }
   }
-
-  .page-desc {
-    font-size: 13.5px;
-    color: #909399;
-    line-height: 1.7;
-    padding-left: 11px;
-  }
-
-  .contract-alert {
-    margin-bottom: 14px;
-  }
 }
 
 .my-payslip-detail-modal {

+ 0 - 26
src/views/salary/statistics/index.vue

@@ -132,14 +132,6 @@
           <el-table-column prop="costText" label="单位成本" align="right" min-width="110" />
         </el-table>
       </div>
-      <p class="amount-note">
-        <i class="el-icon-info"></i>
-        <span>
-          金额由服务端按法人级汇总权限校验后聚合返回;
-          <b>到人明细金额</b>不在本页汇总,需在批次明细里点「查看金额」按需调取
-          (能否查看由菜单权限 + 当前组织范围决定,无权查看时显示「***」)。
-        </span>
-      </p>
     </section>
 
 
@@ -1154,24 +1146,6 @@ export default {
   background-color: rgba(0, 190, 255, 0.2);
 }
 
-.amount-note {
-  display: flex;
-  align-items: flex-start;
-  gap: 6px;
-  margin: 12px 16px 0;
-  padding: 8px 10px;
-  border-left: 2px solid rgba(0, 230, 255, 0.55);
-  background: rgba(0, 60, 95, 0.28);
-  color: #8fc4de;
-  font-size: 14px;
-  line-height: 18px;
-}
-
-.amount-note i {
-  margin-top: 1px;
-  color: #7af0ff;
-}
-
 /* KPI 指标卡 */
 .metric-grid {
   position: relative;