| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- 头é¨å¯¼èª --> |
| | | <!-- <div class="header"> |
| | | <h2>ä¼ä¸é讯å½ç®¡ç</h2> |
| | | <p>管ç个人ãå
Œ
±ååä½çèç³»æ¹å¼</p> |
| | | </div> --> |
| | | |
| | | <!-- æ ç¾é¡µåæ¢ --> |
| | | <el-tabs v-model="activeTab" @tab-change="handleTabChange" type="border-card"> |
| | | <el-tab-pane label="个人é讯å½" name="personal"> |
| | | <div class="tab-content"> |
| | | <!-- æç´¢æ¡ --> |
| | | <el-input |
| | | v-model="personalSearch.staffName" |
| | | placeholder="æç´¢è系人" |
| | | clearable |
| | | prefix-icon="Search" |
| | | class="search-input" |
| | | @keyup.enter="getPersonalContactsList" |
| | | /> |
| | | <el-button style="margin: 0 0 20px 20px;" type="primary" @click="showAddContactDialog=true">æ·»å è系人</el-button> |
| | | <!-- è系人å表 --> |
| | | <div class="contact-list"> |
| | | <div |
| | | v-for="contact in personalContacts" |
| | | :key="contact.id" |
| | | class="contact-card" |
| | | @click="showContactDetail(contact)" |
| | | > |
| | | <div class="contact-avatar">{{ contact.staffName.charAt(0) }}</div> |
| | | <div class="contact-info"> |
| | | <h4>{{ contact.staffName }}</h4> |
| | | <p>{{ contact.profession }} - {{ contact.postJob }}</p> |
| | | <div class="contact-phone">{{ contact.phone }}</div> |
| | | </div> |
| | | <div class="contact-actions"> |
| | | <!-- <el-button |
| | | type="text" |
| | | icon="Phone" |
| | | @click.stop="callContact(contact)" |
| | | ></el-button> --> |
| | | <el-button |
| | | type="text" |
| | | icon="Message" |
| | | @click.stop="messageContact(contact)" |
| | | ></el-button> |
| | | <el-button |
| | | type="text" |
| | | icon="Delete" |
| | | @click.stop="removeFromPersonalContacts(contact.id)" |
| | | ></el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- ç©ºç¶æ |
| | | <div v-if="personalContacts.length === 0 && !loading" class="empty-state"> |
| | | <el-empty description="ææ è系人" /> |
| | | <el-button type="primary" @click="showAddContactDialog=true">æ·»å è系人</el-button> |
| | | </div> --> |
| | | </div> |
| | | </div> |
| | | </el-tab-pane> |
| | | |
| | | <el-tab-pane label="å
Œ
±é讯å½" name="public"> |
| | | <div class="tab-content"> |
| | | <!-- æç´¢æ¡ --> |
| | | <el-input |
| | | v-model="publicSearch.staffName" |
| | | placeholder="æç´¢å
Œ
±è系人" |
| | | clearable |
| | | prefix-icon="Search" |
| | | class="search-input" |
| | | @keyup.enter="getPublicContactsList" |
| | | /> |
| | | |
| | | <!-- è系人å表 publicContacts--> |
| | | <div class="contact-list"> |
| | | <div |
| | | v-for="contact in EmployeeList" |
| | | :key="contact.id" |
| | | class="contact-card" |
| | | @click="showContactDetail(contact)" |
| | | > |
| | | <div class="contact-avatar">{{ contact.staffName.charAt(0) }}</div> |
| | | <div class="contact-info"> |
| | | <h4>{{ contact.staffName }}</h4> |
| | | <p>{{ contact.postJob }} - {{ contact.profession }}</p> |
| | | <div class="contact-phone">{{ contact.phone }}</div> |
| | | </div> |
| | | <div class="contact-actions"> |
| | | <!-- <el-button |
| | | type="text" |
| | | icon="Phone" |
| | | @click.stop="callContact(contact)" |
| | | ></el-button> --> |
| | | <el-button |
| | | type="text" |
| | | icon="Message" |
| | | @click.stop="messageContact(contact)" |
| | | ></el-button> |
| | | <el-button |
| | | type="text" |
| | | icon="Delete" |
| | | :type="isInPersonalContacts(contact.id) ? 'primary' : ''" |
| | | @click.stop="togglePersonalContact(contact)" |
| | | ></el-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-tab-pane> |
| | | |
| | | <el-tab-pane label="åä½é讯å½" name="company"> |
| | | <div class="tab-content"> |
| | | <div class="company-contacts-layout"> |
| | | <!-- 左侧é¨é¨æ --> |
| | | <div class="department-tree"> |
| | | <!-- <h3>é¨é¨ç»æ</h3> |
| | | <el-tree |
| | | :data="departmentTree" |
| | | :props="{ label: 'deptName', children: 'children' }" |
| | | node-key="deptId" |
| | | ref="departmentTreeRef" |
| | | highlight-current |
| | | default-expand-all |
| | | @node-click="handleDepartmentClick" |
| | | /> --> |
| | | <el-col > |
| | | <div class="head-container"> |
| | | <el-input |
| | | v-model="deptName" |
| | | placeholder="请è¾å
¥é¨é¨åç§°" |
| | | clearable |
| | | prefix-icon="Search" |
| | | style="margin-bottom: 20px" |
| | | /> |
| | | </div> |
| | | <div class="head-container"> |
| | | <el-tree |
| | | :data="departmentTree" |
| | | :props="{ label: 'label', children: 'children' }" |
| | | :expand-on-click-node="false" |
| | | :filter-node-method="filterNode" |
| | | ref="deptTreeRef" |
| | | node-key="id" |
| | | highlight-current |
| | | default-expand-all |
| | | @node-click="handleDepartmentClick" |
| | | /> |
| | | </div> |
| | | </el-col> |
| | | </div> |
| | | |
| | | <!-- å³ä¾§é¨é¨æå --> |
| | | <div class="department-members"> |
| | | <h3>{{ currentDepartment?.label || 'å
¨é¨æå' }}</h3> |
| | | <el-input |
| | | v-model="companySearch.staffName" |
| | | placeholder="æç´¢é¨é¨æå" |
| | | clearable |
| | | prefix-icon="Search" |
| | | class="search-input" |
| | | @keyup.enter="getCompanyContactsList" |
| | | /> |
| | | |
| | | <div class="contact-list"> |
| | | <div |
| | | v-for="contact in companyContacts" |
| | | :key="contact.id" |
| | | class="contact-card" |
| | | @click="showContactDetail(contact)" |
| | | > |
| | | <div class="contact-avatar">{{ contact.staffName.charAt(0) }}</div> |
| | | <div class="contact-info"> |
| | | <h4>{{ contact.staffName }}</h4> |
| | | <p>{{ contact.profession }}</p> |
| | | <div class="contact-phone">{{ contact.phone }}</div> |
| | | </div> |
| | | <div class="contact-actions"> |
| | | |
| | | <el-button |
| | | type="text" |
| | | icon="Message" |
| | | @click.stop="messageContact(contact)" |
| | | ></el-button> |
| | | <el-button |
| | | type="text" |
| | | icon="Delete" |
| | | :type="isInPersonalContacts(contact.id) ? 'primary' : ''" |
| | | @click.stop="togglePersonalContact(contact)" |
| | | ></el-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | |
| | | <!-- è系人详æ
å¼¹çª --> |
| | | <el-dialog |
| | | v-model="showDetailDialog" |
| | | title="è系人详æ
" |
| | | width="400px" |
| | | > |
| | | <div v-if="selectedContact" class="contact-detail"> |
| | | <div class="detail-avatar">{{ selectedContact.staffName?.charAt(0) }}</div> |
| | | <h3>{{ selectedContact.staffName }}</h3> |
| | | <p class="detail-position">{{ selectedContact.profession }} - {{ selectedContact.postJob }}</p> |
| | | |
| | | <div class="detail-info"> |
| | | <div class="info-item"> |
| | | <span class="label">ç¼å·ï¼</span> |
| | | <span class="value">{{ selectedContact.staffNo }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="label">ææºå·ç ï¼</span> |
| | | <span class="value">{{ selectedContact.phone }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="label">é®ç®±ï¼</span> |
| | | <span class="value">{{ selectedContact.sex }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="label">ä½åï¼</span> |
| | | <span class="value">{{ selectedContact.adress || 'ææ ' }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="label">身份è¯å·ï¼</span> |
| | | <span class="value">{{ selectedContact.identityCard || 'ææ ' }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <template #footer> |
| | | <el-button @click="showDetailDialog = false">å
³é</el-button> |
| | | <el-button |
| | | type="primary" |
| | | v-if="activeTab !== 'personal'" |
| | | @click="togglePersonalContact(selectedContact); showDetailDialog = false" |
| | | > |
| | | {{ isInPersonalContacts(selectedContact?.id) ? 'ä»ä¸ªäººé讯å½ç§»é¤' : 'æ·»å å°ä¸ªäººé讯å½' }} |
| | | </el-button> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- æ·»å èç³»äººå¼¹çª --> |
| | | <el-dialog |
| | | v-model="showAddContactDialog" |
| | | title="æ·»å è系人" |
| | | width="500px" |
| | | > |
| | | <el-form :model="addContactForm" ref="addContactFormRef" label-width="80px"> |
| | | <!-- <el-form-item label="å§å" prop="name"> |
| | | <el-input v-model="addContactForm.name" placeholder="请è¾å
¥å§å" /> |
| | | </el-form-item> |
| | | <el-form-item label="ææºå·ç " prop="phone"> |
| | | <el-input v-model="addContactForm.phone" placeholder="请è¾å
¥ææºå·ç " /> |
| | | </el-form-item> |
| | | <el-form-item label="é®ç®±" prop="email"> |
| | | <el-input v-model="addContactForm.email" placeholder="请è¾å
¥é®ç®±" /> |
| | | </el-form-item> |
| | | <el-form-item label="é¨é¨" prop="department"> |
| | | <el-input v-model="addContactForm.department" placeholder="请è¾å
¥é¨é¨" /> |
| | | </el-form-item> --> |
| | | <el-form-item label="å§å" prop="name"> |
| | | <!-- <select v-model="addContactForm.contactId"> |
| | | <option v-for="item in EmployeeList" :key="item.id" :value="item.id">{{ item.staffName }}</option> |
| | | </select> --> |
| | | <el-select v-model="addContactForm.contactId" placeholder="è¯·éæ©" style="width: 100%"> |
| | | <el-option |
| | | v-for="option in EmployeeList" |
| | | :key="option.id" |
| | | :label="option.staffName" |
| | | :value="option.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="showAddContactDialog = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="addContact">ç¡®å®</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, reactive, computed } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { |
| | | getPersonalContacts, |
| | | addPersonalContact, |
| | | removePersonalContact, |
| | | getPublicContacts, |
| | | getCompanyContacts, |
| | | getDepartmentTree, |
| | | getEmployeeDetail |
| | | } from '@/api/collaborativeApproval/enterpriseBook.js' |
| | | import { getUserProfile } from '@/api/system/user.js' |
| | | import {staffJoinListPage} from "@/api/personnelManagement/onboarding.js"; |
| | | import { |
| | | changeUserStatus, |
| | | listUser, |
| | | resetUserPwd, |
| | | delUser, |
| | | getUser, |
| | | updateUser, |
| | | addUser, |
| | | deptTreeSelect, |
| | | } from "@/api/system/user"; |
| | | |
| | | // æ ç¾é¡µç¶æ |
| | | const activeTab = ref('personal') |
| | | const loading = ref(false) |
| | | const EmployeeList = ref([]) |
| | | const page = reactive({ |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }) |
| | | // 个人éè®¯å½æ°æ® |
| | | const personalContacts = ref([]) |
| | | const personalSearch = ref({ |
| | | staffName: '', |
| | | }) |
| | | |
| | | // å
Œ
±éè®¯å½æ°æ® |
| | | const publicContacts = ref([]) |
| | | const publicSearch = ref({ |
| | | staffName: '', |
| | | staffState: 1 |
| | | }) |
| | | |
| | | // åä½éè®¯å½æ°æ® |
| | | const companyContacts = ref([]) |
| | | const companySearch = ref({ |
| | | staffName: '', |
| | | staffState: 1 |
| | | }) |
| | | const departmentTree = ref([]) |
| | | const departmentTreeRef = ref(null) |
| | | const currentDepartment = ref(null) |
| | | |
| | | // å¼¹çªç¶æ |
| | | const showDetailDialog = ref(false) |
| | | const showAddContactDialog = ref(false) |
| | | const selectedContact = ref(null) |
| | | |
| | | // æ·»å è系人表å |
| | | const addContactForm = reactive({ |
| | | contactId: '', |
| | | name: '', |
| | | phone: '', |
| | | email: '', |
| | | department: '', |
| | | position: '' |
| | | }) |
| | | const addContactFormRef = ref(null) |
| | | |
| | | // åå§åæ°æ® |
| | | onMounted(() => { |
| | | getEmployeeList() |
| | | getPersonalContactsList() |
| | | if (activeTab.value === 'public') { |
| | | getPublicContactsList() |
| | | } else if (activeTab.value === 'company') { |
| | | getDepartmentTreeData() |
| | | getCompanyContactsList() |
| | | } |
| | | }) |
| | | |
| | | // å¤çæ ç¾é¡µåæ¢ |
| | | const handleTabChange = (tabName) => { |
| | | if (tabName === 'public') { |
| | | getPublicContactsList() |
| | | } else if (tabName === 'company') { |
| | | getDepartmentTreeData() |
| | | getCompanyContactsList() |
| | | } |
| | | } |
| | | |
| | | // è·å个人é讯å½å表 |
| | | const getPersonalContactsList = async () => { |
| | | loading.value = true |
| | | getPersonalContacts(page,personalSearch.value).then(res => { |
| | | personalContacts.value = res.data.records |
| | | }) |
| | | loading.value = false |
| | | } |
| | | |
| | | // è·åå
Œ
±é讯å½å表 |
| | | const getPublicContactsList = async () => { |
| | | loading.value = true |
| | | getEmployeeList() |
| | | // publicContacts.value = generateMockPublicContacts() |
| | | loading.value = false |
| | | } |
| | | //è·ååå·¥å表 |
| | | const getEmployeeList = async () => { |
| | | staffJoinListPage(publicSearch.value).then(res => { |
| | | console.log(res.data.records) |
| | | EmployeeList.value = res.data.records |
| | | }).catch(err => {}) |
| | | } |
| | | // è·ååä½é讯å½å表 |
| | | const getCompanyContactsList = async () => { |
| | | loading.value = true |
| | | staffJoinListPage(companySearch.value).then(res => { |
| | | // console.log(res.data.records) |
| | | companyContacts.value = res.data.records |
| | | }).catch(err => {}) |
| | | |
| | | loading.value = false |
| | | loading.value = false |
| | | // } |
| | | } |
| | | |
| | | // è·åé¨é¨æ ç»æ |
| | | const getDepartmentTreeData = async () => { |
| | | deptTreeSelect().then((response) => { |
| | | // console.log("Tree",response.data) |
| | | departmentTree.value = response.data; |
| | | // enabledDeptOptions.value = filterDisabledDept( |
| | | // JSON.parse(JSON.stringify(response.data)) |
| | | // ); |
| | | }); |
| | | } |
| | | // /** è¿æ»¤ç¦ç¨çé¨é¨ */ |
| | | // function filterDisabledDept(deptList) { |
| | | // return deptList.filter((dept) => { |
| | | // if (dept.disabled) { |
| | | // return false; |
| | | // } |
| | | // if (dept.children && dept.children.length) { |
| | | // dept.children = filterDisabledDept(dept.children); |
| | | // } |
| | | // return true; |
| | | // }); |
| | | // } |
| | | // å¤çé¨é¨ç¹å» |
| | | const handleDepartmentClick = (data) => { |
| | | // console.log("ç¹å»",data) |
| | | companySearch.value = { |
| | | ...companySearch.value, |
| | | deptId: data.id, |
| | | } |
| | | // currentDepartment.value = data.id |
| | | // è·å该é¨é¨çæåå表 |
| | | |
| | | getCompanyContactsList() |
| | | } |
| | | |
| | | // æ¾ç¤ºè系人详æ
|
| | | const showContactDetail = async (contact) => { |
| | | selectedContact.value = contact |
| | | showDetailDialog.value = true |
| | | } |
| | | |
| | | // æ¨æçµè¯ |
| | | const callContact = (contact) => { |
| | | ElMessage.info(`æ£å¨æ¨æ ${contact.name} ççµè¯: ${contact.phone}`) |
| | | } |
| | | |
| | | // åéæ¶æ¯ |
| | | const messageContact = (contact) => { |
| | | ElMessage.info(`æ£å¨åéæ¶æ¯ç» ${contact.name}`) |
| | | } |
| | | |
| | | |
| | | // æ·»å è系人 |
| | | const addContact = async () => { |
| | | |
| | | try { |
| | | // 表åéªè¯ |
| | | // if (!addContactForm.name || !addContactForm.phone) { |
| | | // ElMessage.warning('请填åå§ååææºå·ç ') |
| | | // return |
| | | // } |
| | | |
| | | const res = await addPersonalContact(addContactForm) |
| | | if (res.code === 200) { |
| | | ElMessage.success('æ·»å æå') |
| | | showAddContactDialog.value = false |
| | | getPersonalContactsList() |
| | | // é置表å |
| | | Object.keys(addContactForm).forEach(key => { |
| | | addContactForm[key] = '' |
| | | }) |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error('æ·»å 失败') |
| | | // æ¨¡ææ·»å æå |
| | | personalContacts.value.push({ |
| | | ...addContactForm, |
| | | id: Date.now(), |
| | | createTime: new Date().toISOString() |
| | | }) |
| | | ElMessage.success('æ·»å æå') |
| | | showAddContactDialog.value = false |
| | | // é置表å |
| | | Object.keys(addContactForm).forEach(key => { |
| | | addContactForm[key] = '' |
| | | }) |
| | | } |
| | | } |
| | | |
| | | // ä»ä¸ªäººé讯å½ç§»é¤ |
| | | const removeFromPersonalContacts = async (contactId) => { |
| | | ElMessageBox.confirm( |
| | | 'ç¡®å®è¦ä»ä¸ªäººé讯å½ä¸ç§»é¤è¯¥è系人åï¼', |
| | | 'æç¤º', |
| | | { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | } |
| | | ).then(async () => { |
| | | try { |
| | | const res = await removePersonalContact(contactId) |
| | | if (res.code === 200) { |
| | | ElMessage.success('ç§»é¤æå') |
| | | getPersonalContactsList() |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error('ç§»é¤å¤±è´¥') |
| | | // 模æç§»é¤æå |
| | | // personalContacts.value = personalContacts.value.filter(item => item.id !== contactId) |
| | | ElMessage.success('ç§»é¤æå') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // åæ¢ä¸ªäººéè®¯å½ |
| | | const togglePersonalContact = async (contact) => { |
| | | const isInPersonal = isInPersonalContacts(contact.id) |
| | | const contactId = contact.id |
| | | if (isInPersonal) { |
| | | // ä»ä¸ªäººé讯å½ç§»é¤ |
| | | //æ ¹æ®contactIdæ¥æ¾personalContactsä¸å¯¹åºç项ï¼ç¶åå é¤è¯¥é¡¹ |
| | | const index = personalContacts.value.findIndex(item => item.contactId === contactId) |
| | | const personId = personalContacts.value[index].id |
| | | // console.log(personId) |
| | | await removeFromPersonalContacts(personId) |
| | | } else { |
| | | // æ·»å å°ä¸ªäººéè®¯å½ |
| | | try { |
| | | const res = await addPersonalContact({contactId: contactId}) |
| | | if (res.code === 200) { |
| | | ElMessage.success('æ·»å æå') |
| | | getPersonalContactsList() |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error('æ·»å 失败') |
| | | // æ¨¡ææ·»å æå |
| | | // personalContacts.value.push({ |
| | | // ...contact, |
| | | // id: contact.id || Date.now(), |
| | | // createTime: new Date().toISOString() |
| | | // }) |
| | | // ElMessage.success('æ·»å æå') |
| | | } |
| | | } |
| | | } |
| | | |
| | | // æ£æ¥æ¯å¦å¨ä¸ªäººé讯å½ä¸ |
| | | const isInPersonalContacts = (contactId) => { |
| | | return personalContacts.value.some(item => item.contactId === contactId) |
| | | } |
| | | |
| | | // çææ¨¡æé¨é¨æ æ°æ® |
| | | const generateMockDepartmentTree = () => { |
| | | return [ |
| | | { |
| | | deptId: 1, |
| | | deptName: 'ææ¯é¨', |
| | | children: [ |
| | | { |
| | | deptId: 101, |
| | | deptName: 'å端ç»' |
| | | }, |
| | | { |
| | | deptId: 102, |
| | | deptName: 'å端ç»' |
| | | }, |
| | | { |
| | | deptId: 103, |
| | | deptName: 'æµè¯ç»' |
| | | } |
| | | ] |
| | | }, |
| | | { |
| | | deptId: 2, |
| | | deptName: '产åé¨' |
| | | }, |
| | | { |
| | | deptId: 3, |
| | | deptName: '人äºé¨' |
| | | }, |
| | | { |
| | | deptId: 4, |
| | | deptName: 'è´¢å¡é¨' |
| | | } |
| | | ] |
| | | } |
| | | |
| | | // çææ¨¡æåä½éè®¯å½æ°æ® |
| | | // const generateMockCompanyContacts = (deptName) => { |
| | | // const allContacts = getEmployeeList() |
| | | |
| | | // if (deptName) { |
| | | // return allContacts.filter(contact => contact.postJob === deptName) |
| | | // } |
| | | // return allContacts |
| | | // } |
| | | |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .header { |
| | | margin-bottom: 20px; |
| | | padding: 15px; |
| | | background: #f5f7fa; |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .header h2 { |
| | | margin: 0 0 5px 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .header p { |
| | | margin: 0; |
| | | color: #909399; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .tab-content { |
| | | padding: 15px; |
| | | background: #fff; |
| | | border-radius: 8px; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); |
| | | } |
| | | |
| | | .search-input { |
| | | margin-bottom: 20px; |
| | | width: 300px; |
| | | } |
| | | |
| | | .contact-list { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 15px; |
| | | } |
| | | |
| | | .contact-card { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 15px; |
| | | width: 500px; |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | cursor: pointer; |
| | | transition: all 0.3s; |
| | | } |
| | | |
| | | .contact-card:hover { |
| | | background: #e9ecef; |
| | | transform: translateY(-2px); |
| | | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .contact-avatar { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | width: 48px; |
| | | height: 48px; |
| | | background: #409eff; |
| | | color: #fff; |
| | | font-size: 20px; |
| | | font-weight: bold; |
| | | border-radius: 50%; |
| | | margin-right: 15px; |
| | | } |
| | | |
| | | .contact-info { |
| | | flex: 1; |
| | | } |
| | | |
| | | .contact-info h4 { |
| | | margin: 0 0 5px 0; |
| | | color: #303133; |
| | | font-size: 16px; |
| | | } |
| | | |
| | | .contact-info p { |
| | | margin: 0 0 5px 0; |
| | | color: #606266; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .contact-phone { |
| | | color: #409eff; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .contact-actions { |
| | | display: flex; |
| | | gap: 5px; |
| | | } |
| | | |
| | | .empty-state { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 60px 20px; |
| | | color: #909399; |
| | | } |
| | | |
| | | .company-contacts-layout { |
| | | display: flex; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .department-tree { |
| | | width: 250px; |
| | | padding: 15px; |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .department-tree h3 { |
| | | margin: 0 0 15px 0; |
| | | padding-bottom: 10px; |
| | | border-bottom: 1px solid #e4e7ed; |
| | | color: #303133; |
| | | font-size: 16px; |
| | | } |
| | | |
| | | .department-members { |
| | | flex: 1; |
| | | } |
| | | |
| | | .department-members h3 { |
| | | margin: 0 0 15px 0; |
| | | color: #303133; |
| | | font-size: 16px; |
| | | } |
| | | |
| | | .contact-detail { |
| | | text-align: center; |
| | | } |
| | | |
| | | .detail-avatar { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | width: 80px; |
| | | height: 80px; |
| | | background: #409eff; |
| | | color: #fff; |
| | | font-size: 32px; |
| | | font-weight: bold; |
| | | border-radius: 50%; |
| | | margin: 0 auto 20px; |
| | | } |
| | | |
| | | .contact-detail h3 { |
| | | margin: 0 0 10px 0; |
| | | color: #303133; |
| | | font-size: 20px; |
| | | } |
| | | |
| | | .detail-position { |
| | | margin: 0 0 30px 0; |
| | | color: #606266; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .detail-info { |
| | | text-align: left; |
| | | } |
| | | |
| | | .info-item { |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .info-item .label { |
| | | display: inline-block; |
| | | width: 100px; |
| | | color: #909399; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .info-item .value { |
| | | color: #303133; |
| | | font-size: 14px; |
| | | } |
| | | </style> |