|  | @@ -94,21 +94,29 @@
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      <!-- 选择简历 -->
 | 
	
		
			
				|  |  |      <selectResumeDialog v-model="showResume" :list="resumeList" @submit="handleSubmit" @close="handleClose"></selectResumeDialog>
 | 
	
		
			
				|  |  | -    <!-- 复制分享链接 -->
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    <!-- 职位分享 -->
 | 
	
		
			
				|  |  |      <Dialog
 | 
	
		
			
				|  |  |        :visible="shareDialog" :widthType="2" :footer="false" titleClass="text-h6"
 | 
	
		
			
				|  |  |        :title="$t('position.rewardsShared')"
 | 
	
		
			
				|  |  |        @close="shareDialog = false"
 | 
	
		
			
				|  |  |      >
 | 
	
		
			
				|  |  |        <div>
 | 
	
		
			
				|  |  | -        <div class="mb-3">方式一:生成图片并下载</div>
 | 
	
		
			
				|  |  | -        <v-btn color="primary" variant="outlined" @click="generateAndDownloadImage">点击生成职位详情图片并下载</v-btn>
 | 
	
		
			
				|  |  | +        <div class="mb-3">方式一:保存图片分享给好友</div>
 | 
	
		
			
				|  |  | +        <div class="d-flex align-center flex-column">
 | 
	
		
			
				|  |  | +          <v-img :src="previewSrc" width="200" height="250"></v-img>
 | 
	
		
			
				|  |  | +          <div>
 | 
	
		
			
				|  |  | +            <v-btn color="primary" variant="outlined" prepend-icon="mdi-eye-outline" @click="showPreview = true">预览</v-btn>
 | 
	
		
			
				|  |  | +            <v-btn class="ml-3" color="primary" variant="outlined" prepend-icon="mdi-arrow-down-bold-box-outline" @click="handleDownloadImage">保存到本地</v-btn>
 | 
	
		
			
				|  |  | +          </div>
 | 
	
		
			
				|  |  | +        </div>
 | 
	
		
			
				|  |  |        </div>
 | 
	
		
			
				|  |  | -      <div class="mt-10" v-if="getToken()">
 | 
	
		
			
				|  |  | +      <div class="mt-10">
 | 
	
		
			
				|  |  |          <div class="mb-3">方式二:复制以下链接分享给好友</div>
 | 
	
		
			
				|  |  |          <div class="pa-4" style="background-color: #f0f0f0; border-radius: 8px;">{{ shareUrlTxt }}</div>
 | 
	
		
			
				|  |  | -        <v-btn v-if="!getToken()" class="mt-1" color="warning" variant="text">您还未登录,登录后分享可享受分享有礼活动!</v-btn>
 | 
	
		
			
				|  |  | -        <v-btn class="mt-4 ml-3" color="primary" variant="outlined" v-clipboard="() => shareUrlTxt" @click="copyText">点击复制分享链接</v-btn>
 | 
	
		
			
				|  |  | +        <div class="text-center">
 | 
	
		
			
				|  |  | +          <v-btn class="mt-5 ml-3" color="primary" variant="outlined" v-clipboard="() => shareUrlTxt" @click="copyText">点击复制分享链接</v-btn>
 | 
	
		
			
				|  |  | +        </div>
 | 
	
		
			
				|  |  |        </div>
 | 
	
		
			
				|  |  |        <template #footer>
 | 
	
		
			
				|  |  |          <v-divider></v-divider>
 | 
	
	
		
			
				|  | @@ -117,17 +125,21 @@
 | 
	
		
			
				|  |  |          </div>
 | 
	
		
			
				|  |  |        </template>
 | 
	
		
			
				|  |  |      </Dialog>
 | 
	
		
			
				|  |  | +    <PreviewImg v-if="showPreview" :list="[previewSrc]" @close="showPreview = false" :isImage="true"></PreviewImg>
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      <Loading :visible="loading"></Loading>
 | 
	
		
			
				|  |  |      <div style="position: absolute; left: -9999px; bottom: 0">
 | 
	
		
			
				|  |  |        <PosterPage :jobId="id" ref="share"></PosterPage>
 | 
	
		
			
				|  |  |      </div>
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    <!-- 快速登录 -->
 | 
	
		
			
				|  |  | +    <login-page v-if="showLogin" @loginSuccess="loginSuccess" @close="loginClose"></login-page>
 | 
	
		
			
				|  |  |    </div>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
 | 
	
		
			
				|  |  |  </template>
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  <script setup>
 | 
	
		
			
				|  |  | -import { commissionCalculation } from '@/utils/position'
 | 
	
		
			
				|  |  |  defineOptions({ name: 'position-details' })
 | 
	
		
			
				|  |  | +import { commissionCalculation } from '@/utils/position'
 | 
	
		
			
				|  |  |  import { computed, ref } from 'vue'
 | 
	
		
			
				|  |  |  import { useRouter } from 'vue-router'
 | 
	
		
			
				|  |  |  import { timesTampChange } from '@/utils/date'
 | 
	
	
		
			
				|  | @@ -145,64 +157,57 @@ import { prologue, defaultText } from '@/hooks/web/useIM'
 | 
	
		
			
				|  |  |  import { getUserAvatar } from '@/utils/avatar'
 | 
	
		
			
				|  |  |  import PosterPage from './poster.vue'
 | 
	
		
			
				|  |  |  import html2canvas from 'html2canvas'
 | 
	
		
			
				|  |  | +import { downloadBase64, DPR } from '@/utils'
 | 
	
		
			
				|  |  | +import loginPage from '@/views/common/loginDialog.vue'
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  const { t } = useI18n()
 | 
	
		
			
				|  |  |  const router = useRouter()
 | 
	
		
			
				|  |  |  const { id } = router.currentRoute.value.params
 | 
	
		
			
				|  |  |  const delivery = ref(false) // 是否已投递简历
 | 
	
		
			
				|  |  |  const loading = ref(false)
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -const DPR = () => {
 | 
	
		
			
				|  |  | -  // 获取设备dpi
 | 
	
		
			
				|  |  | -  if (window.devicePixelRatio && window.devicePixelRatio > 1) {
 | 
	
		
			
				|  |  | -    return window.devicePixelRatio * 2
 | 
	
		
			
				|  |  | -  }
 | 
	
		
			
				|  |  | -  // 直接返回高像素比
 | 
	
		
			
				|  |  | -  return 8
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -const downloadBase64 = (content, fileName) => {
 | 
	
		
			
				|  |  | -  const base64ToBlob = function (code) {
 | 
	
		
			
				|  |  | -    let parts = code.split(';base64,')
 | 
	
		
			
				|  |  | -    let contentType = parts[0].split(':')[1]
 | 
	
		
			
				|  |  | -    let raw = window.atob(parts[1])
 | 
	
		
			
				|  |  | -    let rawLength = raw.length
 | 
	
		
			
				|  |  | -    let uInt8Array = new Uint8Array(rawLength)
 | 
	
		
			
				|  |  | -    for (let i = 0; i < rawLength; ++i) {
 | 
	
		
			
				|  |  | -      uInt8Array[i] = raw.charCodeAt(i)
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    return new Blob([uInt8Array], {
 | 
	
		
			
				|  |  | -      type: contentType
 | 
	
		
			
				|  |  | -    })
 | 
	
		
			
				|  |  | -  }
 | 
	
		
			
				|  |  | -  let aLink = document.createElement('a')
 | 
	
		
			
				|  |  | -  let blob = base64ToBlob(content)
 | 
	
		
			
				|  |  | -  aLink.download = fileName + '.png'
 | 
	
		
			
				|  |  | -  aLink.href = URL.createObjectURL(blob)
 | 
	
		
			
				|  |  | -  aLink.click()
 | 
	
		
			
				|  |  | -  loading.value = false
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | +const showLogin = ref(false)
 | 
	
		
			
				|  |  | +const previewSrc = ref('')
 | 
	
		
			
				|  |  | +const showPreview = ref(false)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  const share = ref()
 | 
	
		
			
				|  |  |  // 生成图片
 | 
	
		
			
				|  |  | -const generateAndDownloadImage = async () => {  
 | 
	
		
			
				|  |  | -  if (!share.value) {  
 | 
	
		
			
				|  |  | -    return
 | 
	
		
			
				|  |  | -  }
 | 
	
		
			
				|  |  | +const generateAndDownloadImage = async () => {
 | 
	
		
			
				|  |  | +  if (!share.value) return
 | 
	
		
			
				|  |  |    loading.value = true
 | 
	
		
			
				|  |  | -  shareDialog.value = false
 | 
	
		
			
				|  |  |    try {  
 | 
	
		
			
				|  |  |      const canvas = await html2canvas(share.value.$el, { scale: DPR(), useCORS: true })
 | 
	
		
			
				|  |  |      const image = canvas.toDataURL().replace(/^data:image\/(png|jpg);base64,/, '')
 | 
	
		
			
				|  |  | -    const base64img = `data:image/png;base64,${image}`
 | 
	
		
			
				|  |  | -    const { name, areaName, payFrom, payTo } = info.value
 | 
	
		
			
				|  |  | -    downloadBase64(base64img, `${name}_${areaName}_${payFrom}-${payTo}-${positionInfo.value.payName}`)
 | 
	
		
			
				|  |  | +    previewSrc.value = `data:image/png;base64,${image}`
 | 
	
		
			
				|  |  | +    loading.value = false
 | 
	
		
			
				|  |  |    } catch (error) {  
 | 
	
		
			
				|  |  |      console.error('Error generating image:', error)
 | 
	
		
			
				|  |  |      Snackbar.error('图片生成失败')
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -}  
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// 保存图片到本地
 | 
	
		
			
				|  |  | +const handleDownloadImage = () => {
 | 
	
		
			
				|  |  | +  const { name, areaName, payFrom, payTo } = info.value
 | 
	
		
			
				|  |  | +  downloadBase64(previewSrc.value, `${name}_${areaName}_${payFrom}-${payTo}-${positionInfo.value.payName}`)
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// 快速登录
 | 
	
		
			
				|  |  | +const loginSuccess = () => {
 | 
	
		
			
				|  |  | +  showLogin.value = false
 | 
	
		
			
				|  |  | +  Snackbar.success('登录成功')
 | 
	
		
			
				|  |  | +  userInfo.value = localStorage.getItem('userInfo') ? JSON.parse(localStorage.getItem('userInfo')) : {}
 | 
	
		
			
				|  |  | +  shareUrl.value = '/shareJob?' + new URLSearchParams({
 | 
	
		
			
				|  |  | +    jobId: id,
 | 
	
		
			
				|  |  | +    sharedById: userInfo.value?.id,
 | 
	
		
			
				|  |  | +  }).toString()
 | 
	
		
			
				|  |  | +  shareDialog.value = true
 | 
	
		
			
				|  |  | +  generateAndDownloadImage()
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +const loginClose = () => {
 | 
	
		
			
				|  |  | +  showLogin.value = false
 | 
	
		
			
				|  |  | +  Snackbar.warning('您已取消登录,无法分享职位给好友')
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  // 相似职位
 | 
	
		
			
				|  |  |  const similarList = ref([])
 | 
	
	
		
			
				|  | @@ -249,31 +254,29 @@ getCollectionStatus()
 | 
	
		
			
				|  |  |  // 分享有礼
 | 
	
		
			
				|  |  |  const shareDialog = ref(false)
 | 
	
		
			
				|  |  |  const shareUrl = ref('')
 | 
	
		
			
				|  |  | -const userInfo = localStorage.getItem('userInfo') ? JSON.parse(localStorage.getItem('userInfo')) : {}
 | 
	
		
			
				|  |  | +const userInfo = ref(localStorage.getItem('userInfo') ? JSON.parse(localStorage.getItem('userInfo')) : {})
 | 
	
		
			
				|  |  |  const handleShare = async () => {
 | 
	
		
			
				|  |  |    // 分享链接携带参数: 用户id、职位id
 | 
	
		
			
				|  |  | -  // if (!id || !userInfo.id) return
 | 
	
		
			
				|  |  | +  if (!getToken()) {
 | 
	
		
			
				|  |  | +    Snackbar.warning('您还未登录,请先登录后再试')
 | 
	
		
			
				|  |  | +    showLogin.value = true
 | 
	
		
			
				|  |  | +    return
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +  generateAndDownloadImage()
 | 
	
		
			
				|  |  |    shareUrl.value = '/shareJob?' + new URLSearchParams({
 | 
	
		
			
				|  |  |      jobId: id,
 | 
	
		
			
				|  |  | -    sharedById: userInfo?.id,
 | 
	
		
			
				|  |  | +    sharedById: userInfo.value?.id,
 | 
	
		
			
				|  |  |    }).toString()
 | 
	
		
			
				|  |  |    shareDialog.value = true
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  | -// const openShareLink = () => { window.open(shareUrl.value) }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  // 复制分享链接
 | 
	
		
			
				|  |  |  const accessUrl = import.meta.env.VITE_ACCESS_BASE_URL
 | 
	
		
			
				|  |  |  const shareUrlTxt = computed(() => {
 | 
	
		
			
				|  |  |    return accessUrl + shareUrl.value
 | 
	
		
			
				|  |  |  })
 | 
	
		
			
				|  |  | -const copyText = async () => {
 | 
	
		
			
				|  |  | +const copyText = () => {
 | 
	
		
			
				|  |  |    Snackbar.success('复制成功')
 | 
	
		
			
				|  |  | -  // try {
 | 
	
		
			
				|  |  | -  //   await navigator.clipboard.writeText(shareUrlTxt.value)
 | 
	
		
			
				|  |  | -  //   Snackbar.success('复制成功')
 | 
	
		
			
				|  |  | -  // } catch (err) {
 | 
	
		
			
				|  |  | -  //   Snackbar.error('复制失败,请手动复制。')
 | 
	
		
			
				|  |  | -  // }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  // 收藏&取消收藏职位
 | 
	
	
		
			
				|  | @@ -335,6 +338,7 @@ const handleSubmit = async (val) =>{
 | 
	
		
			
				|  |  |    }, 3000)
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +// 沟通
 | 
	
		
			
				|  |  |  const toDetails = async (info) => {
 | 
	
		
			
				|  |  |    const userId = info.contact.userId
 | 
	
		
			
				|  |  |    const enterpriseId = info.contact.enterpriseId
 | 
	
	
		
			
				|  | @@ -353,88 +357,5 @@ const toDetails = async (info) => {
 | 
	
		
			
				|  |  |  </script>
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  <style lang="scss" scoped>
 | 
	
		
			
				|  |  | -.banner {
 | 
	
		
			
				|  |  | -  background-color: #fff;
 | 
	
		
			
				|  |  | -  padding: 18px 0 20px;
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -.banner-title {
 | 
	
		
			
				|  |  | -  line-height: 40px;
 | 
	
		
			
				|  |  | -  font-size: 28px;
 | 
	
		
			
				|  |  | -  font-weight: 600;
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -.banner-title h1 {
 | 
	
		
			
				|  |  | -  display: inline-block;
 | 
	
		
			
				|  |  | -  color: #37576c;
 | 
	
		
			
				|  |  | -  font-size: 28px;
 | 
	
		
			
				|  |  | -  margin-right: 30px;
 | 
	
		
			
				|  |  | -  margin-top: 1px;
 | 
	
		
			
				|  |  | -  max-width: 360px;
 | 
	
		
			
				|  |  | -  vertical-align: middle;
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -.button-item {
 | 
	
		
			
				|  |  | -  min-width: 110px;
 | 
	
		
			
				|  |  | -  height: 36px
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -.salary {
 | 
	
		
			
				|  |  | -  color: var(--v-error-base);
 | 
	
		
			
				|  |  | -  line-height: 41px;
 | 
	
		
			
				|  |  | -  font-weight: 600;
 | 
	
		
			
				|  |  | -  height: auto;
 | 
	
		
			
				|  |  | -  display: inline-block;
 | 
	
		
			
				|  |  | -  vertical-align: sub;
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -.refresh-time {
 | 
	
		
			
				|  |  | -  float: right;
 | 
	
		
			
				|  |  | -  color: var(--color-666);
 | 
	
		
			
				|  |  | -  font-size: 14px;
 | 
	
		
			
				|  |  | -  line-height: 66px;
 | 
	
		
			
				|  |  | -  vertical-align: sub;
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -.banner-tags span {
 | 
	
		
			
				|  |  | -  font-weight: 600;
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -.radius {
 | 
	
		
			
				|  |  | -  border-radius: 8px;
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -.content-left {
 | 
	
		
			
				|  |  | -  width: 810px;
 | 
	
		
			
				|  |  | -  padding: 20px 20px;
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -.content-right {
 | 
	
		
			
				|  |  | -  flex: 1;
 | 
	
		
			
				|  |  | -  padding: 20px 20px 20px 0;
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -.label-text {
 | 
	
		
			
				|  |  | -  color: #7f7a7a;
 | 
	
		
			
				|  |  | -  font-weight: 600;
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -.value-text {
 | 
	
		
			
				|  |  | -  color: #000;
 | 
	
		
			
				|  |  | -  font-weight: 400;
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -.requirement {
 | 
	
		
			
				|  |  | -  white-space: pre-wrap;
 | 
	
		
			
				|  |  | -  word-break: break-all;
 | 
	
		
			
				|  |  | -  line-height: 28px;
 | 
	
		
			
				|  |  | -  color: var(--color-333);
 | 
	
		
			
				|  |  | -  font-size: 15px;
 | 
	
		
			
				|  |  | -  text-align: justify;
 | 
	
		
			
				|  |  | -  letter-spacing: 0;
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -.contact {
 | 
	
		
			
				|  |  | -  height: 60px;
 | 
	
		
			
				|  |  | -  line-height: 60px;
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -.contact-name {
 | 
	
		
			
				|  |  | -  font-size: 20px;
 | 
	
		
			
				|  |  | -  font-weight: 500;
 | 
	
		
			
				|  |  | -  color: var(--color-222);
 | 
	
		
			
				|  |  | -  line-height: 28px;
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -.contact-info {
 | 
	
		
			
				|  |  | -  font-size: 15px;
 | 
	
		
			
				|  |  | -  color: var(--color-666);
 | 
	
		
			
				|  |  | -  line-height: 21px;
 | 
	
		
			
				|  |  | -  margin-top: 8px;
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | +@import '@/styles/recruit/position/index.scss'
 | 
	
		
			
				|  |  |  </style>
 |