| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326 |
- <template>
- <a-spin :spinning="loading">
- <div class="internship-personnel-detail" v-if="detailData">
- <!-- 顶部人员概要信息 -->
- <div class="detail-header">
- <div class="header-avatar">
- <a-avatar :size="64" style="background-color: #1890ff">
- {{ detailData.fullName ? detailData.fullName.substring(0, 1) : '?' }}
- </a-avatar>
- </div>
- <div class="header-info">
- <div class="header-name">
- <span class="name">{{ detailData.fullName || '-' }}</span>
- <span class="gender">{{ detailData.gender || '' }}</span>
- <span class="age" v-if="detailData.age != null">{{ detailData.age }}岁</span>
- </div>
- <div class="header-tags">
- <a-tag v-if="detailData.majorTag" color="blue">{{ getDictText('internship_major_tag', detailData.majorTag) }}</a-tag>
- <a-tag v-if="detailData.minorTag" color="green">{{ getDictText('internship_minor_tag', detailData.minorTag) }}</a-tag>
- <a-tag v-if="detailData.internshipStatus" color="blue">{{ detailData.internshipStatus }}</a-tag>
- <a-tag v-if="detailData.postName" color="green">{{ detailData.postName }}</a-tag>
- <a-tag v-if="detailData.companyName" color="purple">{{ detailData.companyName }}</a-tag>
- <template v-if="detailData.customTag">
- <a-tag v-for="tag in customTagList" :key="tag" color="orange">{{ getDictText('internship_custom_tag', tag) || tag }}</a-tag>
- </template>
- </div>
- <div class="header-meta">
- <span v-if="detailData.education">{{ detailData.education }}</span>
- <span v-if="detailData.contactPhone"> | {{ detailData.contactPhone }}</span>
- <span v-if="detailData.jobSearchStatus"> | {{ detailData.jobSearchStatus }}</span>
- <span v-if="detailData.internshipStatus"> | {{ detailData.internshipStatus }}</span>
- <span v-if="detailData.postName"> | {{ detailData.postName }}</span>
- <span v-if="detailData.companyName"> | {{ detailData.companyName }}</span>
- </div>
- </div>
- </div>
- <a-divider style="margin: 12px 0" />
- <!-- 见习信息区域 -->
- <a-descriptions bordered :column="2" size="small" title="见习信息" style="margin-bottom: 16px">
- <a-descriptions-item label="见习状态">{{ detailData.internshipStatus || '-' }}</a-descriptions-item>
- <a-descriptions-item label="见习岗位">{{ detailData.postName || '-' }}</a-descriptions-item>
- <a-descriptions-item label="见习单位">{{ detailData.companyName || '-' }}</a-descriptions-item>
- <a-descriptions-item label="见习开始日期">{{ formatDate(detailData.startDate) }}</a-descriptions-item>
- <a-descriptions-item label="见习结束日期">{{ formatDate(detailData.endDate) }}</a-descriptions-item>
- <a-descriptions-item label="审核状态">{{ detailData.auditStatus || '-' }}</a-descriptions-item>
- <a-descriptions-item label="审核意见" :span="2">{{ detailData.auditOpinion || '-' }}</a-descriptions-item>
- </a-descriptions>
- <!-- 个人信息区域 -->
- <a-descriptions bordered :column="2" size="small" title="个人信息">
- <a-descriptions-item label="证件类型">{{ detailData.idType || '-' }}</a-descriptions-item>
- <a-descriptions-item label="证件号码">{{ detailData.idNumber || '-' }}</a-descriptions-item>
- <a-descriptions-item label="出生日期">{{ formatDate(detailData.birthDate) }}</a-descriptions-item>
- <a-descriptions-item label="年龄">{{ detailData.age != null ? detailData.age + '岁' : '-' }}</a-descriptions-item>
- <a-descriptions-item label="民族">{{ detailData.nation || '-' }}</a-descriptions-item>
- <a-descriptions-item label="国籍">{{ detailData.nationality || '-' }}</a-descriptions-item>
- <a-descriptions-item label="婚姻状况">{{ detailData.maritalStatus || '-' }}</a-descriptions-item>
- <a-descriptions-item label="政治面貌">{{ detailData.politicalStatus || '-' }}</a-descriptions-item>
- <a-descriptions-item label="学历">{{ detailData.education || '-' }}</a-descriptions-item>
- <a-descriptions-item label="毕业日期">{{ formatDate(detailData.graduationDate) }}</a-descriptions-item>
- <a-descriptions-item label="毕业院校">{{ detailData.graduateSchool || '-' }}</a-descriptions-item>
- <a-descriptions-item label="专业">{{ detailData.major || '-' }}</a-descriptions-item>
- <a-descriptions-item label="工作经验">{{ detailData.workExperience || '-' }}</a-descriptions-item>
- <a-descriptions-item label="职业技能等级">{{ detailData.skillLevel || '-' }}</a-descriptions-item>
- <a-descriptions-item label="户口性质">{{ detailData.householdType || '-' }}</a-descriptions-item>
- <a-descriptions-item label="户口所在地">{{ (xzqhMap[detailData.householdLocation] || xzqhMap[String(detailData.householdLocation).substring(0, 6)] || detailData.householdLocation || '').replace('广东省湛江市','') || '-' }}</a-descriptions-item>
- <a-descriptions-item label="现居住地">{{ xzqhMap[detailData.currentResidence] || xzqhMap[String(detailData.currentResidence).substring(0, 6)] || detailData.currentResidence || '-' }}</a-descriptions-item>
- <a-descriptions-item label="现居住地址">{{ detailData.currentAddress || '-' }}</a-descriptions-item>
- <a-descriptions-item label="求职人员类别">{{ getDictText('JobSeekerCategory', detailData.jobSeekerCategory) || detailData.jobSeekerCategory || '-' }}</a-descriptions-item>
- <a-descriptions-item label="联系电话">{{ detailData.contactPhone || '-' }}</a-descriptions-item>
- <a-descriptions-item label="邮箱">{{ detailData.email || '-' }}</a-descriptions-item>
- <a-descriptions-item label="QQ号码">{{ detailData.qqNumber || '-' }}</a-descriptions-item>
- <a-descriptions-item label="微信号">{{ detailData.wechatId || '-' }}</a-descriptions-item>
- <a-descriptions-item label="是否留学人才">{{ detailData.isOverseasTalent || '-' }}</a-descriptions-item>
- <a-descriptions-item label="求职状态">{{ detailData.jobSearchStatus || '-' }}</a-descriptions-item>
- <a-descriptions-item label="是否接受推荐职位">{{ detailData.acceptRecommend || '-' }}</a-descriptions-item>
- </a-descriptions>
- <!-- 标签信息区域 -->
- <a-divider style="margin: 16px 0" />
- <a-descriptions bordered :column="2" size="small" title="标签信息">
- <a-descriptions-item label="人员大类标签">
- <a-tag v-if="detailData.majorTag">{{ getDictText('internship_major_tag', detailData.majorTag) }}</a-tag>
- <span v-else>-</span>
- </a-descriptions-item>
- <a-descriptions-item label="人员小类标签">
- <a-tag v-if="detailData.minorTag">{{ getDictText('internship_minor_tag', detailData.minorTag) }}</a-tag>
- <span v-else>-</span>
- </a-descriptions-item>
- </a-descriptions>
- <!-- 自定义标签区域 -->
- <a-divider style="margin: 16px 0" />
- <a-descriptions bordered :column="1" size="small" title="自定义标签">
- <a-descriptions-item label="自定义标签">
- <template v-if="!editingCustomTag">
- <div class="custom-tag-view">
- <template v-if="customTagList.length > 0">
- <a-tag v-for="tagVal in customTagList" :key="tagVal" class="custom-tag-item">{{ getDictText('internship_custom_tag', tagVal) || tagVal }}</a-tag>
- </template>
- <span v-else class="tag-empty">-</span>
- <a-button type="dashed" size="small" class="edit-tag-btn" @click="startEditCustomTag">
- <template #icon><EditOutlined /></template>
- 编辑
- </a-button>
- </div>
- </template>
- <template v-else>
- <div class="custom-tag-edit">
- <a-select
- v-model:value="editingCustomTagList"
- mode="multiple"
- placeholder="请选择自定义标签"
- :options="customTagDictOptions"
- allow-clear
- />
- <div class="edit-actions">
- <a-button type="primary" size="small" :loading="saving" @click="saveCustomTags">保存</a-button>
- <a-button size="small" @click="cancelEditCustomTag">取消</a-button>
- </div>
- </div>
- </template>
- </a-descriptions-item>
- </a-descriptions>
- </div>
- <div v-else-if="!loading" style="text-align: center; padding: 40px; color: #999">
- 暂无数据
- </div>
- </a-spin>
- </template>
- <script lang="ts" setup>
- import { computed, ref, watch } from 'vue';
- import { EditOutlined } from '@ant-design/icons-vue';
- import { queryDetailById, saveOrUpdate, listCustomTags } from '../InternshipPersonnel.api';
- import dayjs from 'dayjs';
- import { useMessage } from '/@/hooks/web/useMessage';
- import { useDict } from '/@/hooks/dictionary/useDict';
- const { createMessage } = useMessage();
- // 从自定义 DICTIONARY_ITEM 表加载标签字典(含求职人员类别字典)
- const { getDictText, getDictOptions } = useDict(['internship_major_tag', 'internship_minor_tag', 'internship_custom_tag', 'JobSeekerCategory', 'XZQH']);
- const xzqhMap = computed(() => {
- const map: Record<string, string> = {};
- for (const item of getDictOptions('XZQH')) {
- map[item.value] = item.label;
- }
- return map;
- });
- const props = defineProps({
- record: { type: Object, default: () => ({}) },
- });
- const loading = ref<boolean>(false);
- const saving = ref<boolean>(false);
- const detailData = ref<any>(null);
- // 自定义标签字典选项(用于下拉多选)
- const customTagDictOptions = ref<any[]>([]);
- // 自定义标签编辑相关
- const editingCustomTag = ref<boolean>(false);
- const editingCustomTagList = ref<string[]>([]);
- /** 加载自定义标签字典选项 */
- async function loadCustomTagOptions() {
- try {
- const tags = await listCustomTags();
- if (Array.isArray(tags)) {
- // listCustomTags 返回 DictionaryItem 对象 { value, name, ... }
- customTagDictOptions.value = tags.map(tag => ({ label: tag.name, value: String(tag.value) }));
- }
- } catch (e) {
- console.error('加载自定义标签字典失败', e);
- }
- }
- /** 自定义标签列表(逗号分隔转数组) */
- const customTagList = computed(() => {
- if (!detailData.value?.customTag) return [];
- return detailData.value.customTag.split(',').filter((t: string) => t.trim());
- });
- /** 格式化日期 */
- function formatDate(date: any) {
- if (!date) return '-';
- return dayjs(date).format('YYYY-MM-DD');
- }
- /** 开始编辑自定义标签:将当前标签列表复制到编辑区 */
- function startEditCustomTag() {
- editingCustomTagList.value = [...customTagList.value];
- editingCustomTag.value = true;
- }
- /** 取消编辑 */
- function cancelEditCustomTag() {
- editingCustomTag.value = false;
- editingCustomTagList.value = [];
- }
- /** 保存自定义标签 */
- async function saveCustomTags() {
- const id = detailData.value?.id;
- if (!id) return;
- saving.value = true;
- try {
- const customTagStr = editingCustomTagList.value.join(',');
- await saveOrUpdate({ id, customTag: customTagStr }, true);
- detailData.value.customTag = customTagStr;
- editingCustomTag.value = false;
- createMessage.success('自定义标签保存成功');
- } catch (e) {
- console.error('保存自定义标签失败', e);
- createMessage.error('保存失败');
- } finally {
- saving.value = false;
- }
- }
- /** 加载详情 */
- async function loadDetail() {
- const id = props.record?.id;
- if (!id) return;
- loading.value = true;
- try {
- const res = await queryDetailById({ id });
- if (res) {
- detailData.value = res;
- }
- } catch (e) {
- console.error('加载详情失败', e);
- } finally {
- loading.value = false;
- }
- }
- /** 监听记录变化,加载详情和自定义标签选项 */
- watch(
- () => props.record,
- () => {
- loadDetail();
- loadCustomTagOptions();
- },
- { immediate: true, deep: true }
- );
- </script>
- <style lang="less" scoped>
- .internship-personnel-detail {
- padding: 16px;
- .detail-header {
- display: flex;
- align-items: center;
- gap: 16px;
- .header-info {
- flex: 1;
- .header-name {
- margin-bottom: 6px;
- .name {
- font-size: 20px;
- font-weight: 600;
- margin-right: 8px;
- }
- .gender, .age {
- font-size: 14px;
- color: #666;
- margin-right: 6px;
- }
- }
- .header-tags {
- margin-bottom: 6px;
- }
- .header-meta {
- font-size: 13px;
- color: #999;
- }
- }
- }
- /* 自定义标签查看模式:flex行内排列 */
- .custom-tag-view {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 6px;
- .custom-tag-item {
- margin: 0;
- border-radius: 4px;
- }
- .tag-empty {
- color: #999;
- }
- .edit-tag-btn {
- flex-shrink: 0;
- margin-left: 4px;
- }
- }
- /* 自定义标签编辑模式 */
- .custom-tag-edit {
- display: flex;
- flex-direction: column;
- gap: 10px;
- max-width: 500px;
- .edit-actions {
- display: flex;
- gap: 8px;
- }
- }
- }
- </style>
|