item.vue 9.3 KB

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