index.vue 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. <!-- 面试 -->
  2. <template>
  3. <v-card class="pa-5" style="height: 100%;font-size: 14px;">
  4. <div class="d-flex justify-space-between">
  5. <div class="d-flex mb-3">
  6. <!-- 职位 -->
  7. <v-autocomplete
  8. v-model="query.jobId"
  9. :items="positionItems"
  10. density="compact"
  11. variant="outlined"
  12. item-title="label"
  13. item-value="value"
  14. clearable
  15. hide-details
  16. label="职位"
  17. color="primary"
  18. style="width: 300px;"
  19. class="mr-3"
  20. ></v-autocomplete>
  21. <v-select
  22. v-model="query.status"
  23. :items="statusList"
  24. density="compact"
  25. variant="outlined"
  26. item-title="label"
  27. item-value="value"
  28. clearable
  29. hide-details
  30. label="面试状态"
  31. color="primary"
  32. style="width: 300px;"
  33. ></v-select>
  34. <v-btn color="primary" class="half-button ml-3" @click="handleSearch">查 询</v-btn>
  35. <v-btn class="half-button ml-3" variant="outlined" color="primary" @click="handleReset">重 置</v-btn>
  36. </div>
  37. </div>
  38. <v-divider class="mb-3"></v-divider>
  39. <div class="d-flex">
  40. <div>
  41. <div class="d-flex justify-space-between px-5">
  42. <div v-if="selectDateValue">
  43. <span>{{ timesTampChange(selectDateValue).slice(0, 10) }}</span>
  44. <span class="ml-2" style="cursor: pointer;" @click="handleClear">{{ $t('common.cleanUp') }}</span>
  45. </div>
  46. <div v-else class="color-999">{{ $t('interview.noDateSelected') }}</div>
  47. <v-btn color="primary" variant="text" size="small" @click="selectDateValue = new Date()">{{ $t('interview.today') }}</v-btn>
  48. </div>
  49. <v-date-picker
  50. v-model="selectDateValue"
  51. color="primary"
  52. :hide-header="true"
  53. @update:modelValue="handleChangeDate"
  54. class="mr-3"
  55. >
  56. </v-date-picker>
  57. </div>
  58. <v-divider style="height: auto;" class="mr-5" vertical></v-divider>
  59. <div style="flex: 1;overflow: hidden;">
  60. <div v-if="items.length">
  61. <div
  62. class="listItem d-flex align-center pa-3 mb-3"
  63. v-for="(item, index) in items" :key="'item_' + index"
  64. >
  65. <div class="d-flex align-center">
  66. <span class="mr-5 font-size-16" style="color: orange;">{{ timesTampChange(item.time) }}</span>
  67. <v-avatar class="mr-2" size=40 :image="item?.person?.avatar || 'https://minio.citupro.com/dev/menduner/7.png'"></v-avatar>
  68. <div class="d-flex flex-column mr-3" style="width: 110px;">
  69. <span class="ellipsis mb-1">{{ item?.person?.name }}</span>
  70. <span class="ellipsis" style="color: var(--color-999);">{{ item?.job?.name }}</span>
  71. </div>
  72. </div>
  73. <div class="d-flex align-center right-item">
  74. <div style="min-width: 80px;text-align: center;">
  75. <v-icon v-if="item?.phone" class="mx-1" size="20" color="primary">mdi-phone-outline</v-icon>
  76. <span>{{ item?.phone || '-' }}</span>
  77. </div>
  78. <div>
  79. <!-- 面试类型: 线下面试 -->
  80. <span v-if="item.type === '1'">
  81. <v-icon class="mx-3" size="20" color="primary">mdi-account-multiple-outline</v-icon>
  82. <span>{{ $t('interview.offlineInterview') }}</span>
  83. </span>
  84. <!-- 面试类型: 线上面试 -->
  85. <span v-else class="d-flex">
  86. <v-icon class="mx-3 mt-2" size="20" color="primary">mdi mdi-video-account</v-icon>
  87. <span class="d-flex flex-column">
  88. <span>{{ $t('interview.onlineInterview') }}</span>
  89. <span style="color: var(--color-999);">腾讯会议</span>
  90. </span>
  91. </span>
  92. </div>
  93. <!-- 面试状态: '待接受'/'已取消' -->
  94. <div :style="{ 'color': item.status !== '99' ? 'orange' :'var(--color-999)'}">
  95. <v-icon size="30">mdi mdi-circle-small</v-icon>
  96. <span>{{ statusList.find(e => e.value === item.status)?.label }}</span>
  97. </div>
  98. <div>
  99. <span v-if="editStatus.indexOf(item.status)" class="font-size-15 color-primary" @click="handleActionClick(2, item)">修改面试</span>
  100. <v-menu>
  101. <template v-slot:activator="{ props }">
  102. <v-icon v-bind="props" class="mx-3" size="20" color="primary">mdi-dots-horizontal</v-icon>
  103. </template>
  104. <v-list>
  105. <v-list-item
  106. v-for="(k, index) in actionItems"
  107. :key="index"
  108. :value="index"
  109. color="primary"
  110. @click="handleActionClick(k.value, item)"
  111. >
  112. <v-list-item-title>{{ k.title }}</v-list-item-title>
  113. </v-list-item>
  114. </v-list>
  115. </v-menu>
  116. </div>
  117. </div>
  118. </div>
  119. <CtPagination
  120. v-if="total > 0"
  121. :total="total"
  122. :page="query.pageNo"
  123. :limit="query.pageSize"
  124. @handleChange="handleChangePage"
  125. ></CtPagination>
  126. </div>
  127. <Empty v-else :elevation="false"></Empty>
  128. </div>
  129. </div>
  130. </v-card>
  131. <!-- 修改面试 -->
  132. <CtDialog :visible="showInvite" :widthType="2" titleClass="text-h6" title="面试邀请" @close="handleClose" @submit="handleSubmit">
  133. <InvitePage v-if="showInvite" ref="inviteRef" :itemData="itemData" :position="positionItems"></InvitePage>
  134. </CtDialog>
  135. <CtDialog :visible="cancelInvite" :widthType="2" titleClass="text-h6" title="取消面试" @close="handleCancelClose" @submit="handleCancelSubmit">
  136. <TextInput v-model="cancelQuery.reason" :item="textItems"></TextInput>
  137. </CtDialog>
  138. </template>
  139. <script setup>
  140. defineOptions({ name: 'enterprise-interview'})
  141. import { ref } from 'vue'
  142. import { getInterviewInvitePage, saveInterviewInvite, cancelInterviewInvite } from '@/api/recruit/enterprise/interview'
  143. import InvitePage from './components/invite.vue'
  144. import { getDict } from '@/hooks/web/useDictionaries'
  145. import Snackbar from '@/plugins/snackbar'
  146. import { getJobAdvertised } from '@/api/enterprise'
  147. import { dealDictArrayData } from '@/utils/position'
  148. import { timesTampChange, getStartAndEndOfDay } from '@/utils/date'
  149. const cancelInvite = ref(false)
  150. const showInvite = ref(false)
  151. const inviteRef = ref()
  152. const items = ref([])
  153. const cancelQuery = ref({
  154. id: null,
  155. reason: null
  156. })
  157. const editStatus = ['99', '1', '0']
  158. const statusList = ref()
  159. const itemData = ref({})
  160. // 状态
  161. const actionItems = ref([
  162. // { title: '沟通', value: 1 },
  163. // { title: '修改面试', value: 2 },
  164. { title: '取消面试', value: 3 },
  165. // { title: '面试记录', value: 4 }
  166. ])
  167. const total = ref(0)
  168. const query = ref({
  169. pageSize: 10,
  170. pageNo: 1,
  171. status: null,
  172. jobId: null,
  173. time: []
  174. })
  175. const textItems = ref({
  176. type: 'text',
  177. label: '取消原因 *',
  178. clearable: true
  179. })
  180. // 状态字典
  181. const getStatusList = async () => {
  182. const { data } = await getDict('menduner_interview_invite_status')
  183. statusList.value = data
  184. }
  185. getStatusList()
  186. // 列表
  187. const getData = async () => {
  188. const { list, total: number } = await getInterviewInvitePage(query.value)
  189. items.value = list
  190. total.value = number
  191. }
  192. getData()
  193. // 分页
  194. const handleChangePage = (e) => {
  195. query.value.pageNo = e
  196. getData()
  197. }
  198. // 日期选择
  199. const selectDateValue = ref(null)
  200. const handleChangeDate = () => {
  201. const time = getStartAndEndOfDay(selectDateValue.value)
  202. if (!time || !time.length) return delete query.value.time
  203. query.value.time = time
  204. query.value.pageNo = 1
  205. getData()
  206. }
  207. // 清除
  208. const handleClear = () => {
  209. query.value.pageNo = 1
  210. selectDateValue.value = null
  211. delete query.value.time
  212. getData()
  213. }
  214. const handleSearch = () => {
  215. query.value.pageNo = 1
  216. getData()
  217. }
  218. const handleReset = () => {
  219. query.value = {
  220. pageSize: 10,
  221. pageNo: 1,
  222. status: null,
  223. jobId: null,
  224. time: []
  225. }
  226. selectDateValue.value = null
  227. getData()
  228. }
  229. // 职位
  230. const positionItems = ref([])
  231. const getPositionList = async () => {
  232. const data = await getJobAdvertised({ hire: false })
  233. if (!data.length) return
  234. const list = dealDictArrayData([], data)
  235. positionItems.value = list.map(e => {
  236. return { label: `${e.name}${e.areaName ? '_' + e.areaName : ''} ${e.payFrom}-${e.payTo}/${e.payName}`, value: e.id }
  237. })
  238. }
  239. getPositionList()
  240. // 操作按钮
  241. const handleActionClick = (value, item) => {
  242. // 修改
  243. if (value === 2) {
  244. itemData.value = item
  245. showInvite.value = true
  246. }
  247. // 取消
  248. if (value === 3) {
  249. cancelQuery.value.id = item.id
  250. cancelInvite.value = true
  251. }
  252. }
  253. // 修改面试
  254. const handleClose = () => {
  255. itemData.value = {}
  256. showInvite.value = false
  257. }
  258. const handleSubmit = async () => {
  259. const query = inviteRef.value.getQuery()
  260. if (!Object.keys(query).length) return
  261. await saveInterviewInvite(query)
  262. Snackbar.success('操作成功')
  263. handleClose()
  264. getData()
  265. }
  266. // 取消面试
  267. const handleCancelClose = () => {
  268. cancelInvite.value = false
  269. cancelQuery.value = {
  270. id: null,
  271. reason: null
  272. }
  273. }
  274. const handleCancelSubmit = async () => {
  275. if (!cancelQuery.value.reason) return Snackbar.warning('请填写取消原因')
  276. await cancelInterviewInvite(cancelQuery.value)
  277. Snackbar.success('操作成功')
  278. handleCancelClose()
  279. getData()
  280. }
  281. </script>
  282. <style scoped lang="scss">
  283. .listItem {
  284. cursor: pointer;
  285. width: 100%;
  286. min-width: 600px;
  287. overflow: auto;
  288. height: 76px;
  289. border: 1px solid #e5e6eb;
  290. border-radius: 5px;
  291. &:hover {
  292. background-color: var(--color-f8);
  293. }
  294. .right-item {
  295. width: 100%;
  296. div {
  297. width: 25%;
  298. }
  299. }
  300. }
  301. </style>