Pārlūkot izejas kodu

Merge branch 'master' of https://git.citupro.com/zhengnaiwen_citu/menduner-uniapp

lifanagju_citu 9 mēneši atpakaļ
vecāks
revīzija
dae12346f6

+ 79 - 0
api/common.js

@@ -178,4 +178,83 @@ export const getAreaTreeData = () => {
       auth: false
     }
   })
+}
+
+
+
+
+// 同步最近会话
+export const getConversationSync = async (data) => {
+  return request({
+    url: '/app-api/im/conversation/sync',
+    method: 'POST',
+    data,
+    custom: {
+      showLoading: false,
+      auth: true
+    }
+  })
+}
+
+
+// 获取聊天秘钥信息
+export const getChatKey = async (data) => {
+  return request({
+    url: '/app-api/im/user/get',
+    method: 'POST',
+    data,
+    custom: {
+      showLoading: false,
+      auth: true
+    }
+  })
+}
+
+// 同步最近会话
+export const getMessageSync = async (data) => {
+  return request({
+    url: '/app-api/im/im/channel/messagesync',
+    method: 'POST',
+    data,
+    custom: {
+      showLoading: false,
+      auth: true
+    }
+  })
+}
+// 设置最近会话未读数量
+export const setUnread = async (data) => {
+  return request({
+    url: '/app-api/im/conversations/setUnread',
+    method: 'POST',
+    data,
+    custom: {
+      showLoading: false,
+      auth: true
+    }
+  })
+}
+// 设置最近会话未读数量
+export const deleteConversation = async (data) => {
+  return request({
+    url: '/app-api/im/conversation/delete',
+    method: 'POST',
+    data,
+    custom: {
+      showLoading: false,
+      auth: true
+    }
+  })
+}
+
+// 求职端-根据邀请人id获取面试邀约列表
+export const getInterviewInviteListByInviteUserId = async (inviteUserId) => {
+  return request({
+    url: `/app-api/menduner/system/interview-invite/get/list/by/${inviteUserId}`,
+    method: 'GET',
+    custom: {
+      showLoading: false,
+      auth: true
+    }
+  })
 }

+ 26 - 0
api/personalCenter.js

@@ -0,0 +1,26 @@
+
+import request from "@/utils/request"
+// 同意邀约面试
+export const userInterviewInviteConsent = async (params) => {
+  return request({
+    url: '/app-api/menduner/system/interview-invite/consent',
+    method: 'POST',
+    params,
+    custom: {
+      showLoading: false,
+      auth: false
+    }
+  })
+}
+
+// 拒绝邀约面试
+export const userInterviewInviteReject = async (id) => {
+  return request({
+    url: `/app-api/menduner/system/interview-invite/reject?id=${id}`,
+    method: 'POST',
+    custom: {
+      showLoading: false,
+      auth: false
+    }
+  })
+}

+ 53 - 0
api/position.js

@@ -93,3 +93,56 @@ export const jobCvRelSend = (data) => {
     }
   })
 }
+
+// 众聘分享-投递简历
+export const jobCvRelHireSend = (data) => {
+  return request({
+    url: '/app-api/menduner/system/job-cv-rel/hire/recommend/send',
+    method: 'POST',
+    data,
+    custom: {
+      auth: true,
+      showLoading: false
+    }
+  })
+}
+
+// 获取众聘职位分页
+export const getJobAdvertisedHire = (params) => {
+  return request({
+    url: '/app-api/menduner/system/job/advertised/get/hire',
+    method: 'GET',
+    params,
+    custom: {
+      showLoading: false,
+      auth: false
+    }
+  })
+}
+
+// 获取统计信息
+export const getRecommendCount = (params) => {
+  return request({
+    url: '/app-api/menduner/system/job-cv-rel/hire/get/recommend/count',
+    method: 'GET',
+    params,
+    custom: {
+      showLoading: false,
+      auth: true
+    }
+  })
+}
+
+// 获取统计信息
+export const getRecommendationList = (params) => {
+  return request({
+    url: '/app-api/menduner/system/job-cv-rel/hire/page',
+    method: 'GET',
+    params,
+    custom: {
+      showLoading: false,
+      auth: true
+    }
+  })
+}
+

+ 7 - 1
components/PositionList/index.vue

@@ -12,7 +12,8 @@
               <rich-text v-if="item.job?.name?.indexOf('style')" class="job-name" :nodes="item.job.name"></rich-text>
               <view v-else class="job-name" style="">{{item.job?.name}}</view>
             </view>
-            <span class="salary-text">{{ item.job?.payFrom }}-{{ item.job?.payTo }}{{ item.job?.payName ? '/' + item.job?.payName : '' }}</span>
+            <span v-if="!item.job?.payFrom && !item.job?.payTo" class="salary-text">面议</span>
+            <span v-else class="salary-text">{{ item.job?.payFrom }}-{{ item.job?.payTo }}{{ item.job?.payName ? '/' + item.job?.payName : '' }}</span>
           </view>
           <!-- 工作地 -->
           <view style="font-size: 13px;" class="mt">
@@ -82,10 +83,15 @@ const props = defineProps({
   list: { type: Array, default: () => [] },
   noMore: { type: Boolean, default: false },
   showWelfareTag: { type: Boolean, default: true }
+  // toDetail: Function
 })
 
 //岗位详情
 const toDetail = (isPosition, item) =>{
+	// if (props.toDetail) {
+	// 	props.toDetail(item)
+	// 	return
+	// }
   const url = isPosition
     ? `/pagesB/positionDetail/index?id=${item.job?.id}`
     : `/pagesB/companyDetail/index?id=${item.enterprise?.id}`

+ 88 - 0
components/ResumeStatus/index.vue

@@ -0,0 +1,88 @@
+<template>
+	<slot name="header"></slot>
+	<view class="content">
+		<view v-for="(item,index) in items" :key="item.label" class="content-box" @tap="handleTo(item)">
+			<view class="content-box-value">
+				{{ item.count }}
+			</view>
+			<view class="content-box-title">
+				{{ item.label }}
+			</view>
+		</view>
+	</view>
+	
+</template>
+
+<script setup>
+import { ref, watch } from 'vue';
+import { getRecommendCount } from '@/api/position.js'
+import { getDict } from '@/hooks/useDictionaries.js'
+// const props = defineProps({
+// 	type: {
+// 		type: String,
+// 		default: 'menduner_hire_job_cv_status'
+// 	}
+// })
+
+import { userStore } from '@/store/user'
+const useUserStore = userStore()
+
+watch(() => useUserStore.refreshToken, (newVal, oldVal) => {
+  if (useUserStore.refreshToken) {
+		// 监听登录状态
+		console.log('重新登录了')
+		recommendCount()
+	}
+})
+
+const items = ref([])
+
+const handleTo = (item) => {
+	uni.navigateTo({
+		url: `/pagesA/recommendation/index?id=${item.value}`
+	})
+}
+
+async function recommendCount () {
+	try {
+		const { data: dict } = await getDict('menduner_hire_job_cv_status')
+		if (!dict?.data) {
+			return
+		}
+		items.value = dict.data.map(e => {
+			return {
+				...e,
+				count: 0
+			}
+		})
+		// console.log(items)
+		const { data } = await getRecommendCount()
+		if (!data) {
+			return
+		}
+		items.value.forEach(e => {
+			e.count = data.find(_e => _e.key === e.value)?.value || 0
+		})
+	} catch (error) {
+		// console.log(error)
+	}
+}
+recommendCount()
+</script>
+
+<style scoped lang="scss">
+.content {
+	display: flex;
+	justify-content: space-around;
+	padding: 36rpx 12rpx;
+	&-box {
+		font-size: 24rpx;
+		color: #999;
+		text-align: center;
+		&-value {
+			font-size: 1.8em;
+			color: #000;
+		}
+	}
+}
+</style>

+ 1 - 1
hooks/useDictionaries.js

@@ -24,7 +24,7 @@ export const getDict = (type, params, apiType = 'dict') => {
     return new Promise((resolve) => {
       const item = uni.getStorageSync(type)
       const catchData = item ? JSON.parse(item) : null
-      if (catchData && catchData.expire && (Date.now() <= catchData.expire)) {
+      if (catchData && catchData.expire && (Date.now() <= catchData.expire) && catchData.data) {
         return resolve({ data: catchData.data })
       }
       // 传参按照规范参数传

+ 441 - 0
hooks/useIM.js

@@ -0,0 +1,441 @@
+
+
+
+import { ref, onMounted, onUnmounted, watch } from 'vue';
+import { getConversationSync, getMessageSync, getChatKey, setUnread, deleteConversation } from '@/api/common'
+import { Base64 } from 'js-base64'
+
+import { userStore } from '@/store/user'
+import { useIMStore } from '@/store/im'
+
+
+// 配置悟空IM
+import {
+  MessageText,
+  Channel,
+  WKSDK,
+  ChannelTypePerson,
+  MessageContent,
+} from "wukongimjssdk"
+
+// 默认招呼语
+export const defaultText = '您好,关注到您发布该职位信息,请问有机会与您进一步沟通吗?'
+
+// 企业默认招呼语
+// export const defaultTextEnt = '您好,我们正在寻找充满激情、勇于挑战的您,快来和我聊一聊吧~'
+
+
+const { ObjectContent } = initRegister(101)
+const { ObjectContent: ObjectContent2 } = initRegister(102)
+const { ObjectContent: ObjectContent3 } = initRegister(103)
+const { ObjectContent: ObjectContent4 } = initRegister(104)
+const { ObjectContent: ObjectContent5 } = initRegister(105) // 发送简历
+
+
+const contentType = {
+  101: ObjectContent,
+  102: ObjectContent2,
+  103: ObjectContent3,
+  104: ObjectContent4,
+  105: ObjectContent5, // 发送简历
+}
+
+// 注册消息体
+function initRegister (type) {
+  class ObjectContent extends MessageContent {
+    constructor(text) {
+      super();
+      this.content = text
+    }
+    get conversationDigest() {
+        // 这里需要实现具体的逻辑
+        return this.content
+    }
+    get contentType() {
+        // 这里需要实现具体的逻辑
+        return type; // 示例实现
+    }
+    decodeJSON(content) {
+        this.content = content.text;
+    }
+    encodeJSON() {
+        return {
+          content: this.content
+        };
+    }
+  }
+  // 注册101类型为面试
+  WKSDK.shared().register(type, () => new ObjectContent(''))
+  return {
+    ObjectContent
+  }
+}
+
+
+const HISTORY_QUERY = {
+  limit: 20,
+  startMessageSeq: 0,
+  endMessageSeq: 0,
+  pullMode: 1
+}
+
+const ConnectStatus = {
+  Disconnect: 0, // 断开连接
+  Connected: 1, // 连接成功
+  Connecting: 2, // 连接中
+  ConnectFail: 3, // 连接错误
+  ConnectKick: 4, // 连接被踢,服务器要求客户端断开(一般是账号在其他地方登录,被踢)
+}
+// api 接入
+export function useDataSource () {
+  // 最近会话数据源
+  WKSDK.shared().config.provider.syncConversationsCallback  = async () => {
+    const query = {
+      msg_count: 1
+    }
+
+    const resultConversations = []
+    const resp = await getConversationSync(query)
+    const { data:conversationList } = resp
+    if (conversationList) {
+      conversationList.forEach(conversation => {
+        conversation.channel = new Channel(conversation.channel_id, conversation.channel_type)
+        conversation.unread = +(conversation.unread || 0)
+        resultConversations.push(conversation)
+      })
+    }
+    return resultConversations
+  }
+    // 同步频道消息数据源
+  WKSDK.shared().config.provider.syncMessagesCallback = async function(channel) {
+    // 后端提供的获取频道消息列表的接口数据 然后构建成 Message对象数组返回
+    let resultMessages  = new Array()
+    const {
+      startMessageSeq: start_message_seq,
+      endMessageSeq: end_message_seq,
+      limit,
+      pullMode: pull_mode
+    } = HISTORY_QUERY
+    const query = {
+      channel_id: channel.channelID,
+      channel_type: channel.channelType,
+      start_message_seq,
+      end_message_seq,
+      limit,
+      pull_mode,
+    }
+
+    const { data } = await getMessageSync(query)
+    const resp = data
+    const messageList = resp && resp["messages"]
+    if (messageList) {
+      messageList.forEach((msg) => {
+        // const message = Convert.toMessage(msg);
+        // msg.channel = new Channel(msg.channel_id, msg.channel_type)
+        msg.payload = JSON.parse(Base64.decode(msg.payload))
+        if (contentType[msg.payload.type]) {
+          msg.payload.content = JSON.parse(msg.payload.content ?? '{}')
+        }
+        resultMessages.push(msg)
+      })
+    }
+    // console.log(resultMessages)
+    const more = resp.more === 1
+    return {
+      more,
+      resultMessages
+    }
+  }
+}
+
+async function getKey () {
+  const useUserStore = userStore()
+
+  const keyQuery = {
+    userId: useUserStore.accountInfo.userId
+  }
+
+  const { data } = await getChatKey(keyQuery)
+  return {
+    ...data
+  }
+}
+
+export const useIM = () => {
+  useDataSource()
+  const key = ref(0)
+  const IM = useIMStore()
+  
+  onMounted( async () => {
+    // 通过自身userId和企业id获取token和uid
+    await resetConfig()
+    // 连接状态监听
+    WKSDK.shared().connectManager.addConnectStatusListener(connectStatusListener)
+    // 常规消息监听
+    WKSDK.shared().chatManager.addMessageListener(messageListen)
+    // 连接
+    WKSDK.shared().connectManager.connect()
+  })
+  onUnmounted(() => {
+    WKSDK.shared().connectManager.removeConnectStatusListener(connectStatusListener)
+    // 常规消息监听移除
+    WKSDK.shared().chatManager.removeMessageListener(messageListen)
+    // 连接状态监听移除
+    WKSDK.shared().connectManager.disconnect()
+  })
+  
+  async function messageListen (message) {
+    // console.log('收到消息', message)
+    IM.setFromChannel(message.channel.channelID)
+    setUnreadCount()
+  }
+
+  async function connectStatusListener (status) {
+    // console.log('连接状态', status === ConnectStatus.Connected)
+    // 连接成功 获取点击数
+    const connected = status === ConnectStatus.Connected
+    IM.setConnected(connected)
+    if (connected) {
+      // 必须同步最近会话才能获取未读总数
+      await syncConversation()
+      setUnreadCount()
+    }
+  }
+
+  function setUnreadCount () {
+    const count = WKSDK.shared().conversationManager.getAllUnreadCount()
+    key.value++
+    IM.setNewMsg(key.value)
+    IM.setUnreadCount(count)
+    console.log('未读消息总数', count)
+  }
+
+  async function resetConfig () {
+    const { uid, wssUrl, token } = await getKey()
+    console.log(uid, wssUrl, token)
+    IM.setUid(uid)
+    // 单机模式可以直接设置地址
+    WKSDK.shared().config.addr = 'wss://' + wssUrl// 默认端口为5200 + wsUrl 
+    // 认证信息
+    WKSDK.shared().config.uid = uid // 用户uid(需要在悟空通讯端注册过)
+    WKSDK.shared().config.token = token // 用户token (需要在悟空通讯端注册过)
+  }
+  return {
+    resetConfig
+  }
+}
+
+export function initConnect (callback = () => {}, mounted = () => {}) {
+  useDataSource()
+  const IM = useIMStore()
+  const conversationList = ref([])
+  const messageItems = ref([])
+
+  watch(
+    () => IM.newMsg,
+    async () => {
+      // 未读消息变化
+      updateConversation()
+      // 拉取最新消息 查看是否是自己的数据
+    },
+    {
+      deep: true,
+      immediate: true
+    }
+  )
+  onMounted(async () => {
+    // 消息发送状态监听
+    WKSDK.shared().chatManager.addMessageStatusListener(statusListen)
+    // 常规消息监听
+    // WKSDK.shared().chatManager.addMessageListener(messageListen)
+
+    mounted()
+  })
+  onUnmounted(() => {
+    // 消息发送状态监听移除
+    WKSDK.shared().chatManager.removeMessageStatusListener(statusListen)
+    // 常规消息监听移除
+    // WKSDK.shared().chatManager.removeMessageListener(messageListen)
+  })
+
+  // 消息发送状态监听
+  function statusListen (packet) {
+    if (packet.reasonCode === 1) {
+      // 发送成功
+      console.log('发送成功')
+      // 添加一组成功数据
+      callback(true)
+    } else {
+      // 发送失败
+      console.log('发送失败')
+      // 添加一组失败数据
+      callback(false)
+    }
+  }
+
+  async function updateConversation () {
+    const res = await syncConversation()
+    conversationList.value = res
+  }
+
+  function updateUnreadCount () {
+    const count = WKSDK.shared().conversationManager.getAllUnreadCount()
+    IM.setUnreadCount(count)
+  } 
+
+  async function deleteConversations (channel, enterpriseId) {
+    const query = {
+      channel_id: channel.channelID,
+      channel_type: channel.channelType,
+      enterpriseId
+    }
+    await deleteConversation(query)
+  }
+
+  async function resetUnread (channel, enterpriseId) {
+    const query = {
+      channel_id: channel.channelID,
+      channel_type: channel.channelType,
+      enterpriseId,
+      unread: 0
+    }
+    const res = await setUnread(query)
+    return res
+  }
+
+  return {
+    resetUnread,
+    deleteConversations,
+    updateConversation,
+    updateUnreadCount,
+    conversationList,
+    messageItems,
+    // channel
+  }
+}
+
+// 同步最近会话
+async function syncConversation () {
+  const res = await WKSDK.shared().conversationManager.sync()
+  return res
+}
+
+// 发起聊天
+export async function initChart (userId, enterpriseId) {
+  const channel = ref()
+  // const list = ref([])
+  const query = {
+    userId,
+    enterpriseId
+  }
+  // 创建聊天频道
+  const { data } = await getChatKey(query)
+  const { uid } = data
+  const _channel = new Channel(uid, ChannelTypePerson)
+  channel.value = _channel
+  const conversation = WKSDK.shared().conversationManager.findConversation(_channel)
+  if(!conversation) {
+    // 如果最近会话不存在,则创建一个空的会话
+    WKSDK.shared().conversationManager.createEmptyConversation(_channel)
+  }
+  const res = await getMoreMessages(1, _channel)
+  return {
+    channel,
+    ...res
+  }
+}
+
+// 翻页
+export async function getMoreMessages (pageSize, channel) {
+  const list = ref([])
+  Object.assign(HISTORY_QUERY, {
+    startMessageSeq: (pageSize - 1) * HISTORY_QUERY.limit
+  })
+  const { resultMessages, more } = await WKSDK.shared().chatManager.syncMessages(channel)
+  list.value = resultMessages
+  return {
+    list,
+    more
+  }
+}
+
+/**
+ * 
+ * @param {*} text 
+ * @param {*} _channel 
+ * @param { Number } type : 101 面试主体 
+ * @returns 
+ */
+  // 发送职位使用101
+export function send (text, _channel, type) {
+  let _text
+  if (contentType[type]) {
+    _text = new contentType[type](text)
+    WKSDK.shared().chatManager.send(_text, _channel)
+    return
+  }
+  // if (type === 101) {
+  //   _text = new ObjectContent(text)
+  //   WKSDK.shared().chatManager.send(_text, _channel)
+  //   // console.log(_text)
+  //   return
+  // }
+  // if (type === 102) {
+  //   _text = new ObjectContent2(text)
+  //   WKSDK.shared().chatManager.send(_text, _channel)
+  //   // console.log(_text)
+  //   return
+  // }
+  // // 求职者拒绝面试邀请
+  // if (type === 103) {
+  //   _text = new ObjectContent3(text)
+  //   WKSDK.shared().chatManager.send(_text, _channel)
+  //   return
+  // }
+  // // 求职者接受面试邀请
+  // if (type === 104) {
+  //   _text = new ObjectContent4(text)
+  //   WKSDK.shared().chatManager.send(_text, _channel)
+  //   return
+  // }
+  _text = new MessageText(text)
+  // console.log(_text)
+  WKSDK.shared().chatManager.send(_text, _channel)
+}
+
+// 对话开场白 用户 to 企业
+export async function prologue ({userId, enterpriseId, text}) {
+  const { channel } = await checkConversation(userId, enterpriseId)
+  send(text, channel, 102)
+  return channel
+}
+
+// 企业 to 用户
+export async function talkToUser ({userId, text}) {
+  const { channel, isNewTalk } = await checkConversation(userId)
+  if (!isNewTalk) send(text, channel)
+}
+
+// 检测是否存在频道
+export async function checkConversation (userId, enterpriseId) {
+  const query = {
+    userId,
+    enterpriseId
+  }
+  // 创建聊天频道
+  const { data } = await getChatKey(query)
+  const { uid } = data
+  const _channel = new Channel(uid, ChannelTypePerson)
+  console.log('生成channel', _channel)
+  const conversation = WKSDK.shared().conversationManager.findConversation(_channel)
+  const isNewTalk = ref(false)
+  if(!conversation) {
+    // 如果最近会话不存在,则创建一个空的会话
+    WKSDK.shared().conversationManager.createEmptyConversation(_channel)
+    isNewTalk.value = true
+  }
+  return {
+    channel: _channel,
+    isNewTalk: isNewTalk.value
+  }
+}

+ 31 - 5
layout/components/authModal/login/index.vue

@@ -1,7 +1,7 @@
 <template>
   <view class="ss-p-30 head-box">
     <view class="head-title">欢迎来到门墩儿招聘</view>
-    <uni-segmented-control class="ss-m-t-60" :current="current" :values="items" style-type="text" active-color="#00897B" @clickItem="onClickItem" />
+    <uni-segmented-control class="ss-m-t-30" :current="current" :values="items" style-type="text" active-color="#00897B" @clickItem="onClickItem" />
     <view class="head-subtitle ss-m-t-10">未注册的手机号,验证后自动注册账号</view>
 
     <view class="ss-m-t-30">
@@ -50,6 +50,19 @@
       </uni-forms>
 
       <button class="send-button" @tap="handleLogin"> 登录/注册 </button>
+      <view class="agreement-box ss-flex ss-row-center">
+        <uni-icons size="20" :type="protocol ? 'checkbox-filled' : 'circle'" :color="protocol ? '#00897B' : '#ccc'" @tap="protocol = !protocol"></uni-icons>
+        <view class="color-999 ss-flex ss-col-center ss-m-l-8 font-size-13">
+          我已阅读并遵守
+          <view class="color-primary" @tap.stop="handleToDetail('user')">
+            《用户协议》
+          </view>
+          <view class="agreement-text">与</view>
+          <view class="color-primary" @tap.stop="handleToDetail('privacy')">
+            《隐私协议》
+          </view>
+        </view>
+      </view>
     </view>
   </view>
 </template>
@@ -65,6 +78,7 @@ const items = ['短信登录', '账号登录']
 const current = ref(1)
 const accountLoginRef = ref()
 const smsLoginRef = ref()
+const protocol = ref(false)
 const state = ref({
   isMobileEnd: false, // 手机号输入完毕
   codeText: '获取验证码',
@@ -73,8 +87,8 @@ const state = ref({
     code: ''
   },
   account: {
-    phone: '13229740091',
-    password: 'Citu123'
+    phone: '13229740092',
+    password: 'Citu123456'
   },
   rules: {
     phone: mobile,
@@ -106,11 +120,24 @@ const handleCode = () => {
   getSmsCode('smsLogin', state.value.sms.phone)
 }
 
+// 查看协议详情
+const handleToDetail = (type) => {
+  const url = type === 'user' ? '/pagesB/agreement/user' : '/pagesB/agreement/privacy'
+  uni.navigateTo({
+    url
+  })
+}
+
 // 登录
 const handleLogin = async () => {
+  if (!protocol.value) return uni.showToast({ title: '请先阅读并同意用户协议和隐私政策', icon: 'none' })
   const validate = await unref(current.value === 0 ? smsLoginRef : accountLoginRef).validate()
   if (!validate) return
-  await useUserStore.handleSmsLogin(current.value === 0 ? state.value.sms : state.value.account, current.value === 0 ? true : false)
+  const query = current.value === 0 ? state.value.sms : state.value.account
+  Object.assign(query, {
+    account: query.phone
+  })
+  await useUserStore.handleSmsLogin(query, current.value === 0 ? true : false)
 }
 </script>
 
@@ -131,6 +158,5 @@ const handleLogin = async () => {
   text-align: center;
   color: #00897B;
   margin-top: 30rpx;
-  margin-bottom: 100rpx;
 }
 </style>

+ 11 - 0
layout/index.vue

@@ -21,6 +21,17 @@
 
 <script setup>
 import authModal from './components/auth-modal.vue'
+import { useIM } from '@/hooks/useIM'
+import { watch } from 'vue'
+import { userStore } from '@/store/user'
+const { resetConfig } = useIM()
+const useUserStore = userStore()
+watch(() => useUserStore.accountInfo.userId, (newVal, oldVal) => {
+  if (useUserStore.refreshToken) {
+		// 监听登录状态
+    resetConfig()
+	}
+})
 
 </script>
 <style lang="scss" scoped>

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 137 - 143
package-lock.json


+ 3 - 1
package.json

@@ -1,11 +1,13 @@
 {
   "dependencies": {
     "dayjs": "^1.11.13",
+    "js-base64": "^3.7.7",
     "lodash-es": "^4.17.21",
     "luch-request": "^3.1.1",
     "pinia": "^2.2.2",
     "pinia-plugin-persist-uni": "^1.3.1",
     "pinia-plugin-persistedstate": "^4.0.1",
-    "qs": "^6.13.0"
+    "qs": "^6.13.0",
+    "wukongimjssdk": "^1.2.10"
   }
 }

+ 46 - 9
pages.json

@@ -10,7 +10,13 @@
 		{
 			"path": "pages/index/crowdsourcing",
 			"style": {
-				"navigationBarTitleText": "全员猎聘"
+				"navigationBarTitleText": "赏金"
+			}
+		},
+		{
+			"path": "pages/index/welfare",
+			"style": {
+				"navigationBarTitleText": "会员福利"
 			}
 		},
 		{
@@ -28,7 +34,7 @@
 		{
 			"path": "pages/index/communicate",
 			"style": {
-				"navigationBarTitleText": "沟通"
+				"navigationBarTitleText": "最近联系人"
 			}
 		}
 	],
@@ -65,6 +71,18 @@
 					"style": {
 						"navigationBarTitleText": "个人信息"
 					}
+				},
+				{
+					"path": "recommendation/index",
+					"style": {
+						"navigationBarTitleText": "我的推荐"
+					}
+				},
+				{
+					"path": "chart/index",
+					"style": {
+						"navigationBarTitleText": "我的聊天"
+					}
 				}
 			]
 		},
@@ -82,6 +100,18 @@
 					"style": {
 						"navigationBarTitleText": "职位详情"
 					}
+				},
+				{
+					"path": "agreement/user",
+					"style": {
+						"navigationBarTitleText": "用户协议"
+					}
+				},
+				{
+					"path": "agreement/privacy",
+					"style": {
+						"navigationBarTitleText": "隐私协议"
+					}
 				}
 			]
 		}
@@ -99,25 +129,32 @@
 		"backgroundColor": "#ffffff",
 		"height": "65px",
 		"fontSize": "24rpx",
+
 		"list": [
 			{
 				"pagePath": "pages/index/position",
-				"text": "职",
+				"text": "职",
 				"iconPath": "/static/img/position.png",
 				"selectedIconPath": "/static/img/position-fill.png"
 			},
-			{
-				"pagePath": "pages/index/crowdsourcing",
-				"text": "猎聘",
-				"iconPath": "/static/img/pin.png",
-				"selectedIconPath": "/static/img/pin-fill.png"
-			},
 			{
 				"pagePath": "pages/index/communicate",
 				"text": "沟通",
 				"iconPath": "/static/img/message.png",
 				"selectedIconPath": "/static/img/message-fill.png"
 			},
+			{
+				"pagePath": "pages/index/welfare",
+				"text": "会员福利",
+				"iconPath": "/static/img/welfare.png",
+				"selectedIconPath": "/static/img/welfare.png"
+			},
+			{
+				"pagePath": "pages/index/crowdsourcing",
+				"text": "赏金",
+				"iconPath": "/static/img/pin.png",
+				"selectedIconPath": "/static/img/pin-fill.png"
+			},
 			{
 				"pagePath": "pages/index/my",
 				"text": "我的",

+ 173 - 4
pages/index/communicate.vue

@@ -1,12 +1,181 @@
 <template>
-  <view class="nodata-img-parent">
-    <image src="https://minio.citupro.com/dev/static/nodata.png" mode="widthFix" style="width: 100vw;height: 100vh;"></image>
-  </view>
+  <layout-page class="ss-m-x-15">
+    <view class="box" v-for="item in items" :key="item.id" @tap="handleTo(item)">
+			<view class="box-header">
+				<template v-if="item.unread === '0'">
+					<image
+						class="enterAvatar"
+						:src="getUserAvatar(item?.userInfoVo?.userInfoResp?.avatar, item?.userInfoVo?.userInfoResp?.sex)"
+					></image>
+				</template>
+				<template v-else>
+					<uni-badge class="uni-badge-left-margin" :text="item.unread" absolute="rightTop" size="small">
+						<image
+							class="enterAvatar"
+							:src="getUserAvatar(item?.userInfoVo?.userInfoResp?.avatar, item?.userInfoVo?.userInfoResp?.sex)"
+						></image>
+					</uni-badge>
+				</template>
+				
+			</view>
+			<view class="box-content">
+				<view class="box-content-names">
+					<view class="name">
+						{{ item.thatName }}
+						<text class="nameSub">{{ item.enterpriseAnotherName }}</text>
+						<span class="line" v-if="item.postNameCn && item.enterpriseAnotherName"></span>
+						<text class="nameSub">{{ item.postNameCn }}</text>
+					</view>
+					<!-- <view class="time">{{ item.updateTime }}</view> -->
+				</view>
+				<view class="box-content-text">{{ timesTampChange(+item.timestamp.padEnd(13, '0')) }}</view>
+			</view>
+		</view>
+		<image
+			v-if=" items.length===0 "
+			src="https://minio.citupro.com/dev/static/nodata.png"
+			mode="widthFix"
+			style="width: 100vw;height: 100vh;">
+		</image>
+  </layout-page>
 </template>
 
 <script setup>
+import { ref, watch } from 'vue'
+import layoutPage from '@/layout'
+import { getAccessToken } from '@/utils/request'
+import { showAuthModal } from '@/hooks/useModal'
+import { getConversationSync } from '@/api/common'
+import { onShow } from '@dcloudio/uni-app'
+import { getUserAvatar } from '@/utils/avatar'
+import { timesTampChange } from '@/utils/date'
+import { userStore } from '@/store/user'
+import { useIMStore } from '@/store/im'
+  const IM = useIMStore()
+
+const useUserStore = userStore()
+
+const items = ref([])
+watch([() => useUserStore.refreshToken, () => IM.newMsg], () => {  
+	console.log('变了')
+	init()
+})
+
+onShow(() => {
+	init()
+})
+
+
+
+const handleTo = ({ userInfoVo, thatName, postNameCn, enterpriseAnotherName, channel_id }) => {
+	const query = {
+		id: userInfoVo?.userInfoResp?.userId,
+		name: thatName,
+		postName: postNameCn,
+		enterpriseName: enterpriseAnotherName,
+		enterpriseId: userInfoVo?.userInfoResp?.enterpriseId,
+		channelId: channel_id,
+		avatar: userInfoVo?.userInfoResp?.avatar,
+		sex: userInfoVo?.userInfoResp?.sex,
+	}
+	const queryStr = Object.keys(query).reduce((r, v) => {
+		return r += `${v}=${encodeURIComponent(query[v])}&`
+	}, '?')
+	uni.navigateTo({
+    url: `/pagesA/chart/index${queryStr.slice(0, -1)}`
+  })
+}
+
+// if (!getAccessToken()) {
+// 	uni.showToast({
+// 		title: '请先登录',
+// 		icon: 'none'
+// 	})
+// 	showAuthModal()
+// 	return
+// }
+
+async function init () {
+	try {
+		const { data } = await getConversationSync({ msg_count: 1 })
+		if (!data) {
+			return
+		}
+		items.value = data.map(item => {
+			return {
+				thatName: item?.userInfoVo ? (item.userInfoVo.userInfoResp?.name ? item.userInfoVo.userInfoResp.name : '游客') : '系统消息',
+				enterpriseAnotherName: item?.userInfoVo?.userInfoResp?.enterpriseAnotherName ?? '',
+				postNameCn: item?.userInfoVo?.userInfoResp?.postNameCn ?? '',
+				...item
+			}
+		})
+	} catch (error) {
+
+	}
+}
+
+
+
 </script>
 
 <style scoped lang="scss">
-
+.box {
+	height: 130rpx;
+	padding: 20rpx;
+	box-sizing: border-box;
+	display: flex;
+	border-bottom: 2rpx solid #eee;
+	&-header {
+		width: 120rpx;
+		height: 100%;
+		position: relative;
+	}
+	&-content {
+		flex: 1;
+		width: 0;
+		display: flex;
+		flex-direction: column;
+		justify-content: space-between;
+		&-names {
+			display: flex;
+			justify-content: space-between;
+			
+			.name {
+				flex: 1;
+				overflow: hidden;
+				white-space: nowrap;
+				text-overflow: ellipsis;
+				.nameSub {
+					font-size: 0.75em;
+					color: #999;
+				}
+				.line {
+					display: inline-block;
+					width: 2rpx;
+					height: 24rpx;
+					vertical-align: middle;
+					background-color: #e0e0e0;
+					margin: 0 6rpx;
+				}
+			}
+			.time {
+				color: #999;
+				font-size: .85em;
+			}
+		}
+		&-text {
+			color: #999;
+			font-size: .85em;
+			overflow: hidden;
+			white-space: nowrap;
+			text-overflow: ellipsis;
+		}
+	}
+}
+.enterAvatar{
+	width: 80rpx;
+	height: 80rpx;
+	border-radius: 50%;
+	margin: 0 auto;
+}
 </style>

+ 244 - 4
pages/index/crowdsourcing.vue

@@ -1,12 +1,252 @@
 <template>
-  <view class="nodata-img-parent">
-    <image src="https://minio.citupro.com/dev/static/nodata.png" mode="widthFix" style="width: 100vw;height: 100vh;"></image>
-  </view>
+  <layout-page>
+    <scroll-view class="scrollBox" scroll-y="true" @scrolltolower="loadingMore" style="position:relative;">
+	  <view class="content defaultBgc">
+		  <view class="content-top">
+			<view class="content-top-title">
+				<view class="content-top-title-label">
+					<text class="content-top-title-label-l">全员猎聘</text>
+					<text class="content-top-title-label-s">海量岗位 | 推荐有赏</text>
+				</view>
+			</view>
+			<view class="content-top-carousel">
+				<view class="content-top-carousel-box">
+					<SwiperAd :list="swiperAdList"></SwiperAd>
+				</view>
+			</view>
+			<view class="content-top-recommend">
+				<view class="content-top-recommend-box">
+					<resume-status>
+						<template #header>
+							<view class="content-top-recommend-box-title">
+								<text class="title">我的推荐</text>
+							</view>
+						</template>
+					</resume-status>
+				</view>
+			</view>
+		  </view>
+		  <view class="content-main">
+			<view class="content-main-list">
+				<PositionList class="pb-10" :list="items" :noMore="items.length === total"></PositionList>
+			</view>
+		  </view>
+	  </view>
+	</scroll-view>
+  </layout-page>
 </template>
 
 <script setup>
+import { ref, reactive } from 'vue'
+import SwiperAd from '@/components/SwiperAd'
+import { showAuthModal } from '@/hooks/useModal'
+import { swiperAdListTest } from '@/utils/testData'
+// import { userStore } from '@/store/user'
+import layoutPage from '@/layout'
+import ResumeStatus from '@/components/ResumeStatus'
+import { getJobAdvertisedHire } from '@/api/position.js'
+import { getDict } from '@/hooks/useDictionaries.js'
+import { dealDictArrayData } from '@/utils/position.js'
+import PositionList from '@/components/PositionList'
+
+const swiperAdList = ref(swiperAdListTest)
+const items = reactive([])
+const pageInfo = ref({
+	pageNo: 1,
+	pageSize: 10
+})
+
+const loading = ref(false)
+const total = ref(0)
+
+
+
+
+const loadingMore = (e) => {
+	if (total.value === items.length) {
+		return
+	}
+	if (loading.value) {
+		return
+	}
+	pageInfo.value.pageNo++
+	getList()
+}
+
+async function getList () {
+	loading.value = true
+	try {
+		const { data } = await getJobAdvertisedHire({
+			...pageInfo.value
+		})
+		if (!data?.list) {
+			pageInfo.pageNo--
+			return
+		}
+		const _items = dealDictArrayData([], data.list)
+		items.push(..._items.map(e => {
+			return {
+				job: e,
+				enterprise: {
+					welfareList: e.tagList,
+					logoUrl: e.logoUrl,
+					anotherName: e.anotherName,
+					industryName: e.industryName,
+					scaleName: e.scaleName
+				}
+			}
+		}))
+		total.value = +data.total
+	} catch (error) {
+		pageInfo.pageNo--
+	} finally {
+		loading.value = false
+	}
+	
+}
+
+getList()
 </script>
 
 <style scoped lang="scss">
-
+$defaultColor: #999;
+@mixin box {
+	// border-radius: 24rpx;
+	width: 100%;
+	height: 100%;
+	background: #FFF;
+	overflow: hidden;
+}
+.content {
+	&-top {		
+		&-title {
+			padding: 24rpx;
+			box-sizing: border-box;
+			width: 100%;
+			display: flex;
+			justify-content: space-between;
+			align-items: flex-end;
+			background-color: #FFF;
+			&-label {
+				display: flex;
+				align-items: flex-end;
+				&-l {
+					font-size: 46rpx;
+					margin-right: 16rpx;
+				}
+				&-s {
+					font-size: 24rpx;
+					color: $defaultColor;
+				}
+			}
+			&-local {
+				color: $defaultColor;
+				display: flex;
+				align-items: flex-end;
+			}
+		}
+		&-carousel {
+			padding: 24rpx;
+		}
+		&-recommend {
+			padding: 0 24rpx;
+			&-box {
+				@include box;
+				&-title {
+					display: flex;
+					justify-content: space-between;
+					align-items: flex-end;
+					padding: 20rpx;
+					font-size: 30rpx;
+					.route {
+						color: $defaultColor;
+						font-size: .75em;
+					}
+				}
+			}
+		}
+	}
+	&-main {
+		&-filter {
+			padding: 24rpx;
+			font-size: 30rpx;
+			color: $defaultColor;
+			display: flex;
+			justify-content: space-between;
+			&-type{
+				&-item {
+					padding: 20rpx;
+					&.active {
+						color: #000;
+					}
+				}
+			}
+		}
+		&-list {
+			width: 100%;
+			padding: 0 24rpx 24rpx 24rpx;
+			box-sizing: border-box;
+			&-box {
+				padding: 24rpx;
+				margin-bottom: 24rpx;
+				box-sizing: border-box;
+				@include box;
+				.top {
+					width: 100%;
+					display: flex;
+					justify-content: space-between;
+					.title {
+						flex: 1;
+						overflow: hidden; /* 隐藏超出部分 */  
+						white-space: nowrap; /* 不换行 */  
+						text-overflow: ellipsis; /* 超出部分显示省略号 */  
+					}
+					.remuneration {
+						color: aquamarine;
+					}
+				}
+				.main {
+					display: flex;
+					font-size: 24rpx;
+					margin: 28rpx 0;
+					.tag {
+						&.blue {
+							background: aliceblue;
+							color: royalblue;
+						}
+						font-size: 20rpx;
+						border-radius: 8rpx;
+						color: #666;
+						background: #eee;
+						padding: 6rpx 10rpx;
+						margin-right: 20rpx;
+					}
+				}
+				.bottom {
+					color: #666;
+					display: flex;
+					justify-content: space-between;
+					.origin {
+						font-size: 0.8em;
+						.interval {
+							padding: 0 16rpx;
+						}
+					}
+					.local {
+						color: #aaa;
+						font-size: 0.6em;
+					}
+				}
+			}
+			.noMore {
+				font-size: 24rpx;
+				color: $defaultColor;
+				text-align: center;
+			}
+		}
+	}
+}
+.scrollBox {
+	height: 100vh;
+}
 </style>

+ 9 - 4
pages/index/my.vue

@@ -6,7 +6,11 @@
 				<view v-if="!useUserStore.isLogin" class="font-weight-bold font-size-20">点击登录</view>
 				<view v-else class="font-weight-bold font-size-20">{{ baseInfo?.name || userInfo?.phone }}</view>
 			</view>
-			<view class="d-flex" style="margin-top: 80rpx;">
+			<view style="padding: 40rpx 0;">
+				<resume-status></resume-status>
+			</view>
+			
+			<view class="d-flex">
 				<view v-for="(item, index) in itemList" :key="index" @tap="handleToLink(item)" class="parent">
 					<view class="d-flex justify-space-between">
 						<view>
@@ -43,6 +47,7 @@
 
 <script setup>
 import { ref, computed } from 'vue'
+import ResumeStatus from '@/components/ResumeStatus'
 import { userStore } from '@/store/user'
 import { getUserAvatar } from '@/utils/avatar'
 import { getAccessToken } from '@/utils/request'
@@ -94,9 +99,9 @@ const handleTap = () => {
 		showAuthModal()
 		return
 	}
-	// uni.navigateTo({
-	// 	url: '/pagesA/info/index'
-	// })
+	uni.navigateTo({
+		url: '/pagesA/info/index'
+	})
 }
 
 // 退出登录

+ 1 - 0
pages/index/position.vue

@@ -30,6 +30,7 @@ import PositionList from '@/components/PositionList'
 import { swiperAdListTest } from '@/utils/testData'
 import { dealDictObjData } from '@/utils/position'
 import { getJobAdvertisedSearch } from '@/api/position';
+
 import { ref, reactive } from 'vue'
 
 const swiperAdList = ref(swiperAdListTest)

+ 22 - 0
pages/index/welfare.vue

@@ -0,0 +1,22 @@
+<template>
+  <layout-page>
+		<view class="box">
+			<SwiperAd :list="swiperAdList"></SwiperAd>
+			<view class=""></view>
+		</view>
+	</layout-page>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+import layoutPage from '@/layout'
+import SwiperAd from '@/components/SwiperAd'
+
+import { swiperAdListTest } from '@/utils/testData'
+
+const swiperAdList = ref(swiperAdListTest)
+</script>
+
+<style scoped lang="scss">
+
+</style>

+ 756 - 0
pagesA/chart/index.vue

@@ -0,0 +1,756 @@
+<template>
+  <view class="box">
+    <view class="box-top">
+      <view class="box-top-title">
+        {{ info.name }}
+        <text class="subText">
+          {{ info.postName }}
+          <text v-if="info.postName && info.enterpriseName" class="gun">|</text>
+          {{ info.enterpriseName }}
+        </text>
+      </view>
+      <view class="box-top-content" v-if="interview.length">
+        <view v-for="val in interview" :key="val.id" class="color-666">
+          <view class="box-top-content-t">
+            <view class="font-weight-bold color-primary">
+              <text>{{ val.job.name }}</text>
+              <text v-if="!val.job.payFrom && !val.job.payTo" class="ml-3">面议</text>
+              <text v-else class="ml-3">{{ val.job.payFrom ? val.job.payFrom + '-' : '' }}{{ val.job.payTo }}</text>
+            </view>
+            <view :style="`color: ${val.statusColor};`" >
+              {{ val.statusText }}
+            </view>
+          </view>
+          <view class="mt-1 font-size-14 ellipsis" style="max-width: 100%;">
+            <view class="py-1">面试时间:{{ timesTampChange(val.time, 'Y-M-D h:m') }}</view>
+            <view class="py-1">面试地点:{{ val.address }}</view>
+            <view class="py-1">联系电话:{{ val.invitePhone }}</view>
+          </view>
+          <view class="bottom">
+            <view class="tipsText" @click="handleToCenter">在“个人中心-面试”中管理我的面试</view>
+            <view v-if="val.status === '0'" class="btnBox">
+              <button size="mini" type="warn" @click="handleRefuse(val)">拒绝邀请</button>
+              <button size="mini" type="primary" @click="handleAgree(val)">接受邀请</button>
+            </view>
+          </view>
+        </view>
+      </view>
+    </view>
+    <!-- <view class="box-main"  ref="chatRef"> -->
+      <scroll-view class="box-main"  ref="chatRef" scroll-y="true" :scroll-top="scrollInto"  >
+        <view class="box-main-more" v-if="hasMore">
+          <text @click="handleMore">查看更多</text>
+        </view>
+        <view v-for="val in items" :key="val.id" :id="'s'+val.id+index">
+          <view class="box-main-time">{{ timesTampChange(+(val.timestamp.padEnd(13, '0'))) }}</view>
+          <template v-if="val.payload?.type === 102">
+            <view class="jobCard">
+              <view class="jobCard-title"> {{ val.payload?.content?.positionInfo?.name }}</view>
+              <view
+                v-if="!val.payload?.content?.positionInfo?.payFrom && !val.payload?.content?.positionInfo?.payTo"
+                class="jobCard-subtitle">
+                薪酬待遇: 面议
+              </view>
+              <view
+                v-else
+                class="jobCard-subtitle"
+              >
+                薪酬待遇: 
+                {{ val.payload?.content?.positionInfo?.payFrom ? val.payload?.content?.positionInfo?.payFrom + ' - ' : '' }}
+                {{ val.payload?.content?.positionInfo?.payTo }}
+              </view>
+              <view class="jobCard-tag">
+                <view
+                  v-for="(v, i) in (val.payload?.content?.positionInfo?.enterprise?.welfareList || [])"
+                  :key="val.message_id + v + i"
+                  style="margin: 10rpx"
+                >
+                  <uni-tag
+                    :text="v"
+                    type="success"
+                  />
+                </view>
+              </view>
+              <view class="jobCard-divider"></view>
+              <view class="jobCard-subtitle text-right">
+                <v-avatar size="24">
+                  <v-img :src="val.payload?.content?.positionInfo?.contact?.avatar"></v-img>
+                </v-avatar>
+                {{ val.payload?.content?.positionInfo?.contact?.name }}
+                {{ val.payload?.content?.positionInfo?.contact?.postNameCn }}
+                {{ val.payload?.content?.positionInfo?.enterprise?.name }}
+              </view>
+              <div class="jobCard-subtitle text-right">
+                地址:{{ val.payload?.content?.positionInfo?.address }}
+              </div>
+            </view>
+          </template>
+          <view :class="['message-view_item', val.from_uid === IM.uid ? 'is-self' : 'is-other']">
+            <view class="image">
+              <image
+                :data-target="getUserAvatar(info.avatar, info.sex)"
+                class="header"
+                :src="(
+                  val.from_uid === IM.uid ?
+                  getUserAvatar(useUserStore.baseInfo?.avatar, useUserStore.baseInfo?.sex) :
+                  getUserAvatar(info.avatar, info.sex)
+                )"
+              ></image>
+            </view>
+            <!-- 显示沟通职位 -->
+            <template v-if="val.payload?.type === 102">              
+              <view class="message-text" :class="val.from_uid === IM.uid ? 'active' : ''">
+                {{ val.payload?.content.text }}
+              </view>
+            </template>
+            <!-- 发起面试邀请 -->
+            <view class="message-text none" v-else-if="val.payload?.type === 101">
+              <uni-tag text="发起了面试邀请" type="primary" />
+            </view>
+            <view class="message-text none" v-else-if="val.payload?.type === 103">
+              <uni-tag text="拒绝了面试邀请" type="error" />
+            </view>
+            <view class="message-text none" v-else-if="val.payload?.type === 104">
+              <uni-tag text="接受了面试邀请" type="success" />
+            </view>
+            <view v-else-if="val.payload.type === 105" class="text-end">
+              <uni-tag
+                v-if="val.from_uid === IM.uid"
+                :text="val.payload.content?.type === 1 ? '附件简历已发送' : '简历请求已发送'"
+                type="success"
+              />
+              <view
+                v-if="val.payload.content?.type !== 2 || val.from_uid !== IM.uid"
+                class="message-text card"
+              >
+                <view class="text-left">
+                  <text v-if="val.payload.content?.type === 1">{{
+                    val.payload.content?.query?.title || '附件简历' }}
+                  </text>
+                  <text v-if="val.payload.content?.type === 2">
+                    我想要一份您的简历,您是否同意
+                  </text>
+                </view>
+                <view class="btn-actions">
+                  <text class="btn" v-if="val.payload.content?.type === 1" @tap="handlePreview(val.payload)">点击预览附件简历</text>
+                  <text class="btn" v-if="val.payload.content?.type === 2" @tap="handleFindResume">点击发送附件简历</text>
+                </view>
+              </view>
+            </view>
+            <view v-else class="message-text" :class="{ active: val.from_uid === IM.uid}">
+              {{ val.payload?.content }}
+            </view>
+          </view>
+        </view>
+      </scroll-view>
+      
+    <!-- </view> -->
+    <view class="box-bottom">
+      <view class="box-bottom-tool">
+        <uni-tag text="发送简历" type="success" @tap="handleFindResume"/>
+      </view>
+      
+      <textarea
+        v-model="inputValue"
+        auto-height
+        confirm-type="send"
+        @confirm="handleSend"
+      />
+    </view>
+    <uni-popup ref="popup" background-color="#fff">
+      <view class="popup-title">
+        <text>请选择简历</text>
+        <uni-icons type="closeempty" size="20" @tap="handleClose"></uni-icons>
+      </view>
+      <view v-for="resume in resumeList" :key="resume.id" class="popup-content" @tap="resumeCheck = resume">
+        <view class="iconBox">
+          <uni-icons
+            v-show="resumeCheck.id === resume.id"
+            type="checkmarkempty"
+            size="20"
+            :color="resumeCheck.id === resume.id ? '#43AC57' : '#999'"></uni-icons>
+        </view>
+        <text class="text" :class="resumeCheck.id === resume.id ? 'active' : ''">{{ resume.title }}</text>
+      </view>
+      <view v-if="!resumeList.length" class="popup-upload" @click="handleUploadResume">
+        <view class="popup-upload-box">
+          <uni-icons type="plusempty" size="50" color="#f1f1f1"></uni-icons>
+        </view>
+        <text>请先上传简历</text>
+      </view>
+      <view v-if="resumeList.length" class="popup-actions">
+        <button class="default" type="default" @click="handleSendResume">发送简历</button>
+      </view>
+    </uni-popup>
+
+    <uni-popup ref="confirm" type="dialog">
+      <uni-popup-dialog
+        :type="isAgree ? 'success' : 'warn'"
+        cancelText="取消"
+        confirmText="确认" 
+        title="系统提示"
+        :content="isAgree ? '确认接受面试吗?' : '确认拒绝面试吗?'"
+        @confirm="handleConfirm"
+        @close="handleCloseConfirm"
+      ></uni-popup-dialog>
+    </uni-popup>
+  </view>
+</template>
+
+<script setup>
+import { ref, nextTick, watch, computed } from 'vue'
+import { onLoad } from '@dcloudio/uni-app'
+import { useIMStore } from '@/store/im'
+import { userStore } from '@/store/user'
+import { initConnect, send, initChart, getMoreMessages } from '@/hooks/useIM'
+import { getDict } from '@/hooks/useDictionaries'
+import { timesTampChange } from '@/utils/date'
+import { getUserAvatar } from '@/utils/avatar'
+import { preview } from '@/utils/preview'
+import { getPersonResumeCv, saveResume } from '@/api/user'
+import { getInterviewInviteListByInviteUserId } from '@/api/common'
+import { userInterviewInviteReject, userInterviewInviteConsent } from '@/api/personalCenter'
+
+const useUserStore = userStore()
+const IM = useIMStore()
+const info = ref({})
+const chatRef = ref()
+const items = ref([])
+const channelItem = ref(null)
+const hasMore = ref(false)
+
+const popup = ref()
+const resumeCheck = ref({})
+const resumeList = ref([]) // 简历列表
+
+const pageSize = ref(1)
+
+// 求职者面试列表
+const interview = ref([])
+// 求职端-获取求职者与当前邀请人的面试记录
+const statusList = ref([])
+const inputValue = ref('')
+
+const isAgree = ref(false)
+const confirm = ref()
+const chooseInvite = ref(null)
+
+const scrollInto = ref(0)
+
+const {
+  conversationList,
+  // updateConversation,
+  updateUnreadCount,
+  // deleteConversations,
+  resetUnread
+} = initConnect(async (successful) => {
+  if (!successful) {
+    uni.showToast({
+      title: '发送失败',
+      icon: 'none',
+      mask: true,
+    })
+    return
+  }
+  inputValue.value = ''
+  // chatRef.value.reset()
+  // // 发送成功
+  const { list } = await getMoreMessages(1, channelItem.value)
+  // updateConversation()
+  items.value = list.value
+  // chatRef.value.scrollBottom()
+})
+
+watch(
+  () => conversationList.value,
+  async (val) => {
+    if (!channelItem.value) {
+      return
+    }
+    const { list } = await getMoreMessages(1, channelItem.value)
+    if (list.value.length) {
+      const item = list.value[list.value.length - 1]
+      const arr = [101, 103, 104]
+      if (arr.includes(item.payload?.type)) {
+        getInterviewInviteList()
+      }
+    }
+    
+    items.value = list.value
+    setScrollBottom()
+    // 清除未读消息
+    resetUnread(channelItem.value)
+  },
+  {
+    deep: true,
+    immediate: true
+  }
+)
+
+async function init(userId, enterpriseId) {
+  const { channel, list, more } = await initChart(userId, enterpriseId)
+  hasMore.value = more
+  channelItem.value = channel.value
+  items.value = list.value
+  setScrollBottom()
+}
+
+// 滑动到底部
+function setScrollBottom () {
+  // scrollInto.value = 200
+}
+
+async function getInterviewInviteList () {
+  if (!info.value.id) return
+  const { data } = await getInterviewInviteListByInviteUserId(info.value.id)
+  console.log(data, statusList)
+  interview.value = data.slice(0, 1).map(e => {
+    const statusItem = statusList.value.find(_e => _e.value === e.status)
+    const statusText = statusItem?.label || ''
+    const statusColor = ['5', '98', '99'].includes(e.status)
+    return {
+      ...e,
+      statusColor: statusColor ? '#FE574A' : '#0E8E80',
+      statusText
+    }
+  })
+}
+
+const getStatusList = async () => {
+  try {
+    const { data } = await getDict('menduner_interview_invite_status')
+    if (data.data.length) {
+      statusList.value = data.data
+    }
+  } catch (error) {
+
+  }
+}
+
+function handleSend () {
+  send(inputValue.value, channelItem.value)
+}
+
+// 预览简历
+function handlePreview (payload) {
+  if (!payload?.content?.query?.src) {
+    uni.showToast({ title: '简历地址不存在', icon: 'none' })
+    return
+  }
+  preview(payload.content.query.src)
+}
+// 获取简历
+async function handleFindResume () {
+  uni.showLoading({
+    title: '真正查找简历'
+  })
+  try {
+    // 获取简历列表
+    const { data } = await getPersonResumeCv()
+    if (data.length === 0) {
+      uni.showToast({
+        title: '您还未上传过简历,请先上传简历后再投递',
+        icon: 'none',
+        mask: true,
+      })
+      return
+    }
+    resumeList.value = data
+    resumeCheck.value = data[0]
+    popup.value.open('center')
+  } finally {
+    uni.hideLoading()
+  }
+}
+
+// 关闭简历窗口
+function handleClose () {
+  popup.value.close()
+}
+
+// 发送简历
+function handleSendResume () {
+  if (!Object.keys(resumeCheck.value).length) {
+    uni.showToast({ title: '请选择要投递的简历', icon: 'none' })
+    return
+  }
+  const text = {
+    remark: '发送简历',
+    query: {
+      src: resumeCheck.url,
+      title: resumeCheck.title,
+      id: resumeCheck.id,
+    },
+    type: 1
+  }
+  send (JSON.stringify(text), channelItem.value, 105)
+  popup.value.close()
+}
+
+// 拒绝邀请
+function handleRefuse (val) {
+  isAgree.value = false
+  chooseInvite.value = val
+  confirm.value.open()
+}
+
+// 接受邀请
+function handleAgree (val) {
+  isAgree.value = true
+  chooseInvite.value = val
+  confirm.value.open()
+}
+
+// 确认
+async function handleConfirm () {
+  // 拒绝
+  if (!isAgree.value) {
+    await userInterviewInviteReject(chooseInvite.value.id)
+  } else {
+    await userInterviewInviteReject(chooseInvite.value.id)
+  }
+  uni.showToast({ title: '操作成功', icon: 'none' })
+  send(JSON.stringify({ id: chooseInvite.value.id }), channelItem.value, isAgree.value ? 104 : 103)
+}
+
+// 关闭
+function handleCloseConfirm () {
+  confirm.value.close()
+}
+
+// 查看更多
+async function handleMore () {
+  try {
+    uni.showLoading({
+      title: '加载中...'
+    })
+    pageSize.value++
+    const { list, more } = await getMoreMessages(pageSize.value, channelItem.value)
+    items.value.unshift(...list.value)
+    hasMore.value = more
+  } finally {
+    uni.hideLoading()
+  }
+}
+
+// 上传简历
+function handleUploadResume () {
+  wx.chooseMessageFile({
+    count: 1,
+    type: 'file',
+    success (res) {
+      const title = res.tempFiles[0].name
+      const path = res.tempFiles[0].path
+      const test = /\.(pdf|docx|doc)$/.test(title)
+      if (!test) {
+        uni.showToast({
+          icon: 'none',
+          title: '请上传pdf、word类型的文件',
+          duration: 2000
+        })
+        return
+      }
+      //效验是否为支持的文件格式
+      uploadFile(path).then(async (res) => {
+        if (!res.data) {
+          uni.showToast({
+            title: '上传失败',
+            icon: 'none'
+          })
+          return
+        }
+        await saveResume({ title, url: res.data })
+        uni.showToast({
+          title: '上传成功',
+          icon: 'success'
+        })
+        handleFindResume()
+      })
+    }
+  })
+}
+
+onLoad(async (options) => {
+  info.value = Object.keys(options).reduce((r, k) => {
+    r[k] = decodeURIComponent(options[k])
+    return r
+  }, {})
+  await init(options.id, options.enterpriseId)
+  await getStatusList()
+  getInterviewInviteList()
+  // 清除未读消息
+  resetUnread(channelItem.value)
+  // 更新未读消息
+  updateUnreadCount()
+})
+</script>
+
+<style lang="scss" scoped>
+.white {
+  color: #FFF !important;
+}
+.text-left {
+  text-align: left !important;
+}
+.text-right {
+  text-align: right !important;
+}
+.box {
+  width: 100%;
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+  &-top {
+    &-title {
+      padding: 0 60rpx;
+      box-sizing: border-box;
+      width: 100%;
+      height: 80rpx;
+      line-height: 80rpx;
+      // text-align: center;
+      border-bottom: 2rpx solid #EEE;
+      overflow: hidden;
+      white-space: nowrap;
+      text-overflow: ellipsis;
+      .subText {
+        font-size: .85em;
+        color: #999;
+        .gun {
+          padding: 0 10rpx;
+        }
+      }
+    }
+    &-content {
+      padding: 20rpx 50rpx;
+      padding-bottom: 20rpx;
+      border-bottom: 2rpx solid #eee;
+      .color-666 {
+        color: #666;
+      }
+      .font-weight-bold {
+        font-weight: bold;
+      }
+      .color-primary {
+        color: #009688;
+      }
+      .ml-3 {
+        margin-left: 40rpx;
+      }
+      .mt-1 {
+        margin-top: 12rpx;
+      }
+      .font-size-14 {
+        font-size: 24rpx;
+      }
+      .py-1 {
+        padding: 4rpx 0;
+      }
+      .tipsText {
+        font-size: .75em;
+        color: #999;
+      }
+      &-t {
+        display: flex;
+        justify-content: space-between;
+      }
+      .btnBox {
+        display: flex;
+        padding: 20rpx 60rpx;
+        justify-content: space-around;
+      }
+    }
+  }
+
+  &-main {
+    flex: 1;
+    height: 0;
+    padding: 40rpx;
+    // overflow-y: auto;
+    box-sizing: border-box;
+    &-more {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      color: #24bc3e;
+      font-size: .9em;
+      padding: 20rpx 0;
+    }
+    &-time {
+      user-select: none;
+      position: relative;
+      top: 16rpx;
+      margin: 40rpx 0;
+      text-align: center;
+      max-height: 40rpx;
+      text-align: center;
+      font-weight: 400;
+      font-size: .85em;
+      color: #999;
+    }
+    .jobCard {
+      padding: 30rpx;
+      background: #E2F2F0;
+      color: #009688 ;
+      margin-top: 20rpx;
+      max-width: unset;
+      margin-right: 0;
+      &-title {
+        font-size: 1.2em;
+      }
+      &-subtitle {
+        padding: 10rpx 0;
+        // font-size: .5em;
+      }
+      &-divider {
+        width: 100%;
+        height: 2rpx;
+        margin: 20rpx 0;
+        background: #ddd;
+      }
+      &-tag {
+        display: flex;
+        flex-wrap: wrap;
+      }
+    }
+    .message-view_item {
+      display: flex;
+      flex-direction: row;
+      align-items: flex-start;
+      margin: 16rpx 0;
+      position: relative;
+      .image {
+        width: 60rpx;
+        height: 60rpx;
+        border-radius: 180rpx;
+        // flex-grow: 1;
+        // flex-shrink: 0;
+        overflow: hidden;
+        .header {
+          width: 60rpx;
+          height: 60rpx;
+        }
+      }
+      .text-end {
+        text-align: right !important;
+        width: 400rpx;
+        margin-right: 20rpx;
+      }
+      .message-text {
+        overflow-wrap: break-word;
+        background-color: #f0f2f5;
+        border-radius: 12rpx;
+        max-width: 75%;
+        padding: 20rpx;
+        &.active {
+          background: #d5e6e8;
+        }
+        &.card {
+          background: #E2F2F0;
+          color: #009688 ;
+          margin-top: 20rpx;
+          max-width: unset;
+          margin-right: 0;
+          .btn-actions {
+            margin: 40rpx auto 20rpx auto ;
+            text-align: center;
+            .btn {
+              padding: 10rpx 30rpx;
+              background: #C8E7D8;
+              color: #43AC57;
+              font-size: .75em;
+              border-radius: 10rpx;
+            }
+          }
+        }
+        &.none {
+          padding: 10rpx 0;
+          background-color: unset;
+        }
+        &.active {
+          background: #d5e6e8;
+        }
+      }
+    }
+    .is-self {
+      flex-direction: row-reverse;
+      display: flex;
+      .message-text {
+        margin-right: 20rpx;
+      }
+    }
+    .is-other {
+      .message-text {
+        margin-left: 20rpx;
+      }
+    }
+  }
+  &-bottom {
+    max-height: 300rpx;
+    border-top: 2rpx solid #EEE;
+    background: rgba(230, 230, 230, 0.5);
+    padding: 20rpx 40rpx;
+    box-sizing: border-box;
+    &-tool {
+      margin-bottom: 40rpx;
+    }
+    textarea {
+      border-radius: 10rpx;
+      width: 100%;
+      min-height: 80rpx;
+      max-height: 180rpx;
+      padding: 20rpx;
+      box-sizing: border-box;
+      background: #FFF;
+    }
+  }
+  .popup-title {
+    padding: 30rpx 20rpx;
+    display: flex;
+    justify-content: space-between;
+    border-bottom: 2rpx solid #DDD;
+  }
+  .popup-content {
+    padding: 20rpx 40rpx;
+    color: #999;
+    display: flex;
+    align-content: center;
+    justify-items: center;
+    .iconBox {
+      width: 40rpx;
+    }
+    .text {
+      margin-left: 20rpx;
+      &.active {
+        color: #00897b;
+      }
+    }
+  }
+  .popup-upload {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-direction: column;
+    width: 400rpx;
+    font-size: .75em;
+    color: #999;
+    padding-bottom: 40rpx;
+    &-box {
+      margin-top: 20rpx;
+      width: 200rpx;
+      height: 200rpx;
+      text-align: center;
+      line-height: 200rpx;
+      border: 4rpx solid #ddd;
+      border-radius: 8rpx;
+    }
+  }
+  .popup-actions {
+    padding: 60rpx;
+    .default {
+      background: #00897b;
+      color: #DDD;
+      font-size: .9em;
+    }
+  }
+}
+</style>

+ 25 - 10
pagesA/info/index.vue

@@ -3,9 +3,9 @@
 		<uni-forms ref="form" :modelValue="formData" :rules="rules" validateTrigger="bind" label-width="105px" label-align="right">
 			<uni-forms-item label="头像" name="avatar" class="f-straight">
         <view style="display: flex;flex-wrap: wrap;">
-          <view class="upload-img" v-if="formData.avatar" @click="handlePreviewImage">
-            <uni-icons size="30" type="clear" color="#00897B" style="position: absolute;right: -11px; top: -11px; z-index: 9" @click="formData.avatar = ''"></uni-icons>
-            <image :src="formData.avatar" mode="contain" style="width: 200rpx;height: 200rpx;"></image>
+          <view class="upload-img" v-if="formData?.avatar" @click="handlePreviewImage">
+            <uni-icons size="30" type="clear" color="#fe574a" style="position: absolute;right: -11px; top: -11px; z-index: 9" @click="formData.avatar = ''"></uni-icons>
+            <image :src="formData?.avatar" mode="contain" style="width: 200rpx;height: 200rpx;"></image>
           </view>
           <view v-else class="upload-file" @click="uploadPhotos">
             <uni-icons type="plusempty" size="50" color="#f1f1f1"></uni-icons>
@@ -70,9 +70,25 @@ const form = ref()
 const sexData = ref([])
 const map = { text: 'label', value: 'value' }
 const useUserStore = userStore()
+
 const formData = ref({})
 const getInfo = () => {
-  formData.value = cloneDeep(useUserStore?.baseInfo)
+  formData.value = cloneDeep(useUserStore?.baseInfo) || {
+    avatar: '',
+    name: '',
+    sex: '',
+    regId: '',
+    birthday: '',
+    phone: '',
+    email: '',
+    eduType: '',
+    firstWorkTime: '',
+    expType: '',
+    jobType: '',
+    jobStatus: '',
+    maritalStatus: '',
+    areaId: ''
+  }
 }
 getInfo()
 
@@ -153,13 +169,12 @@ const rules = {
 const submit = async () => {
   const valid = await unref(form).validate()
   if (!valid) return
-  console.log(formData.value.avatar, formData.value)
 
-  // await updatePersonAvatar(formData.value.avatar)
-  // await saveBaseInfo(formData.value)
-  // uni.showToast({ title: '编辑成功', icon: 'success' })
-  // useUserStore.getInfo()
-  // getInfo()
+  await updatePersonAvatar(formData.value.avatar)
+  await saveBaseInfo(formData.value)
+  uni.showToast({ title: '编辑成功', icon: 'success' })
+  await useUserStore.getInfo()
+  getInfo()
 }
 </script>
 

+ 123 - 0
pagesA/recommendation/index.vue

@@ -0,0 +1,123 @@
+<template>
+	<layout-page>
+		<uni-segmented-control :current="current" :values="controlListText" @clickItem="handleChange" styleType="text" activeColor="#00897B"></uni-segmented-control>
+		<scroll-view class="scrollBox defaultBgc" scroll-y="true" @scrolltolower="loadingMore" style="height: calc(100vh - 72rpx);">
+		  <view v-if="items.length" class="listBox">
+		    <m-list :items="items"></m-list>
+		    <uni-load-more :status="more" />
+		  </view>
+		  <view v-else class="nodata-img-parent">
+		    <image
+				src="https://minio.citupro.com/dev/static/nodata.png"
+				mode="widthFix"
+				style="width: 100vw;height: 100vh;"
+			></image>
+		  </view>
+		</scroll-view>
+	</layout-page>
+</template>
+
+<script setup>
+import { ref, watch } from 'vue'
+import layoutPage from '@/layout'
+
+import MList from './list'
+import { getDict } from '@/hooks/useDictionaries.js'
+import { getRecommendationList } from '@/api/position.js'
+import { onLoad } from '@dcloudio/uni-app'
+
+import { userStore } from '@/store/user'
+const useUserStore = userStore()
+
+watch(() => useUserStore.refreshToken, (newVal, oldVal) => {
+  if (useUserStore.refreshToken) {
+		// 监听登录状态
+		console.log('重新登录了')
+		handleChange({ currentIndex: current.value })
+	}
+})
+
+
+const current = ref(0)
+
+// 获取参数
+const controlList = ref([])
+const controlListText = ref([])
+const pageInfo = ref({
+	pageNo: 1,
+	pageSize: 10
+})
+const total = ref(0)
+const items = ref([])
+const loading = ref(false)
+const more = ref('more')
+
+async function initDict () {
+	try {
+		const { data } = await getDict('menduner_hire_job_cv_status')
+		if (!data?.data) {
+			return
+		}
+		controlList.value = data.data
+		controlListText.value = data.data.map(e => e.label)
+		// current.value = +controlList.value[0].value
+		init()
+	} catch (error) {
+		// console.log(error)
+	}
+}
+
+
+function handleChange (val) {
+	current.value = val.currentIndex
+	pageInfo.value.pageNo = 1
+	total.value = 0
+	items.value = []
+	init()
+}
+
+function loadingMore () {
+	if (total.value === items.value.length) {
+		return
+	}
+	if (loading.value) {
+		return
+	}
+	more.value = 'loading'
+  pageInfo.value.pageNo++
+	init()
+}
+
+async function init () {
+	try {
+		loading.value = true
+		const { data } = await getRecommendationList({
+			...pageInfo.value,
+			status: current.value
+		})
+		if (!data?.list) {
+			pageInfo.value.pageNo--
+			return
+		}
+		items.value.push(...data.list)
+		total.value = +data.total
+		more.value = items.value.length === total.value ? 'noMore' : 'more'
+	} catch (error) {
+		pageInfo.value.pageNo--
+	} finally {
+		loading.value = false
+	}
+}
+
+onLoad(async (options) => {
+  if (options?.id) {
+    current.value = +options.id
+	}
+	initDict()
+})
+
+</script>
+
+<style lang="scss" scoped>
+
+</style>

+ 66 - 0
pagesA/recommendation/list.vue

@@ -0,0 +1,66 @@
+<template>
+	<view
+		class="list"
+		v-for="item in items"
+		:key="item.id"
+	>
+		<view class="list-top">
+			<text class="list-top-person">牛人:{{ item.sendPerson?.name }}</text>
+			<text class="list-top-time">{{ timesTampChange(item.createTime) }}</text>
+		</view>
+		<view class="list-remuneration">
+			薪酬:
+			{{ item.job?.payFrom }}
+			{{ item.job?.payFrom && item.job?.payTo ? ' - ' : '' }}
+			{{ item.job?.payTo }}
+		</view>
+		<view class="list-company">
+			<text>{{ item.enterprise?.anotherName }}</text>
+			<text>{{ item.enterprise?.anotherName && item.job?.name ? ' · ' : '' }}</text>
+			<text>{{ item.job?.name }}</text>
+		</view>
+	</view>
+</template>
+
+<script setup>
+import { timesTampChange } from '@/utils/date'
+const props = defineProps({
+	items: {
+		type: Array,
+		default: () => []
+	}
+})
+</script>
+
+<style lang="scss" scoped>
+.list {
+	background: #FFF;
+	margin-top: 20rpx;
+	&-top {
+		padding: 20rpx;
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		&-person {
+			font-size: .9em;
+			color: #333;
+		}
+		&-time {
+			font-size: .75em;
+			color: #999;
+		}
+	}
+	&-company {
+		padding: 30rpx 20rpx;
+		font-size: 28rpx;
+		color: #666;
+		background: linear-gradient(90deg, #f5fcfc 0, #fcfbfa 100%);
+	}
+	&-remuneration {
+		padding: 20rpx;
+		font-size: 28rpx;
+		color: #666;
+	}
+	
+}
+</style>

+ 159 - 0
pagesB/agreement/privacy.vue

@@ -0,0 +1,159 @@
+<template>
+  <view class="wrap">
+    <view class="wrap-box">
+      <view class="text-center font-weight-bold font-size-20">门墩儿招聘隐私政策</view>
+      <view class="my tac">(本协议于2024年9月20日最新修订)</view>
+      <view>尊敬的用户: </view>
+      <view class="my">在您成为门墩儿招聘注册用户前,请您仔细阅读下面条款内容</view>
+      <view>版本更新提示: </view>
+      <view class="my">1、按照产品功能和业务场景重新梳理、归类了个人信息收集和使用情况。</view>
+      <view>正文: </view>
+      <view class="my">
+        门墩儿招聘非常重视用户的隐私和个人信息保护。您在使用我们服务时(如进行找工作、招聘、职业技能学习及职业问答活动等),我们可能会收集和使用您的相关信息。我们希望通过《门墩儿招聘隐私政策》(以下简称“本隐私政策”)帮助您了解我们会收集哪些信息、
+        为什么收集这些信息,会利用这些信息做些什么及如何保护这些信息。了解这些内容对于您行使隐私权及保护您的个人信息至关重要。请您在使用我们服务前仔细阅读此《门墩儿招聘隐私政策》并充分理解本隐私政策的全部内容,确定了解我们对您个人信息的处理规则。阅读过程中,如您有任何疑问,可联系我们进行咨询;一旦您继续访问或使用门墩儿招聘,
+        即表示您同意此等隐私政策,授权并接受我们按照本隐私政策的规定收集、保存、使用、共享、披露您的信息。如您不同意协议中的任何条款,您应立即停止访问门墩儿招聘及使用相关服务。
+      </view>
+      <view>本隐私政策适用于您通过任何方式(包括但不限于微信公众号、微信小程序等)对门墩儿招聘各项服务的访问和使用。另外,若您通过使用第三方产品和/或服务(如第三方账号)来使用门墩儿招聘服务,您的信息还应当适用该第三方的隐私政策。</view>
+      <view class="mt">本隐私政策的相关术语,在未作特别说明的情况下可参照《门墩儿招聘用户协议》。</view>
+
+      <view v-for="(item,index) in data" :key="index">
+        <uni-title type="h1" :title="item.title" color="#000"></uni-title>
+        <view>{{ item.describe }}</view>
+        <view v-for="(val,i) in item.subData" :key="i" class="mb">
+          <uni-title class="my" type="h2" :title="val.text" color="#000"></uni-title>
+          <view>{{ val.subText }}</view>
+          <view :class="{'my': val.content.length > 0}" v-for="(k,kIn) in val.content" :key="kIn">
+            <view :class="{'mb': k.content.length > 0, 'ml-10': k.content.length > 0}">{{ k.text }}</view>
+            <view :class="{'mb': k.subContent.length > 0}" v-for="(r,rIn) in k.subContent" :key="rIn">
+              <view :class="{'mb': k.subContent.length > 0, 'ml-10': k.subContent.length > 0}">{{ r }}</view>
+            </view>
+            <view>{{ k.endText }}</view>
+          </view>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+const data = [
+  {
+    title: '一、个人信息的收集及用途',
+    describe: '根据国家法律法规的规定,网络运营者为用户提供信息发布、即时通讯等服务,在与用户签订协议或者确认提供服务时,应当要求用户提供真实身份信息。用户不提供真实身份信息的,网络运营者不得为其提供相关服务。为了符合法律法规要求并更好地为您提供服务,我们将收集并使用您的个人信息。我们承诺将坚持“最小必要化”原则收集、使用、存储和传输用户信息,并通过用户协议和隐私政策告知您相关信息的使用目的和范围。',
+    subData: [
+      {
+        text: '(一)个人信息的定义',
+        subText: '个人信息,是指以电子或者其他方式记录的能够单独或者与其他信息结合识别特定自然人身份或者反映特定 自然人活动情况的各种信息,在本隐私政策中涉及的个人信息包括但不限于:',
+        content: [
+          { text: '1. 基本信息:包括个人姓名、年龄(生日)、性别、个人电话号码、行业、工作经历、教育经历、职业技能、所属公司等;'},
+          { text: '2. 个人身份信息:个人证件、面部特征等;'},
+          { text: '3. 账号信息:包括系统账号、IP地址、邮箱地址及与前述有关的密码等;'},
+          { text: '4. 个人常用设备信息:包括硬件型号、设备MAC地址、操作系统类型、AndroidID、软件安装列表等,详情请见《第三方SDK目录》'},
+          { text: '5. 个人位置信息:包括定位信息等;'},
+          { text: '6. 个人财产信息:包括交易和支付记录、账户及积分余额、各种优惠券、兑换券等;'},
+        ],
+        endText: '如无特别说明,本《隐私政策》中使用的特定词语,均遵循以上定义。'
+      },
+      {
+        text: '(二)为使用基本功能/服务用户须提供并授权平台收集、使用的信息',
+        subText: '在您使用平台基本功能/服务的过程中,我们可能会收集、保存和使用下列与您有关的信息。如果您不提供相关信息,您将无法使用平台的服务/具体功能。请您注意,您在使用平台服务/功能时所提供的所有个人信息,除非您删除或通过系统设置拒绝我们收集,否则将在您使用我们的产品与/或服务期间持续授权我们使用。部分个人信息的收集,需要您同意调取所使用设备的相应系统权限,具体参见第本条款第(五)部分需要您授权同意调取系统权限的情形。我们收集的您的个人信息类型会因产品/服务的内容不同而有所区别,具体以产品/服务实际提供为准。点击查看《门墩儿招聘已收集个人信息清单》。这些服务/功能包括但不限于:',
+        content: [
+          { 
+            text: '1. 用户注册',
+            subContent: [
+              '  1). 求职者:当您注册时,您需要至少向我们提供您本人的手机号码用于注册账号,我们可能会通过发送短信验证码、微信验证、支付宝验证、QQ验证等任意方式来验证您的身份是否有效。您可以自主设置您的账户密码,并补充个人基本信息包括用户名、个人姓名、性别、年龄、行业、工作经历、职业技能、所属公司以及相关信息等。这些信息均属于您的“账户信息”,您补充的账户信息将有助于我们为您提供个性化求职或招聘体验等',
+              '  2). 招聘者:当您注册时,您需要至少向我们提供您本人的邮箱或手机号用于注册账号,我们将通过发送邮箱验证码或手机短信的方式来验证您的身份是否有效;同时您还需要绑定您本人的手机号用于身份认证。您可以自主设置您的账户密码,并选择完善账号信息和绑定第三方账号(如微信、QQ、新浪微博、Apple账号等)。',
+              '  3). 请注意,您作为企事业单位或组织的招聘人员进行招聘者身份注册时,需要提供您的个人身份信息,包括但不限于身份证、护照和/或其他辅助证件,并进行实名认证(具体要求以注册页面的指示为准);另外,还需提供您所属的公司信息。如您不能或拒绝提供上述信息,将无法以招聘者身份完成注册。'
+            ]
+          },
+          { 
+            text: '2. 找工作',
+            subContent: [
+              '  1). 为了更好的为您匹配并推荐职位信息并让企业招聘负责人更好地发现您,我们将收集您的基本信息、个人上网记录、个人位置信息等。通过收集这些信息,平台将为您推荐更符合您的职位(例如与您所在城市、所在行业相匹配的职位)。',
+              '  2). 关于简历:由于本平台是连接求职者与用人者的中介信息平台及职业社区,如您作为求职者,您必须先完善您的个人在线简历后方可投递本网站上招聘企业发布的职位。个人在线简历包含前述个人基本信息以及其他因找工作所需提供的信息,如求职意向(期望职位、期望行业、期望城市和求职性质)、主要经历、图片作品等。当您注册成为求职者用户向平台上传附件简历或编辑在线简历,并选择了公开您的简历时,代表您已经同意:使用平台相应服务的招聘单位(及人员)可以通过我们的系统功能(如职位、行业、从业时间等关键词)发现您;对您感兴趣的招聘单位可以向您发送职位邀约,邀请您投递简历;购买了相应服务的用人者(及人员)拥有法律上许可的对您的简历进行查询、浏览、下载、使用、存储的权利;同时我们也有权根据您的简历特点、职业需求等匹配适合您的岗位,并帮您投递申请,以提高求职成功率。但是您仍然拥有通过系统隐私设置功能,选择是否完全公开您的简历或屏蔽某些招聘企业发现您的信息的权利,对于因此而引起的任何法律纠纷(包括但不限于招聘企业或人员错误或非法使用前述简历信息),本平台不承担任何法律责任',
+              '  3). 本平台致力于保护您的信息安全,针对上述内容您拥有通过系统的隐私设置功能,选择是否完全公开您的简历或屏蔽某些用人者/职业顾问发现您的信息的权利(具体见第四条第(二)款)。'
+            ]
+          },
+          { 
+            text: '3. 招聘',
+            subContent: [
+              '  1). 您在代表用人者注册招聘人员账号并入驻平台时提交的用人者相应资质材料(包括但不限于用人者的执照、用人单位出具的经办人员身份证明/委托证明等),并不属于您的个人信息,而是用于证明用人者的资质及用人者真实招聘需求。您作为用人者招聘人员代表企业在平台上提供的信息及使用平台所产生的信息,用人者均有权向平台申请获取或查看上述信息。',
+            ]
+          },
+          { 
+            text: '4. 沟通功能',
+            subContent: [
+              '  1). 平台为招聘企业及求职者之间提供了在线沟通的功能,为此平台需要使用双方的账号信息等。',
+            ]
+          },
+          { 
+            text: '5. 其它功能',
+            subContent: [
+              '  1). 依据《网络安全法》等相关法律法规规定,响应维护网络秩序和防欺诈的要求,您通过平台与他人在线沟通、拨打电话以及视频面试等商务场景下产生的文字、语音及视频等形式的沟通信息可能会被存储,但仅用于投诉举报的处理、安全风控及离线暂存功能的实现。本平台对该信息的采集、传输及存储均会采取加密、防泄露等相关措施。',
+            ]
+          }
+        ],
+        endText: '如无特别说明,本《隐私政策》中使用的特定词语,均遵循以上定义。'
+      },
+      {
+        text: '(三)需要您授权同意调取系统权限的情形',
+        subText: '',
+        content: [
+          { 
+            text: '1.为使您使用平台服务更加高效便捷,从而提升您在平台的服务使用体验,我们可能会通过您的设备收集和使用您的个人信息。如果您不授权这些具体应用权限,您依然可以使用平台提供的招聘、求职或职业问答等基本功能,但您可能无法充分使用这些可以为您带来高效便捷服务体验的附加功能。这些附加功能包括但不限于:',
+            subContent: [
+              '1).基于位置信息的个性化推荐功能:我们会收集您的位置信息(我们仅收集您当时所处的地理位置,但不会将您各时段的位置信息进行结合以判断您的行踪轨迹)来判断您所处的地点,为您推荐您所在区域的招聘信息。',
+              '2).基于摄像头(相机)的附加功能:您可以使用这个附加功能完成视频拍摄、拍照等功能,帮助您更好地使用在线沟通、言职问答等服务。',
+              '3).基于图片上传的附加功能:您可以在平台上传您的照片来设置头像、更好地使用招聘信息发布、在线沟通、言职问答等功能。',
+              '5).基于设备外部存储文件访问、读写、存储的附加功能:您可以调用您设备外部存储中的文件(比如文档文件、照片等)用于上传简历、编辑个人头像、进行在线沟通等;也可以相应下载、保存各类文件至外部存储中。',
+              '6).基于通知的附加功能:您开启通知权限后,平台才能向您发送各类服务通知(如职位推荐、其他招聘/求职用户的消息等)。'
+            ]
+          },
+          { text: '2.上述附加功能需要您在您的设备中向我们开启您的地理位置(位置信息)、相机(摄像头)、相册(图片库)、麦克风以及外部存储的访问权限。您可以在您的终端设备中逐项查看您上述权限的开启状态,并可以随时开启或关闭这些权限。请您注意,您开启这些权限即代表您授权我们可以收集和使用这些个人信息来实现上述的功能,您关闭权限即代表您取消了这些授权,则我们将不再继续收集和使用您的这些个人信息,也无法为您提供上述与这些授权所对应的功能。您关闭权限的决定不会影响此前基于您的授权所进行的个人信息的处理。'}
+        ],
+        endText: '如无特别说明,本《隐私政策》中使用的特定词语,均遵循以上定义。'
+      },
+      {
+        text: '(四) 从第三方获取您的个人信息',
+        subText: '我们可能从第三方获取您授权共享的账户信息(头像、昵称),并在您同意本隐私政策后将您的第三方账户与您的门墩儿招聘账户绑定,使您可以通过第三方账户直接登录并使用我们的产品与/或服务。我们会将依据与第三方的约定、对个人 信息来源的合法性进行确认后,在符合相关法律和法规规定的前提下,使用您的这些个人信息。',
+        content: [],
+        endText: ''
+      },
+      {
+        text: '(五) 个人信息去标识化后的使用',
+        subText: '在收集您的个人信息后,我们可以通过技术手段对数据进行去标识化处理,去标识化处理的信息将无法识别主体。您了解并同意,在此情况下我们有权使用已经去标识化的信息;并对您的个人信息进行脱敏处理的前提下,我们有权对用户数据库进行分析并予以商业化的利用。比如,我们会对我们服务使用情况进行统计,并可能会与公众或第三方共享这些统计信息,以展示互联网行业及招聘领域发展趋势等,但这些统计信息不包含您的任何身份识别信息。',
+        content: [],
+        endText: ''
+      }
+    ]
+  }
+]
+</script>
+
+<style scoped lang="scss">
+.wrap{
+  background-color: #f1f4f6;
+  padding: 30rpx;
+}
+.wrap-box{
+  font-size: 28rpx;
+  background-color: #fff;
+  border-radius: 12rpx;
+  padding: 20rpx;
+}
+.mb{
+  margin-bottom: 30rpx;
+}
+.my{
+  margin: 30rpx 0;
+}
+.tac{
+  text-align: center;
+}
+.ml-10{
+  margin-left: 30rpx;
+}
+.mt{
+  margin-top: 30rpx;
+}
+</style>

+ 190 - 0
pagesB/agreement/user.vue

@@ -0,0 +1,190 @@
+<template>
+  <view class="wrap">
+    <view class="wrap-box">
+      <view class="text-center font-weight-bold font-size-20">门墩儿招聘用户协议</view>
+      <view class="my tac">(本协议于2024年9月20日最新修订)</view>
+      <text>尊敬的用户,我们对《门墩儿招聘用户协议》进行了更新,此版本主要更新内容涉及门墩儿招聘企业账号身份和资质认证、推广营销相关规则等内容,请您仔细阅读更新后的条款。</text>
+      <view class="my">正文: </view>
+      <view>
+        《门墩儿招聘用户协议》(以下简称“本协议”)作为门墩儿招聘站(以下简称“本网站”)提供服务的依据,确定用户在何种条件、以何种方式使用本网站及本网站的服务(具体载体包括但不限于微信公众号平台、微信小程序等)。
+        请您认真阅读本协议(尤其是字体加粗及/或下划线的内容),当您点击“注册”或者“登录”或其他方式确认即表示您已经仔细阅读并完全理解、同意本协议项下的全部条款;如您对本协议的任何条款表示异议,您应当立即停止访问门墩儿招聘及使用相关服务。
+        用户根据自身需求可以向本平台购买付费服务/产品,相关服务/产品的部分或全部利用本网站所提供的互联网信息业务和增值业务,在遵守本用户协议的规定基础上还需遵守《门墩儿招聘在线增值服务协议》
+        (适用于在线支付购买的产品)、服务合同等付费服务涉及的特殊条款;您应当在购买前认真阅读,一旦购买付费服务即视为接受相关条款。
+      </view>
+      <view class="mb">
+        本协议包括基于本协议制定的各项规则,所有规则为本协议不可分割的一部分,与本协议具有同等效力。随着平台业务经营的发展及相关政策的变更,本网站用户协议将不时更新,我们会通过在网站公告、APP端推送、电子邮件等适当方式提醒您相关内容的更新。
+        您也可以随时访问我们的用户协议页面来获知最新版本。当您继续使用本网站及相关服务,则视为您接受协议的变更,否则您应当停止访问网站及使用服务。
+      </view>
+
+      <uni-title type="h1" title="一、服务描述及定义" color="#000"></uni-title>
+      <view class="mb">
+        本网站是连接求职者与用人者的中介信息平台及职业社区。“本网站”“本平台”“门墩儿招聘招聘”:指苏州识喜识谊信息科技有限公司(简称“门墩儿”)全权所有并运营的门墩儿招聘,载体包括但不限于微信公众号、微信小程序等。
+      </view>
+      <text>“用户”:指具有完全民事行为能力的门墩儿招聘使用者。用户身份包括“用人者”、“求职者”等。</text>
+      <view class="my">“用人者”:指有用人需求,具有用人资格,在本网站上发布招聘及相关信息的企事业单位或其他组织。</view>
+      <view>“求职者”:指利用本网站进行职位浏览、投递等等行为的个人。</view>
+      <view class="my">“账号”:指用户为使用本网站及相关服务而登记注册的用户身份识别信息;用户只有注册账号才能使用本网站的服务。</view>
+
+      <view v-for="(item,index) in data" :key="index">
+        <uni-title type="h1" :title="item.title" color="#000"></uni-title>
+        <view>{{ item.describe }}</view>
+        <view class="mb">{{ item.describe1 }}</view>
+        <view v-for="(val,i) in item.subData" :key="i" class="mb">
+          <uni-title class="my" type="h2" :title="val.text" color="#000"></uni-title>
+          <view>{{ val.subText }}</view>
+          <view :class="{'my': val.content.length > 0}" v-for="(k,kIn) in val.content" :key="kIn">
+            <view :class="{'mb': k.content.length > 0, 'ml-10': k.content.length > 0}">{{ k.text }}</view>
+            <view :class="{'mb': k.subContent.length > 0}" v-for="(r,rIn) in k.subContent" :key="rIn">
+              <view :class="{'mb': k.subContent.length > 0, 'ml-10': k.subContent.length > 0}">{{ r }}</view>
+            </view>
+            <view>{{ k.endText }}</view>
+          </view>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+const data = [
+  { 
+    title: '二、成为注册用户',
+    describe: '',
+    describe1: '',
+    subData: [
+      {
+        text: '(一)用户主体适格',
+        subText: '符合下列条件之一且未曾被本网站封禁账户的自然人或者组织可以注册成为本网站用户,但本网站不保证一定能够通过注册。',
+        content: [
+          { text: '  1. 具备完全民事行为能力的自然人。' },
+          { text: '  2. 符合中华人民共和国法律、行政法规、部门规章等规范性文件要求,合法设立并存续的企业、机关、社团、事业单位和其他组织,并且具有合法用人资格及真实招聘需求(统称为“用人者”);代表用人者进行注册的自然人需具备用人者的有效授权、并保证为职务行为。' }
+        ]
+      },
+      {
+        text: '(二)用户信息完备',
+        subText: '用户需要提供明确的联系方式、通讯地址、注册者真实姓名或名称、资质文件(如企业营业执照、个人身份证)等信息方能完成注册或使用本网站的服务。为了合法、合规地为用户提供更好的服务,本网站将不时调整用户需提交的信息。如用户不同意提供上述信息,则应当停止使用本网站的服务。用户保证以上信息真实、合法、准确,并同意本网站对其注册信息资料进行审查,并就相关疑问或问题进行解答。本网站将在必要的限度内收集、使用这些信息,并会妥善保存、处理这些信息,具体规则可参见《门墩儿招聘隐私政策》',
+        content: []
+      },
+      {
+        text: '(三)禁止入驻情形',
+        subText: '',
+        content: [
+          { 
+            text: '  1. 用人者入驻本平台需经过本平台审核,用人者有以下情形的不得入驻;如入驻之后出现此等情形的,平台有权对账户进行封禁,用人者违反法律、行政法规、部门规章、地方性法规等规范性文件,本网站的各项规则和协议以及社会公序良俗的,包括但不限于以下情形:' ,
+            subContent: [
+              ' 1). 涉嫌传销、赌博、色情、暴力、邪教与封建迷信、欺诈、非法集资等违法犯罪情形的;',
+              ' 2). 违法失信企业或法定代表人为失信被执行人的企业;',
+              ' 3). 被市监、工商、公安、税务、工信、网信、人社、外管、商委、海关等行政机关查处或行政处罚,情节严重的;',
+              ' 4). 涉嫌拖欠/未按法律规定支付薪资/劳务报酬或其他可能损害劳动者合法权益的情形;',
+              ' 5). 涉嫌以虚假招聘名义或其他不诚实的方式,实现招生等目的,在平台上发布与真实职位不符或存在虚假成分的招聘信息的;',
+              ' 6). 涉及其他违反法律、行政法规、部门规章等规范性文件的,以及违反社会公序良俗的。'
+            ],
+            endText: '对于上述情形,本网站将参考相关政府部门“黑名单”、用户的举报投诉、新闻媒体曝光、用人者提供的资质文件或说明文件等,并将依据常识及社会大众通常认知水平做出是否合规的判断而不必须依据公权力机关事后通知、查处、判决等文件。'
+          },
+          { 
+            text: '  2. 本网站有权要求用人者提供相应的资质文件(包括但不限于营业执照、经营许可证等),并依据常识及社会大众通常认知水平做出是否合规的判断而不必须依据公权力机关事后通知、查处、判决等文件。对于涉及经营劳务派遣、猎头业务(用户类型明示标记为猎头的除外)、招聘业务、职业教育培训业务的用人者,基于安全性、真实性、产品政策、行业竞争、利益冲突等原因,本平台保留拒绝注册或限制使用部分功能的权利。' 
+          }
+        ]
+      }
+    ]
+  },
+  {
+    title: '三、本网站的使用',
+    describe: '用户依据协议及本网站发布的相关规则,在本站进行找工作、招聘及职业问答、课程学习等活动,享受本网站的用户福利、接受本网站的资讯信息及使用其他服务,具体形式包括但不限于上传简历、发布招聘岗位、学习职业技能课程等。用户与平台基于服务各自享有必要的权利,承担相应的义务。',
+    describe1: '如用户违反下列条款中的各项承诺或触发各类行为限制,本网站有权立即采取下列一个或多个措施:立即停止提供服务;对相关信息予以下线、删除或要求整改;封禁用户账号;要求损害赔偿等。对于用户已支付的服务费用(如有),本网站不予退还。',
+    subData: [
+      {
+        text: '(一)用户承诺',
+        subText: '',
+        content: [
+          { text: '  1. 用户承诺使用“门墩儿招聘”仅用于用户自身招聘(含招聘宣传、雇主品牌建设)、找工作、职业技能学习以及进行职业问答等用途,不得偏离网站具体功能模块之目的。' },
+          { text: '  2. 为了使用本网站的服务,用户应当提供真实、合法、准确的信息,包括但不限于姓名或名称、个人身份证明、资质证明、通讯地址、职位信息、公司介绍等(为了进行招聘/求职活动,另一方用户通过本网站的特定功能可以查看此类信息)。用户承诺使用以上内容均出于自愿并已得到有效授权,且应如实描述和及时更新。'},
+          { text: '  3. 用户应妥善设置及保管自己的账户和密码(例如设置较为复杂的密码,不将密码泄露给任何人),该账户在本网站进行的所有操作和活动(包括但不限于信息浏览、上传简历、发布招聘相关等)均视为用户自身真实的意思表示。 尤其用人者应对其在本网站下账号及子账号的实际使用人(包括但不限于用人者员工)进行严格管理,并保证实际使用人严格遵守本网站的协议与规则,实际使用人在本网站上进行的任何行为均视为用人者行为,用人者就上述行为承担全部责任。'},
+          { text: '  4. 用户承诺使用本网站服务过程中的任何行为以及发布的相关信息均应严格遵守法律、法规等规范性文件、本网站的各项规则和协议以及社会公序良俗;并确保不侵犯任何第三人的合法权益(包括但不限于知识产权、商业秘密等),在使用本网站的服务过程中将最大程度的遵守诚实信用原则。'},
+          { text: '  5. 如用户违反上述承诺,导致自身、平台或第三人遭受任何损失的,应由其自行承担所有责任。'},
+          { text: '  6. 付费用户除应遵守本协议外,还应遵守付费服务对应的合同条款的约定。'}
+        ]
+      },
+      {
+        text: '(二)用户行为限制',
+        subText: '',
+        content: [
+          { text: '  1. 用户的行为(包括但不限于使用本网站过程中的所有行为、针对本网站所进行的任何行为、利用本网站服务进行的后续行为等)不得违反法律、行政法规、部门规章、地方性法规等规范性文件,本网站的各项规则和协议以及社会公序良俗。本网站有权依据常识及社会大众通常认知水平做出是否合规的判断而不必须依据公权力机关事后通知、查处、判决等文件。' },
+          { text: '  2. 用户不得在本网站或者利用本网站的服务制作、复制、发布、传播以下信息:反对宪法所确定的基本原则的;危害国家安全,泄露国家秘密,颠覆国家政权,破坏国家统一的;损害国家荣誉和利益的;煽动民族仇恨、民族歧视、破坏民族团结的;破坏国家宗教政策,宣扬邪教和封建迷信的;散布谣言,扰乱社会秩序,破坏社会稳定的;散布淫秽、色情、赌博、暴力、凶杀、恐怖或者教唆犯罪的;侮辱或者诽谤他人,侵害他人合法权利的;涉及政治敏感内容的;含有虚假、有害、胁迫、侵害他人隐私、骚扰、侵害、中伤、粗俗、猥亵、人身攻击、或其它有悖社会公序良俗令人反感的内容;对本网站可能造成严重负面影响的内容的;含有中国法律、法规、规章、条例以及任何具有法律效力的规范所限制或禁止的其它内容的;其他本网站认为不合适的内容。'},
+          { text: '  3. 用户不得破坏网站运营秩序,不得从事与本网站服务无关的行为,不得发布除招聘信息、自身简历、职业问答所必需信息外的其他内容。'},
+          { text: '  4. 用户不得以任何形式骚扰、侮辱、伤害其他用户。'},
+          { text: '  5. 用户不得以虚假信息注册账号,不得冒用、盗用、借用他人、关联机构或社会名人的名义注册或使用账号,或以虚伪不实的方式陈述或谎称与任何人或机构有关。'},
+          { text: '  6. 用户在未经本网站同意的情况下,不得转让或授权他人使用其所有的本网站账号。'},
+          { text: '  7. 用户不得使用任何机器人软件、脚本等其他方式,自动访问或登录本网站;未经许可,不得通过其他第三方工具或运营平台如外挂、插件、第三方招聘管理系统(ATS)等,接入、访问本网站或使用本网站服务,包括但不限于通过第三方软件登录门墩儿招聘账号、发布职位、浏览职位、收发简历等。'},
+          { text: '  8. 用户不得以任何手段或形式干扰本网站服务,包括但不限于利用技术手段恶意破坏网站、干扰或破坏与本网站相连线之服务器和网络、大量发布垃圾信息等。'},
+          { text: '  9. 用户不得以任何方式对本网站(以及已有或可能发布的相关App、微信公众号、微信小程序等平台服务载体)进行反向工程;未经网站允许,不得采取任何爬虫、抓取、批量检索等手段对公开或非公开网站信息进行复制、收集。'},
+          { text: '  10. 用户不得在未经本网站和招聘/找工作行为相对方的同意下,将本网站提供的服务和/或本网站的任何信息、资料以及在招聘找工作过程中任何信息、资料进行转让、出售、传播(包括但不限于以复制、修改、转发、收集、翻译等形式制作复制品、衍生品,或使其公布于众)或其他商业、非商业用途。'}
+        ]
+      },
+      {
+        text: '(三)用人者的特别保证',
+        subText: '',
+        content: [
+          { text: '  1. 对于用人者及其关联单位(包括但不限于分公司、子公司、独立事业部等)入驻本网站并使用招聘相关服务事宜,用人者承诺并担保如下: 用人者使用门墩儿招聘服务仅限用于经合法授权的有效招聘,承诺不利用服务从事其他事宜,包括但不限于收集求职者数据;将简历用作他用;以自身招聘为名实则招收学员或办理贷款等营利性活动;使用平台服务从事公司业务经营活动;利用平台服务中聊天沟通等功能进行广告营销等情形。用人者成功入驻后,不得出现本协议“禁止入驻情形”条款中所列举的禁止情形。用人者使用本网站服务,保证严格遵守平台用户协议、平台规则及与本网站签署的一切合同文件(如有)。在使用服务期间,保证不从事任何违反平台规则的业务、不发布任何违反平台规则的信息。' },
+          { text: '  2. 如用人者违反上述承诺,本网站有权立即停止提供服务并对用人者在平台上的公司主页和/或发布的信息进行封禁,且对于用人者已支付的服务费用或保证金(如有),本网站有权不予退还;若给本网站造成任何不良影响,用人者需负责赔偿,赔偿金额不低于伍拾万元整。'}
+        ]
+      },
+      {
+        text: '(四)找工作行为',
+        subText: '',
+        content: [
+          { text: '  1. 用户(求职者)可发布找工作相关信息,通过本网站简历模块完善个人简历及相关信息,也可上传独立简历。上述行为均为用户自行操作,本网站不对用户的简历做任何审查、修改或指导。' },
+          { text: '  2. 用户投递简历、利用本网站提供的沟通工具与用人者进行沟通的,应严格遵守本用户协议第三条第(二)款项下的全部规则,简历内容、沟通行为及沟通内容应仅限于找工作相关。用户不得利用简历投递、简历曝光、在线沟通等功能进行垃圾营销、广告宣传等非求职行为。'},
+          { text: '  3. 用户找工作,应当如实提供本人基本信息以及应聘岗位相关的知识、技能、工作经历等情况,保证遵守法律、法规对于服务期、从业限制、保密等方面的规定。如前述信息不实或行为违反法律法规的,用户自行承担相应的不利后果,包括被用人者不予录用或录用后解除劳动关系等。因此给用人者、本网站或第三人造成损失的,还应承担相应的赔偿责任。'},
+          { text: '  4. 本网站将不时举办某些专项活动,用于满足用人者特殊的招聘需求和求职者的职业发展需求。用户可能需要满足一定的要求,如工作经验、技能证书等,经过本网站一定审查,方可获得参与资格。'}
+        ]
+      },
+      {
+        text: '(五)招聘行为',
+        subText: '',
+        content: [
+          { text: '  1. 用户(用人者)通过本平台审核,并依据法律法规及本协议第二条第(二)款规定提供招聘简章、营业执照或有关部门批准设立的文件、经办人身份证件、用人单位的委托证明等资料后,方可发布招聘信息、进行招聘活动。' },
+          { text: '  2. 用户在本网站指定位置(包括本网站各级页面、为用人者生成的公司主页等)发布招聘相关信息,包括但不限于职位信息、公司简介(包含公司商标、名称、Logo、文字说明等)、配图等。上述行为均为用户自行操作。'},
+          { text: '  3. 用人者发布的信息除用于招聘所需的必要信息外不得有其他内容,包括但不限于自身产品或服务的推广、销售等。'},
+          { text: '  4. 本网站认为有必要的情况下,有权对用户发布的信息做出审查、指导,并有权要求用户做出解释、更正或说明。'},
+          { text: '  5. 用户应当确保发布的招聘信息真实合法。用户招聘信息不实或违规的,用户自行承担相应的不利后果。因此给求职者、本网站或第三人造成损失的,还应承担相应的赔偿责任。涉及违法或构成刑事犯罪的,依据法律法规承担相应的责任。'},
+          { text: '  6. 用户发布招聘信息或利用本网站提供的沟通工具与求职者进行沟通的,应严格遵守本用户协议第三条第(二)款项下的全部规则,另外还不得涉及以下内容:性别、地域等各类歧视;联系方式等内容;夸张性描述、夸大性承诺或虚假宣传;职位内容重复描述、无意义描述;多个职位相似;职位要求与职位描述不一致;侮辱、诽谤、骚扰、诋毁、攻击第三人的;虚假招聘信息;与招聘无关内容;其他可能违反国家法律法规、社会公序良俗或伤害求职者权益的情形。如用户违反上述要求,本网站有权立即采取下列一个或多个措施:立即停止提供服务;对相关信息予以下线、删除或要求整改;封禁用户账号;已支付的服务费用或保证金(如有)不予退还;损害赔偿,赔偿金额不低于伍拾万元整等。职位发布详情规则可查看《门墩儿招聘职位信息发布规则》。'}
+        ]
+      },
+      {
+        text: '(六)用户间保密',
+        subText: '用户对利用本网站服务进行找工作、招聘和职业问答活动,从而获取的其他用户的任何信息,负有保密的义务。保密的程度依据法律法规、用户间的合意以及一般人的合理注意义务决定。',
+        content: []
+      }
+    ]
+  }
+]
+</script>
+
+<style scoped lang="scss">
+.wrap{
+  background-color: #f1f4f6;
+  padding: 30rpx;
+}
+.wrap-box{
+  font-size: 28rpx;
+  background-color: #fff;
+  border-radius: 12rpx;
+  padding: 20rpx;
+}
+.mb{
+  margin-bottom: 30rpx;
+}
+.my{
+  margin: 30rpx 0;
+}
+.tac{
+  text-align: center;
+}
+.ml-10{
+  margin-left: 30rpx;
+}
+.mt{
+  margin-top: 30rpx;
+}
+</style>

+ 1 - 0
pagesB/companyDetail/index.vue

@@ -197,6 +197,7 @@ const getData = async () => {
 
 // 招聘职位列表
 const getPositionList = async () => {
+  queryParams.value.enterpriseId = id.value
   const { data } = await getJobAdvertisedSearch(queryParams.value)
   const list = data?.list || []
   if (list?.length) {

+ 384 - 207
pagesB/positionDetail/index.vue

@@ -9,16 +9,17 @@
             <h2 class="JobName ellipsis">
               {{ info.name }}
             </h2>
-            <span class="salary w-600">{{ info.payFrom }}-{{ info.payTo }}/{{ positionInfo.payName }}</span>
+            <span v-if="!info.payFrom && !info.payTo" class="salary w-600">面议</span>
+            <span v-else class="salary w-600">{{ info.payFrom }}-{{ info.payTo }}/{{ positionInfo.payName }}</span>
           </view>
           <!-- 职位地区 收藏职位 -->
           <view class="d-flex justify-space-between mt-5">
             <view class="bold" style="font-size: 14px;">
               <span>
                 <span>{{positionInfo?.areaName }}</span>
-                <span class="viewider-mx">|</span>
+                <span class="viewider-mx" v-if="positionInfo?.areaName && positionInfo?.eduName">|</span>
                 <span>{{positionInfo?.eduName }}</span>
-                <span class="viewider-mx">|</span>
+                <span class="viewider-mx" v-if="positionInfo?.expName">|</span>
                 <span>{{positionInfo?.expName }}</span>
               </span>
             </view>
@@ -62,7 +63,7 @@
             </view>
             <view >
               <view class="contact-name">{{ info.contact?.name }}</view>
-              <view class="contact-info">{{ info.enterprise?.name }} · {{ info.contact?.postNameCn }}</view>
+              <view class="contact-info">{{ info.enterprise?.name }} {{ info.contact?.postNameCn ? '· ' + info.contact?.postNameCn : '' }}</view>
             </view>
           </view>
           <!-- 工作地址 -->
@@ -75,7 +76,7 @@
                 class="mr"
                 size="25"
               ></uni-icons>
-              <span style="color: var(--color-666);font-size: 15px;line-height: 26px;">{{ info.address }}</span>
+              <span style="color: var(--color-666);font-size: 15px;line-height: 26px;">{{ info.address || '暂无' }}</span>
             </view>
           </view>
         </view>
@@ -83,15 +84,35 @@
     </scroll-view>
     <!-- 分享 投递 -->
     <view class="bottom-sticky" v-if="!loading && jobId">
-      <view style="display: flex;justify-content: space-evenly;align-items: center;width: 100%;margin: 20rpx 0;">
-        <view @click="handleClickShare" style="display: flex;justify-content: center;flex-direction: column;align-items: center;">
+      <view class="bottom-content">
+        <view @click="handleClickShare" class="bottom-content-tool">
           <uni-icons type="redo-filled" size="24" color="#00897B"/>
           <span style="color:#00897B;font-weight:bold;">分享</span>
         </view>
-        <button v-if="delivery" :disabled="true" class="buttons disable">我已投递</button>
-        <button v-else class="buttons" @click="handleDelivery">我要投递</button>
+        <button class="btnStyle bgButtons" @tap="handleSend">立即沟通</button>
+        <button v-if="delivery" :disabled="true" class="buttons btnStyle disable">我已投递</button>
+        <button v-else class="buttons btnStyle" @click="handleDelivery">我要投递</button>
       </view>
     </view>
+    <uni-popup
+        ref="poster"
+        type="center"
+        class="f-straight"
+        style="position: relative;"
+    >
+        <canvas
+          :style="{
+            width:`${appInfo.windowWidth}px;`,
+            height:`${appInfo.windowHeight}px;margin-left:-9999px; margin-top:-99.5vh;`
+          }" canvas-id="firstCanvas" id="firstCanvas"></canvas>
+        <image
+          :style="{ width:`${appInfo.windowWidth}px;`, height:`${appInfo.windowHeight*.9}px;`}"
+          :src="imgSrc"
+          mode="aspectFit"
+        />
+        <!-- <uni-icons v-if="!!imgSrc" type="clear" size="35" color="#FFF" style="position: absolute;top: 10px;right: 28px;" @click="posterClose"></uni-icons> -->
+    </uni-popup>
+    
     <!-- 选择简历 -->
     <uni-popup ref="popup" background-color="#fff" :mask-click="false" >
       <view class="dialogBox" style="width: 86vw;">
@@ -141,6 +162,34 @@
 				@close="uploadPopup.close()"
       ></uni-popup-dialog>
 		</uni-popup>
+
+    <!-- 职位分享 -->
+    <uni-popup ref="sharePopup" type="share">
+      <uni-popup-share title="分享到">
+        <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> -->
+        </view>
+      </uni-popup-share>
+    </uni-popup>
   </layout-page>
 </template>
 
@@ -157,20 +206,29 @@ import {
   jobCvRelCheckSend,
   getPersonJobUnfavorite, // 取消收藏
   getPersonJobFavorite, // 收藏
-  getJobFavoriteCheck
+  getJobFavoriteCheck,
+  jobCvRelHireSend
 } from '@/api/position'
 import { getPersonResumeCv, saveResume } from '@/api/user'
 import { dealDictObjData } from '@/utils/position'
 import { getAccessToken } from '@/utils/request'
-import { onLoad } from '@dcloudio/uni-app';
+import { onLoad,onShareAppMessage,onShareTimeline } from '@dcloudio/uni-app'
+import { prologue, defaultText } from '@/hooks/useIM'
 
+const sharePopup = ref()
 const loading = ref(false)
 const loadingText = ref('加载中 . . . ')
 
-
 // 职位详情
 const info = ref({})
 const positionInfo = ref({})
+
+const isEmployment = ref(null)
+
+const imgSrc = ref('')
+const appInfo = ref({})
+const poster = ref()
+
 const getPositionDetail = async () => {
   try {
     loading.value = true
@@ -178,13 +236,16 @@ const getPositionDetail = async () => {
     info.value = data
     positionInfo.value = { ...dealDictObjData({}, info.value), ...info.value }
     loading.value = false
-    console.log('positionInfo', positionInfo.value)
   } finally {
   }
 }
 
 let jobId = ''
-onLoad((options) => {
+onLoad((options) => {  
+  // 是否是众聘
+  if (options.userId) {
+    isEmployment.value = options.userId
+  }
   jobId = options?.id || ''
   if (jobId) {
     loading.value = true
@@ -228,10 +289,13 @@ const handleDelivery = async () => {
     uni.showToast({ title: '您已投递过该职位!', icon: 'none', duration: 2000, })
     return
   }
+
   const { data } = await getPersonResumeCv()
   resumeList.value = data
   // 未上传简历
-  if (!resumeList.value?.length) return uploadPopup.value.open()
+  if (!resumeList.value?.length) {
+    return uploadPopup.value.open()
+  }
   popup.value.open()
 }
 const deliverySubmit = async (uploadFile) => {
@@ -241,7 +305,22 @@ const deliverySubmit = async (uploadFile) => {
     uni.showToast({ title: '请选择简历', icon: 'none', duration: 2000, })
     return
   }
-  await jobCvRelSend({ jobId, title: resume.title, url: resume.url, type: info.value.hire ? 1 : 0 })
+
+  if (isEmployment.value) {
+    await jobCvRelHireSend({
+      jobId,
+      url: resume.url,
+      recommendUserId: isEmployment.value
+    })
+  } else {
+    await jobCvRelSend({
+      jobId,
+      title: resume.title,
+      url: resume.url,
+      type: info.value.hire ? 1 : 0
+    })
+  }
+  
   uni.showToast({ title: '投递成功', icon: 'none', duration: 2000, })
   deliveryCheck()
   popup.value.close()
@@ -252,6 +331,35 @@ const popupClose = () => {
   popup.value.close()
 }
 
+// 发起聊天
+async function handleSend () {
+  const userId = info.value.contact.userId
+  const enterpriseId = info.value.contact.enterpriseId
+  const textObj = {
+    text: defaultText,
+    positionInfo: positionInfo.value
+  }
+  const channel = await prologue({userId, enterpriseId, text: JSON.stringify(textObj)})
+  console.log('channel', channel)
+  // 跳转
+  const query = {
+		id: userId,
+		name: info.value?.contact?.name,
+		postName: info.value?.contact?.postNameCn,
+		enterpriseName: info.value?.enterprise?.anotherName,
+		enterpriseId: info.value?.enterpriseId,
+		channelId: channel.channel_id,
+		avatar: info.value?.contact?.avatar,
+		sex: info.value?.contact?.sex,
+	}
+  console.log('query', query)
+	const queryStr = Object.keys(query).reduce((r, v) => {
+		return r += `${v}=${encodeURIComponent(query[v])}&`
+	}, '?')
+	uni.navigateTo({
+    url: `/pagesA/chart/index${queryStr.slice(0, -1)}`
+  })
+}
 
 // 效验求职者是否有收藏该职位
 const isCollection = ref(false)
@@ -308,217 +416,286 @@ const handleUpload = () => {
 
 // 分享
 const handleClickShare = () => {
-  uni.showToast({ title: '正在努力建设中 . . .', icon: 'none', duration: 2000, })
-}
-
-</script>
-
-<style scoped lang="scss">
-.mb5 { margin-bottom: 5px; }
-.my5 { margin: 5px 0; }
-.my10 { margin: 10px 0; }
-.mt10 { margin-top: 10px; }
-.fs14 { font-size: 14px; }
-.fs15 { font-size: 15px; }
-.box {
-  padding: 10px 30rpx 100px;
-}
-
-.JobName {
-  color: #37576c;
-  font-size: 24px;
-  margin-right: 30px;
-  margin-top: 1px;
-  vertical-align: middle;
-  flex: 1;
-}
-
-.tagList {
-  width: 100%;
-  display: flex;
-  flex-wrap: wrap;
-  .tagListItem {
-    margin: 10rpx 10rpx 10rpx 0;
+  sharePopup.value.open()
+}
+//在点击open-type="share"按钮后会触发以下函数,可以在函数中写需要的逻辑,当然函数的返回值必须是一个对象,用于设置分享卡片的展示形式
+//发送给微信好友  
+onShareAppMessage((res) => {
+  let path = `/pagesB/positionDetail/index?id=${info.value.id}`
+  if (info.value.hire) {
+    path += `&userId=${info.value.userId}`
   }
+  return {
+    title: '我发现了一个好职位,快来看看吧', //分享的名称
+    path
+  }
+})
+// 获取设备信息
+function getSystemInfo(){
+  return new Promise((resolve, reject) =>{
+    uni.getSystemInfo({
+        success: (result) => {
+          console.log(result)
+          resolve(result)
+        },
+        fail: (err) => {
+          console.log(err)
+        }
+    })
+  })
 }
-.topLine {
-  border-top: 1px solid #EDEDED;
-  // border-bottom: 1px solid #EDEDED;
-  padding-top: 10px;
-}
-.tag {
-  padding: 5px 10px;
-  background-color: #e2f0ef;
-  color: #00897B;
-  border-radius: 5px;
-  font-size: 14px;
-  margin-right: 8px;
-  margin-top: 4px;
-}
-.hirePrice {
-  padding: 5px 10px;
-  background-color: #fc6d5e63;
-  color: #ff250e;
-  border-radius: 5px;
-  font-size: 14px;
-  margin-left: 8px;
-  margin-top: 4px;
+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)
+            }
+        })
+    })
 }
 
-.htmlCss {
-  white-space: pre-wrap;
-  word-break: break-all;
-  line-height: 28px;
-  color: var(--color-333);
-  font-size: 15px;
-  text-align: justify;
-  letter-spacing: 0;
-}
+/**
+ * 
+ * @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 = ''
 
-//底部按钮公用定位
-.bottom-sticky{
-  display: flex;
-  width:100vw;
-  position: fixed;
-  bottom:0;
-  left: 0;
-  background-image:linear-gradient(rgba(255,255,255,1),white);
-}
-
-.buttons{
-  width: 60vw;
-  height: 44px;
-  border-radius: 25px;
-  margin: 0;
-  color: #fff !important;
-  background-color: #00897b !important;
-}
-.disable { background-color: #00897bbe !important; color: #ffffffec !important;}
+    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; // 如果没有超出宽度,更新当前行  
+        }  
+    })
 
-.noMore{
-  margin: 20px 0;
-}
+    // 绘制剩余的文本  
+    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)
 
-.date-time{
-  color:#d9d0d2;
-  float: right;
-}
+  // 地区频率时长薪资
+  // 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)
 
-.viewided-line {
-  width: 100%;
-  height: 1px;
-  background-color: #f0f2f7;
-  margin: 20px 0;
+  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()
+  })
 }
-.avatarBox{
-	max-width: 40px;
-	max-height: 40px;
-  margin: 0 10px;
-}
-.avatar{
-	width: 40px;
-	height: 40px;
-}
 
-.sub-li-bottom {
+
+</script>
+<style scoped lang="scss">
+@import '../../static/style/position/index.scss';
+.bottom-content {
   display: flex;
-  justify-content:space-between;
+  justify-content: space-evenly;
   align-items: center;
-  background: linear-gradient(90deg, #f5fcfc 0, #fcfbfa 100%);
-  font-size: 13px;
-  padding: 5px 30rpx;
-}
-
-.salary {
-  color: #fe574a;
-  line-height: 41px;
-  font-weight: 600;
-  height: auto;
-  display: inline-block;
-  vertical-align: sub;
-}
-.list-shape {
-	padding: 10px 30rpx 10px;
-  margin-top: 10px;
-  background-color: #fff;
-  .titleBox {
+  width: 100%;
+  margin: 20rpx 0;
+  .btnStyle {
+    flex: 1;
+    margin-right: 20rpx;
+  }
+  .bgButtons {
+    border: 2rpx solid #00897b;
+    color: #00897b;
+    background: #FFF;
+    border-radius: 50rpx;
+  }
+  &-tool {
+    width: 160rpx;
     display: flex;
+    justify-content: center;
+    flex-direction: column;
     align-items: center;
-    justify-content: space-between;
   }
 }
-.viewider-mx{
-	margin: 0 10rpx;
-}
-.viewider {
-	color:#e4d4d2;
-}
-
-//公司名称
-.cer-end{
-  position: absolute;
-  top: 85%;
-  right: 16%;
-}
-.cer-text{
-  text-decoration: underline;
-  margin: 0 5rpx;
-}
-//一行展示不全...
-.dis{
-	display: flex;
-	align-items: center;
-}
-.showPopup-more{
-	width: 26vw;
-	white-space: nowrap;
-	overflow: hidden;
-	text-overflow: ellipsis;
-}
-/* 列表触底暂无更多 */
-.noMore{ text-align:center; color:grey; }
-.mt { margin-top: 10rpx; }
-.mb { margin-bottom: 10rpx; }
-.ml { margin-left: 20rpx; }
-.mr { margin-right: 20rpx; }
-.mr-10{ margin-right: 10rpx; }
-.my-5{ margin: 5px 0; }
-
-
-// 选择简历
-.dialogBox {
-  .dialog-title {
+.share-pop {
+    width: 100%;
+    // height:300rpx;
     display: flex;
-    justify-content: space-between;
-    align-items: center;
-    color:#767a82;
-    padding: 20rpx;
-    .title {
-      font-weight: bold;
-      margin-left: 10rpx;
+    justify-content: center;
+    .f-straight {
+      margin: 40rpx;
+      background: unset;
+      &::after{
+        border:none !important;
+      }
     }
-  }
-  .dialog-content{
-    padding: 20rpx;
-    padding-bottom: 50rpx;
-    .selected {
-      background-color: #00897b !important;
+    .share-round {
+      border-radius:50%;
+      height:100rpx;
+      width:100rpx;
+      display: flex;
+      align-items: center;
+      justify-content: center;
     }
-  }
-  .selectOnline {
-    font-size: 14px;
-    color: #00897b;
-    text-align: center;
-    margin-top: 10rpx;
-  }
-  .dialog-bottom{
-    width: 100%;
-    height: 44px;
-    line-height: 44px;
-    text-align: center;
-    color: #fff !important;
-    background-color: #00897b !important;
+    .share-round-1 {
+        background-color:#22a039;
+    }
+    .share-round-2 {
+        background-color:#3693cd;
+    }
+}
+.preview {
+  position: fixed;
+  z-index: 9;
+  height: 100vh;
+  width: 100vw;
+  left: 0;
+  top: 0;
+  .image {
+    position: absolute;
+    width: 80%;
+    left: 10%;
+    top: 100rpx;
   }
 }
 </style>

+ 29 - 0
project.config.json

@@ -0,0 +1,29 @@
+{
+  "compileType": "miniprogram",
+  "setting": {
+    "coverView": true,
+    "es6": true,
+    "postcss": true,
+    "minified": true,
+    "enhance": true,
+    "showShadowRootInWxmlPanel": true,
+    "packNpmRelationList": [],
+    "babelSetting": {
+      "ignore": [],
+      "disablePlugins": [],
+      "outputPath": ""
+    },
+    "ignoreUploadUnusedFiles": true
+  },
+  "condition": {},
+  "editorSetting": {
+    "tabIndent": "insertSpaces",
+    "tabSize": 2
+  },
+  "libVersion": "3.5.8",
+  "packOptions": {
+    "ignore": [],
+    "include": []
+  },
+  "appid": "wx5dd538ccc752b03a"
+}

+ 7 - 0
project.private.config.json

@@ -0,0 +1,7 @@
+{
+  "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
+  "projectname": "menduner-uniapp-recruit",
+  "setting": {
+    "compileHotReLoad": true
+  }
+}

BIN
static/iconfont.ttf


+ 12 - 68
static/iconfont.wxss

@@ -11,40 +11,28 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
-.icon-ClearFilter:before {
-  content: "\e611";
-}
-
-.icon-docx-:before {
-  content: "\e646";
-}
-
-.icon-pdf-:before {
-  content: "\e649";
-}
-
-.icon-doc:before {
-  content: "\e639";
+.icon-renminbi1688:before {
+  content: "\e6cb";
 }
 
-.icon-icon-test1:before {
-  content: "\e602";
+.icon-right:before {
+  content: "\e840";
 }
 
-.icon-icon-test:before {
-  content: "\e627";
+.icon-Filter:before {
+  content: "\e605";
 }
 
-.icon-qiye1:before {
-  content: "\e640";
+.icon-map:before {
+  content: "\e6a2";
 }
 
-.icon-zhiwei:before {
-  content: "\e60a";
+.icon-pin:before {
+  content: "\e647";
 }
 
-.icon-zhiweisvg:before {
-  content: "\eac0";
+.icon-ClearFilter:before {
+  content: "\e611";
 }
 
 .icon-dunpai1:before {
@@ -71,47 +59,3 @@
   content: "\e621";
 }
 
-.icon-wode:before {
-  content: "\e60f";
-}
-
-.icon-xiaoxi:before {
-  content: "\e62c";
-}
-
-.icon-sousuo:before {
-  content: "\e653";
-}
-
-.icon-jianli:before {
-  content: "\e6b2";
-}
-
-.icon-jianli1:before {
-  content: "\e608";
-}
-
-.icon-xiaoxi1:before {
-  content: "\e614";
-}
-
-.icon-qiye:before {
-  content: "\e91d";
-}
-
-.icon-sousuo1:before {
-  content: "\f8b0";
-}
-
-.icon-icon-myself-1:before {
-  content: "\e601";
-}
-
-.icon-shouye-:before {
-  content: "\e629";
-}
-
-.icon-shouye:before {
-  content: "\e6fa";
-}
-

BIN
static/img/welfare.png


+ 261 - 0
static/style/position/index.css

@@ -0,0 +1,261 @@
+@charset "UTF-8";
+.mb5 {
+  margin-bottom: 5px;
+}
+
+.my5 {
+  margin: 5px 0;
+}
+
+.my10 {
+  margin: 10px 0;
+}
+
+.mt10 {
+  margin-top: 10px;
+}
+
+.fs14 {
+  font-size: 14px;
+}
+
+.fs15 {
+  font-size: 15px;
+}
+
+.box {
+  padding: 10px 30rpx 100px;
+}
+
+.JobName {
+  color: #37576c;
+  font-size: 24px;
+  margin-right: 30px;
+  margin-top: 1px;
+  vertical-align: middle;
+  flex: 1;
+}
+
+.tagList {
+  width: 100%;
+  display: flex;
+  flex-wrap: wrap;
+}
+
+.tagList .tagListItem {
+  margin: 10rpx 10rpx 10rpx 0;
+}
+
+.topLine {
+  border-top: 1px solid #EDEDED;
+  padding-top: 10px;
+}
+
+.tag {
+  padding: 5px 10px;
+  background-color: #e2f0ef;
+  color: #00897B;
+  border-radius: 5px;
+  font-size: 14px;
+  margin-right: 8px;
+  margin-top: 4px;
+}
+
+.hirePrice {
+  padding: 5px 10px;
+  background-color: #fc6d5e63;
+  color: #ff250e;
+  border-radius: 5px;
+  font-size: 14px;
+  margin-left: 8px;
+  margin-top: 4px;
+}
+
+.htmlCss {
+  white-space: pre-wrap;
+  word-break: break-all;
+  line-height: 28px;
+  color: var(--color-333);
+  font-size: 15px;
+  text-align: justify;
+  letter-spacing: 0;
+}
+
+.bottom-sticky {
+  display: flex;
+  width: 100vw;
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  background-image: linear-gradient(white, white);
+}
+
+.buttons {
+  width: 60vw;
+  height: 44px;
+  border-radius: 25px;
+  margin: 0;
+  color: #fff !important;
+  background-color: #00897b !important;
+}
+
+.disable {
+  background-color: #41a79d !important;
+  color: #ffffec !important;
+}
+
+.noMore {
+  margin: 20px 0;
+}
+
+.date-time {
+  color: #d9d0d2;
+  float: right;
+}
+
+.viewided-line {
+  width: 100%;
+  height: 1px;
+  background-color: #f0f2f7;
+  margin: 20px 0;
+}
+
+.avatarBox {
+  max-width: 40px;
+  max-height: 40px;
+  margin: 0 10px;
+}
+
+.avatar {
+  width: 40px;
+  height: 40px;
+}
+
+.sub-li-bottom {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  background: linear-gradient(90deg, #f5fcfc 0, #fcfbfa 100%);
+  font-size: 13px;
+  padding: 5px 30rpx;
+}
+
+.salary {
+  color: #fe574a;
+  line-height: 41px;
+  font-weight: 600;
+  height: auto;
+  display: inline-block;
+  vertical-align: sub;
+}
+
+.list-shape {
+  padding: 10px 30rpx 10px;
+  margin-top: 10px;
+  background-color: #fff;
+}
+
+.list-shape .titleBox {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.viewider-mx {
+  margin: 0 10rpx;
+}
+
+.viewider {
+  color: #e4d4d2;
+}
+
+.cer-end {
+  position: absolute;
+  top: 85%;
+  right: 16%;
+}
+
+.cer-text {
+  text-decoration: underline;
+  margin: 0 5rpx;
+}
+
+.dis {
+  display: flex;
+  align-items: center;
+}
+
+.showPopup-more {
+  width: 26vw;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+/* 列表触底暂无更多 */
+.noMore {
+  text-align: center;
+  color: grey;
+}
+
+.mt {
+  margin-top: 10rpx;
+}
+
+.mb {
+  margin-bottom: 10rpx;
+}
+
+.ml {
+  margin-left: 20rpx;
+}
+
+.mr {
+  margin-right: 20rpx;
+}
+
+.mr-10 {
+  margin-right: 10rpx;
+}
+
+.my-5 {
+  margin: 5px 0;
+}
+
+.dialogBox .dialog-title {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  color: #767a82;
+  padding: 20rpx;
+}
+
+.dialogBox .dialog-title .title {
+  font-weight: bold;
+  margin-left: 10rpx;
+}
+
+.dialogBox .dialog-content {
+  padding: 20rpx;
+  padding-bottom: 50rpx;
+}
+
+.dialogBox .dialog-content .selected {
+  background-color: #00897b !important;
+}
+
+.dialogBox .selectOnline {
+  font-size: 14px;
+  color: #00897b;
+  text-align: center;
+  margin-top: 10rpx;
+}
+
+.dialogBox .dialog-bottom {
+  width: 100%;
+  height: 44px;
+  line-height: 44px;
+  text-align: center;
+  color: #fff !important;
+  background-color: #00897b !important;
+}

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/style/position/index.min.css


+ 207 - 0
static/style/position/index.scss

@@ -0,0 +1,207 @@
+.mb5 { margin-bottom: 5px; }
+.my5 { margin: 5px 0; }
+.my10 { margin: 10px 0; }
+.mt10 { margin-top: 10px; }
+.fs14 { font-size: 14px; }
+.fs15 { font-size: 15px; }
+.box {
+  padding: 10px 30rpx 100px;
+}
+
+.JobName {
+  color: #37576c;
+  font-size: 24px;
+  margin-right: 30px;
+  margin-top: 1px;
+  vertical-align: middle;
+  flex: 1;
+}
+
+.tagList {
+  width: 100%;
+  display: flex;
+  flex-wrap: wrap;
+  .tagListItem {
+    margin: 10rpx 10rpx 10rpx 0;
+  }
+}
+.topLine {
+  border-top: 1px solid #EDEDED;
+  // border-bottom: 1px solid #EDEDED;
+  padding-top: 10px;
+}
+.tag {
+  padding: 5px 10px;
+  background-color: #e2f0ef;
+  color: #00897B;
+  border-radius: 5px;
+  font-size: 14px;
+  margin-right: 8px;
+  margin-top: 4px;
+}
+.hirePrice {
+  padding: 5px 10px;
+  background-color: #fc6d5e63;
+  color: #ff250e;
+  border-radius: 5px;
+  font-size: 14px;
+  margin-left: 8px;
+  margin-top: 4px;
+}
+
+.htmlCss {
+  white-space: pre-wrap;
+  word-break: break-all;
+  line-height: 28px;
+  color: var(--color-333);
+  font-size: 15px;
+  text-align: justify;
+  letter-spacing: 0;
+}
+
+//底部按钮公用定位
+.bottom-sticky{
+  display: flex;
+  width:100vw;
+  position: fixed;
+  bottom:0;
+  left: 0;
+  background-image:linear-gradient(rgba(255,255,255,1),white);
+}
+
+.buttons{
+  width: 60vw;
+  height: 44px;
+  border-radius: 25px;
+  margin: 0;
+  color: #fff !important;
+  background-color: #00897b !important;
+}
+.disable { background-color: #41a79d !important; color: #ffffec !important;}
+
+.noMore{
+  margin: 20px 0;
+}
+
+.date-time{
+  color:#d9d0d2;
+  float: right;
+}
+
+.viewided-line {
+  width: 100%;
+  height: 1px;
+  background-color: #f0f2f7;
+  margin: 20px 0;
+
+}
+.avatarBox{
+	max-width: 40px;
+	max-height: 40px;
+  margin: 0 10px;
+}
+.avatar{
+	width: 40px;
+	height: 40px;
+}
+
+.sub-li-bottom {
+  display: flex;
+  justify-content:space-between;
+  align-items: center;
+  background: linear-gradient(90deg, #f5fcfc 0, #fcfbfa 100%);
+  font-size: 13px;
+  padding: 5px 30rpx;
+}
+
+.salary {
+  color: #fe574a;
+  line-height: 41px;
+  font-weight: 600;
+  height: auto;
+  display: inline-block;
+  vertical-align: sub;
+}
+.list-shape {
+	padding: 10px 30rpx 10px;
+  margin-top: 10px;
+  background-color: #fff;
+  .titleBox {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+  }
+}
+.viewider-mx{
+	margin: 0 10rpx;
+}
+.viewider {
+	color:#e4d4d2;
+}
+
+//公司名称
+.cer-end{
+  position: absolute;
+  top: 85%;
+  right: 16%;
+}
+.cer-text{
+  text-decoration: underline;
+  margin: 0 5rpx;
+}
+//一行展示不全...
+.dis{
+	display: flex;
+	align-items: center;
+}
+.showPopup-more{
+	width: 26vw;
+	white-space: nowrap;
+	overflow: hidden;
+	text-overflow: ellipsis;
+}
+/* 列表触底暂无更多 */
+.noMore{ text-align:center; color:grey; }
+.mt { margin-top: 10rpx; }
+.mb { margin-bottom: 10rpx; }
+.ml { margin-left: 20rpx; }
+.mr { margin-right: 20rpx; }
+.mr-10{ margin-right: 10rpx; }
+.my-5{ margin: 5px 0; }
+
+
+// 选择简历
+.dialogBox {
+  .dialog-title {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    color:#767a82;
+    padding: 20rpx;
+    .title {
+      font-weight: bold;
+      margin-left: 10rpx;
+    }
+  }
+  .dialog-content{
+    padding: 20rpx;
+    padding-bottom: 50rpx;
+    .selected {
+      background-color: #00897b !important;
+    }
+  }
+  .selectOnline {
+    font-size: 14px;
+    color: #00897b;
+    text-align: center;
+    margin-top: 10rpx;
+  }
+  .dialog-bottom{
+    width: 100%;
+    height: 44px;
+    line-height: 44px;
+    text-align: center;
+    color: #fff !important;
+    background-color: #00897b !important;
+  }
+}

+ 40 - 0
store/im.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { ref } from 'vue'
+
+export const useIMStore = defineStore('IM', 
+  () => {
+    const uid = ref('')
+    const connected = ref(false)
+
+    const newMsg = ref(0)
+    const fromChannel = ref('')
+    const unreadCount = ref(0)
+
+    const setConnected = (val) => {
+      connected.value = val
+    }
+    const setUid = (val) => {
+      uid.value = val
+    }
+    const setNewMsg = (val) => {
+      newMsg.value = val
+    }
+    const setFromChannel = (val) => {
+      fromChannel.value = val
+    }
+    const setUnreadCount = (val) => {
+      unreadCount.value = val
+    }
+    return {
+      connected,
+      uid,
+      newMsg,
+      fromChannel,
+      unreadCount,
+      setUnreadCount,
+      setFromChannel,
+      setNewMsg,
+      setUid,
+      setConnected
+    }
+})

+ 6 - 0
store/user.js

@@ -34,11 +34,15 @@ export const userStore = defineStore({
     baseInfo: {}, // 用户信息
     userInfo: {},
     isLogin: !!uni.getStorageSync('token'), // 登录状态
+    refreshToken: uni.getStorageSync('refresh-token'), // 用户切换
     lastUpdateTime: 0, // 上次更新时间
     accountInfo: cloneDeep(defaultAccountInfo), // 账号信息
   }),
 
   actions: {
+    setLogin (val) {
+      this.isLogin = val
+    },
     // 登录
     async handleSmsLogin (query, type, route) {
       const api = type ? smsLogin : passwordLogin
@@ -89,11 +93,13 @@ export const userStore = defineStore({
     setToken(token = '', refreshToken = '') {
       if (token === '') {
         this.isLogin = false;
+        this.refreshToken = ''
         uni.removeStorageSync('token');
         uni.removeStorageSync('refresh-token');
       } else {
         this.isLogin = true;
         uni.setStorageSync('token', token);
+        this.refreshToken = refreshToken
         uni.setStorageSync('refresh-token', refreshToken);
         this.loginAfter();
       }

+ 31 - 23
uni_modules/uni-popup/components/uni-popup-share/uni-popup-share.vue

@@ -3,10 +3,11 @@
 		<view class="uni-share-title"><text class="uni-share-title-text">{{shareTitleText}}</text></view>
 		<view class="uni-share-content">
 			<view class="uni-share-content-box">
-				<view class="uni-share-content-item" v-for="(item,index) in bottomData" :key="index" @click.stop="select(item,index)">
+				<slot></slot>
+				<!-- <view class="uni-share-content-item" v-for="(item,index) in bottomData" :key="index" @click.stop="select(item,index)">
 					<image class="uni-share-image" :src="item.icon" mode="aspectFill"></image>
 					<text class="uni-share-text">{{item.text}}</text>
-				</view>
+				</view> -->
 
 			</view>
 		</view>
@@ -39,26 +40,32 @@
 		},
 		data() {
 			return {
-				bottomData: [{
-						text: '微信',
-						icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/c2b17470-50be-11eb-b680-7980c8a877b8.png',
-						name: 'wx'
-					},
-					{
-						text: '支付宝',
-						icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/d684ae40-50be-11eb-8ff1-d5dcf8779628.png',
-						name: 'ali'
-					},
-					{
-						text: 'QQ',
-						icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/e7a79520-50be-11eb-b997-9918a5dda011.png',
-						name: 'qq'
-					},
-					{
-						text: '新浪',
-						icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/0dacdbe0-50bf-11eb-8ff1-d5dcf8779628.png',
-						name: 'sina'
-					},
+				bottomData: [
+					// {
+					// 	text: '微信',
+					// 	icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/c2b17470-50be-11eb-b680-7980c8a877b8.png',
+					// 	name: 'wx'
+					// },
+					// {
+					// 	text: '生成海报',
+					// 	icon: 'https://minio.citupro.com/dev/menduner/down-icon.png',
+					// 	name: 'poster'
+					// }
+					// {
+					// 	text: '支付宝',
+					// 	icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/d684ae40-50be-11eb-8ff1-d5dcf8779628.png',
+					// 	name: 'ali'
+					// },
+					// {
+					// 	text: 'QQ',
+					// 	icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/e7a79520-50be-11eb-b997-9918a5dda011.png',
+					// 	name: 'qq'
+					// },
+					// {
+					// 	text: '新浪',
+					// 	icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/0dacdbe0-50bf-11eb-8ff1-d5dcf8779628.png',
+					// 	name: 'sina'
+					// },
 					// {
 					// 	text: '百度',
 					// 	icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/1ec6e920-50bf-11eb-8a36-ebb87efcf8c0.png',
@@ -134,10 +141,11 @@
 	.uni-share-content-box {
 		/* #ifndef APP-NVUE */
 		display: flex;
+		justify-content: space-around;
 		/* #endif */
 		flex-direction: row;
 		flex-wrap: wrap;
-		width: 360px;
+		width: 100%;
 	}
 
 	.uni-share-content-item {

+ 46 - 6
unpackage/dist/cache/.vite/deps/_metadata.json

@@ -1,15 +1,55 @@
 {
-  "hash": "62488134",
-  "configHash": "fe45daba",
-  "lockfileHash": "b09eb1c5",
-  "browserHash": "a43a12fe",
+  "hash": "47dcb4f1",
+  "configHash": "06645ce3",
+  "lockfileHash": "8c101f21",
+  "browserHash": "f0bed5f8",
   "optimized": {
     "pinia-plugin-persistedstate": {
       "src": "../../../../../node_modules/pinia-plugin-persistedstate/dist/index.js",
       "file": "pinia-plugin-persistedstate.js",
-      "fileHash": "d7221025",
+      "fileHash": "de8dc243",
+      "needsInterop": false
+    },
+    "luch-request": {
+      "src": "../../../../../node_modules/luch-request/src/lib/luch-request.js",
+      "file": "luch-request.js",
+      "fileHash": "c5810002",
+      "needsInterop": false
+    },
+    "qs": {
+      "src": "../../../../../node_modules/qs/lib/index.js",
+      "file": "qs.js",
+      "fileHash": "e4fd86e6",
+      "needsInterop": true
+    },
+    "lodash-es": {
+      "src": "../../../../../node_modules/lodash-es/lodash.js",
+      "file": "lodash-es.js",
+      "fileHash": "cc0e5b4c",
+      "needsInterop": false
+    },
+    "dayjs": {
+      "src": "../../../../../node_modules/dayjs/dayjs.min.js",
+      "file": "dayjs.js",
+      "fileHash": "faa24560",
+      "needsInterop": true
+    },
+    "js-base64": {
+      "src": "../../../../../node_modules/js-base64/base64.mjs",
+      "file": "js-base64.js",
+      "fileHash": "9be10531",
+      "needsInterop": false
+    },
+    "wukongimjssdk": {
+      "src": "../../../../../node_modules/wukongimjssdk/lib/wukongimjssdk.esm.js",
+      "file": "wukongimjssdk.js",
+      "fileHash": "1f9bb9cc",
       "needsInterop": false
     }
   },
-  "chunks": {}
+  "chunks": {
+    "chunk-BQWMX7FD": {
+      "file": "chunk-BQWMX7FD.js"
+    }
+  }
 }

+ 8 - 4
utils/avatar.js

@@ -3,8 +3,12 @@ const female = 'https://minio.citupro.com/dev/menduner/7.png'
 
 // 根据性别返回默认头像
 export const getUserAvatar = (avatar, sex) => {
-  if (avatar) return avatar
-  if (!sex) return female
-  if (sex === '1') return male
-  else return female
+  if (avatar) {
+    return avatar
+  }
+  // if (!sex) return female
+  if (sex === '1') {
+    return male
+  }
+  return female
 }

+ 3 - 1
utils/request.js

@@ -296,11 +296,13 @@ const handleAuthorized = () => {
 		title:'请先登录',
 		icon: 'none'
 	})
+	const msg = useUserStore.isLogin ? '您的登陆已过期' : '请先登录'
+	useUserStore.setLogin(false)
 	showAuthModal()
   // 登录超时
   return Promise.reject({
     code: 401,
-    msg: useUserStore.isLogin ? '您的登陆已过期' : '请先登录'
+    msg
   })
 }
 

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels