index.vue 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. <template>
  2. <div>
  3. <v-card class="card-box pa-5">
  4. <div class="d-flex justify-center mt-3">
  5. <TextUI :item="textItem" @enter="handleEnter" @appendInnerClick="handleEnter"></TextUI>
  6. </div>
  7. <div class="text-end">
  8. <v-btn prepend-icon="mdi-plus" color="primary" @click="handleAdd">新增</v-btn>
  9. <span>
  10. <v-btn :loading="uploadLoading" prepend-icon="mdi-download-box-outline" color="primary" variant="tonal" class="ml-3" @click="handleUploadBefore">
  11. 职位批量导入
  12. </v-btn>
  13. <File ref="uploadFile" :custom="true" customName="multipartFile" accept=".xlsx, .xls" @success="handleUploadPosition"></File>
  14. </span>
  15. <v-btn :loading="templateLoading" prepend-icon="mdi-export-variant" color="primary" variant="tonal" class="ml-3" @click="handleDownloadTemplate">批量导入模版下载</v-btn>
  16. <v-btn :loading="exportLoading" prepend-icon="mdi-export-variant" color="primary" variant="tonal" class="ml-3" @click="handleExport">职位导出</v-btn>
  17. </div>
  18. <div class="color-666 font-size-14">
  19. <span>可发布职位数 <strong class="color-primary">{{ baseInfo?.entitlement?.publishJobCount || 0 }}</strong> 个, </span>
  20. <span class="color-primary border-bottom-primary cursor-pointer" @click="router.push('/recruit/enterprise/membershipPackage/index?fromName=position')">可发布职位数不够用?点击去购买</span>
  21. </div>
  22. <div class="mt-3">
  23. <v-tabs v-model="tab" align-tabs="start" color="primary" bg-color="#f7f8fa" @update:model-value="handleChangeTab">
  24. <v-tab v-for="val in tabList" :key="val.value" :value="val.value"> {{ val.label }}</v-tab>
  25. </v-tabs>
  26. <v-window v-model="tab" class="mt-1">
  27. <v-window-item v-for="val in tabList" :key="val.value" :value="val.value">
  28. <PositionItem v-if="items.length" :tab="val.value" :items="items" @refresh="handleRefresh"></PositionItem>
  29. </v-window-item>
  30. </v-window>
  31. <Empty v-if="!items.length" :message="loading ? '加载中...' : tipsText" :elevation="false"></Empty>
  32. <CtPagination
  33. v-else
  34. :total="total"
  35. :page="query.pageNo"
  36. :limit="query.pageSize"
  37. @handleChange="handleChangePage"
  38. ></CtPagination>
  39. </div>
  40. </v-card>
  41. </div>
  42. </template>
  43. <script setup>
  44. defineOptions({ name: 'enterprise-position-list'})
  45. import { ref } from 'vue'
  46. import TextUI from '@/components/FormUI/TextInput'
  47. import PositionItem from './components/item.vue'
  48. import { useRouter } from 'vue-router'; const router = useRouter()
  49. import { getJobAdvertisedList, getJobAdvertisedExport, jobAdvertisedTemplateDownload, jobAdvertisedUpload } from '@/api/position'
  50. import { dealDictArrayData } from '@/utils/position'
  51. import { useI18n } from '@/hooks/web/useI18n'
  52. import { useUserStore } from '@/store/user'
  53. import { getEnterprisePubJobTypePermission } from '@/api/recruit/enterprise/position'
  54. import download from '@/utils/download'
  55. import Snackbar from '@/plugins/snackbar'
  56. import Confirm from '@/plugins/confirm'
  57. import { useRoute } from 'vue-router'; const route = useRoute()
  58. const store = useUserStore()
  59. const { t } = useI18n()
  60. const total = ref(0)
  61. const tipsText = ref(t('common.noData'))
  62. const query = ref({
  63. pageSize: 10,
  64. pageNo: 1,
  65. status: 0, // 0招聘中 1已关闭
  66. hasExpiredData: false, // true 到期职位
  67. hire: false // true 众聘岗位
  68. })
  69. const templateLoading = ref(false)
  70. const uploadLoading = ref(false)
  71. const exportLoading = ref(false)
  72. const uploadFile = ref()
  73. const tab = ref(route.query?.tab ? route.query?.tab-0 : 1)
  74. const tabList = [
  75. { label: '待发布', value: 0, status: 99 },
  76. { label: t('position.recruitmentInProgress'), value: 1, status: 0 },
  77. { label: t('position.closed'), value: 2, status: 1 },
  78. { label: t('position.expiredPosition'), value: 3 }
  79. ]
  80. let baseInfo = ref(JSON.parse(localStorage.getItem('entBaseInfo')) || {})
  81. store.$subscribe((mutation, state) => {
  82. if (Object.keys(state.entBaseInfo).length) baseInfo.value = state.entBaseInfo
  83. })
  84. if (router.currentRoute.value.query?.key) tab.value = Number(router.currentRoute.value.query.key)
  85. const items = ref([])
  86. const textItem = ref({
  87. type: 'text',
  88. width: 600,
  89. value: '',
  90. label: '请输入职位名称',
  91. clearable: true,
  92. appendInnerIcon: 'mdi-magnify'
  93. })
  94. const handleAdd = async () => {
  95. const data = await getEnterprisePubJobTypePermission()
  96. if (!data || !data.length) return Snackbar.warning('没有该操作权限,请联系平台管理员升级后再试')
  97. // 新增职位时查询是否有可发布职位数
  98. await store.getEnterpriseInfo(true)
  99. // if (baseInfo.value?.entitlement.publishJobCount <= 0) return Snackbar.warning('可发布职位数不足,请联系平台管理员')
  100. router.push('/recruit/enterprise/position/add')
  101. await store.getEnterpriseUserAccountInfo()
  102. }
  103. const loading = ref(false)
  104. // 获取职位列表
  105. const getPositionList = async () => {
  106. await store.getEnterpriseInfo(true)
  107. items.value = []; total.value = 0
  108. loading.value = true
  109. if (tab.value !== 3) {
  110. query.value.status = tabList[tab.value].status
  111. query.value.hasExpiredData = false
  112. } else {
  113. query.value.hasExpiredData = true
  114. delete query.value.status
  115. }
  116. const { list, total: number } = await getJobAdvertisedList(query.value)
  117. if (!list.length) {
  118. if (query.value.name) tipsText.value = '暂无数据,请更换关键词后再试'
  119. }
  120. total.value = number
  121. items.value = list.length ? dealDictArrayData([], list) : []
  122. loading.value = false
  123. }
  124. getPositionList()
  125. const handleRefresh = (index) => {
  126. query.pageNo = 1
  127. if (index) tab.value = index
  128. getPositionList()
  129. }
  130. // 职位列表导出
  131. const handleExport = async () => {
  132. exportLoading.value = true
  133. try {
  134. const data = await getJobAdvertisedExport(query.value)
  135. const label = tabList.find(e => e.value === tab.value)?.label || ''
  136. const txt = `职位列表${label? '(' + label + ')' : ''}`
  137. download.excel(data, txt + '.xlsx')
  138. } finally {
  139. exportLoading.value = false
  140. }
  141. }
  142. // 批量上传模版导出
  143. const handleDownloadTemplate = async () => {
  144. templateLoading.value = true
  145. try {
  146. const data = await jobAdvertisedTemplateDownload()
  147. download.excel(data, '批量上传模版下载.xlsx')
  148. } finally {
  149. templateLoading.value = false
  150. }
  151. }
  152. // 批量上传
  153. const handleUploadBefore = () => {
  154. const option = {
  155. otherBtnText: '去下载模板',
  156. sureText: '继续上传',
  157. }
  158. Confirm(t('common.confirmTitle'), '如还未下载过批量上传的模板,请先下载,并且使用模板格式上传职位', option).then((obj) => {
  159. if (obj?.otherClick) {
  160. Snackbar.info('开始下载!')
  161. handleDownloadTemplate()
  162. } else {
  163. uploadFile.value.trigger()
  164. }
  165. })
  166. }
  167. const handleUploadPosition = async (formData) => {
  168. uploadLoading.value = true
  169. try {
  170. await jobAdvertisedUpload(formData)
  171. Snackbar.success('上传成功')
  172. getPositionList()
  173. } finally {
  174. uploadLoading.value = false
  175. }
  176. }
  177. const handleChangeTab = () => {
  178. query.value.pageNo = 1
  179. getPositionList()
  180. }
  181. const handleChangePage = (index) => {
  182. query.value.pageNo = index
  183. getPositionList()
  184. }
  185. // 职位名称检索
  186. const handleEnter = (e) => {
  187. query.value.name = e
  188. query.value.pageNo = 1
  189. getPositionList()
  190. }
  191. </script>
  192. <style scoped lang="scss">
  193. </style>