| | |
| | | <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 |
| | |
| | | :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="状态"> |
| | |
| | | <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"> |
| | |
| | | <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 }"> |
| | |
| | | <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> |
| | |
| | | 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" |
| | |
| | | <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"> |
| | |
| | | </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 |
| | |
| | | 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"> |
| | |
| | | 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 = { |
| | |
| | | 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[]>([]); |
| | |
| | | realName: "", |
| | | staffNo: "", |
| | | mobile: "", |
| | | position: "" as string, |
| | | status: undefined as number | undefined, |
| | | staffGroupId: undefined as number | undefined, |
| | | }); |
| | |
| | | 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, |
| | | }; |
| | |
| | | filters.realName = ""; |
| | | filters.staffNo = ""; |
| | | filters.mobile = ""; |
| | | filters.position = ""; |
| | | filters.status = undefined; |
| | | filters.staffGroupId = undefined; |
| | | page.value = 1; |
| | |
| | | } |
| | | 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("已添加"); |
| | |
| | | |
| | | 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 : "操作失败"); |
| | | } |
| | | } |
| | |
| | | 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 : "头像上传失败"); |
| | |
| | | display: grid; |
| | | grid-template-columns: 250px minmax(0, 1fr); |
| | | gap: 12px; |
| | | height: calc(100vh - 170px); |
| | | min-height: calc(100vh - 170px); |
| | | } |
| | | |
| | |
| | | |
| | | .dept-panel { |
| | | padding: 14px 12px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .content-panel { |
| | |
| | | flex-direction: column; |
| | | gap: 12px; |
| | | min-width: 0; |
| | | min-height: 0; |
| | | } |
| | | |
| | | .panel-title { |
| | |
| | | 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 { |
| | |
| | | |
| | | .table-card { |
| | | padding: 14px 16px 12px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | flex: 1; |
| | | min-height: 0; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .table-toolbar { |
| | |
| | | align-items: center; |
| | | gap: 12px; |
| | | margin-bottom: 12px; |
| | | flex: none; |
| | | } |
| | | |
| | | .staff-table-wrap { |
| | | flex: 1; |
| | | min-height: 0; |
| | | } |
| | | |
| | | .toolbar-left { |
| | |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-top: 14px; |
| | | flex: none; |
| | | } |
| | | |
| | | .pager-total { |