|
@@ -0,0 +1,241 @@
|
|
|
+<template>
|
|
|
+ <div class="listItem d-flex align-center pa-3 mb-3" v-for="(item, index) in items" :key="'item_' + index">
|
|
|
+ <div class="d-flex align-center">
|
|
|
+ <span class="mr-5 font-size-16" style="color: orange;">{{ timesTampChange(item.time) }}</span>
|
|
|
+ <v-avatar class="mr-2" size=40 :image="item?.person?.avatar || 'https://minio.citupro.com/dev/menduner/7.png'"></v-avatar>
|
|
|
+ <div class="d-flex flex-column mr-3" style="width: 110px;">
|
|
|
+ <span class="ellipsis mb-1">{{ item?.person?.name }}</span>
|
|
|
+ <span class="ellipsis" style="color: var(--color-999);">{{ item?.job?.name }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="d-flex align-center right-item">
|
|
|
+ <div style="min-width: 80px;text-align: center;">
|
|
|
+ <v-icon v-if="item?.phone" class="mx-1" size="20" color="primary">mdi-phone-outline</v-icon>
|
|
|
+ <span>{{ item?.phone || '-' }}</span>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <!-- 面试类型: 线下面试 -->
|
|
|
+ <span v-if="item.type === '1'">
|
|
|
+ <v-icon class="mx-3" size="20" color="primary">mdi-account-multiple-outline</v-icon>
|
|
|
+ <span>{{ $t('interview.offlineInterview') }}</span>
|
|
|
+ </span>
|
|
|
+ <!-- 面试类型: 线上面试 -->
|
|
|
+ <span v-else class="d-flex">
|
|
|
+ <v-icon class="mx-3 mt-2" size="20" color="primary">mdi mdi-video-account</v-icon>
|
|
|
+ <span class="d-flex flex-column">
|
|
|
+ <span>{{ $t('interview.onlineInterview') }}</span>
|
|
|
+ <span style="color: var(--color-999);">腾讯会议</span>
|
|
|
+ </span>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <!-- 面试状态: '待接受'/'已取消' -->
|
|
|
+ <div :style="{ 'color': item.status !== '98' ? 'orange' :'var(--color-999)'}">
|
|
|
+ <v-icon size="30">mdi mdi-circle-small</v-icon>
|
|
|
+ <span>{{ statusList.find(e => e.value === item.status)?.label }}</span>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <span v-if="editStatus.indexOf(item.status) !== -1" class="font-size-15 color-primary" @click="handleActionClick('edit', item)">修改面试</span>
|
|
|
+ <span v-if="againStatus.indexOf(item.status) !== -1" class="font-size-15 color-primary" @click="handleActionClick('edit', item)">重新邀约</span>
|
|
|
+ <v-menu v-if="actionItems(item.status).length">
|
|
|
+ <template v-slot:activator="{ props }">
|
|
|
+ <v-icon v-bind="props" class="mx-3" size="20" color="primary">mdi-dots-horizontal</v-icon>
|
|
|
+ </template>
|
|
|
+ <v-list>
|
|
|
+ <v-list-item
|
|
|
+ v-for="(k, index) in actionItems(item.status)"
|
|
|
+ :key="index"
|
|
|
+ :value="index"
|
|
|
+ color="primary"
|
|
|
+ @click="handleActionClick(k.value, item)"
|
|
|
+ >
|
|
|
+ <v-list-item-title>{{ k.title }}</v-list-item-title>
|
|
|
+ </v-list-item>
|
|
|
+ </v-list>
|
|
|
+ </v-menu>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 修改面试、重新邀约 -->
|
|
|
+ <CtDialog :visible="showInvite" :widthType="2" titleClass="text-h6" title="面试信息" @close="handleEditClose" @submit="handleEditSubmit">
|
|
|
+ <InvitePage v-if="showInvite" ref="inviteRef" :itemData="itemData" :position="positionItems"></InvitePage>
|
|
|
+ </CtDialog>
|
|
|
+
|
|
|
+ <!-- 取消面试 -->
|
|
|
+ <CtDialog :visible="cancelInvite" :widthType="2" titleClass="text-h6" title="取消面试" @close="handleCancelClose" @submit="handleCancelSubmit">
|
|
|
+ <TextArea v-model="cancelQuery.reason" :item="textItems"></TextArea>
|
|
|
+ </CtDialog>
|
|
|
+
|
|
|
+ <!-- 爽约、填写反馈 -->
|
|
|
+ <CtDialog :visible="show" :widthType="2" titleClass="text-h6" :title="currentAction === 'feedback' ? '填写反馈' : '填写爽约原因'" @close="handleClose" @submit="handleSubmit">
|
|
|
+ <TextArea v-if="currentAction === 'feedback'" v-model="query.evaluate" :item="textItems2"></TextArea>
|
|
|
+ <TextArea v-else v-model="query.reason" :item="textItems2"></TextArea>
|
|
|
+ </CtDialog>
|
|
|
+
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+defineOptions({ name: 'interview-item'})
|
|
|
+import { ref } from 'vue'
|
|
|
+import { timesTampChange } from '@/utils/date'
|
|
|
+import { useI18n } from '@/hooks/web/useI18n'
|
|
|
+import { completedInterviewInvite, cancelInterviewInvite, saveInterviewInvite, noAttendInterviewInvite, feedbackInterviewInvite } from '@/api/recruit/enterprise/interview'
|
|
|
+import InvitePage from './invite.vue'
|
|
|
+import Snackbar from '@/plugins/snackbar'
|
|
|
+import Confirm from '@/plugins/confirm'
|
|
|
+
|
|
|
+defineProps({
|
|
|
+ items: Array,
|
|
|
+ statusList: Array
|
|
|
+})
|
|
|
+const emit = defineEmits(['refresh', 'action'])
|
|
|
+
|
|
|
+const { t } = useI18n()
|
|
|
+const editStatus = ['1', '0'] // 修改面试状态
|
|
|
+const againStatus = ['98', '99'] // 重新邀约状态
|
|
|
+const actions = ref([
|
|
|
+ { title: '完成面试', value: 'completed' },
|
|
|
+ { title: '取消面试', value: 'cancel' },
|
|
|
+ { title: '填写反馈', value: 'feedback' },
|
|
|
+ { title: '爽约', value: 'attended' }
|
|
|
+])
|
|
|
+// 邀请
|
|
|
+const itemData = ref({})
|
|
|
+const showInvite = ref(false)
|
|
|
+const inviteRef = ref()
|
|
|
+// 取消
|
|
|
+const cancelInvite = ref(false)
|
|
|
+const cancelQuery = ref({
|
|
|
+ id: null,
|
|
|
+ reason: null
|
|
|
+})
|
|
|
+const textItems = ref({
|
|
|
+ label: '取消原因 *',
|
|
|
+ clearable: true
|
|
|
+})
|
|
|
+
|
|
|
+// 爽约、反馈
|
|
|
+const currentAction = ref('feedback')
|
|
|
+const show = ref(false)
|
|
|
+const query = ref({})
|
|
|
+const textItems2 = ref({
|
|
|
+ label: '反馈 *',
|
|
|
+ clearable: true
|
|
|
+})
|
|
|
+
|
|
|
+const obj = {
|
|
|
+ '0': [1],
|
|
|
+ '1': [1],
|
|
|
+ '2': [0],
|
|
|
+ '3': [2]
|
|
|
+}
|
|
|
+const actionItems = (status) => {
|
|
|
+ const type = obj[status]
|
|
|
+ if (!type || !type.length) return []
|
|
|
+ const data = type.map(e => actions.value[e])
|
|
|
+ return data
|
|
|
+}
|
|
|
+
|
|
|
+// 完成面试
|
|
|
+const handleFinish = (item) => {
|
|
|
+ if (!item.id) return
|
|
|
+ Confirm(t('common.confirmTitle'), '是否确认已完成面试?').then(async () => {
|
|
|
+ await completedInterviewInvite(item.id)
|
|
|
+ Snackbar.success(t('common.operationSuccessful'))
|
|
|
+ emit('refresh')
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 操作按钮
|
|
|
+const handleActionClick = (value, item) => {
|
|
|
+ // 修改、重新邀约
|
|
|
+ if (value === 'edit') {
|
|
|
+ itemData.value = item
|
|
|
+ showInvite.value = true
|
|
|
+ }
|
|
|
+ // 取消
|
|
|
+ if (value === 'cancel') {
|
|
|
+ cancelQuery.value.id = item.id
|
|
|
+ cancelInvite.value = true
|
|
|
+ }
|
|
|
+ // 完成
|
|
|
+ if (value === 'completed') handleFinish(item)
|
|
|
+ // 爽约、反馈
|
|
|
+ if (value === 'feedback' || value === 'attended') {
|
|
|
+ currentAction.value = value
|
|
|
+ textItems2.value.label = value === 'feedback' ? '反馈 *' : '爽约原因 *'
|
|
|
+ query.value = value === 'feedback' ? { id: item.id, evaluate: null } : { id: item.id, reason: null }
|
|
|
+ show.value = true
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 修改面试、重新邀约
|
|
|
+const handleEditClose = () => {
|
|
|
+ itemData.value = {}
|
|
|
+ showInvite.value = false
|
|
|
+}
|
|
|
+
|
|
|
+const handleEditSubmit = async () => {
|
|
|
+ const query = inviteRef.value.getQuery()
|
|
|
+ if (!Object.keys(query).length) return
|
|
|
+ await saveInterviewInvite(query)
|
|
|
+ Snackbar.success(t('common.operationSuccessful'))
|
|
|
+ handleEditClose()
|
|
|
+ emit('refresh')
|
|
|
+}
|
|
|
+
|
|
|
+// 取消面试
|
|
|
+const handleCancelClose = () => {
|
|
|
+ cancelInvite.value = false
|
|
|
+ cancelQuery.value = {
|
|
|
+ id: null,
|
|
|
+ reason: null
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const handleCancelSubmit = async () => {
|
|
|
+ if (!cancelQuery.value.reason) return Snackbar.warning('请填写取消原因')
|
|
|
+ await cancelInterviewInvite(cancelQuery.value)
|
|
|
+ Snackbar.success(t('common.operationSuccessful'))
|
|
|
+ handleCancelClose()
|
|
|
+ emit('refresh')
|
|
|
+}
|
|
|
+
|
|
|
+// 爽约、反馈
|
|
|
+const handleClose = () => {
|
|
|
+ show.value = false
|
|
|
+ query.value = {}
|
|
|
+}
|
|
|
+
|
|
|
+const handleSubmit = async () => {
|
|
|
+ const key = currentAction.value === 'feedback' ? 'evaluate' : 'reason'
|
|
|
+ if (!query.value[key]) return Snackbar.warning('请填写您的' + (currentAction.value === 'feedback' ? '反馈' : '爽约原因'))
|
|
|
+ const api = currentAction.value === 'feedback' ? feedbackInterviewInvite : noAttendInterviewInvite
|
|
|
+ await api(query.value)
|
|
|
+ Snackbar.success(t('common.operationSuccessful'))
|
|
|
+ emit('refresh')
|
|
|
+ handleClose()
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.listItem {
|
|
|
+ cursor: pointer;
|
|
|
+ width: 100%;
|
|
|
+ min-width: 600px;
|
|
|
+ overflow: auto;
|
|
|
+ height: 76px;
|
|
|
+ border: 1px solid #e5e6eb;
|
|
|
+ border-radius: 5px;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background-color: var(--color-f8);
|
|
|
+ }
|
|
|
+ .right-item {
|
|
|
+ width: 100%;
|
|
|
+ div {
|
|
|
+ width: 25%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|