Files
f9-web-ai-test/web/src/views/base/employee/index.vue
T
yanpeng200212 c3e2665257 生产模块拆分为前缫/后缫;修复菜单卡顿与重复请求;支持 IIS 单站点部署
1. 生产模块按工序拆分为两个一级菜单
   - 生产-前缫(落丝记录)、生产-后缫(纤度检验、黑板检验)
   - 旧的「生产管理」(/prod) 目录及子菜单由种子自动清理,页面组件与权限标识保持不变
   - 消息中心跳转路径、首页快捷入口路径同步更新

2. 职工管理独立为一级菜单(原挂在「基础数据」目录下),并清理历史子菜单

3. 修复侧边栏菜单展开卡顿(基础数据等子项较多的目录尤其明显)
   - 去掉 Element Plus 的 height 过渡动画(会逐帧重排全部子项)
   - 收起时改用 height:0 + visibility:hidden 而非 display:none,子项提前完成布局
   - 菜单区独立滚动并预留滚动条宽度(scrollbar-gutter),消除展开时的横向抖动
   - 菜单项增加 contain 布局隔离,淡入动画改为容器级

4. 修复页面首次打开重复请求
   - 分页组件挂载时会触发一次 current-change,页码未变化时不再重复加载(18 个页面)
   - 职工管理去掉 onMounted 与默认选中部门的重复加载
   - 工种管理、原料庄口的注册表下拉由按 key 多次请求改为一次全量拉取

5. 后端增加前端静态托管与 SPA 路由回退,支持 IIS 单站点部署(前后端同源)
6. 新增部署脚本 deploy-iis.ps1(首次部署)与 update-iis.ps1(更新发布)
2026-10-12 14:09:59 +08:00

1022 lines
37 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
import { ref, reactive, nextTick, onMounted } from 'vue';
import request from '../../../utils/request';
import { buildDeptTree } from '../../../utils/dept';
import { ElMessage, ElMessageBox } from 'element-plus';
const workStatusOptions = [
{ label: '全部', value: null },
{ label: '在职', value: 0 },
{ label: '离职', value: 1 }
];
const banciOptions = [
{ label: '全部', value: null },
{ label: '甲', value: 1 },
{ label: '乙', value: 2 }
];
const deptTree = ref([]);
const deptTreeRef = ref(null);
const currentDept = ref(null);
const worktypes = ref([]);
const list = ref([]);
const total = ref(0);
const loading = ref(false);
const selectedRow = ref(null);
const query = reactive({ workStatus: 0, worktypeId: 0, banci: null, like: '', pageIndex: 1, pageSize: 20 });
// 注册表下拉(基础数据\员工档案)
const registryOptions = reactive({
gender: [],
minzu: [],
hunyin: [],
xueli: [],
hetongType: [],
lizhiYuanyin: []
});
const dialogVisible = ref(false);
const dialogTitle = ref('新增职工');
const isEdit = ref(false);
const editId = ref(null);
const form = reactive({
depId: null,
worktypeId: 0,
banci: 1,
code: '',
name: '',
gangwei: '',
phone: '',
idCard: '',
birthDate: null,
address: '',
gender: '',
minzu: '',
hunyinStatus: '',
huji: '',
ruzhiDate: null,
jinjiLianxiren: '',
jinjiLianxirenPhone: '',
hetongType: '',
hetongStartDate: null,
hetongEndDate: null,
tuixiuDate: null,
shebaoDate: null,
shebaoDesc: '',
bankcardCode: '',
biyeYuanxiao: '',
zhuanye: '',
xueli: '',
workGroup: '',
sort: 0,
namePinyin: '',
memo: ''
});
// 离职 / 调岗 / 生产信息
const lizhiVisible = ref(false);
const lizhiForm = reactive({ lizhiDate: null, lizhiYuanyin: '' });
const tiaogangVisible = ref(false);
const tiaogangTitle = ref('调岗');
const isRenew = ref(false);
const tiaogangForm = reactive({ date: null, depId: null, worktypeId: 0, gangweiName: '', banci: 1 });
const scVisible = ref(false);
const scForm = reactive({ workGroup: '', sort: 0 });
// 变更记录 / 调岗记录
const changeVisible = ref(false);
const changeRecords = ref([]);
const tiaogangRecordVisible = ref(false);
const tiaogangRecords = ref([]);
async function loadDepts() {
const res = await request.get('/sys/department');
deptTree.value = buildDeptTree(res.data);
await nextTick();
if (!currentDept.value && res.data?.length) {
const first = res.data[0];
selectDept(first.id, first.name);
}
}
// 工种及其可属部门一次性拉取(接口已返回 departmentIds),避免逐条请求导致页面卡顿
async function loadWorktypes() {
const res = await request.get('/sys/worktype', { params: { pageIndex: 1, pageSize: 500 } });
worktypes.value = (res.data.items || []).map((w) => ({
id: Number(w.id),
workName: w.workName,
depIds: (w.departmentIds || []).map(Number)
}));
}
function worktypesOfDep(depId) {
if (!depId) return [];
return worktypes.value.filter((w) => (w.depIds || []).includes(Number(depId)));
}
function onDepSelectWorktype() {
const options = worktypesOfDep(form.depId);
if (!options.some((w) => w.id === form.worktypeId)) form.worktypeId = 0;
}
function onWorktypeSelect() {
const hit = worktypes.value.find((w) => w.id === form.worktypeId);
if (hit) form.gangwei = hit.workName;
}
function onTgDepChange() {
const options = worktypesOfDep(tiaogangForm.depId);
if (!options.some((w) => w.id === tiaogangForm.worktypeId)) tiaogangForm.worktypeId = 0;
}
function onTgWorktypeChange() {
const hit = worktypes.value.find((w) => w.id === tiaogangForm.worktypeId);
if (hit) tiaogangForm.gangweiName = hit.workName;
}
// 注册表下拉:一次拉取全量后本地取用,避免每个键各发一次请求
async function loadRegistryOptions() {
try {
const res = await request.get('/sys/registry');
const all = res.data || [];
const pick = (keyName) => {
const hit = all.find((x) => x.category === '基础数据\\员工档案' && x.keyName === keyName);
return (hit?.value || '')
.split(',')
.map((s) => s.trim())
.filter(Boolean);
};
registryOptions.gender = pick('性别');
registryOptions.minzu = pick('民族');
registryOptions.hunyin = pick('婚姻状态');
registryOptions.xueli = pick('学历');
registryOptions.hetongType = pick('合同类型');
registryOptions.lizhiYuanyin = pick('离职原因');
} catch (e) {
// 注册表未配置时下拉为空
}
}
function selectDept(id, name) {
currentDept.value = { id, name };
deptTreeRef.value?.setCurrentKey(id);
query.pageIndex = 1;
loadList();
}
function handleDeptClick(node) {
currentDept.value = { id: node.id, name: node.name };
query.pageIndex = 1;
loadList();
}
// 分页组件挂载时会触发一次 current-change:页码未变化时直接忽略,避免页面首次打开重复请求
function handlePageChange(p) {
if (p === query.pageIndex) return;
query.pageIndex = p;
loadList();
}
async function loadList() {
if (!currentDept.value) {
ElMessage.warning('请选择部门');
return;
}
loading.value = true;
try {
const res = await request.get('/sys/employee', {
params: {
depId: currentDept.value.id,
worktypeId: query.worktypeId || 0,
workStatus: query.workStatus === null ? undefined : query.workStatus,
banci: query.banci === null ? undefined : query.banci,
like: query.like || undefined,
pageIndex: query.pageIndex,
pageSize: query.pageSize
}
});
list.value = res.data.items || [];
total.value = res.data.total || 0;
} finally {
loading.value = false;
}
}
function handleSearch() {
query.pageIndex = 1;
loadList();
}
function handleRowClick(row) {
selectedRow.value = row;
}
function requireSelected() {
if (!selectedRow.value) {
ElMessage.warning('请先选择职工');
return false;
}
return true;
}
/* ==================== 新增 / 编辑 ==================== */
function openCreate() {
if (!currentDept.value) {
ElMessage.warning('请选择部门');
return;
}
isEdit.value = false;
dialogTitle.value = '新增职工';
Object.assign(form, {
depId: currentDept.value.id,
worktypeId: 0,
banci: 1,
code: '',
name: '',
gangwei: '',
phone: '',
idCard: '',
birthDate: null,
address: '',
gender: '男',
minzu: '汉',
hunyinStatus: '未婚',
huji: '',
ruzhiDate: new Date().toISOString().slice(0, 10),
jinjiLianxiren: '',
jinjiLianxirenPhone: '',
hetongType: '',
hetongStartDate: null,
hetongEndDate: null,
tuixiuDate: null,
shebaoDate: null,
shebaoDesc: '',
bankcardCode: '',
biyeYuanxiao: '',
zhuanye: '',
xueli: '',
workGroup: '',
sort: 0,
namePinyin: '',
memo: ''
});
dialogVisible.value = true;
}
async function openEdit(row) {
const res = await request.get(`/sys/employee/${row.id}`);
const e = res.data;
isEdit.value = true;
editId.value = e.id;
dialogTitle.value = `修改职工「${e.name}」`;
Object.assign(form, {
depId: Number(e.depId),
worktypeId: Number(e.worktypeId),
banci: e.banci ?? 1,
code: e.code || '',
name: e.name || '',
gangwei: e.gangwei || '',
phone: e.phone || '',
idCard: e.idCard || '',
birthDate: e.birthDate ? String(e.birthDate).slice(0, 10) : null,
address: e.address || '',
gender: e.gender || '',
minzu: e.minzu || '',
hunyinStatus: e.hunyinStatus || '',
huji: e.huji || '',
ruzhiDate: e.ruzhiDate ? String(e.ruzhiDate).slice(0, 10) : null,
jinjiLianxiren: e.jinjiLianxiren || '',
jinjiLianxirenPhone: e.jinjiLianxirenPhone || '',
hetongType: e.hetongType || '',
hetongStartDate: e.hetongStartDate ? String(e.hetongStartDate).slice(0, 10) : null,
hetongEndDate: e.hetongEndDate ? String(e.hetongEndDate).slice(0, 10) : null,
tuixiuDate: e.tuixiuDate ? String(e.tuixiuDate).slice(0, 10) : null,
shebaoDate: e.shebaoDate ? String(e.shebaoDate).slice(0, 10) : null,
shebaoDesc: e.shebaoDesc || '',
bankcardCode: e.bankcardCode || '',
biyeYuanxiao: e.biyeYuanxiao || '',
zhuanye: e.zhuanye || '',
xueli: e.xueli || '',
workGroup: e.workGroup || '',
sort: e.sort || 0,
namePinyin: e.namePinyin || '',
memo: e.memo || ''
});
dialogVisible.value = true;
}
async function handleSave() {
if (!form.name) {
ElMessage.warning('请输入姓名');
return;
}
if (!form.depId) {
ElMessage.warning('请选择部门');
return;
}
try {
if (isEdit.value) {
await request.put(`/sys/employee/${editId.value}`, {
code: form.code,
name: form.name,
gangwei: form.gangwei,
phone: form.phone,
idCard: form.idCard,
birthDate: form.birthDate,
address: form.address,
gender: form.gender,
minzu: form.minzu,
hunyinStatus: form.hunyinStatus,
huji: form.huji,
ruzhiDate: form.ruzhiDate,
jinjiLianxiren: form.jinjiLianxiren,
jinjiLianxirenPhone: form.jinjiLianxirenPhone,
hetongType: form.hetongType,
hetongStartDate: form.hetongStartDate,
hetongEndDate: form.hetongEndDate,
tuixiuDate: form.tuixiuDate,
shebaoDate: form.shebaoDate,
shebaoDesc: form.shebaoDesc,
bankcardCode: form.bankcardCode,
biyeYuanxiao: form.biyeYuanxiao,
zhuanye: form.zhuanye,
xueli: form.xueli,
namePinyin: form.namePinyin,
memo: form.memo
});
ElMessage.success('修改成功');
} else {
await request.post('/sys/employee', {
depId: form.depId,
worktypeId: form.worktypeId,
code: form.code,
name: form.name,
gangwei: form.gangwei,
phone: form.phone,
idCard: form.idCard,
birthDate: form.birthDate,
address: form.address,
gender: form.gender,
minzu: form.minzu,
hunyinStatus: form.hunyinStatus,
huji: form.huji,
ruzhiDate: form.ruzhiDate,
jinjiLianxiren: form.jinjiLianxiren,
jinjiLianxirenPhone: form.jinjiLianxirenPhone,
hetongType: form.hetongType,
hetongStartDate: form.hetongStartDate,
hetongEndDate: form.hetongEndDate,
tuixiuDate: form.tuixiuDate,
shebaoDate: form.shebaoDate,
shebaoDesc: form.shebaoDesc,
bankcardCode: form.bankcardCode,
biyeYuanxiao: form.biyeYuanxiao,
zhuanye: form.zhuanye,
xueli: form.xueli,
banci: form.banci,
workGroup: form.workGroup,
sort: Number(form.sort) || 0,
namePinyin: form.namePinyin,
memo: form.memo
});
ElMessage.success('新增成功');
}
dialogVisible.value = false;
loadList();
} catch (e) {}
}
async function handleDelete(row) {
await ElMessageBox.confirm(`确定删除职工「${row.name}」吗?`, '提示', { type: 'warning' });
try {
await request.delete(`/sys/employee/${row.id}`);
ElMessage.success('删除成功');
loadList();
} catch (e) {}
}
/* ==================== 下拉操作 ==================== */
async function handleCommand(command) {
if (!requireSelected()) return;
const row = selectedRow.value;
if (command === 'zhuanzheng') {
try {
await request.put(`/sys/employee/${row.id}/zhuanzheng`, { zhuanzhengDate: new Date().toISOString().slice(0, 10) });
ElMessage.success('转正成功');
loadList();
} catch (e) {}
} else if (command === 'zhuanzhengCancel') {
await ElMessageBox.confirm('是否取消转正?', '提示', { type: 'warning' });
try {
await request.put(`/sys/employee/${row.id}/zhuanzheng-cancel`);
ElMessage.success('已取消转正');
loadList();
} catch (e) {}
} else if (command === 'lizhi') {
lizhiForm.lizhiDate = new Date().toISOString().slice(0, 10);
lizhiForm.lizhiYuanyin = registryOptions.lizhiYuanyin[0] || '';
lizhiVisible.value = true;
} else if (command === 'lizhiCancel') {
await ElMessageBox.confirm('是否取消离职?', '提示', { type: 'warning' });
try {
await request.put(`/sys/employee/${row.id}/lizhi-cancel`);
ElMessage.success('已取消离职');
loadList();
} catch (e) {}
} else if (command === 'tiaogang' || command === 'renew') {
if (command === 'renew' && Number(row.workStatus) !== 1) {
ElMessage.warning('请选择离职人员');
return;
}
isRenew.value = command === 'renew';
tiaogangTitle.value = isRenew.value ? '重新入职' : '调岗';
Object.assign(tiaogangForm, {
date: new Date().toISOString().slice(0, 10),
depId: Number(row.depId),
worktypeId: Number(row.worktypeId),
gangweiName: row.gangwei || '',
banci: row.banci ?? 1
});
tiaogangVisible.value = true;
} else if (command === 'sc') {
scForm.workGroup = row.workGroup || '';
scForm.sort = row.sort || 0;
scVisible.value = true;
} else if (command === 'changeRecords') {
const res = await request.get(`/sys/employee/${row.id}/change-records`);
changeRecords.value = res.data || [];
changeVisible.value = true;
} else if (command === 'tiaogangRecords') {
const res = await request.get(`/sys/employee/${row.id}/tiaogang-records`);
tiaogangRecords.value = res.data || [];
tiaogangRecordVisible.value = true;
}
}
async function saveLizhi() {
if (!lizhiForm.lizhiYuanyin) {
ElMessage.warning('请选择离职原因');
return;
}
try {
await request.put(`/sys/employee/${selectedRow.value.id}/lizhi`, {
lizhiDate: lizhiForm.lizhiDate,
lizhiYuanyin: lizhiForm.lizhiYuanyin
});
ElMessage.success('已办理离职');
lizhiVisible.value = false;
loadList();
} catch (e) {}
}
async function saveTiaogang() {
if (!tiaogangForm.depId) {
ElMessage.warning('请选择部门');
return;
}
const row = selectedRow.value;
const payload = {
depId: tiaogangForm.depId,
workTypeId: tiaogangForm.worktypeId,
date: tiaogangForm.date,
gangweiName: tiaogangForm.gangweiName,
sc_Banci: tiaogangForm.banci
};
try {
if (isRenew.value) await request.put(`/sys/employee/${row.id}/renew-ruzhis`, payload);
else await request.put(`/sys/employee/${row.id}/tiaogang`, payload);
ElMessage.success(isRenew.value ? '重新入职成功' : '调岗成功');
tiaogangVisible.value = false;
loadList();
} catch (e) {}
}
async function saveSc() {
try {
await request.put(`/sys/employee/${selectedRow.value.id}/sc`, {
sc_WorkGroup: scForm.workGroup,
sc_Sort: Number(scForm.sort) || 0
});
ElMessage.success('修改成功');
scVisible.value = false;
loadList();
} catch (e) {}
}
function fmtDate(v) {
return v ? String(v).slice(0, 10) : '';
}
onMounted(async () => {
await Promise.all([loadDepts(), loadWorktypes(), loadRegistryOptions()]);
// loadDepts 默认选中首个部门时已触发加载,此处不再重复请求
if (!currentDept.value) loadList();
});
</script>
<template>
<el-card shadow="never">
<template #header>
<div class="card-header">
<span>职工管理</span>
<div>
<el-button type="primary" @click="openCreate">新增职工</el-button>
<el-dropdown style="margin-left: 8px" @command="handleCommand">
<el-button>
更多操作<el-icon class="el-icon--right"><arrow-down /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="zhuanzheng">转正</el-dropdown-item>
<el-dropdown-item command="zhuanzhengCancel">取消转正</el-dropdown-item>
<el-dropdown-item command="tiaogang">调岗</el-dropdown-item>
<el-dropdown-item command="sc">修改生产信息</el-dropdown-item>
<el-dropdown-item command="lizhi">离职</el-dropdown-item>
<el-dropdown-item command="lizhiCancel">取消离职</el-dropdown-item>
<el-dropdown-item command="renew">重新入职</el-dropdown-item>
<el-dropdown-item divided command="changeRecords">变更记录</el-dropdown-item>
<el-dropdown-item command="tiaogangRecords">调岗记录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</template>
<div class="employee-body">
<div class="tree-panel">
<div class="tree-title">部门</div>
<el-tree
ref="deptTreeRef"
:data="deptTree"
node-key="id"
highlight-current
default-expand-all
:props="{ label: 'name', children: 'children' }"
@node-click="handleDeptClick"
/>
</div>
<div class="table-panel">
<div class="search-bar">
<el-select v-model="query.workStatus" style="width: 110px" @change="handleSearch">
<el-option v-for="s in workStatusOptions" :key="String(s.value)" :label="s.label" :value="s.value" />
</el-select>
<el-select v-model="query.worktypeId" placeholder="工种(全部)" clearable filterable style="width: 150px" @change="handleSearch">
<el-option v-for="w in worktypes" :key="w.id" :label="w.workName" :value="w.id" />
</el-select>
<el-select v-model="query.banci" placeholder="班次(全部)" clearable style="width: 130px" @change="handleSearch">
<el-option v-for="b in banciOptions" :key="String(b.value)" :label="b.label" :value="b.value" />
</el-select>
<el-input
v-model="query.like"
placeholder="姓名 / 工号 / 电话 / 身份证 / 岗位"
clearable
style="width: 250px"
@keyup.enter="handleSearch"
@clear="handleSearch"
/>
<el-button type="primary" @click="handleSearch">查询</el-button>
<span class="tip">当前部门:{{ currentDept?.name || '未选择' }}(含子部门)</span>
</div>
<el-table
v-loading="loading"
:data="list"
row-key="id"
border
stripe
height="560"
highlight-current-row
style="width: 100%"
@row-click="handleRowClick"
>
<el-table-column type="index" label="序号" width="60" align="center" fixed="left" />
<el-table-column prop="depName" label="部门" width="110" fixed="left" />
<el-table-column prop="worktypeName" label="工种" width="100" />
<el-table-column label="班次" width="70">
<template #default="{ row }">{{ row.banci === 1 ? '甲' : row.banci === 2 ? '乙' : '' }}</template>
</el-table-column>
<el-table-column prop="gangwei" label="岗位" width="100" />
<el-table-column label="状态" width="130" fixed="left">
<template #default="{ row }">
<el-tag :type="Number(row.workStatus) === 0 ? 'success' : 'warning'">
{{ Number(row.workStatus) === 0 ? '在职' : '离职' }}
</el-tag>
<el-tag v-if="!row.isZhuanzheng" type="danger" style="margin-left: 4px">未转正</el-tag>
</template>
</el-table-column>
<el-table-column prop="name" label="姓名" width="90" />
<el-table-column prop="gender" label="性别" width="60" />
<el-table-column prop="nianling" label="年龄" width="60" />
<el-table-column prop="idCard" label="身份证" width="180" />
<el-table-column prop="phone" label="电话" width="120" />
<el-table-column label="入职日期" width="110">
<template #default="{ row }">{{ fmtDate(row.ruzhiDate) }}</template>
</el-table-column>
<el-table-column label="转正" width="70">
<template #default="{ row }">{{ row.isZhuanzheng ? '已转正' : '未转正' }}</template>
</el-table-column>
<el-table-column label="转正日期" width="110">
<template #default="{ row }">{{ fmtDate(row.zhuanzhengDate) }}</template>
</el-table-column>
<el-table-column prop="workGroup" label="工作组" width="100" />
<el-table-column prop="sort" label="顺序号" width="80" />
<el-table-column prop="bankcardCode" label="银行卡" width="180" />
<el-table-column prop="hetongType" label="合同类型" width="100" />
<el-table-column label="合同开始" width="110">
<template #default="{ row }">{{ fmtDate(row.hetongStartDate) }}</template>
</el-table-column>
<el-table-column label="合同结束" width="110">
<template #default="{ row }">{{ fmtDate(row.hetongEndDate) }}</template>
</el-table-column>
<el-table-column label="退休日期" width="110">
<template #default="{ row }">{{ fmtDate(row.tuixiuDate) }}</template>
</el-table-column>
<el-table-column label="社保参保" width="110">
<template #default="{ row }">{{ fmtDate(row.shebaoDate) }}</template>
</el-table-column>
<el-table-column prop="minzu" label="民族" width="80" />
<el-table-column prop="hunyinStatus" label="婚姻" width="80" />
<el-table-column prop="xueli" label="学历" width="80" />
<el-table-column prop="huji" label="户籍" width="110" />
<el-table-column prop="address" label="住址" width="160" show-overflow-tooltip />
<el-table-column prop="jinjiLianxiren" label="紧急联系人" width="110" />
<el-table-column prop="jinjiLianxirenPhone" label="紧急联系电话" width="130" />
<el-table-column label="离职日期" width="110">
<template #default="{ row }">{{ fmtDate(row.lizhiDate) }}</template>
</el-table-column>
<el-table-column prop="lizhiYuanyin" label="离职原因" width="110" />
<el-table-column prop="biyeYuanxiao" label="毕业院校" width="130" show-overflow-tooltip />
<el-table-column prop="zhuanye" label="专业" width="110" />
<el-table-column prop="code" label="工号" width="100" />
<el-table-column prop="memo" label="备注" min-width="140" show-overflow-tooltip />
<el-table-column label="操作" width="130" fixed="right">
<template #default="{ row }">
<el-button size="small" type="primary" link @click.stop="openEdit(row)">编辑</el-button>
<el-button size="small" type="danger" link @click.stop="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
:current-page="query.pageIndex"
:page-size="query.pageSize"
:total="total"
layout="total, prev, pager, next"
class="pager"
@current-change="handlePageChange"
/>
</div>
</div>
<!-- 新增 / 编辑 -->
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="760px" top="6vh">
<el-tabs>
<el-tab-pane label="基本信息">
<el-form :model="form" label-width="110px">
<el-row :gutter="12">
<el-col :span="12">
<el-form-item label="部门" required>
<el-select v-model="form.depId" filterable style="width: 100%" @change="onDepSelectWorktype">
<template v-for="root in deptTree" :key="root.id">
<el-option :label="root.name" :value="root.id" />
<el-option v-for="c in root.children || []" :key="c.id" :label="` ${c.name}`" :value="c.id" />
<template v-for="c in root.children || []" :key="`g-${c.id}`">
<el-option v-for="g in c.children || []" :key="g.id" :label="`  ${g.name}`" :value="g.id" />
</template>
</template>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="工种">
<el-select v-model="form.worktypeId" filterable clearable style="width: 100%" @change="onWorktypeSelect">
<el-option v-for="w in worktypesOfDep(form.depId)" :key="w.id" :label="w.workName" :value="w.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="姓名" required>
<el-input v-model="form.name" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="岗位">
<el-input v-model="form.gangwei" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="工号">
<el-input v-model="form.code" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="班次" required>
<el-select v-model="form.banci" style="width: 100%">
<el-option label="甲" :value="1" />
<el-option label="乙" :value="2" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="性别">
<el-select v-model="form.gender" filterable allow-create style="width: 100%">
<el-option v-for="v in registryOptions.gender" :key="v" :label="v" :value="v" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="电话">
<el-input v-model="form.phone" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="身份证">
<el-input v-model="form.idCard" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="出生日期">
<el-date-picker v-model="form.birthDate" type="date" value-format="YYYY-MM-DD" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="民族">
<el-select v-model="form.minzu" filterable allow-create style="width: 100%">
<el-option v-for="v in registryOptions.minzu" :key="v" :label="v" :value="v" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="婚姻状态">
<el-select v-model="form.hunyinStatus" filterable allow-create style="width: 100%">
<el-option v-for="v in registryOptions.hunyin" :key="v" :label="v" :value="v" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="户籍">
<el-input v-model="form.huji" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="住址">
<el-input v-model="form.address" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="紧急联系人">
<el-input v-model="form.jinjiLianxiren" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="紧急联系电话">
<el-input v-model="form.jinjiLianxirenPhone" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-tab-pane>
<el-tab-pane label="合同与社保">
<el-form :model="form" label-width="130px">
<el-row :gutter="12">
<el-col :span="12">
<el-form-item label="入职日期">
<el-date-picker v-model="form.ruzhiDate" type="date" value-format="YYYY-MM-DD" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="合同类型">
<el-select v-model="form.hetongType" filterable allow-create style="width: 100%">
<el-option v-for="v in registryOptions.hetongType" :key="v" :label="v" :value="v" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="合同开始日期">
<el-date-picker v-model="form.hetongStartDate" type="date" value-format="YYYY-MM-DD" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="合同结束日期">
<el-date-picker v-model="form.hetongEndDate" type="date" value-format="YYYY-MM-DD" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="社保参保日期">
<el-date-picker v-model="form.shebaoDate" type="date" value-format="YYYY-MM-DD" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="退休日期">
<el-date-picker v-model="form.tuixiuDate" type="date" value-format="YYYY-MM-DD" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="社保参保情况">
<el-input v-model="form.shebaoDesc" type="textarea" :rows="2" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="银行卡号">
<el-input v-model="form.bankcardCode" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-tab-pane>
<el-tab-pane label="教育与生产">
<el-form :model="form" label-width="130px">
<el-row :gutter="12">
<el-col :span="12">
<el-form-item label="学历">
<el-select v-model="form.xueli" filterable allow-create style="width: 100%">
<el-option v-for="v in registryOptions.xueli" :key="v" :label="v" :value="v" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="毕业院校">
<el-input v-model="form.biyeYuanxiao" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="专业">
<el-input v-model="form.zhuanye" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="姓名简拼">
<el-input v-model="form.namePinyin" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="工作组">
<el-input v-model="form.workGroup" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="顺序号">
<el-input-number v-model="form.sort" :min="0" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="备注">
<el-input v-model="form.memo" type="textarea" :rows="2" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-tab-pane>
</el-tabs>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSave">确定</el-button>
</template>
</el-dialog>
<!-- 离职 -->
<el-dialog v-model="lizhiVisible" title="离职" width="420px">
<el-form :model="lizhiForm" label-width="90px">
<el-form-item label="离职日期">
<el-date-picker v-model="lizhiForm.lizhiDate" type="date" value-format="YYYY-MM-DD" style="width: 100%" />
</el-form-item>
<el-form-item label="离职原因" required>
<el-select v-model="lizhiForm.lizhiYuanyin" filterable allow-create style="width: 100%">
<el-option v-for="v in registryOptions.lizhiYuanyin" :key="v" :label="v" :value="v" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="lizhiVisible = false">取消</el-button>
<el-button type="primary" @click="saveLizhi">确定</el-button>
</template>
</el-dialog>
<!-- 调岗 / 重新入职 -->
<el-dialog v-model="tiaogangVisible" :title="tiaogangTitle" width="480px">
<el-form :model="tiaogangForm" label-width="90px">
<el-form-item :label="isRenew ? '入职日期' : '调岗日期'">
<el-date-picker v-model="tiaogangForm.date" type="date" value-format="YYYY-MM-DD" style="width: 100%" />
</el-form-item>
<el-form-item label="部门" required>
<el-select v-model="tiaogangForm.depId" filterable style="width: 100%" @change="onTgDepChange">
<template v-for="root in deptTree" :key="root.id">
<el-option :label="root.name" :value="root.id" />
<el-option v-for="c in root.children || []" :key="c.id" :label="` ${c.name}`" :value="c.id" />
</template>
</el-select>
</el-form-item>
<el-form-item label="工种">
<el-select v-model="tiaogangForm.worktypeId" filterable clearable style="width: 100%" @change="onTgWorktypeChange">
<el-option v-for="w in worktypesOfDep(tiaogangForm.depId)" :key="w.id" :label="w.workName" :value="w.id" />
</el-select>
</el-form-item>
<el-form-item label="岗位">
<el-input v-model="tiaogangForm.gangweiName" />
</el-form-item>
<el-form-item label="班次">
<el-select v-model="tiaogangForm.banci" style="width: 100%">
<el-option label="甲" :value="1" />
<el-option label="乙" :value="2" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="tiaogangVisible = false">取消</el-button>
<el-button type="primary" @click="saveTiaogang">确定</el-button>
</template>
</el-dialog>
<!-- 生产信息 -->
<el-dialog v-model="scVisible" title="修改生产信息" width="420px">
<el-form :model="scForm" label-width="90px">
<el-form-item label="工作组">
<el-input v-model="scForm.workGroup" />
</el-form-item>
<el-form-item label="顺序号">
<el-input-number v-model="scForm.sort" :min="0" style="width: 100%" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="scVisible = false">取消</el-button>
<el-button type="primary" @click="saveSc">确定</el-button>
</template>
</el-dialog>
<!-- 变更记录 -->
<el-dialog v-model="changeVisible" title="变更记录" width="620px">
<el-table :data="changeRecords" border stripe max-height="420">
<el-table-column label="日期" width="120">
<template #default="{ row }">{{ fmtDate(row.date) }}</template>
</el-table-column>
<el-table-column prop="changeStatusName" label="类型" width="100" />
<el-table-column prop="changeShuoming" label="变更说明" min-width="180" />
<el-table-column prop="changeYuanyin" label="原因" width="120" />
</el-table>
</el-dialog>
<!-- 调岗记录 -->
<el-dialog v-model="tiaogangRecordVisible" title="调岗记录" width="720px">
<el-table :data="tiaogangRecords" border stripe max-height="420">
<el-table-column label="调岗日期" width="120">
<template #default="{ row }">{{ fmtDate(row.tiaogangDate) }}</template>
</el-table-column>
<el-table-column prop="oldDepName" label="原部门" width="120" />
<el-table-column prop="oldWorktypeName" label="原工种" width="110" />
<el-table-column prop="newDepName" label="新部门" width="120" />
<el-table-column prop="newWorktypeName" label="新工种" width="110" />
</el-table>
</el-dialog>
</el-card>
</template>
<style scoped>
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.employee-body {
display: flex;
gap: 12px;
}
.tree-panel {
width: 220px;
flex-shrink: 0;
border-right: 1px solid var(--el-border-color-lighter);
padding-right: 8px;
max-height: 640px;
overflow: auto;
}
.tree-title {
font-weight: 600;
margin-bottom: 8px;
}
.table-panel {
flex: 1;
min-width: 0;
}
.search-bar {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
margin-bottom: 12px;
}
.tip {
color: var(--el-text-color-secondary);
font-size: 12px;
}
.pager {
margin-top: 12px;
justify-content: flex-end;
}
</style>