add.vue 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. <template>
  2. <div>
  3. <v-card class="pa-5" v-if="show">
  4. <v-timeline align="start" side="end">
  5. <v-timeline-item
  6. v-for="(val, i) in list"
  7. :key="i"
  8. :dot-color="val.color"
  9. :icon="val.icon"
  10. >
  11. <div>
  12. <h2 class="mt-n1 headline font-weight-regular">{{ val.title }}</h2>
  13. <div class="mb-4 color-666 font-size-13">{{ val.desc }}</div>
  14. <component :is="val.path" :ref="val.ref" :isFair="props.isFair" :itemData="itemData"></component>
  15. </div>
  16. </v-timeline-item>
  17. <!-- <v-timeline-item v-if="showExtend" dot-color="light-blue darken-1" icon="mdi-numeric-3">
  18. <div>
  19. <h2 class="mt-n1 headline font-weight-regular">其他</h2>
  20. <CtForm ref="formPageRef" class="mt-3" :items="items" style="width: 650px;"></CtForm>
  21. </div>
  22. </v-timeline-item> -->
  23. <slot name="timeline"></slot>
  24. </v-timeline>
  25. <div class="text-center mb">
  26. <v-btn class="half-button mr-3" color="primary" variant="outlined" @click="handleCancel()">{{ $t('common.cancel') }}</v-btn>
  27. <v-btn class="half-button" color="primary" :loading="loading" @click="handleSave">{{ $t('common.release') }}</v-btn>
  28. </div>
  29. </v-card>
  30. <!-- 弹窗支付 -->
  31. <confirmPaymentDialog
  32. v-if="showConfirmPaymentDialog"
  33. :cost="payInfo.cost"
  34. :spuId="payInfo.spuId"
  35. :spuName="payInfo.spuName"
  36. :showEnterpriseJump="true"
  37. :showOriginalPrice="true"
  38. :orderType="1"
  39. @paySuccess="paySuccess"
  40. @close="handlePayClose"
  41. ></confirmPaymentDialog>
  42. </div>
  43. </template>
  44. <script setup>
  45. defineOptions({ name: 'enterprise-position-add'})
  46. import { ref } from 'vue'
  47. import { useRouter, useRoute } from 'vue-router'
  48. import { dealDictObjData } from '@/utils/position'
  49. import { saveJobAdvertised, getJobDetails } from '@/api/position'
  50. import baseInfo from './baseInfo.vue'
  51. import jobRequirements from './jobRequirements.vue'
  52. import Snackbar from '@/plugins/snackbar'
  53. import { useI18n } from '@/hooks/web/useI18n'
  54. import { useUserStore } from '@/store/user'
  55. import { createTradeOrder } from '@/api/position'
  56. // import { saveJobAdvertisedExtend, getJobAdvertisedExtend } from '@/api/recruit/enterprise/jobFair'
  57. // import { schoolMajorByName, schoolMajorById } from '@/api/recruit/personal/resume'
  58. // 添加只为之后是否需要额外操作
  59. const props = defineProps({
  60. afterAdd: Function,
  61. // valid: Function,
  62. isFair: Boolean
  63. })
  64. const { t } = useI18n()
  65. const route = useRoute()
  66. const router = useRouter()
  67. const userStore = useUserStore()
  68. const baseInfoRef = ref()
  69. const jobRequirementsRef = ref()
  70. const loading = ref(false)
  71. const itemData = ref({})
  72. const showConfirmPaymentDialog = ref(false)
  73. const payInfo = ref({})
  74. const list = [
  75. {
  76. color: '#00B760',
  77. icon: 'mdi-numeric-1',
  78. title: t('position.positionInformation'),
  79. desc: '',
  80. path: baseInfo,
  81. ref: baseInfoRef
  82. },
  83. {
  84. color: 'indigo-lighten-2',
  85. icon: 'mdi-numeric-2',
  86. title: t('position.jobRequirements'),
  87. desc: t('position.requirementDesc'),
  88. path: jobRequirements,
  89. ref: jobRequirementsRef
  90. }
  91. ]
  92. // const formPageRef = ref()
  93. // const items = ref({
  94. // options: [
  95. // {
  96. // type: 'text',
  97. // key: 'dept',
  98. // value: null,
  99. // col: 6,
  100. // label: '招聘部门 '
  101. // },
  102. // {
  103. // type: 'autocomplete',
  104. // key: 'majorId',
  105. // search: getMajorList,
  106. // value: null,
  107. // label: '专业要求 ',
  108. // itemText: 'nameCn',
  109. // col: 6,
  110. // itemValue: 'id',
  111. // flexStyle: 'ml-3',
  112. // noDataText: '请输入检索专业',
  113. // items: []
  114. // },
  115. // {
  116. // type: 'autocomplete',
  117. // key: 'frequency-dateType',
  118. // value: null,
  119. // label: '工作频率 ',
  120. // col: 6,
  121. // items: [
  122. // { label: '每周', value: 'week' },
  123. // { label: '每月', value: 'month' },
  124. // { label: '每年', value: 'year' }
  125. // ]
  126. // },
  127. // {
  128. // type: 'number',
  129. // key: 'frequency-day',
  130. // value: null,
  131. // flexStyle: 'ml-3',
  132. // col: 6,
  133. // label: '出勤天数 '
  134. // }
  135. // ]
  136. // })
  137. // 是否展示扩展信息
  138. // const showExtend = ref(false)
  139. // const handleChangeType = (show) => {
  140. // console.log(show, '招聘类型是否为实习')
  141. // showExtend.value = show
  142. // if (!show) {
  143. // items.value.options.forEach(e => e.value = null)
  144. // }
  145. // }
  146. // console.log(route)
  147. // async function initPosition (jobId) {
  148. // const res = await getJobAdvertisedExtend(jobId)
  149. // console.log(res, '获取扩展信息')
  150. // if (!res) return
  151. // items.value.options.forEach(e => {
  152. // if (e.key.includes('frequency') && res.frequency) {
  153. // const keys = e.key.split('-')
  154. // e.value = res[keys[0]][keys[1]]
  155. // return
  156. // }
  157. // if (e.key === 'majorId') {
  158. // getMajorById(res.majorId)
  159. // }
  160. // e.value = res[e.key]
  161. // })
  162. // showExtend.value = true
  163. // }
  164. // async function getMajorList (name) {
  165. // if (!name) {
  166. // return
  167. // }
  168. // const res = await schoolMajorByName({ name })
  169. // items.value.options.find(e => e.key === 'majorId').items = res
  170. // }
  171. // async function getMajorById (id) {
  172. // if (!id) return
  173. // const res = await schoolMajorById({ id })
  174. // items.value.options.find(e => e.key === 'majorId').items = [res]
  175. // }
  176. // 保存扩展信息
  177. // const handleSaveExtend = async (jobId) => {
  178. // const query = items.value.options.reduce((r, v) => {
  179. // if (v.key.includes('frequency')) {
  180. // const keys = v.key.split('-')
  181. // if (!r[keys[0]]) {
  182. // r[keys[0]] = {}
  183. // }
  184. // r[keys[0]][keys[1]] = v.type === 'number' ? +v.value : v.value
  185. // return r
  186. // }
  187. // if (v.key === 'majorId') {
  188. // r.major = v.items.find(e => e.id === v.value)?.nameCn || ''
  189. // }
  190. // r[v.key] = v.type === 'number' ? +v.value : v.value
  191. // return r
  192. // }, { jobId })
  193. // console.log(query, 'extendInfo')
  194. // await saveJobAdvertisedExtend(query)
  195. // }
  196. let submitParams = {}
  197. // 发布
  198. const handleSave = async () => {
  199. const baseInfo = await baseInfoRef.value[0].getQuery()
  200. if (baseInfo === 'failed') return
  201. const requirement = await jobRequirementsRef.value[0].getQuery()
  202. if (!requirement) return Snackbar.warning('请按要求填写信息')
  203. if (!baseInfo || !requirement) return Snackbar.warning('请将信息填写完整')
  204. submitParams = Object.assign(baseInfo, requirement, { currency_type: 0 }) // currency_type: 写死0(人民币) source: 0职位管理|1招聘会
  205. submitParams.source = props.isFair ? '2' : submitParams.source ? submitParams.source : '0' // 职位来源(0职位管理|1众聘职位|2招聘会)
  206. // submitParams.bizId = props.isFair ? route.params.id : submitParams.bizId ? submitParams.bizId : null
  207. if (props.isFair) submitParams.bizId = route.params.id // 招聘会职位新增添加招聘会id
  208. if (route.query && route.query.id) submitParams.id = route.query.id // 有id则为编辑
  209. console.log('发布职位参数', submitParams, isClone.value)
  210. if (isClone.value) delete submitParams.id // 克隆职位删除id
  211. saveEmit()
  212. }
  213. const saveEmit = async (retry) => {
  214. loading.value = true
  215. try {
  216. const res = await saveJobAdvertised({ ...submitParams, fair: props.isFair ? true : false }) // fair:是否为招聘会职位编辑-必填
  217. // if (props.isFair) await handleSaveExtend(res) // 保存扩展信息
  218. // status:99为待支付职位,弹窗支付
  219. if (submitParams?.status && submitParams?.status === '99') {
  220. loading.value = true
  221. // 金额*100,页面展示/100
  222. await createTradeOrder({ spuId: res, spuName: submitParams.name, price: 39900, type: 1 }) // 普通职位type 1
  223. loading.value = false
  224. payInfo.value = { cost: 39900, spuId: res, spuName: submitParams.name }
  225. showConfirmPaymentDialog.value = true
  226. Snackbar.warning('当前可发布职位额度不足,请扫码支付')
  227. return
  228. }
  229. Snackbar.success(submitParams.id ? t('common.editSuccessMsg') : '发布成功')
  230. handleCancel()
  231. } catch (error) {
  232. console.log('error', error)
  233. // 可发布职位额度不足时,将status设为99重新提交
  234. if (error === '企业额度已超过') {
  235. submitParams.status = '99'
  236. if (!retry) saveEmit(true) // true:重新提交避免死循环
  237. }
  238. } finally {
  239. loading.value = false
  240. }
  241. }
  242. const paySuccess = async () => {
  243. showConfirmPaymentDialog.value = false
  244. if (props.afterAdd) {
  245. await props.afterAdd(payInfo.value.spuId)
  246. payInfo.value = {}
  247. return
  248. }
  249. payInfo.value = {}
  250. Snackbar.success(submitParams.id ? t('common.editSuccessMsg') : '发布成功')
  251. handleCancel()
  252. }
  253. const handlePayClose = () => {
  254. Snackbar.warning('您已取消支付')
  255. itemData.value = {}
  256. showConfirmPaymentDialog.value = false
  257. router.push({ path: '/recruit/enterprise/position', query: { key: 0 }})
  258. }
  259. const show = ref(false)
  260. const isClone = ref(false)
  261. // 获取编辑的职位详情
  262. const getPositionDetail = async (id) => {
  263. const data = await getJobDetails({ id })
  264. if (!data && !Object.keys(data).length) return
  265. itemData.value = {...data, ...dealDictObjData({}, data)}
  266. show.value = true
  267. // 招聘类型为实习则获取扩展信息
  268. // if (data.type === '3') initPosition(id)
  269. }
  270. // 有id为编辑
  271. if (route.query?.id) {
  272. // 是否为克隆职位
  273. isClone.value = route.query.clone === '1'
  274. getPositionDetail(route.query.id)
  275. } else show.value = true
  276. // 取消
  277. const handleCancel = () => {
  278. itemData.value = {}
  279. const currentPage = router.currentRoute.value.path
  280. router.push({ path: currentPage.indexOf('jobFair') !== -1 ? `/recruit/enterprise/jobFair/details/${route.params.id}` : '/recruit/enterprise/position' })
  281. // 新增职位发布需更新账户信息
  282. if (route.query && !route.query?.id) {
  283. setTimeout(async () => {
  284. await userStore.getEnterpriseUserAccountInfo()
  285. }, 2000)
  286. }
  287. }
  288. </script>
  289. <style scoped lang="scss">
  290. .mb {
  291. margin-bottom: 100px;
  292. }
  293. </style>