baseInfo.vue 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. <template>
  2. <div>
  3. <CtForm ref="formPageRef" :items="items" style="width: 650px;">
  4. <template #positionId="{ item }">
  5. <v-menu :close-delay="1" :open-delay="0" v-bind="$attrs">
  6. <template v-slot:activator="{ props }">
  7. <textUI
  8. :modelValue="item.value"
  9. :item="item"
  10. v-bind="props"
  11. style="position: relative;"
  12. ></textUI>
  13. </template>
  14. <jobTypeCard class="jobTypeCardBox" :select="[query.positionId].filter(Boolean)" :isSingle="true" @handleJobClick="handleJobClickItem"></jobTypeCard>
  15. </v-menu>
  16. <v-btn v-if="showTemplateBtn" class="ml-3 half-button" color="primary" style="margin-top: 2px;" @click="useJobTemplate(item)">职位模板</v-btn>
  17. </template>
  18. <template #jobFairSign>
  19. <svg-icon name="jobFair" size="40"></svg-icon>
  20. </template>
  21. </CtForm>
  22. <CtDialog :visible="showDialog" :widthType="1" titleClass="text-h6" :footer="false" title="重复职位列表" @close="showDialog = false">
  23. <CtTable
  24. :items="tableItems"
  25. :headers="headers"
  26. :loading="false"
  27. :elevation="0"
  28. :isTools="false"
  29. :showPage="false"
  30. >
  31. <template #actions="{ item }">
  32. <v-btn :disabled="!item.id" color="primary" variant="text" @click="handleDetails(item)">详情</v-btn>
  33. </template>
  34. </CtTable>
  35. </CtDialog>
  36. </div>
  37. </template>
  38. <script setup>
  39. defineOptions({ name: 'position-add-baseInfo'})
  40. import CtForm from '@/components/CtForm'
  41. import { reactive, ref, watch } from 'vue'
  42. import textUI from '@/components/FormUI/TextInput'
  43. import jobTypeCard from '@/components/jobTypeCard'
  44. import { getRecruitPositionDetails } from '@/api/recruit/enterprise/position'
  45. import Confirm from '@/plugins/confirm'
  46. import Snackbar from '@/plugins/snackbar'
  47. import { useI18n } from '@/hooks/web/useI18n';
  48. import { getJobAdvertised } from '@/api/enterprise'
  49. import { formatName } from '@/utils/getText'
  50. import { dealDictArrayData } from '@/utils/position'
  51. import { timesTampChange } from '@/utils/date'
  52. const { t } = useI18n()
  53. const props = defineProps({
  54. itemData: Object,
  55. isFair: {
  56. type: Boolean,
  57. default: false
  58. }
  59. })
  60. const getValue = (key) => {
  61. return items.value.options.find(e => e.key === key)
  62. }
  63. const showTemplateBtn = ref(true)
  64. const formPageRef = ref()
  65. let query = reactive({})
  66. // 重复职位弹窗
  67. const showDialog = ref(false)
  68. const tableItems = ref([])
  69. const headers = [
  70. { title: '职位名称', key: 'name', sortable: false, value: item => formatName(item.name) },
  71. { title: '薪资', key: 'payTo', sortable: false, value: item => !item.payFrom && !item.payTo ? '面议' : `${item.payFrom}-${item.payTo}/${item.payName}` },
  72. { title: '学历要求', key: 'eduName', sortable: false },
  73. { title: '工作经验', key: 'expName', sortable: false },
  74. { title: '地区', key: 'area', sortable: false, value: item => !item.areaId ? '全国' : item.area?.str },
  75. { title: '状态', key: 'status', sortable: false, value: item => !item.status ? '' : item.status === '0' ? '招聘中' : '已关闭' },
  76. { title: '创建时间', key: 'createTime', sortable: false, value: item => timesTampChange(item.createTime) },
  77. { title: '更新时间', key: 'updateTime', sortable: false, value: item => timesTampChange(item.updateTime) },
  78. { title: '操作', key: 'actions', sortable: false }
  79. ]
  80. const handleDetails = (item) => {
  81. window.open(`/recruit/enterprise/position/details?id=${item.id}`)
  82. }
  83. let showConfirm = false
  84. // 效验职位名称是否重复
  85. const handleValidName = async (item, val) => {
  86. if (!val || showConfirm) return
  87. try {
  88. const data = await getJobAdvertised({ jobName: val })
  89. tableItems.value = data ? dealDictArrayData([], data) : []
  90. if (data && data.length > 0) {
  91. showConfirm = true // 避免快速点击出现多个弹窗提示
  92. Confirm(
  93. t('common.confirmTitle'),
  94. '该职位名称已在您的招聘职位列表中存在,是否继续发布?',
  95. { sureText: '查看已有职位', cancelText: '继续发布', cancelCallback: true }
  96. ).then(() => {
  97. // 弹窗查看已有职位
  98. showDialog.value = true
  99. showConfirm = false
  100. }).catch(() => {
  101. showConfirm = false
  102. })
  103. }
  104. } catch {}
  105. }
  106. const items = ref({
  107. options: [
  108. {
  109. slotName: 'positionId',
  110. key: 'positionId',
  111. value: '',
  112. labelKey: 'positionName',
  113. label: '职位类型 *',
  114. noParam: true,
  115. readonly: true,
  116. rules: [v => !!v || '请选择职位类型']
  117. },
  118. // 招聘会职位展示标识
  119. {
  120. slotName: 'jobFairSign',
  121. hide: !props.isFair,
  122. col: 1,
  123. noParam: true
  124. },
  125. {
  126. type: 'text',
  127. key: 'name',
  128. value: '',
  129. col: props.isFair ? 11 : 12,
  130. label: '职位名称 *',
  131. blur: handleValidName, // 效验职位名称是否重复
  132. rules: [v => !!v || '请填写职位名称']
  133. },
  134. {
  135. type: 'wangEditor',
  136. key: 'content',
  137. value: '',
  138. label: '岗位职责 *',
  139. maxLength: 5000,
  140. rules: '请填写岗位职责'
  141. },
  142. {
  143. type: 'wangEditor',
  144. key: 'requirement',
  145. value: '',
  146. label: '岗位要求 *',
  147. maxLength: 5000,
  148. rules: '请填写岗位要求'
  149. }
  150. ]
  151. })
  152. // 编辑回显
  153. watch(
  154. () => props.itemData,
  155. (val) => {
  156. if (!Object.keys(val).length) return
  157. items.value.options.forEach(e => {
  158. if (e.labelKey) {
  159. query[e.key] = val[e.key]
  160. e.value = val[e.labelKey]
  161. return
  162. }
  163. if (e.noParam) return
  164. e.value = val[e.key]
  165. })
  166. },
  167. { immediate: true },
  168. { deep: true }
  169. )
  170. // 职位类型
  171. const handleJobClickItem = (list, name) => {
  172. const positionId = getValue('positionId')
  173. if (!list.length) {
  174. delete query.positionId
  175. positionId.value = ''
  176. return
  177. }
  178. showTemplateBtn.value = true
  179. query.positionId = list[0]
  180. positionId.value = name
  181. }
  182. const useJobTemplate = async () => {
  183. if (!query.positionId) return Snackbar.warning('请先选择职位类型')
  184. // 获取职位模板内容-赋值
  185. const res = await getRecruitPositionDetails(query.positionId)
  186. if (!res || !res.content || !res.requirement) {
  187. Snackbar.warning('此职位类型没有可使用的模板!')
  188. showTemplateBtn.value = false
  189. return
  190. }
  191. const content = items.value.options.find(e => e.key === 'content')
  192. const requirement = items.value.options.find(e => e.key === 'requirement')
  193. if ((content && content.value) || (requirement && requirement.value)) {
  194. // 弹窗提示
  195. Confirm(t('common.confirmTitle'), '您确定要放弃目前岗位描述的内容吗?').then(() => {
  196. content.value = res.content
  197. requirement.value = res.requirement
  198. Snackbar.success('模板填充完成!')
  199. })
  200. } else {
  201. // 无内容点击默认填充
  202. if (content) content.value = res.content
  203. if (requirement) requirement.value = res.requirement
  204. Snackbar.success('模板填充完成!')
  205. }
  206. }
  207. const getQuery = async () => {
  208. const { valid } = await formPageRef.value.formRef.validate()
  209. if (!valid) return
  210. const obj = {
  211. hirePrice: 0,
  212. expireTime: null, // 默认为长期有效
  213. bizId: null, // 招聘会id
  214. hire: false
  215. }
  216. items.value.options.forEach(e => {
  217. if (e.noParam || e.value === null) return
  218. else obj[e.key] = e.value
  219. })
  220. if (!obj.content) {
  221. Snackbar.warning('请填写岗位职责')
  222. return 'failed'
  223. }
  224. if (!obj.requirement) {
  225. Snackbar.warning('请填写岗位要求')
  226. return 'failed'
  227. }
  228. obj.source = props.isFair ? '2' : '0' // 职位来源(0职位管理|1众聘职位|2招聘会)
  229. Object.assign(query, obj)
  230. return query
  231. }
  232. defineExpose({
  233. formPageRef,
  234. getQuery
  235. })
  236. </script>
  237. <style scoped lang="scss">
  238. .jobTypeCardBox {
  239. position: absolute;
  240. top: -22px;
  241. left: 0;
  242. }
  243. .calculation {
  244. display: block;
  245. width: 120px;
  246. }
  247. .bizTips {
  248. width: 650px;
  249. position: absolute;
  250. left: -460px;
  251. color: var(--v-error-base);
  252. margin-top: 8px;
  253. font-size: 14px;
  254. }
  255. </style>