Sfoglia il codice sorgente

门墩儿猎头-全员猎寻-佣金配置

Xiao_123 5 mesi fa
parent
commit
5ac205c88d

+ 81 - 0
src/store/modules/mall/kefu.ts

@@ -0,0 +1,81 @@
+import { store } from '@/store'
+import { defineStore } from 'pinia'
+import { KeFuConversationApi, KeFuConversationRespVO } from '@/api/mall/promotion/kefu/conversation'
+import { KeFuMessageRespVO } from '@/api/mall/promotion/kefu/message'
+import { isEmpty } from '@/utils/is'
+
+interface MallKefuInfoVO {
+  conversationList: KeFuConversationRespVO[] // 会话列表
+  conversationMessageList: Map<number, KeFuMessageRespVO[]> // 会话消息
+}
+
+export const useMallKefuStore = defineStore('mall-kefu', {
+  state: (): MallKefuInfoVO => ({
+    conversationList: [],
+    conversationMessageList: new Map<number, KeFuMessageRespVO[]>() // key 会话,value 会话消息列表
+  }),
+  getters: {
+    getConversationList(): KeFuConversationRespVO[] {
+      return this.conversationList
+    },
+    getConversationMessageList(): (conversationId: number) => KeFuMessageRespVO[] | undefined {
+      return (conversationId: number) => this.conversationMessageList.get(conversationId)
+    }
+  },
+  actions: {
+    // ======================= 会话消息相关 =======================
+    /** 缓存历史消息 */
+    saveMessageList(conversationId: number, messageList: KeFuMessageRespVO[]) {
+      this.conversationMessageList.set(conversationId, messageList)
+    },
+
+    // ======================= 会话相关 =======================
+    /** 加载会话缓存列表 */
+    async setConversationList() {
+      this.conversationList = await KeFuConversationApi.getConversationList()
+      this.conversationSort()
+    },
+    /** 更新会话缓存已读 */
+    async updateConversationStatus(conversationId: number) {
+      if (isEmpty(this.conversationList)) {
+        return
+      }
+      const conversation = this.conversationList.find((item) => item.id === conversationId)
+      conversation && (conversation.adminUnreadMessageCount = 0)
+    },
+    /** 更新会话缓存 */
+    async updateConversation(conversationId: number) {
+      if (isEmpty(this.conversationList)) {
+        return
+      }
+
+      const conversation = await KeFuConversationApi.getConversation(conversationId)
+      this.deleteConversation(conversationId)
+      conversation && this.conversationList.push(conversation)
+      this.conversationSort()
+    },
+    /** 删除会话缓存 */
+    deleteConversation(conversationId: number) {
+      const index = this.conversationList.findIndex((item) => item.id === conversationId)
+      // 存在则删除
+      if (index > -1) {
+        this.conversationList.splice(index, 1)
+      }
+    },
+    conversationSort() {
+      // 按置顶属性和最后消息时间排序
+      this.conversationList.sort((a, b) => {
+        // 按照置顶排序,置顶的会在前面
+        if (a.adminPinned !== b.adminPinned) {
+          return a.adminPinned ? -1 : 1
+        }
+        // 按照最后消息时间排序,最近的会在前面
+        return (b.lastMessageTime as unknown as number) - (a.lastMessageTime as unknown as number)
+      })
+    }
+  }
+})
+
+export const useMallKefuStoreWithOut = () => {
+  return useMallKefuStore(store)
+}

+ 15 - 1
src/utils/index.ts

@@ -313,7 +313,7 @@ export const fenToYuan = (price: string | number): string => {
  */
 export const calculateRelativeRate = (value?: number, reference?: number) => {
   // 防止除0
-  if (!reference) return 0
+  if (!reference || reference == 0) return 0
 
   return ((100 * ((value || 0) - reference)) / reference).toFixed(0)
 }
@@ -435,3 +435,17 @@ export const areaReplace = (areaName: string) => {
     .replace('自治区', '')
     .replace('省', '')
 }
+
+/**
+ * 解析 JSON 字符串
+ *
+ * @param str
+ */
+export function jsonParse(str: string) {
+  try {
+    return JSON.parse(str)
+  } catch (e) {
+    console.error(`str[${str}] 不是一个 JSON 字符串`)
+    return ''
+  }
+}

+ 6 - 6
src/views/mall/promotion/kefu/index.vue

@@ -102,11 +102,11 @@ onBeforeUnmount(() => {
 <style lang="scss">
 .kefu-layout {
   position: absolute;
-  flex: 1;
   top: 0;
   left: 0;
-  height: 100%;
   width: 100%;
+  height: 100%;
+  flex: 1;
 }
 
 /* 定义滚动条样式 */
@@ -117,15 +117,15 @@ onBeforeUnmount(() => {
 
 /* 定义滚动条轨道 内阴影+圆角 */
 ::-webkit-scrollbar-track {
-  box-shadow: inset 0 0 0 rgba(240, 240, 240, 0.5);
-  border-radius: 10px;
   background-color: #fff;
+  border-radius: 10px;
+  box-shadow: inset 0 0 0 rgb(240 240 240 / 50%);
 }
 
 /* 定义滑块 内阴影+圆角 */
 ::-webkit-scrollbar-thumb {
+  background-color: rgb(240 240 240 / 50%);
   border-radius: 10px;
-  box-shadow: inset 0 0 0 rgba(240, 240, 240, 0.5);
-  background-color: rgba(240, 240, 240, 0.5);
+  box-shadow: inset 0 0 0 rgb(240 240 240 / 50%);
 }
 </style>

+ 119 - 0
src/views/menduner/system/job/RadioForm.vue

@@ -0,0 +1,119 @@
+<template>
+  <Dialog title="佣金比例配置" v-model="dialogVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="170px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="猎头佣金比例" prop="headhuntRate">
+        <el-input-number v-model="formData.headhuntRate" :min="0" :precision="2" />
+      </el-form-item>
+      <el-form-item label="推荐人佣金比例" prop="recommendRate">
+        <el-input-number v-model="formData.recommendRate" :min="0" :precision="2" />
+      </el-form-item>
+      <el-form-item label="投递人佣金比例" prop="cvRate">
+        <el-input-number v-model="formData.cvRate" :min="0" :precision="2" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+
+<script setup lang="ts">
+import * as CommissionRatioApi from '@/api/menduner/system/hire/commissionRatio/index'
+
+defineOptions({ name: 'JobRadioForm' })
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formData = ref({
+  id: null,
+  headhuntRate: 0,
+  recommendRate: 0,
+  cvRate: 0
+})
+
+var checkHeadhuntRate = (rule, value, callback) => {
+  if (!value) return callback(new Error('猎头佣金比例不能空'))
+  if (value >= 0 && value <= 100) {
+    if ((value + formData.value.cvRate + formData.value.recommendRate) !== 100) return callback(new Error('三者加起来需等于100%'))
+    callback()
+  }
+  else callback(new Error('请输入0-100之间的数值'))
+}
+
+var checkRecommendRate = (rule, value, callback) => {
+  if (!value) return callback(new Error('推荐人佣金比例不能空'))
+  if (value >= 0 && value <= 100) {
+    if ((value + formData.value.headhuntRate + formData.value.cvRate) !== 100) return callback(new Error('三者加起来需等于100%'))
+    callback()
+  }
+  else callback(new Error('请输入0-100之间的数值'))
+}
+
+var checkCvRate = (rule, value, callback) => {
+  if (!value) return callback(new Error('投递人佣金比例不能空'))
+  if (value >= 0 && value <= 100) {
+    if ((value + formData.value.headhuntRate + formData.value.recommendRate) !== 100) return callback(new Error('三者加起来需等于100%'))
+    callback()
+  }
+  else callback(new Error('请输入0-100之间的数值'))
+}
+const formRules = reactive({
+  headhuntRate: [{ validator: checkHeadhuntRate, trigger: 'change', required: true }],
+  recommendRate: [{ validator: checkRecommendRate, trigger: 'change', required: true }],
+  cvRate: [{ validator: checkCvRate, trigger: 'change', required: true }]
+})
+const formRef = ref() // 表单 Ref
+
+
+/** 打开弹窗 */
+const open = async () => {
+  dialogVisible.value = true
+  resetForm()
+  // 修改时,设置数据
+  formLoading.value = true
+  try {
+    formData.value = await CommissionRatioApi.getCommissionRatio()
+  } finally {
+    formLoading.value = false
+  }
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  await formRef.value.validate()
+  // 提交请求
+  formLoading.value = true
+  try {
+    await CommissionRatioApi.saveCommissionRatio(formData.value)
+    message.success(t('common.updateSuccess'))
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: null,
+    headhuntRate: 0,
+    recommendRate: 0,
+    cvRate: 0
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 10 - 0
src/views/menduner/system/job/index.vue

@@ -97,6 +97,7 @@
       <el-form-item>
         <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
         <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button @click="handleSetting" type="primary" plain><Icon icon="ep:setting" class="mr-5px" /> 佣金配置</el-button>
         <el-button
           type="primary"
           plain
@@ -174,6 +175,8 @@
 
   <!-- 表单弹窗:添加/修改 -->
   <JobAdvertisedForm ref="formRef" :area="areaTreeData" :position="positionTreeData" @success="getList" />
+
+  <JobRadioForm ref="radioRef" @success="getList" />
 </template>
 
 <script setup lang="ts">
@@ -181,6 +184,7 @@ import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import { JobAdvertisedApi, JobAdvertisedVO } from '@/api/menduner/system/job'
 import JobAdvertisedForm from './JobAdvertisedForm.vue'
+import JobRadioForm from './RadioForm.vue'
 import { dealDictArrayData } from '@/utils/transform/position'
 import { getDict } from '@/hooks/web/useDictionaries'
 import { EnterpriseApi } from '@/api/menduner/system/enterprise/message'
@@ -259,6 +263,12 @@ const resetQuery = () => {
   handleQuery()
 }
 
+// 佣金配置
+const radioRef = ref()
+const handleSetting = () => {
+  radioRef.value.open()
+}
+
 /** 添加/修改操作 */
 const formRef = ref()
 const openForm = (type: string, id?: number) => {