|
|
@@ -0,0 +1,369 @@
|
|
|
+<template>
|
|
|
+ <div class="app-page">
|
|
|
+ <section class="glass-card section-card">
|
|
|
+ <el-form :model="filters" inline class="filter-form">
|
|
|
+ <el-form-item label="工号/姓名">
|
|
|
+ <el-input v-model="filters.keyword" placeholder="工号或姓名" clearable style="width:140px" @keyup.enter="loadData" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="部门">
|
|
|
+ <el-select v-model="filters.department" placeholder="全部部门" clearable style="width:140px">
|
|
|
+ <el-option label="研发部" value="研发部" />
|
|
|
+ <el-option label="销售部" value="销售部" />
|
|
|
+ <el-option label="采购部" value="采购部" />
|
|
|
+ <el-option label="仓储部" value="仓储部" />
|
|
|
+ <el-option label="财务部" value="财务部" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="岗位">
|
|
|
+ <el-input v-model="filters.position" placeholder="岗位名称" clearable style="width:130px" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="角色">
|
|
|
+ <el-select v-model="filters.role" placeholder="全部角色" clearable style="width:130px">
|
|
|
+ <el-option label="管理员" value="admin" />
|
|
|
+ <el-option label="经理" value="manager" />
|
|
|
+ <el-option label="运营" value="operator" />
|
|
|
+ <el-option label="采购" value="procurement" />
|
|
|
+ <el-option label="仓库" value="warehouse" />
|
|
|
+ <el-option label="客服" value="customer_service" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="状态">
|
|
|
+ <el-select v-model="filters.status" placeholder="全部" clearable style="width:100px">
|
|
|
+ <el-option label="在职" value="在职" />
|
|
|
+ <el-option label="离职" value="离职" />
|
|
|
+ <el-option label="待入职" value="待入职" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button type="primary" @click="loadData">查询</el-button>
|
|
|
+ <el-button @click="resetFilters">重置</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <section class="glass-card section-card" style="padding:12px 24px">
|
|
|
+ <div class="table-toolbar" style="margin-bottom:0">
|
|
|
+ <div class="chip-list">
|
|
|
+ <el-button type="primary" @click="openCreate">新建员工</el-button>
|
|
|
+ <el-button @click="doExport">导出</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <section class="glass-card section-card">
|
|
|
+ <el-table :data="filteredItems" stripe style="width:100%" v-loading="loading" @selection-change="onSelection">
|
|
|
+ <el-table-column type="selection" width="45" />
|
|
|
+ <el-table-column prop="employeeNo" label="工号" width="100" />
|
|
|
+ <el-table-column prop="name" label="姓名" width="100" />
|
|
|
+ <el-table-column prop="avatar" label="头像" width="70">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-avatar :size="36" :src="row.avatar" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="department" label="部门" width="100" />
|
|
|
+ <el-table-column prop="position" label="岗位" width="120" />
|
|
|
+ <el-table-column prop="role" label="系统角色" width="100">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag size="small">{{ roleLabel(row.role) }}</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="phone" label="手机号" width="130" />
|
|
|
+ <el-table-column prop="email" label="邮箱" min-width="180" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="status" label="状态" width="80">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag :type="statusTag(row.status)" size="small">{{ row.status }}</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="hireDate" label="入职日期" width="110" />
|
|
|
+ <el-table-column prop="supervisor" label="汇报给" width="100" />
|
|
|
+ <el-table-column label="操作" width="160" fixed="right">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-button link type="primary" @click="openDetail(row)">详情</el-button>
|
|
|
+ <el-button link type="primary" @click="openEdit(row)">编辑</el-button>
|
|
|
+ <el-button link type="danger" @click="terminateEmployee(row)" v-if="row.status === '在职'">离职</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <template #empty>
|
|
|
+ <el-empty description="暂无数据" />
|
|
|
+ </template>
|
|
|
+ </el-table>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <section class="glass-card section-card" style="padding:16px">
|
|
|
+ <h4 style="margin:0 0 12px">员工统计</h4>
|
|
|
+ <div class="stat-grid" style="grid-template-columns:repeat(4, 1fr)">
|
|
|
+ <article class="stat-card">
|
|
|
+ <div class="stat-card__label">总人数</div>
|
|
|
+ <div class="stat-card__value" style="font-size:24px;color:var(--cb-primary)">156</div>
|
|
|
+ </article>
|
|
|
+ <article class="stat-card">
|
|
|
+ <div class="stat-card__label">在职</div>
|
|
|
+ <div class="stat-card__value" style="font-size:24px;color:var(--cb-success)">142</div>
|
|
|
+ </article>
|
|
|
+ <article class="stat-card">
|
|
|
+ <div class="stat-card__label">本月新增</div>
|
|
|
+ <div class="stat-card__value" style="font-size:24px">8</div>
|
|
|
+ </article>
|
|
|
+ <article class="stat-card">
|
|
|
+ <div class="stat-card__label">本月离职</div>
|
|
|
+ <div class="stat-card__value" style="font-size:24px;color:var(--cb-danger)">3</div>
|
|
|
+ </article>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <el-dialog v-model="formVisible" :title="isEdit ? '编辑员工' : '新建员工'" width="600px">
|
|
|
+ <el-form :model="employeeForm" label-width="100px">
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="工号" required>
|
|
|
+ <el-input v-model="employeeForm.employeeNo" placeholder="自动生成或手动输入" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="姓名" required>
|
|
|
+ <el-input v-model="employeeForm.name" placeholder="请输入姓名" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="部门" required>
|
|
|
+ <el-select v-model="employeeForm.department" placeholder="选择部门" style="width:100%">
|
|
|
+ <el-option label="研发部" value="研发部" />
|
|
|
+ <el-option label="销售部" value="销售部" />
|
|
|
+ <el-option label="采购部" value="采购部" />
|
|
|
+ <el-option label="仓储部" value="仓储部" />
|
|
|
+ <el-option label="财务部" value="财务部" />
|
|
|
+ <el-option label="人力资源部" value="人力资源部" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="岗位" required>
|
|
|
+ <el-input v-model="employeeForm.position" placeholder="请输入岗位" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="系统角色" required>
|
|
|
+ <el-select v-model="employeeForm.role" placeholder="选择角色" style="width:100%">
|
|
|
+ <el-option label="管理员" value="admin" />
|
|
|
+ <el-option label="经理" value="manager" />
|
|
|
+ <el-option label="运营" value="operator" />
|
|
|
+ <el-option label="采购" value="procurement" />
|
|
|
+ <el-option label="仓库" value="warehouse" />
|
|
|
+ <el-option label="客服" value="customer_service" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="汇报给">
|
|
|
+ <el-select v-model="employeeForm.supervisorId" placeholder="选择上级" clearable style="width:100%">
|
|
|
+ <el-option v-for="e in employees" :key="e.id" :label="e.name" :value="e.id" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="手机号" required>
|
|
|
+ <el-input v-model="employeeForm.phone" placeholder="请输入手机号" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="邮箱">
|
|
|
+ <el-input v-model="employeeForm.email" placeholder="请输入邮箱" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-form-item label="入职日期">
|
|
|
+ <el-date-picker v-model="employeeForm.hireDate" type="date" placeholder="选择日期" style="width:100%" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="备注">
|
|
|
+ <el-input v-model="employeeForm.remark" type="textarea" rows="3" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="formVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="submitForm">{{ isEdit ? '保存' : '创建' }}</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <el-dialog v-model="detailVisible" title="员工详情" width="600px">
|
|
|
+ <el-descriptions :column="2" border v-if="detailItem">
|
|
|
+ <el-descriptions-item label="工号">{{ detailItem.employeeNo }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="姓名">{{ detailItem.name }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="部门">{{ detailItem.department }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="岗位">{{ detailItem.position }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="系统角色">
|
|
|
+ <el-tag size="small">{{ roleLabel(detailItem.role) }}</el-tag>
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="状态">
|
|
|
+ <el-tag :type="statusTag(detailItem.status)" size="small">{{ detailItem.status }}</el-tag>
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="手机号">{{ detailItem.phone }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="邮箱">{{ detailItem.email }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="入职日期">{{ detailItem.hireDate }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="汇报给">{{ detailItem.supervisor }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="备注" :span="2">{{ detailItem.remark || '-' }}</el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="detailVisible = false">关闭</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { computed, onMounted, ref } from 'vue';
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus';
|
|
|
+
|
|
|
+interface EmployeeItem {
|
|
|
+ id: string;
|
|
|
+ employeeNo: string;
|
|
|
+ name: string;
|
|
|
+ avatar: string;
|
|
|
+ department: string;
|
|
|
+ position: string;
|
|
|
+ role: string;
|
|
|
+ phone: string;
|
|
|
+ email: string;
|
|
|
+ status: string;
|
|
|
+ hireDate: string;
|
|
|
+ supervisor: string;
|
|
|
+ supervisorId: string;
|
|
|
+ remark: string;
|
|
|
+}
|
|
|
+
|
|
|
+const items = ref<EmployeeItem[]>([
|
|
|
+ { id: 'E001', employeeNo: 'EMP001', name: '张明', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Zhang', department: '研发部', position: '技术总监', role: 'admin', phone: '138-0000-1001', email: 'zhangming@company.com', status: '在职', hireDate: '2020-03-15', supervisor: '李总', supervisorId: '', remark: '' },
|
|
|
+ { id: 'E002', employeeNo: 'EMP002', name: '李华', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Li', department: '销售部', position: '销售经理', role: 'manager', phone: '139-0000-1002', email: 'lihua@company.com', status: '在职', hireDate: '2021-06-20', supervisor: '王总', supervisorId: '', remark: '' },
|
|
|
+ { id: 'E003', employeeNo: 'EMP003', name: '王芳', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Wang', department: '采购部', position: '采购主管', role: 'procurement', phone: '137-0000-1003', email: 'wangfang@company.com', status: '在职', hireDate: '2022-01-10', supervisor: '张明', supervisorId: 'E001', remark: '' },
|
|
|
+ { id: 'E004', employeeNo: 'EMP004', name: '刘强', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Liu', department: '仓储部', position: '仓库主管', role: 'warehouse', phone: '136-0000-1004', email: 'liuqiang@company.com', status: '在职', hireDate: '2021-09-05', supervisor: '张明', supervisorId: 'E001', remark: '' },
|
|
|
+ { id: 'E005', employeeNo: 'EMP005', name: '陈静', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Chen', department: '财务部', position: '财务经理', role: 'finance', phone: '135-0000-1005', email: 'chenjing@company.com', status: '在职', hireDate: '2020-08-25', supervisor: '李总', supervisorId: '', remark: '' },
|
|
|
+ { id: 'E006', employeeNo: 'EMP006', name: '周伟', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Zhou', department: '销售部', position: '运营专员', role: 'operator', phone: '134-0000-1006', email: 'zhouwei@company.com', status: '在职', hireDate: '2023-03-01', supervisor: '李华', supervisorId: 'E002', remark: '' },
|
|
|
+ { id: 'E007', employeeNo: 'EMP007', name: '吴婷', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Wu', department: '客服部', position: '客服主管', role: 'customer_service', phone: '133-0000-1007', email: 'wuting@company.com', status: '在职', hireDate: '2022-07-15', supervisor: '李华', supervisorId: 'E002', remark: '' },
|
|
|
+ { id: 'E008', employeeNo: 'EMP008', name: '赵磊', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Zhao', department: '研发部', position: '高级工程师', role: 'operator', phone: '132-0000-1008', email: 'zhaolei@company.com', status: '离职', hireDate: '2021-04-20', supervisor: '张明', supervisorId: 'E001', remark: '2024-12-31离职' }
|
|
|
+]);
|
|
|
+
|
|
|
+const employees = ref<{ id: string; name: string }[]>([
|
|
|
+ { id: 'E001', name: '张明' },
|
|
|
+ { id: 'E002', name: '李华' }
|
|
|
+]);
|
|
|
+
|
|
|
+const loading = ref(false);
|
|
|
+const selected = ref<EmployeeItem[]>([]);
|
|
|
+const formVisible = ref(false);
|
|
|
+const detailVisible = ref(false);
|
|
|
+const isEdit = ref(false);
|
|
|
+const detailItem = ref<EmployeeItem | null>(null);
|
|
|
+
|
|
|
+const filters = ref({
|
|
|
+ keyword: '',
|
|
|
+ department: '',
|
|
|
+ position: '',
|
|
|
+ role: '',
|
|
|
+ status: ''
|
|
|
+});
|
|
|
+
|
|
|
+const employeeForm = ref({
|
|
|
+ employeeNo: '',
|
|
|
+ name: '',
|
|
|
+ department: '',
|
|
|
+ position: '',
|
|
|
+ role: '',
|
|
|
+ supervisorId: '',
|
|
|
+ phone: '',
|
|
|
+ email: '',
|
|
|
+ hireDate: '',
|
|
|
+ remark: ''
|
|
|
+});
|
|
|
+
|
|
|
+const filteredItems = computed(() => {
|
|
|
+ return items.value.filter(item => {
|
|
|
+ if (filters.value.keyword && !item.employeeNo.includes(filters.value.keyword) && !item.name.includes(filters.value.keyword)) return false;
|
|
|
+ if (filters.value.department && item.department !== filters.value.department) return false;
|
|
|
+ if (filters.value.position && !item.position.includes(filters.value.position)) return false;
|
|
|
+ if (filters.value.role && item.role !== filters.value.role) return false;
|
|
|
+ if (filters.value.status && item.status !== filters.value.status) return false;
|
|
|
+ return true;
|
|
|
+ });
|
|
|
+});
|
|
|
+
|
|
|
+const roleLabel = (role: string) => {
|
|
|
+ const map: Record<string, string> = { 'admin': '管理员', 'manager': '经理', 'operator': '运营', 'procurement': '采购', 'warehouse': '仓库', 'customer_service': '客服', 'finance': '财务' };
|
|
|
+ return map[role] || role;
|
|
|
+};
|
|
|
+
|
|
|
+const statusTag = (status: string) => {
|
|
|
+ const map: Record<string, string> = { '在职': 'success', '离职': 'info', '待入职': 'warning' };
|
|
|
+ return map[status] || '';
|
|
|
+};
|
|
|
+
|
|
|
+const loadData = () => {
|
|
|
+ loading.value = true;
|
|
|
+ setTimeout(() => { loading.value = false; }, 300);
|
|
|
+};
|
|
|
+
|
|
|
+const resetFilters = () => {
|
|
|
+ filters.value = { keyword: '', department: '', position: '', role: '', status: '' };
|
|
|
+};
|
|
|
+
|
|
|
+const onSelection = (rows: EmployeeItem[]) => { selected.value = rows; };
|
|
|
+
|
|
|
+const openCreate = () => {
|
|
|
+ isEdit.value = false;
|
|
|
+ employeeForm.value = { employeeNo: '', name: '', department: '', position: '', role: '', supervisorId: '', phone: '', email: '', hireDate: '', remark: '' };
|
|
|
+ formVisible.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+const openEdit = (row: EmployeeItem) => {
|
|
|
+ isEdit.value = true;
|
|
|
+ detailItem.value = null;
|
|
|
+ employeeForm.value = {
|
|
|
+ employeeNo: row.employeeNo,
|
|
|
+ name: row.name,
|
|
|
+ department: row.department,
|
|
|
+ position: row.position,
|
|
|
+ role: row.role,
|
|
|
+ supervisorId: row.supervisorId,
|
|
|
+ phone: row.phone,
|
|
|
+ email: row.email,
|
|
|
+ hireDate: row.hireDate,
|
|
|
+ remark: row.remark
|
|
|
+ };
|
|
|
+ formVisible.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+const submitForm = () => {
|
|
|
+ if (!employeeForm.value.name || !employeeForm.value.department) {
|
|
|
+ ElMessage.warning('请填写必填项');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ ElMessage.success(isEdit.value ? '员工信息已保存' : '员工已创建');
|
|
|
+ formVisible.value = false;
|
|
|
+};
|
|
|
+
|
|
|
+const openDetail = (row: EmployeeItem) => {
|
|
|
+ detailItem.value = row;
|
|
|
+ detailVisible.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+const terminateEmployee = async (row: EmployeeItem) => {
|
|
|
+ await ElMessageBox.confirm(`确认将 ${row.name} 设置为离职状态?`, '员工离职');
|
|
|
+ const idx = items.value.findIndex(e => e.id === row.id);
|
|
|
+ if (idx !== -1) items.value[idx].status = '离职';
|
|
|
+ ElMessage.success('员工已离职');
|
|
|
+};
|
|
|
+
|
|
|
+const doExport = () => {
|
|
|
+ ElMessage.info('导出开始,完成后将自动下载');
|
|
|
+};
|
|
|
+
|
|
|
+onMounted(loadData);
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.filter-form :deep(.el-form-item) { margin-bottom: 0; }
|
|
|
+</style>
|