puzhibing
2026-08-28 e92cd56bb56b111c1afe9922191f9897215ff6b0
frontend-admin/src/views/StaffEmployeesView.vue
@@ -39,13 +39,13 @@
        <div class="panel-title">员工列表</div>
        <el-form class="search-grid" @submit.prevent="doSearch">
          <el-form-item label="姓名">
            <el-input v-model="filters.realName" placeholder="请输入内容" clearable />
            <el-input v-model="filters.realName" placeholder="请输入姓名" clearable />
          </el-form-item>
          <el-form-item label="人员编号">
            <el-input v-model="filters.staffNo" placeholder="请输入内容" clearable />
            <el-input v-model="filters.staffNo" placeholder="请输入人员编号" clearable />
          </el-form-item>
          <el-form-item label="联系方式">
            <el-input v-model="filters.mobile" placeholder="请输入内容" clearable />
            <el-input v-model="filters.mobile" placeholder="请输入联系方式" clearable />
          </el-form-item>
          <el-form-item label="人员分组">
            <el-select
@@ -60,12 +60,6 @@
                :label="g.groupName"
                :value="g.id"
              />
            </el-select>
          </el-form-item>
          <el-form-item label="职位">
            <el-select v-model="filters.position" placeholder="请选择" clearable>
              <el-option label="部门负责人" value="LEADER" />
              <el-option label="员工" value="STAFF" />
            </el-select>
          </el-form-item>
          <el-form-item label="状态">
@@ -103,7 +97,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">
@@ -116,7 +111,6 @@
          <el-table-column prop="staffNo" label="员工编号" width="120" show-overflow-tooltip />
          <el-table-column prop="mobileMasked" label="联系方式" width="130" />
          <el-table-column prop="deptName" label="所属部门" min-width="120" show-overflow-tooltip />
          <el-table-column prop="positionLabel" label="所属职位" width="120" />
          <el-table-column prop="groupDisplay" label="所属分组" min-width="170" show-overflow-tooltip />
          <el-table-column label="状态" width="92">
            <template #default="{ row }">
@@ -137,20 +131,21 @@
                <el-button
                  v-permission="'sys:staff:status'"
                  v-if="row.status === 1"
                  type="warning"
                  type="primary"
                  link
                  @click="patchStatus(row, 0)"
                >
                  禁用
                </el-button>
                <el-button v-else v-permission="'sys:staff:status'" type="success" link @click="patchStatus(row, 1)">
                <el-button v-else v-permission="'sys:staff:status'" type="primary" link @click="patchStatus(row, 1)">
                  启用
                </el-button>
                <el-button v-permission="'sys:staff:delete'" type="danger" link @click="confirmDelete(row)">删除</el-button>
                <el-button v-permission="'sys:staff:delete'" type="primary" link @click="confirmDelete(row)">删除</el-button>
              </div>
            </template>
          </el-table-column>
        </el-table>
          </el-table>
        </div>
        <div class="pager-wrap">
          <div class="pager-total">共 {{ total }} 条</div>
@@ -159,7 +154,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"
@@ -173,7 +168,7 @@
            <el-input v-model="form.nickName" maxlength="128" placeholder="请输入" />
          </el-form-item>
          <el-form-item label="编号">
            <el-input v-model="form.staffNo" maxlength="64" placeholder="可选" />
            <el-input v-model="form.staffNo" maxlength="64" placeholder="请输入人员编号(选填)" />
          </el-form-item>
          <el-form-item label="员工头像">
            <div class="avatar-upload-row">
@@ -193,7 +188,7 @@
            </div>
          </el-form-item>
          <el-form-item label="联系电话" required>
            <el-input v-model="form.mobile" maxlength="32" placeholder="11位手机号" />
            <el-input v-model="form.mobile" maxlength="32" placeholder="请输入11位手机号" />
          </el-form-item>
          <el-form-item label="所属部门" required>
            <el-tree-select
@@ -205,12 +200,6 @@
              placeholder="请选择部门"
              class="fullw"
            />
          </el-form-item>
          <el-form-item label="职位" required>
            <el-radio-group v-model="form.deptLeader">
              <el-radio :label="false">员工</el-radio>
              <el-radio :label="true">部门负责人</el-radio>
            </el-radio-group>
          </el-form-item>
          <el-form-item v-if="detailGroupNames.length" label="所属分组">
            <div class="group-tags">
@@ -240,6 +229,7 @@
import { Refresh, Search } from "@element-plus/icons-vue";
import type { UploadRequestOptions } from "element-plus";
import http from "../api/http";
import { normalizeFormData } from "../utils/formHelper";
type DeptTree = { id: number; label: string; children?: DeptTree[] };
type StaffRow = {
@@ -261,7 +251,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[]>([]);
@@ -274,7 +264,6 @@
  realName: "",
  staffNo: "",
  mobile: "",
  position: "" as string,
  status: undefined as number | undefined,
  staffGroupId: undefined as number | undefined,
});
@@ -382,7 +371,6 @@
    realName: filters.realName?.trim() || undefined,
    staffNo: filters.staffNo?.trim() || undefined,
    mobile: filters.mobile?.trim() || undefined,
    position: filters.position || undefined,
    status: filters.status,
    staffGroupId: filters.staffGroupId,
  };
@@ -410,7 +398,6 @@
  filters.realName = "";
  filters.staffNo = "";
  filters.mobile = "";
  filters.position = "";
  filters.status = undefined;
  filters.staffGroupId = undefined;
  page.value = 1;
@@ -476,14 +463,14 @@
  }
  saving.value = true;
  try {
    const body = {
    const body = normalizeFormData({
      nickName: form.nickName.trim(),
      staffNo: form.staffNo.trim() || undefined,
      avatarUrl: form.avatarUrl.trim() || undefined,
      mobile: form.mobile.trim(),
      deptId: form.deptId,
      deptLeader: form.deptLeader,
    };
      deptLeader: false,
    });
    if (editingId.value == null) {
      await http.post("/sys/staff", body);
      ElMessage.success("已添加");
@@ -506,10 +493,20 @@
async function patchStatus(row: StaffRow, status: number) {
  try {
    if (status === 0) {
      await ElMessageBox.confirm(
        "是否确认禁用? 禁用后该账号不可再登录系统",
        "确认禁用",
        { type: "warning" },
      );
    }
    await http.patch(`/sys/staff/${row.id}/status`, { uuid: row.uuid, status });
    ElMessage.success(status === 1 ? "已启用" : "已禁用");
    row.status = status;
  } catch (e: unknown) {
    if (e === "cancel" || e === "close") {
      return;
    }
    ElMessage.error(e instanceof Error ? e.message : "操作失败");
  }
}
@@ -571,10 +568,10 @@
  fd.append("file", opt.file as File);
  avatarUploading.value = true;
  try {
    const { data } = await http.post("/sys/staff/avatar", fd, {
    const { data } = await http.post("/common/upload/image", fd, {
      headers: { "Content-Type": "multipart/form-data" },
    });
    form.avatarUrl = String(data.result?.avatarUrl || "");
    form.avatarUrl = String(data.result?.url || "");
    ElMessage.success("头像上传成功");
  } catch (e: unknown) {
    ElMessage.error(e instanceof Error ? e.message : "头像上传失败");
@@ -600,6 +597,7 @@
  display: grid;
  grid-template-columns: 250px minmax(0, 1fr);
  gap: 12px;
  height: calc(100vh - 170px);
  min-height: calc(100vh - 170px);
}
@@ -614,6 +612,9 @@
.dept-panel {
  padding: 14px 12px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.content-panel {
@@ -621,6 +622,7 @@
  flex-direction: column;
  gap: 12px;
  min-width: 0;
  min-height: 0;
}
.panel-title {
@@ -628,14 +630,18 @@
  font-size: 18px;
  font-weight: 700;
  margin-bottom: 14px;
  flex-shrink: 0;
}
.dept-search {
  margin-bottom: 14px;
  flex-shrink: 0;
}
.dept-tree-wrap {
  min-height: 520px;
  flex: 1;
  overflow-y: auto;
  min-height: 0;
}
.dept-all {
@@ -700,6 +706,11 @@
.table-card {
  padding: 14px 16px 12px;
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}
.table-toolbar {
@@ -708,6 +719,12 @@
  align-items: center;
  gap: 12px;
  margin-bottom: 12px;
  flex: none;
}
.staff-table-wrap {
  flex: 1;
  min-height: 0;
}
.toolbar-left {
@@ -738,6 +755,7 @@
  justify-content: space-between;
  align-items: center;
  margin-top: 14px;
  flex: none;
}
.pager-total {