index.vue 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. <template>
  2. <div>
  3. <div v-if="showLogin" class="login-content" :style="{'background-image': 'url(' + webContent.loginBgUrl + ')'}">
  4. <div class="login-content-box pa-10">
  5. <div class="login-content-box-title text-center mt-4">请先登录您的企业账号</div>
  6. <passwordFrom class="mt-10" ref="passRef" placeholder="请输入企业邮箱" :validEmail="true"></passwordFrom>
  7. <v-btn :loading="loading" color="primary" class="white--text mt-5" min-width="340" @click="handleLogin">登录</v-btn>
  8. </div>
  9. </div>
  10. <div v-else class="content py-3" @scroll="handleScroll">
  11. <v-card class="py-3 px-5" :style="{'width': isMobile ? '100%' : '750px'}" style="min-height: calc(100vh - 24px); box-sizing: border-box; margin: 0 auto;">
  12. <div class="d-flex align-center" style="width: 340px; margin: auto;">
  13. <TextInput v-model="textItem.value" :item="textItem" @click="openDrawer" @appendInnerClick="openDrawer"></TextInput>
  14. </div>
  15. <!-- 已发布职位 -->
  16. <div class="text-end mb-1">
  17. <v-menu transition="slide-y-transition">
  18. <template v-slot:activator="{ props, isActive }">
  19. <v-btn color="primary" v-bind="props" variant="tonal" :append-icon="isActive ? 'mdi mdi-menu-up' : 'mdi mdi-menu-down'">
  20. {{ query?.jobId ? select : '选择已发布职位推荐' }}
  21. </v-btn>
  22. </template>
  23. <v-list>
  24. <v-list-item v-for="k in selectItems.items" :key="k.value" color="primary" :active="k.value === query?.jobId">
  25. <v-list-item-title @click="handleActive(k)">{{ k.label }}</v-list-item-title>
  26. </v-list-item>
  27. </v-list>
  28. </v-menu>
  29. <v-icon v-if="query?.jobId" color="primary" size="30" class="ml-3" @click="handleClearJob">mdi-close-circle-outline</v-icon>
  30. </div>
  31. <v-divider></v-divider>
  32. <div v-if="items.length">
  33. <div v-for="(val, index) in items" :key="val.id" @click="handleDetail(val)">
  34. <div class="py-3 d-flex align-center">
  35. <v-avatar size="large">
  36. <v-img :src="getUserAvatar(val.avatar, val.sex)" width="50" height="50"></v-img>
  37. </v-avatar>
  38. <div class="ml-3 d-flex flex-column">
  39. <p class="font-size-20">{{ val.name }}</p>
  40. <p class="color-999 font-size-16">
  41. {{ val.jobStatusName }}
  42. <span v-if="val.jobStatusName && val.expName" class="septal-line"></span>
  43. {{ val.expName }}
  44. <span v-if="val.eduName" class="septal-line"></span>
  45. {{ val.eduName }}
  46. </p>
  47. </div>
  48. </div>
  49. <div class="bg-box" v-if="index !== items.length - 1"></div>
  50. </div>
  51. </div>
  52. <Empty v-else :elevation="false" message="暂无数据"></Empty>
  53. </v-card>
  54. </div>
  55. <Loading :visible="loading"></Loading>
  56. <v-navigation-drawer v-model="screen" location="top" temporary class="px-5" style="border-radius: 0 0 10px 10px;">
  57. <FilterPage ref="filterRef" @close="screen = false" @search="handleSearch"></FilterPage>
  58. </v-navigation-drawer>
  59. </div>
  60. </template>
  61. <script setup>
  62. defineOptions({ name: 'talentRecommendation'})
  63. import { ref, onMounted } from 'vue'
  64. import passwordFrom from '@/views/login/components/passwordPage.vue'
  65. import Snackbar from '@/plugins/snackbar'
  66. import { useUserStore } from '@/store/user'
  67. import { passwordLogin } from '@/api/common'
  68. import { getJobAdvertised, getPersonSearchPage, getPersonRecommendPage } from '@/api/enterprise'
  69. import { dealDictArrayData } from '@/utils/position'
  70. import { getUserAvatar } from '@/utils/avatar'
  71. import { useRouter } from 'vue-router'
  72. import FilterPage from './components/filter.vue'
  73. import { webContentStore } from '@/store/webContent'
  74. const webContent = webContentStore()
  75. const router = useRouter()
  76. const loading = ref(false)
  77. const passRef = ref(null)
  78. const items = ref([])
  79. const total = ref(0)
  80. const query = ref({
  81. pageNo: 1,
  82. pageSize: 20
  83. })
  84. const select = ref('')
  85. const screen1 = ref(false)
  86. const token = ref(localStorage.getItem('ENT_ACCESS_TOKEN'))
  87. const showLogin = ref(token.value ? false : true)
  88. const selectItems = ref({
  89. label: '已发布职位',
  90. placeholder: '请选择要进行推荐的职位',
  91. clearable: true,
  92. width: 600,
  93. items: []
  94. })
  95. const textItem = ref({
  96. type: 'text',
  97. width: 600,
  98. value: '',
  99. label: '输入关键词搜索',
  100. clearable: false,
  101. readonly: true,
  102. appendInnerIcon: 'mdi-magnify'
  103. })
  104. // 已发布职位列表
  105. const getJobList = async () => {
  106. const data = await getJobAdvertised({ status: 0 })
  107. if (data.length) {
  108. const list = dealDictArrayData([], data)
  109. selectItems.value.items = list.map(e => {
  110. return { label: `${e.name}${e.areaName ? '_' + e.areaName : ''} ${e.payFrom ? e.payFrom + '-' : ''}${e.payTo}${e.payName ? '/' + e.payName : ''}`, value: e.id }
  111. })
  112. }
  113. }
  114. const handleActive = (k) => {
  115. select.value = k.label
  116. query.value.jobId = k.value
  117. query.value.pageNo = 1
  118. screen1.value = false
  119. getData(true)
  120. }
  121. const handleClearJob = () => {
  122. select.value = ''
  123. delete query.value.jobId
  124. query.value.pageNo = 1
  125. items.value = []
  126. total.value = 0
  127. screen1.value = false
  128. }
  129. // 组件挂载后添加事件监听器
  130. const isMobile = ref(false)
  131. onMounted(async () => {
  132. await webContent.getSystemWebContent()
  133. if (!token.value) {
  134. Snackbar.warning('请先登录')
  135. showLogin.value = true
  136. }
  137. else getJobList()
  138. const userAgent = navigator.userAgent
  139. isMobile.value = /(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i.test(userAgent)
  140. })
  141. // 列表
  142. const getData = async (isEmpty) => {
  143. // isEmpty:是否清空列表
  144. loading.value = true
  145. try {
  146. const res = query.value?.jobId ? await getPersonRecommendPage(query.value) : await getPersonSearchPage(query.value)
  147. const list = res.list || []
  148. items.value = list.length ? !isEmpty ? [...items.value, ...dealDictArrayData([], list)] : dealDictArrayData([], list) : []
  149. total.value = res.total
  150. } catch (err) {
  151. loading.value = false
  152. if (err.code === 401) {
  153. items.value = []
  154. total.value = 0
  155. token.value = null
  156. query.value = {
  157. pageNo: 1,
  158. pageSize: 20
  159. }
  160. Snackbar.warning('请先登录')
  161. showLogin.value = true
  162. }
  163. } finally {
  164. loading.value = false
  165. }
  166. }
  167. // 底部加载
  168. const handleScroll = (e) => {
  169. if (!token.value) {
  170. showLogin.value = true
  171. Snackbar.warning('请先登录')
  172. return
  173. }
  174. if (e.srcElement.scrollTop + e.srcElement.clientHeight > e.srcElement.scrollHeight - 10) {
  175. if (items.value.length < total.value) {
  176. query.value.pageNo++
  177. getData()
  178. }
  179. }
  180. }
  181. // 登录
  182. const handleLogin = async () => {
  183. const { valid } = await passRef.value.passwordForm.validate()
  184. if (!valid) return
  185. loading.value = true
  186. try {
  187. const data = await passwordLogin({ ...passRef.value.loginData, account: passRef.value.loginData.phone })
  188. await useUserStore().changeRole({ ...data, type: 'emailLogin', noJump: true })
  189. await getJobList()
  190. if (data?.accessToken) {
  191. token.value = data?.accessToken
  192. showLogin.value = false
  193. }
  194. } catch (err) {
  195. Snackbar.warning(err)
  196. } finally {
  197. loading.value = false
  198. }
  199. }
  200. // 人才详情
  201. const handleDetail = ({ userId, id }) => {
  202. if (!token.value) {
  203. showLogin.value = true
  204. Snackbar.warning('请先登录')
  205. return
  206. }
  207. if (!userId || !id) return
  208. router.push(`/recruit/enterprise/talentRecommendation/details/${userId}?id=${id}`)
  209. }
  210. // 筛选
  211. const filterRef = ref()
  212. const screen = ref(false)
  213. const openDrawer = () => {
  214. if (!token.value) {
  215. showLogin.value = true
  216. Snackbar.warning('请先登录')
  217. return
  218. }
  219. screen.value = true
  220. }
  221. const handleSearch = (val) => {
  222. if (!token.value) {
  223. showLogin.value = true
  224. Snackbar.warning('请先登录')
  225. return
  226. }
  227. screen.value = false
  228. textItem.value.value = val.content
  229. query.value.pageNo = 1
  230. query.value = Object.assign(query.value, val)
  231. getData(true)
  232. }
  233. </script>
  234. <style scoped lang="scss">
  235. .content {
  236. background-color: #f2f4f7;
  237. height: 100vh;
  238. overflow-y: auto;
  239. }
  240. .login-content {
  241. position: relative;
  242. width: 100%;
  243. height: 100vh;
  244. background-size: cover;
  245. &-box {
  246. position: absolute;
  247. top: 50%;
  248. left: 50%;
  249. translate: -50% -50%;
  250. width: 420px;
  251. height: 380px;
  252. background-color: #fff;
  253. border-radius: 10px;
  254. &-title {
  255. color: #4c4c4c;
  256. font-size: 24px;
  257. }
  258. }
  259. }
  260. .bg-box {
  261. height: 10px;
  262. background-color: #f2f4f7;
  263. }
  264. .active {
  265. color: var(--v-primary-base);
  266. border: 2px solid var(--v-primary-base);
  267. }
  268. </style>