浏览代码

职位分享封面绘制

Xiao_123 6 月之前
父节点
当前提交
81cf547c92

+ 1 - 1
components/PositionList/index.vue

@@ -79,7 +79,7 @@ const props = defineProps({
 
 
 //岗位详情
 //岗位详情
 const toDetail = (item) =>{
 const toDetail = (item) =>{
-  const url = `/pagesB/positionDetail/index?id=${item.job?.id}`
+  const url = `/pagesB/positionDetail/index?id=${item.job?.id}&area=${item.job.areaName}`
   uni.navigateTo({ url })
   uni.navigateTo({ url })
 }
 }
 
 

+ 1 - 1
pagesA/interview/item.vue

@@ -47,7 +47,7 @@ const props = defineProps({
 
 
 //岗位详情
 //岗位详情
 const toDetail = (isPosition, item) =>{
 const toDetail = (isPosition, item) =>{
-  const url = isPosition ? `/pagesB/positionDetail/index?id=${item.job?.id}` : `/pagesB/companyDetail/index?id=${item.enterprise?.id}`
+  const url = isPosition ? `/pagesB/positionDetail/index?id=${item.job?.id}&area=${item.job.areaName}` : `/pagesB/companyDetail/index?id=${item.enterprise?.id}`
   uni.navigateTo({ url })
   uni.navigateTo({ url })
 }
 }
 
 

+ 151 - 237
pagesB/positionDetail/index.vue

@@ -1,6 +1,5 @@
 <template>
 <template>
   <layout-page>
   <layout-page>
-    <view :class="{'shareCss': shareCss}">
     <scroll-view class="scrollBox" style="position:relative;">
     <scroll-view class="scrollBox" style="position:relative;">
       <view class="box">
       <view class="box">
         <view v-if="loading" class="vertical80-center">{{ loadingText }}</view>
         <view v-if="loading" class="vertical80-center">{{ loadingText }}</view>
@@ -81,12 +80,11 @@
         </view>
         </view>
       </view>
       </view>
     </scroll-view>
     </scroll-view>
-    </view>
     <!-- 分享 投递 -->
     <!-- 分享 投递 -->
     <view class="bottom-sticky" v-if="!loading && jobId && info.status === '0'">
     <view class="bottom-sticky" v-if="!loading && jobId && info.status === '0'">
       <view class="bottom-content">
       <view class="bottom-content">
         <!-- 已登录可以分享 -->
         <!-- 已登录可以分享 -->
-        <button v-if="beenLogin" open-type="share" class="bottom-content-tool shareButtonCss" @tap="handleClickShare1">
+        <button v-if="beenLogin" class="bottom-content-tool shareButtonCss" open-type="share">
           <uni-icons type="redo-filled" size="24" color="#00897B" style="line-height: 24px;"/>
           <uni-icons type="redo-filled" size="24" color="#00897B" style="line-height: 24px;"/>
           <span style="color:#00897B;font-weight:bold;line-height: 22px;">分享</span>
           <span style="color:#00897B;font-weight:bold;line-height: 22px;">分享</span>
         </button>
         </button>
@@ -173,29 +171,19 @@
     <uni-popup ref="sharePopup" type="share">
     <uni-popup ref="sharePopup" type="share">
       <uni-popup-share title="分享到">
       <uni-popup-share title="分享到">
         <view class="share-pop">
         <view class="share-pop">
-            <button class="f-straight" open-type="share">
-                <view class="share-round share-round-1" >
-                    <uni-icons
-                        type="weixin"
-                        color="#FFF"
-                        size="30"
-                    />
-                </view>
-                <view style="font-size:12px;">微信</view>
-            </button>
-            <!-- <button class="f-straight" @click="createPoster">
-                <view class="share-round share-round-2" >
-                    <uni-icons
-                        type="download-filled"
-                        color="#FFF"
-                        size="30"
-                    />
-                </view>
-                <view style="font-size:12px;">生成海报</view>
-            </button> -->
+          <button class="f-straight" open-type="share">
+            <view class="share-round share-round-1" >
+              <uni-icons type="weixin" color="#FFF" size="30" />
+            </view>
+            <view style="font-size:12px;">微信</view>
+          </button>
         </view>
         </view>
       </uni-popup-share>
       </uni-popup-share>
     </uni-popup>
     </uni-popup>
+
+    <view class="hideCanvasView">
+      <canvas class="shareCanvas" canvas-id="shareCanvas" style="width: 452px; height: 362px;"></canvas>
+    </view>
   </layout-page>
   </layout-page>
 </template>
 </template>
 
 
@@ -222,8 +210,8 @@ import { getAccessToken } from '@/utils/request'
 import { onLoad, onShareAppMessage, onShow } from '@dcloudio/uni-app'
 import { onLoad, onShareAppMessage, onShow } from '@dcloudio/uni-app'
 import { prologue, defaultText } from '@/hooks/useIM'
 import { prologue, defaultText } from '@/hooks/useIM'
 import { userStore } from '@/store/user'
 import { userStore } from '@/store/user'
-const useUserStore = userStore()
 
 
+const useUserStore = userStore()
 const sharePopup = ref()
 const sharePopup = ref()
 const loading = ref(false)
 const loading = ref(false)
 const loadingText = ref('加载中 . . . ')
 const loadingText = ref('加载中 . . . ')
@@ -231,13 +219,13 @@ const loadingText = ref('加载中 . . . ')
 // 职位详情
 // 职位详情
 const info = ref({})
 const info = ref({})
 const positionInfo = ref({})
 const positionInfo = ref({})
-
 const isEmployment = ref(null)
 const isEmployment = ref(null)
-
 const imgSrc = ref('')
 const imgSrc = ref('')
 const appInfo = ref({})
 const appInfo = ref({})
 const poster = ref()
 const poster = ref()
 const beenLogin = ref(false)
 const beenLogin = ref(false)
+const areaName = ref('')
+const canvasToTempFilePath = ref('')
 
 
 // 监听登录状态
 // 监听登录状态
 watch(() => useUserStore.refreshToken, (newVal) => {
 watch(() => useUserStore.refreshToken, (newVal) => {
@@ -248,6 +236,8 @@ let jobId = ''
 let obj = {}
 let obj = {}
 onLoad(async (options) => {
 onLoad(async (options) => {
   console.log(options, 'options')
   console.log(options, 'options')
+  areaName.value = options?.area || ''
+
   // 网站二维码分享
   // 网站二维码分享
   if (options.scene) {
   if (options.scene) {
     const scene = decodeURIComponent(options.scene)
     const scene = decodeURIComponent(options.scene)
@@ -271,13 +261,124 @@ onLoad(async (options) => {
 })
 })
 
 
 onShow(() => {
 onShow(() => {
-  shareCss.value = false
   if (!jobId) {
   if (!jobId) {
     return
     return
   }
   }
   deliveryCheck()
   deliveryCheck()
 })
 })
 
 
+const getImageTempRatio = (url) => {
+  return new Promise((req, rej)=>{
+    wx.getImageInfo({
+      src:url,
+      success:(res) =>{
+        req(res)
+      }
+    })
+  })
+}
+
+
+/**
+  ctx: 画布的上下文环境
+  fontSize: 文字大小
+  text: 绘制文本
+  maxWidth: 一行文字最大宽度
+  x:文本在x轴显示的位置
+  y:文本在y轴显示的位置
+  maxLine:最多绘制的行数
+**/
+//处理文字多出省略号显示
+const dealWords = (ctx, fontSize, text, maxWidth, x, y, maxLine) => {
+  ctx.setFontSize(fontSize);//设置字体大小
+  var allRow = Math.ceil(ctx.measureText(text).width / maxWidth);//实际总共能分多少行
+  var count = allRow >= maxLine ? maxLine : allRow;//实际能分多少行与设置的最大显示行数比,谁小就用谁做循环次数
+  var endPos = 0;//当前字符串的截断点
+  for (var j = 0; j < count; j++) {
+    var nowStr = text.slice(endPos);//当前剩余的字符串
+    var rowWid = 0;//每一行当前宽度  
+    if (ctx.measureText(nowStr).width > maxWidth) {//如果当前的字符串宽度大于最大宽度,然后开始截取
+      for (var m = 0; m < nowStr.length; m++) {
+        rowWid += ctx.measureText(nowStr[m]).width;//当前字符串总宽度
+        if (rowWid > maxWidth) {            
+          if (j === maxLine - 1) { //如果是最后一行
+            ctx.fillText(nowStr.slice(0, m - 1) + '...', x, y + (j + 1) * 30);  //(j+1)*18这是每一行的高度    
+          } else {
+            ctx.fillText(nowStr.slice(0, m), x, y + (j + 1) * 30);
+          }
+          endPos += m;//下次截断点
+          break;
+        }
+      }
+    } else {//如果当前的字符串宽度小于最大宽度就直接输出
+      ctx.fillText(nowStr.slice(0), x, y + (j + 1) * 30);
+    }
+  }
+}
+
+// 职位分享图片绘制
+const createPoster = async () => {
+  var context = uni.createCanvasContext('shareCanvas')
+
+  //清空画布
+  context.clearRect(0, 0, 452, 362);
+
+  //背景图片
+  context.drawImage('../../static/img/share-cover-border.jpg', 0, 0, 452, 362);
+  
+  // 岗位名称
+  context.setFillStyle('#333333')
+  dealWords(context, 30, positionInfo.value?.name, 350, 40, 30, 2)
+
+  // 工作地区、工作经验、学历要求
+  context.setFillStyle('#6c6e7b')
+  context.setFontSize(20)
+  const area = (areaName.value !== 'undefined' && areaName.value !== '' && areaName.value !== 'null' ? areaName.value + ' | ' : '') || (positionInfo.value?.areaName ? positionInfo.value?.areaName + ' | ' : '')
+  const combinationText = (area || '') + (positionInfo.value.expName || '') + (positionInfo.value.eduName ? ' | ' + positionInfo.value.eduName : '')
+  context.fillText(combinationText, 40, 130)
+
+  // 薪资
+  context.setFontSize(22)
+  context.setFillStyle('#f67272')
+  const { payFrom, payTo, payName } = positionInfo.value
+  const salary = payFrom && payTo ? payFrom + '-' + payTo + (payName ? '元/'+ payName : '') : '面议'
+  context.fillText(salary, 40, 170)
+
+  // 间隔线
+  context.setLineDash([2, 4], 1)
+  context.beginPath()
+  context.moveTo(40, 195)
+  context.lineTo(412, 195)
+  context.setStrokeStyle('#00897B')
+  context.stroke()
+
+  // 企业头像
+  const { logoUrl, anotherName, industryName, scaleName } = positionInfo.value.enterprise
+  const {path : headImg} = await getImageTempRatio(logoUrl ? logoUrl : 'https://minio.citupro.com/dev/menduner/company-avatar.png')
+  context.drawImage(headImg, 40, 220, 90, 90)
+
+  // 企业名称
+  context.setFillStyle('#000000')
+  dealWords(context, 30, anotherName, 250, 150, 210, 2)
+
+  // 企业行业类型、规模
+  context.setFontSize(20)
+  let industry = industryName && industryName.length > 6 ? `${industryName.slice(0, 7)}...` : industryName
+  context.fillText(`${industry || ''}${scaleName ? ' | ' + scaleName : ''}`, 150, 310)
+
+  context.draw(false, () =>{
+    wx.canvasToTempFilePath({ 
+      canvasId: 'shareCanvas',
+      success:(res)=>{
+        canvasToTempFilePath.value = res.tempFilePath
+        console.log('canvas-success', canvasToTempFilePath.value)
+      },
+      fail:(err)=>{
+        console.log('canvasToTemp-fail', err)
+      }
+    })
+  })
+}
 
 
 //在点击open-type="share"按钮后会触发以下函数,可以在函数中写需要的逻辑,当然函数的返回值必须是一个对象,用于设置分享卡片的展示形式
 //在点击open-type="share"按钮后会触发以下函数,可以在函数中写需要的逻辑,当然函数的返回值必须是一个对象,用于设置分享卡片的展示形式
 //发送给微信好友  
 //发送给微信好友  
@@ -287,9 +388,13 @@ onShareAppMessage((res) => {
   if (info.value.hire) {
   if (info.value.hire) {
     path += `&sharedById=${info.value.userId}`
     path += `&sharedById=${info.value.userId}`
   }
   }
+  if(!canvasToTempFilePath.value){
+		setTimeout(() => {},1000)
+	}
   return {
   return {
-    title: '我发现了一个好职位,快来看看吧', //分享的名称
-    path
+    title: '我发现了一个好职位,快来看看吧',
+    path,
+    imageUrl: canvasToTempFilePath.value
   }
   }
 })
 })
 
 
@@ -310,13 +415,17 @@ const handleToEnterprise = () => {
   })
   })
 }
 }
 
 
+// 职位详情
 async function getPositionDetail () {
 async function getPositionDetail () {
   try {
   try {
     loading.value = true
     loading.value = true
     const { data } = await getPositionDetails({ id: jobId })
     const { data } = await getPositionDetails({ id: jobId })
     info.value = data
     info.value = data
-    positionInfo.value = { ...dealDictObjData({}, info.value), ...info.value }
+    positionInfo.value = { ...dealDictObjData({}, info.value), ...info.value, enterprise: dealDictObjData({}, data.enterprise) }
     loading.value = false
     loading.value = false
+
+    // 生成分享图片
+    createPoster()
   } finally {
   } finally {
   }
   }
 }
 }
@@ -406,9 +515,7 @@ async function handleSend () {
     positionInfo: positionInfo.value,
     positionInfo: positionInfo.value,
     
     
   }
   }
-  // console.log('textObj', textObj)
   const channel = await prologue({userId, enterpriseId, text: JSON.stringify(textObj)})
   const channel = await prologue({userId, enterpriseId, text: JSON.stringify(textObj)})
-  console.log('channel', channel)
   // 跳转
   // 跳转
   const query = {
   const query = {
 		id: userId,
 		id: userId,
@@ -421,7 +528,6 @@ async function handleSend () {
 		avatar: info.value?.contact?.avatar,
 		avatar: info.value?.contact?.avatar,
 		sex: info.value?.contact?.sex,
 		sex: info.value?.contact?.sex,
 	}
 	}
-  console.log('query', query)
 	const queryStr = Object.keys(query).reduce((r, v) => {
 	const queryStr = Object.keys(query).reduce((r, v) => {
 		return r += `${v}=${encodeURIComponent(query[v])}&`
 		return r += `${v}=${encodeURIComponent(query[v])}&`
 	}, '?')
 	}, '?')
@@ -493,215 +599,23 @@ const handleClickShare = () => {
     showAuthModal()
     showAuthModal()
     return
     return
   }
   }
-  // sharePopup.value.open()
-}
-const shareCss = ref(false)
-const handleClickShare1 = () => {
-  shareCss.value = true
-}
-// 获取设备信息
-function getSystemInfo(){
-  return new Promise((resolve, reject) =>{
-    uni.getSystemInfo({
-        success: (result) => {
-          console.log(result)
-          resolve(result)
-        },
-        fail: (err) => {
-          console.log(err)
-        }
-    })
-  })
-}
-function roundedRect(ctx, x, y, width, height, radius, color) {
-    ctx.beginPath();
-    ctx.moveTo(x, y + radius);
-    ctx.lineTo(x, y + height - radius);
-    ctx.quadraticCurveTo(x, y + height, x + radius, y + height);
-    ctx.lineTo(x + width - radius, y + height);
-    ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius);
-    ctx.lineTo(x + width, y + radius);
-    ctx.quadraticCurveTo(x + width, y, x + width - radius, y);
-    ctx.lineTo(x + radius, y);
-    ctx.quadraticCurveTo(x, y, x, y + radius);
-    // ctx.fillStyle = "#f5f5f5"; 
-    ctx.fillStyle = color; 
-    ctx.fill()
-    ctx.strokeStyle = color
-    ctx.stroke();
-}
-//微信获取图片信息
-function getImageTempRatio (url){
-    return new Promise((resolve, reject)=>{
-        wx.getImageInfo({
-            src:url,
-            success:(res) =>{
-              resolve(res)
-            }
-        })
-    })
+  sharePopup.value.open()
 }
 }
+</script>
 
 
-/**
- * 
- * @param text 文本
- * @param x 起始位置x
- * @param y 起始位置y
- * @param maxWidth 最大宽度
- * @param lineHeight 行高
- * @param ctx 画布上下文
- * @param type 类型 cut 截断+省略号 wrap 自动换行
- */
-function wrapText(text, x, y, maxWidth, lineHeight, ctx, type = 'cut') {  
-    const words = text.split(' '); // 按空格分割成单词  
-    let line = ''
-
-    words.forEach((word) => {  
-        const testLine = line + word + ' '; // 测试当前行加上新单词  
-        const metrics = ctx.measureText(testLine); // 测量文本宽度  
-        
-        // 检查是否超过最大宽度  
-        if (metrics.width > maxWidth && line) {
-            if (type === 'wrap') {
-              ctx.fillText(line, x, y); // 在当前 y 位置绘制当前行  
-              line = word + ' '; // 重置行并开始新的一行  
-              y += lineHeight; // 更新 y 位置 
-            }
-            if (type === 'cut') {
-              line = line.slice(0, -word.length - 2) + '...'; // 截断当前行并添加省略号
-            }
-        } else {  
-            line = testLine; // 如果没有超出宽度,更新当前行  
-        }  
-    })
-
-    // 绘制剩余的文本  
-    ctx.fillText(line, x, y)
-} 
-// 生成海报
-async function createPoster () {
-  const top = 50
-  const left = 30
-  console.log(info.value)
-  sharePopup.value.close()
-  const res = await getSystemInfo()
-  const { windowWidth, windowHeight } = res
-  appInfo.value = {
-    windowWidth, windowHeight
-  }
-  var context = uni.createCanvasContext('firstCanvas')
-  //底色
-  context.setFillStyle('#03877a')
-  context.fillRect(0,0, windowWidth,  windowHeight)
-  // 职位信息
-  // 名称
-  context.setFillStyle('#FFF')
-  context.font = 'normal bold 26px Microsoft YaHei'
-  context.fillText(info.value.name, left, top)
-  // 薪酬
-  context.fillStyle = 'yellow'; 
-  context.font = 'normal 22px Microsoft YaHei'
-  const payText = `${info.value.payFrom}-${info.value.payTo} / ${positionInfo.value.payName}`
-  const payWidth = context.measureText(payText).width;  
-  const rightX = windowWidth - payWidth - left; 
-  context.fillText(payText, rightX, top);
-  // 地区|年限|学历
-  const areaText = `${positionInfo.value?.areaName} | ${positionInfo.value?.eduName} | ${positionInfo.value?.expName}`
-  context.setFillStyle('#FFF')
-  context.font = 'normal 14px Microsoft YaHei'
-  const subTitleH = top + 20
-  context.fillText(areaText, left, subTitleH)
-  // 企业信息卡片
-  roundedRect(context, left, subTitleH + 20 , windowWidth - 2 * left , 50, 10, 'rgba(255, 255, 255, 0.45)')
-  
-  // 企业信息
-  // const headerMsg = await getImageTempRatio(info.value.contact.avatar)
-  // context.drawImage(
-  //   headerMsg.path,
-  //   left + 5,
-  //   subTitleH + 25, 
-  //   40, 
-  //   40
-  // )
-  // 企业信息
-  context.setFillStyle('#FFF')
-  context.font = 'normal 16px Microsoft YaHei'
-  const emTextH = subTitleH + 50
-  context.fillText(info.value.enterprise.anotherName, left + 20, emTextH)
-  // 企业地点
-  const _areaText = `${info.value.areaName}`
-  const areaTextW = context.measureText(_areaText).width;  
-  const _rightX = windowWidth - areaTextW - left - 20; 
-  context.fillText(_areaText, _rightX, emTextH);
-  // 福利
-  // context.setFillStyle('#FFF')
-  context.font = 'normal 14px Microsoft YaHei'
-  const welfareH = emTextH + 40
-  const welfareText = info.value.tagList && info.value.tagList.join('     ') || ''
-  wrapText(welfareText, left, welfareH, windowWidth - 2 * left, 20, context)
-  // 卡片
-  const cardTop = welfareH + 20 // 卡片顶部距离
-  const cardH = windowHeight - cardTop - 100  // 卡片高度
-  roundedRect(context, 10, cardTop , windowWidth - 20 , cardH, 10,'#fff')
-  // 岗位
-  // context.setFillStyle('#000000')
-  // context.setFontSize(20)
-  // context.font = 'normal bold 20px Microsoft YaHei'
-  // context.fillText('111',windowWidth/10, windowHeight * 0.2)
-
-  // 地区频率时长薪资
-  // context.setFillStyle('#6c6e7b')
-  // context.setFontSize(15)
-  // context.font = 'normal 15px Microsoft YaHei'
-  // context.fillText(' | ',windowWidth/10, windowHeight * 0.2 + 30)
-  // context.setFontSize(18)
-  // context.font = 'normal 18px Microsoft YaHei'
-  // context.setFillStyle('#f67272')
-  // context.fillText('120/天', windowWidth/10, windowHeight * 0.2 + 60)
-  // 小程序码/ 二维码
-  // context.drawImage(
-  //   // image.path,
-  //   windowWidth *0.5 - windowWidth/8,
-  //   windowHeight * 0.60, 
-  //   windowWidth/4, 
-  //   windowWidth/4
-  // )
-  
-  context.setFontSize(16)
-  context.fillText('长按二维码查看岗位', windowWidth/3.2, windowHeight * 0.65 + windowWidth/4)
+<style scoped lang="scss">
+@import '../../static/style/position/index.scss';
 
 
-  context.draw(false, () =>{
-      uni.showLoading({
-          title: '加载中……',
-          icon: 'none',
-      })
-      setTimeout(() => {
-          wx.canvasToTempFilePath({ 
-              canvasId: 'firstCanvas',
-              x:0,
-              y:0,
-              success:(res)=>{
-                console.log('成功', res)
-                  imgSrc.value = res.tempFilePath
-
-              },
-              fail:(err)=>{
-                  console.log('canvasToTemp-fail', err)
-              },
-              complete:()=>{
-                  uni.hideLoading();
-              }
-          })
-      }, 3000)
-      // canvas生成图片
-      poster.value.open()
-  })
+.hideCanvasView{
+	position: relative;
+}
+.shareCanvas {
+	position: fixed;
+	top: -99999upx;
+	left: -99999upx;
+	z-index: -99999;
 }
 }
 
 
-
-</script>
-<style scoped lang="scss">
-@import '../../static/style/position/index.scss';
 .bottom-content {
 .bottom-content {
   display: flex;
   display: flex;
   justify-content: space-evenly;
   justify-content: space-evenly;

+ 1 - 1
pagesB/recommendEnterprise/index.vue

@@ -101,7 +101,7 @@ const handleToEnterprise = (val) => {
 
 
 // 职位详情
 // 职位详情
 const handleToPosition = (k) => {
 const handleToPosition = (k) => {
-  const url = `/pagesB/positionDetail/index?id=${k.id}`
+  const url = `/pagesB/positionDetail/index?id=${k.id}&area=${k.areaName}`
   uni.navigateTo({ url })
   uni.navigateTo({ url })
 }
 }
 
 

二进制
static/img/share-cover-border.jpg


+ 1 - 1
utils/config.js

@@ -4,7 +4,7 @@ const baseUrls = {
   httpTest: 'http://menduner.citupro.com:7878',
   httpTest: 'http://menduner.citupro.com:7878',
   httpsTest: 'https://menduner.citupro.com:2443',
   httpsTest: 'https://menduner.citupro.com:2443',
 }
 }
-export const baseUrl = baseUrls.httpsTest
+export const baseUrl = baseUrls.produce
 
 
 // 租户id
 // 租户id
 export const tenantId = '155'
 export const tenantId = '155'