增加通讯录
This commit is contained in:
parent
8b5915ba0a
commit
b159d71a77
605
pc/src/views/apps/oa/contacts/index.vue
Normal file
605
pc/src/views/apps/oa/contacts/index.vue
Normal file
@ -0,0 +1,605 @@
|
||||
<template>
|
||||
<div class="container-box">
|
||||
<div class="header-bar">
|
||||
<h2>通讯录</h2>
|
||||
<div class="header-actions">
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索姓名、工号、部门、职位"
|
||||
clearable
|
||||
style="width: 300px; margin-right: 12px"
|
||||
@input="handleSearch"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon><Search /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-button @click="refresh">
|
||||
<el-icon><Refresh /></el-icon>
|
||||
刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-divider></el-divider>
|
||||
|
||||
<!-- 筛选栏 -->
|
||||
<div class="filter-bar">
|
||||
<el-select
|
||||
v-model="selectedDepartment"
|
||||
placeholder="选择部门"
|
||||
clearable
|
||||
style="width: 200px; margin-right: 12px"
|
||||
@change="handleFilter"
|
||||
>
|
||||
<el-option
|
||||
v-for="dept in departmentList"
|
||||
:key="dept.id"
|
||||
:label="dept.name"
|
||||
:value="dept.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-model="selectedStatus"
|
||||
placeholder="选择状态"
|
||||
clearable
|
||||
style="width: 150px"
|
||||
@change="handleFilter"
|
||||
>
|
||||
<el-option label="在职" :value="1" />
|
||||
<el-option label="离职" :value="0" />
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<!-- 员工列表 -->
|
||||
<div class="contacts-container" v-loading="loading">
|
||||
<div
|
||||
v-if="filteredEmployees.length === 0 && !loading"
|
||||
class="empty-state"
|
||||
>
|
||||
<el-empty description="暂无员工信息" />
|
||||
</div>
|
||||
<div v-else class="contacts-grid">
|
||||
<div
|
||||
v-for="employee in paginatedEmployees"
|
||||
:key="employee.id"
|
||||
class="contact-card"
|
||||
@click="handleViewDetail(employee)"
|
||||
>
|
||||
<div class="contact-avatar">
|
||||
<div style="display: flex; align-items: center; gap: 10px">
|
||||
<el-avatar
|
||||
:size="30"
|
||||
:style="{ backgroundColor: getAvatarColor(employee.name) }"
|
||||
>
|
||||
{{ getAvatarText(employee.name) }}
|
||||
</el-avatar>
|
||||
<div class="contact-name">{{ employee.name }}</div>
|
||||
<el-tag
|
||||
:type="employee.status === 1 ? 'success' : 'danger'"
|
||||
size="small"
|
||||
>
|
||||
{{ employee.status === 1 ? "在职" : "离职" }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<el-divider style="margin: 20px" />
|
||||
<div class="contact-info">
|
||||
<div class="contact-meta">
|
||||
<div class="meta-item">
|
||||
<el-icon><User /></el-icon>
|
||||
<span>{{ employee.employeeNo || "-" }}</span>
|
||||
</div>
|
||||
<div class="meta-item">
|
||||
<el-icon><OfficeBuilding /></el-icon>
|
||||
<span>{{ employee.departmentName || "-" }}</span>
|
||||
</div>
|
||||
<div class="meta-item">
|
||||
<el-icon><Briefcase /></el-icon>
|
||||
<span>{{ employee.positionName || "-" }}</span>
|
||||
</div>
|
||||
<div class="meta-item">
|
||||
<el-icon><Phone /></el-icon>
|
||||
<span>{{ employee.phone || "-" }}</span>
|
||||
</div>
|
||||
<div class="meta-item">
|
||||
<el-icon><Message /></el-icon>
|
||||
<span>{{ employee.email || "-" }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-bar" v-if="filteredEmployees.length > 0">
|
||||
<el-pagination
|
||||
background
|
||||
v-model:current-page="page"
|
||||
v-model:page-size="pageSize"
|
||||
:total="filteredEmployees.length"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 员工详情对话框 -->
|
||||
<el-dialog
|
||||
v-model="detailVisible"
|
||||
title="员工详情"
|
||||
width="600px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div v-if="currentEmployee" class="employee-detail">
|
||||
<div class="detail-header">
|
||||
<el-avatar
|
||||
:size="80"
|
||||
:style="{ backgroundColor: getAvatarColor(currentEmployee.name) }"
|
||||
>
|
||||
{{ getAvatarText(currentEmployee.name) }}
|
||||
</el-avatar>
|
||||
<div class="detail-title">
|
||||
<h3>{{ currentEmployee.name }}</h3>
|
||||
<el-tag
|
||||
:type="currentEmployee.status === 1 ? 'success' : 'danger'"
|
||||
size="small"
|
||||
>
|
||||
{{ currentEmployee.status === 1 ? "在职" : "离职" }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<el-divider />
|
||||
<div class="detail-content">
|
||||
<div class="detail-row">
|
||||
<span class="label">工号:</span>
|
||||
<span class="value">{{ currentEmployee.employeeNo || "-" }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="label">部门:</span>
|
||||
<span class="value">{{
|
||||
currentEmployee.departmentName || "-"
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="label">职位:</span>
|
||||
<span class="value">{{ currentEmployee.positionName || "-" }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="label">角色:</span>
|
||||
<span class="value">{{ currentEmployee.roleName || "-" }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="label">手机号:</span>
|
||||
<span class="value">{{ currentEmployee.phone || "-" }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="label">邮箱:</span>
|
||||
<span class="value"
|
||||
>{{ currentEmployee.email || "-" }}
|
||||
<a
|
||||
href="mailto:{{ currentEmployee.email }}"
|
||||
style="color: var(--el-color-primary); margin-left: 10px"
|
||||
v-if="currentEmployee?.email"
|
||||
>
|
||||
发送邮件
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="detail-row" v-if="currentEmployee.bankName">
|
||||
<span class="label">开户行:</span>
|
||||
<span class="value">{{ currentEmployee.bankName }}</span>
|
||||
</div>
|
||||
<div class="detail-row" v-if="currentEmployee.bankAccount">
|
||||
<span class="label">银行卡号:</span>
|
||||
<span class="value">{{ currentEmployee.bankAccount }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="label">入职时间:</span>
|
||||
<span class="value">{{ currentEmployee.createTime || "-" }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="detailVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import {
|
||||
Search,
|
||||
Refresh,
|
||||
User,
|
||||
OfficeBuilding,
|
||||
Briefcase,
|
||||
Phone,
|
||||
Message,
|
||||
} from "@element-plus/icons-vue";
|
||||
import { getTenantEmployees } from "@/api/employee";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { useOAStore } from "@/stores/oa";
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const oaStore = useOAStore();
|
||||
|
||||
// 搜索和筛选
|
||||
const searchKeyword = ref("");
|
||||
const selectedDepartment = ref(null);
|
||||
const selectedStatus = ref(null);
|
||||
|
||||
// 分页
|
||||
const page = ref(1);
|
||||
const pageSize = ref(10);
|
||||
|
||||
// 数据
|
||||
const employees = ref([]);
|
||||
const loading = ref(false);
|
||||
const detailVisible = ref(false);
|
||||
const currentEmployee = ref(null);
|
||||
|
||||
// 从 store 获取部门列表
|
||||
const departmentList = computed(() => oaStore.departments);
|
||||
|
||||
// 获取当前租户ID
|
||||
const getCurrentTenantId = () => {
|
||||
if (authStore.user && authStore.user.tenant_id) {
|
||||
return authStore.user.tenant_id;
|
||||
}
|
||||
const userInfo = localStorage.getItem("userInfo");
|
||||
if (userInfo) {
|
||||
try {
|
||||
const user = JSON.parse(userInfo);
|
||||
return user.tenant_id || user.tenantId || 0;
|
||||
} catch (e) {
|
||||
console.error("解析用户信息失败:", e);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
// 获取员工列表
|
||||
const fetchEmployees = async () => {
|
||||
loading.value = true;
|
||||
const tenantId = getCurrentTenantId();
|
||||
try {
|
||||
const res = await getTenantEmployees(tenantId);
|
||||
// 兼容接口返回的数据结构
|
||||
let employeeList = [];
|
||||
if (Array.isArray(res)) {
|
||||
employeeList = res;
|
||||
} else if (res?.data && Array.isArray(res.data)) {
|
||||
employeeList = res.data;
|
||||
} else if (res?.data?.data && Array.isArray(res.data.data)) {
|
||||
employeeList = res.data.data;
|
||||
} else if (res?.data) {
|
||||
employeeList = res.data;
|
||||
}
|
||||
|
||||
// 映射接口字段到所需结构
|
||||
employees.value = employeeList.map((item) => {
|
||||
// 查找部门名称
|
||||
let departmentName = "";
|
||||
const departmentId = item.department_id || null;
|
||||
if (departmentId) {
|
||||
const deptInfo = oaStore.getDepartmentById(departmentId);
|
||||
departmentName = deptInfo ? deptInfo.name : "";
|
||||
}
|
||||
|
||||
// 查找职位名称
|
||||
let positionName = "";
|
||||
const positionId = item.position_id || null;
|
||||
if (positionId) {
|
||||
const posInfo = oaStore.getPositionById(positionId);
|
||||
positionName = posInfo ? posInfo.name : "";
|
||||
}
|
||||
|
||||
// 查找角色名称
|
||||
let roleName = "";
|
||||
const roleId = item.role || null;
|
||||
if (roleId) {
|
||||
const roleInfo = oaStore.getRoleById(roleId);
|
||||
roleName = roleInfo ? roleInfo.roleName || roleInfo.name : "";
|
||||
}
|
||||
|
||||
// 处理入职时间
|
||||
const createTime = item.create_time || item.createTime || null;
|
||||
|
||||
return {
|
||||
id: item.id,
|
||||
employeeNo: item.employee_no || item.employeeNo || "",
|
||||
name: item.name || "",
|
||||
phone: item.phone || "",
|
||||
email: item.email || "",
|
||||
department_id: departmentId,
|
||||
departmentName: departmentName,
|
||||
position_id: positionId,
|
||||
positionName: positionName,
|
||||
role: roleId,
|
||||
roleName: roleName,
|
||||
bankName: item.bank_name || item.bankName || "",
|
||||
bankAccount: item.bank_account || item.bankAccount || "",
|
||||
status: item.status || 1,
|
||||
createTime: createTime
|
||||
? new Date(createTime).toLocaleString("zh-CN", {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
})
|
||||
: "",
|
||||
};
|
||||
});
|
||||
} catch (e) {
|
||||
employees.value = [];
|
||||
ElMessage.error("获取员工列表失败");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 筛选后的员工列表
|
||||
const filteredEmployees = computed(() => {
|
||||
let result = employees.value;
|
||||
|
||||
// 搜索筛选
|
||||
if (searchKeyword.value) {
|
||||
const keyword = searchKeyword.value.toLowerCase();
|
||||
result = result.filter((emp) => {
|
||||
return (
|
||||
emp.name?.toLowerCase().includes(keyword) ||
|
||||
emp.employeeNo?.toLowerCase().includes(keyword) ||
|
||||
emp.departmentName?.toLowerCase().includes(keyword) ||
|
||||
emp.positionName?.toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// 部门筛选
|
||||
if (selectedDepartment.value) {
|
||||
result = result.filter(
|
||||
(emp) => emp.department_id === selectedDepartment.value
|
||||
);
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if (selectedStatus.value !== null && selectedStatus.value !== undefined) {
|
||||
result = result.filter((emp) => emp.status === selectedStatus.value);
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
// 分页后的员工列表
|
||||
const paginatedEmployees = computed(() => {
|
||||
const start = (page.value - 1) * pageSize.value;
|
||||
const end = start + pageSize.value;
|
||||
return filteredEmployees.value.slice(start, end);
|
||||
});
|
||||
|
||||
// 获取头像文字(取姓名第一个字符)
|
||||
const getAvatarText = (name) => {
|
||||
if (!name) return "?";
|
||||
return name.charAt(0).toUpperCase();
|
||||
};
|
||||
|
||||
// 获取头像颜色
|
||||
const getAvatarColor = (name) => {
|
||||
if (!name) return "#409EFF";
|
||||
const colors = [
|
||||
"#409EFF",
|
||||
"#67C23A",
|
||||
"#E6A23C",
|
||||
"#F56C6C",
|
||||
"#909399",
|
||||
"#9C27B0",
|
||||
"#00BCD4",
|
||||
"#FF9800",
|
||||
];
|
||||
const index = name.charCodeAt(0) % colors.length;
|
||||
return colors[index];
|
||||
};
|
||||
|
||||
// 搜索处理
|
||||
const handleSearch = () => {
|
||||
page.value = 1;
|
||||
};
|
||||
|
||||
// 筛选处理
|
||||
const handleFilter = () => {
|
||||
page.value = 1;
|
||||
};
|
||||
|
||||
// 分页处理
|
||||
const handlePageChange = (p) => {
|
||||
page.value = p;
|
||||
};
|
||||
|
||||
const handleSizeChange = (size) => {
|
||||
pageSize.value = size;
|
||||
page.value = 1;
|
||||
};
|
||||
|
||||
// 查看详情
|
||||
const handleViewDetail = (employee) => {
|
||||
currentEmployee.value = employee;
|
||||
detailVisible.value = true;
|
||||
};
|
||||
|
||||
// 刷新
|
||||
const refresh = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
await oaStore.refreshAll();
|
||||
await fetchEmployees();
|
||||
ElMessage.success("刷新成功");
|
||||
} catch (error) {
|
||||
ElMessage.error("刷新失败");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
// 加载基础数据
|
||||
try {
|
||||
await oaStore.fetchAllBaseData();
|
||||
} catch (error) {
|
||||
console.error("获取基础数据失败:", error);
|
||||
}
|
||||
// 获取员工列表
|
||||
fetchEmployees();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.container-box {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-bar {
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.contacts-container {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.contacts-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.contact-card {
|
||||
background: var(--el-bg-color);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
transform: translateY(-2px);
|
||||
border-color: var(--el-color-primary-light-7);
|
||||
}
|
||||
}
|
||||
|
||||
.contact-info {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.contact-name {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.contact-meta {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-regular);
|
||||
|
||||
.el-icon {
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-placeholder);
|
||||
}
|
||||
}
|
||||
|
||||
.contact-status {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.pagination-bar {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.employee-detail {
|
||||
.detail-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
flex: 1;
|
||||
|
||||
h3 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-content {
|
||||
.detail-row {
|
||||
display: flex;
|
||||
margin-bottom: 16px;
|
||||
line-height: 1.6;
|
||||
|
||||
.label {
|
||||
width: 100px;
|
||||
color: var(--el-text-color-regular);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.value {
|
||||
flex: 1;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue
Block a user