item.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. <template>
  2. <view>
  3. <view v-for="(val, index) in items" :key="index" @tap.stop="handleDetail(val)" class="mList default-border">
  4. <!-- 基本信息 -->
  5. <view class="d-flex align-center">
  6. <view class="user-avatar">
  7. <image class="user-avatar-img" :src="getUserAvatar(val.person?.avatar, val.person?.sex)" mode="scaleToFill"></image>
  8. <image class="user-avatar-sex" :src="val?.person?.sex ? val?.person?.sex === '1' ? '/static/img/man.png' : '/static/img/female.png' : ''" alt="" mode="scaleToFill" />
  9. </view>
  10. <view style="flex: 1; margin-left: 10px;">
  11. <view class="d-flex justify-space-between align-center">
  12. <view class="font-size-18 default-text-color">{{ val.person?.name }}</view>
  13. <view v-if="current !== 4">
  14. <span v-if="current === 0" :style="{'color': val.status && val.status === '0' ? '#fb8c00' : '#00B760'}">
  15. {{ val.status && val.status === '0' ? '未查看' : '已查看' }}
  16. </span>
  17. <span v-else class="color-999">{{ val.status ? statusList.find(i => i.value === val.status)?.label : '' }}</span>
  18. </view>
  19. </view>
  20. <view class="ss-m-t-10">
  21. <span
  22. class="color-999"
  23. v-for="(key, index) in desc"
  24. :key="index"
  25. >
  26. {{ val.person?.[key] }}
  27. <span v-if="index !== desc.length - 1 && val.person?.[key]" class="ss-m-x-10">|</span>
  28. </span>
  29. </view>
  30. </view>
  31. </view>
  32. <view class="ss-m-t-15 color-999">
  33. <view>
  34. 投递职位:
  35. <image v-if="val.jobFairId" src="/static/svg/jobFair.svg" style="width: 15px; height: 15px;"></image>
  36. {{ formatName(val.job?.name) }}
  37. </view>
  38. <view>操作时间:{{ timesTampChange(val.createTime) }}</view>
  39. </view>
  40. <view class="sub-li-bottom ss-m-t-20">
  41. <view
  42. class="sub-li-bottom-item color-primary"
  43. v-if="(props.current === 0 && val.url) || (props.current !== 0 && val?.cvRel && val?.cvRel?.url)"
  44. @tap.stop="handlePreview(val)"
  45. >查看/下载附件</view>
  46. <view
  47. class="sub-li-bottom-item d-flex align-center justify-center"
  48. @tap.stop="handleLoadMore(val)"
  49. :style="{'color': !actionItems(val)?.length ? '#ccc' : '#00B760'}"
  50. >
  51. <view>更多操作</view>
  52. <uni-icons type="list" class="ss-m-l-10" size="20" :color="!actionItems(val)?.length ? '#ccc' : '#00B760'"></uni-icons>
  53. </view>
  54. </view>
  55. </view>
  56. <!-- 更多操作 -->
  57. <uni-popup ref="popup" type="bottom" :mask-click="true">
  58. <view class="actions" v-if="itemData && Object.keys(itemData).length">
  59. <view
  60. class="action-item"
  61. v-for="(val, index) in actionItems(itemData)"
  62. :key="index"
  63. :style="{'color': val?.disabled ? '#ccc' : '#00B760'}"
  64. @tap.stop="val.click(itemData)"
  65. >{{ val.title }}</view>
  66. </view>
  67. <button class="big-cancel-button" @tap.stop="handleClosePopup">取消</button>
  68. </uni-popup>
  69. </view>
  70. </template>
  71. <script setup>
  72. import { ref } from 'vue'
  73. import { timesTampChange } from '@/utils/date'
  74. import { getUserAvatar } from '@/utils/avatar'
  75. import { formatName } from '@/utils/getText'
  76. import { preview } from '@/utils/preview'
  77. import { getDict } from '@/hooks/useDictionaries'
  78. import { userStore } from '@/store/user'
  79. import { defaultText, talkToUser } from '@/hooks/useIM'
  80. import { joinToTalentPool, joinEliminate, personCvUnfitCancel, personEntryByEnterprise, hireJobCvRelSettlement, personJobCvLook } from '@/api/resume'
  81. const emit = defineEmits(['refresh'])
  82. const props = defineProps({ items: Array, current: [Number, String] })
  83. const user = userStore()
  84. const statusList = ref([])
  85. getDict('menduner_interview_invite_status').then(({ data }) => {
  86. statusList.value = data.data || []
  87. })
  88. const desc = ['jobStatusName', 'eduName', 'expName']
  89. const popup = ref()
  90. const itemData = ref({})
  91. // 查看在线简历
  92. const handleDetail = async (val) => {
  93. const id = props.current === 0 ? val.id : val?.cvRel?.id
  94. if (!val.userId || !id) {
  95. uni.showToast({ title: '用户ID不存在', icon: 'none' })
  96. return
  97. }
  98. try {
  99. const {data } = await personJobCvLook(id)
  100. if (data) emit('refresh')
  101. } catch {}
  102. uni.navigateTo({
  103. url: `/pagesB/personnelDetails/index?id=${val.userId}&type=1`
  104. })
  105. }
  106. // 查看附件
  107. const handlePreview = async (val) => {
  108. const id = props.current === 0 ? val.id : val?.cvRel?.id
  109. const url = props.current === 0 ? val.url : val?.cvRel?.url
  110. if (!val.userId || !id) {
  111. uni.showToast({ title: '用户ID不存在', icon: 'none' })
  112. return
  113. }
  114. if (!url) {
  115. uni.showToast({ title: '附件地址不存在', icon: 'none' })
  116. return
  117. }
  118. try {
  119. const {data } = await personJobCvLook(id)
  120. if (data) emit('refresh')
  121. } catch {}
  122. preview(url)
  123. }
  124. // 更多操作
  125. const handleLoadMore = (val) => {
  126. if (!actionItems(val).length) {
  127. itemData.value = {}
  128. uni.showToast({ title: '暂无更多操作', icon: 'none' })
  129. return
  130. }
  131. itemData.value = val
  132. popup.value.open()
  133. }
  134. // 关闭操作弹窗
  135. const handleClosePopup = () => {
  136. popup.value.close()
  137. itemData.value = {}
  138. }
  139. // 加入储备
  140. const handleJoinToTalentPool = async (item) => {
  141. if (!item.userId) {
  142. uni.showToast({ title: '用户ID不存在', icon: 'none' })
  143. return
  144. }
  145. try {
  146. await joinToTalentPool(item.userId)
  147. uni.showToast({ title: '加入成功', icon: 'none' })
  148. handleClosePopup()
  149. emit('refresh')
  150. } catch {
  151. handleClosePopup()
  152. }
  153. }
  154. // 邀请面试
  155. const handleInterviewInvite = (item) => {
  156. if (item?.job?.status === '1') {
  157. uni.showToast({ title: '职位已关闭', icon: 'none' })
  158. return
  159. }
  160. if (item.handleStatus) {
  161. uni.showToast({ title: '已邀面试', icon: 'none' })
  162. return
  163. }
  164. uni.navigateTo({
  165. url: `/pagesB/InviteInterview/index?id=${item.userId}&jobId=${item.job.id}&jobCvRelId=${item.id}`
  166. })
  167. handleClosePopup()
  168. }
  169. // 不合适
  170. const handleEliminate = async (item) => {
  171. if (!item.userId) {
  172. uni.showToast({ title: '用户ID不存在', icon: 'none' })
  173. return
  174. }
  175. const query = {
  176. bizId: item.id,
  177. jobId: item.job.id,
  178. userId: item.userId,
  179. jobCvRelId: props.current === 0 ? item.id : item?.cvRel?.id,
  180. type: props.current === 0 ? '0' : '1' // 投递简历0 已邀约1
  181. }
  182. // 招聘会职位则带id
  183. if (item?.jobFairId) query.jobFairId = item.jobFairId
  184. try {
  185. await joinEliminate(query)
  186. uni.showToast({ title: '操作成功', icon: 'none' })
  187. handleClosePopup()
  188. emit('refresh')
  189. } catch {
  190. handleClosePopup()
  191. }
  192. }
  193. // 取消不合适
  194. const handleCancelEliminate = async (item) => {
  195. if (!item.id) {
  196. uni.showToast({ title: 'ID不存在', icon: 'none' })
  197. return
  198. }
  199. try {
  200. await personCvUnfitCancel(item.id)
  201. uni.showToast({ title: '操作成功', icon: 'none' })
  202. handleClosePopup()
  203. emit('refresh')
  204. } catch {
  205. handleClosePopup()
  206. }
  207. }
  208. // 立即沟通
  209. const handleToCommunicate = async (item) => {
  210. if (item?.job?.status === '1') {
  211. uni.showToast({ title: '职位已关闭', icon: 'none' })
  212. return
  213. }
  214. const userId = item.userId
  215. if (!userId) return
  216. const channel = await talkToUser({ userId, text: defaultText })
  217. const query = {
  218. id: userId,
  219. name: item?.person?.name || item?.person?.phone,
  220. channelID: channel.channelID,
  221. channelType: channel.channelType,
  222. avatar: item?.person?.avatar,
  223. sex: item?.person?.sex,
  224. }
  225. const queryStr = Object.keys(query).reduce((r, v) => {
  226. if (!query[v]) {
  227. return r
  228. }
  229. return r += `${v}=${encodeURIComponent(query[v])}&`
  230. }, '?')
  231. uni.navigateTo({
  232. url: `/pagesA/chart/index${queryStr.slice(0, -1)}`
  233. })
  234. handleClosePopup()
  235. }
  236. // 结算
  237. const handleSettlement = async (item) => {
  238. if (!item.id) {
  239. uni.showToast({ title: 'ID不存在', icon: 'none' })
  240. return
  241. }
  242. try {
  243. await hireJobCvRelSettlement(item.id)
  244. uni.showToast({ title: '操作成功', icon: 'none' })
  245. handleClosePopup()
  246. emit('refresh')
  247. // 更新账户信息
  248. setTimeout(async () => {
  249. await user.getAccountInfo()
  250. }, 2000)
  251. } catch {
  252. handleClosePopup()
  253. }
  254. }
  255. // 入职
  256. const handleEnterByEnterprise = async (item) => {
  257. console.log(item, '====入职====')
  258. if (!item.id) {
  259. uni.showToast({ title: 'ID不存在', icon: 'none' })
  260. return
  261. }
  262. try {
  263. await personEntryByEnterprise(item.id)
  264. uni.showToast({ title: '操作成功', icon: 'none' })
  265. handleClosePopup()
  266. emit('refresh')
  267. } catch {
  268. handleClosePopup()
  269. }
  270. }
  271. const actionItems = (item) => {
  272. const jobClosed = item.job.status === '1' || false
  273. const arr = []
  274. if (props.current === 0) {
  275. arr.push(
  276. {
  277. title: jobClosed ? '邀请面试(职位已关闭)' : item.handleStatus ? '邀请面试(已邀面试)' : '邀请面试',
  278. disabled: jobClosed || item.handleStatus,
  279. click: handleInterviewInvite
  280. },
  281. {
  282. title: jobClosed ? '立即沟通(职位已关闭)' : '立即沟通',
  283. disabled: jobClosed,
  284. click: handleToCommunicate
  285. },
  286. )
  287. }
  288. if ([0, 1].includes(props.current)) arr.push({ title: '不合适', click: handleEliminate })
  289. if (props.current === 4) arr.push({ title: '取消不合适', click: handleCancelEliminate })
  290. if (props.current === 2 && item?.job?.hire) arr.push({ title: '结算', click: handleSettlement })
  291. if (props.current === 1 && ['3', '4'].includes(item.status)) arr.push({ title: '入职', click: handleEnterByEnterprise })
  292. // 面试后才能够加入储备
  293. if ([1, 2, 3].includes(props.current) && !item.inTalentPool) arr.push({ title: '加入储备', click: handleJoinToTalentPool })
  294. return arr
  295. }
  296. </script>
  297. <style scoped lang="scss">
  298. .mList {
  299. border-radius: 12px;
  300. box-shadow: 1px 2px 12px rgba(0, 0, 0, 0.17);
  301. margin: 0 30rpx 20rpx 30rpx;
  302. padding: 30rpx;
  303. background-color: #fbfbfb;
  304. font-size: 28rpx;
  305. &:first-child {
  306. margin-top: 20rpx;
  307. }
  308. }
  309. .user-avatar {
  310. position: relative;
  311. &-img {
  312. width: 45px;
  313. height: 45px;
  314. border-radius: 50%;
  315. }
  316. &-sex {
  317. position: absolute;
  318. right: 0;
  319. bottom: 2px;
  320. width: 20px;
  321. height: 20px;
  322. background-color: #fff;
  323. border-radius: 50%;
  324. }
  325. }
  326. .action {
  327. font-size: 28rpx;
  328. &-item {
  329. text-align: center;
  330. width: 90vw;
  331. border-bottom: 1px solid #eee;
  332. height:44px;
  333. line-height: 44px;
  334. margin: 0 auto;
  335. color: #00B760;
  336. background-color: #fff !important;
  337. &:first-child {
  338. border-radius: 5px 5px 0 0;
  339. }
  340. &:last-child {
  341. border-radius: 0 0 5px 5px;
  342. border-bottom: none;
  343. }
  344. }
  345. }
  346. .big-cancel-button {
  347. width: 90vw;
  348. height:44px;
  349. line-height: 44px;
  350. margin: 10px auto;
  351. color: #fe574a;
  352. background-color: #fff !important;
  353. font-size: 28rpx;
  354. }
  355. .sub-li-bottom {
  356. display: flex;
  357. justify-content: space-between;
  358. // align-items: flex-end;
  359. margin-top: 10px;
  360. font-size: 13px;
  361. &-item {
  362. width: 50%;
  363. height: 35px;
  364. line-height: 35px;
  365. text-align: center;
  366. margin-right: 15px;
  367. background-color: #f7f8fa;
  368. border-radius: 4px;
  369. &:nth-child(2) {
  370. margin-right: 0;
  371. }
  372. }
  373. }
  374. </style>