Xiao_123 11 mesi fa
parent
commit
a056e0bad7

+ 1 - 0
components.d.ts

@@ -9,6 +9,7 @@ declare module 'vue' {
   export interface GlobalComponents {
     AreaSelect: typeof import('./src/components/AreaSelect/index.vue')['default']
     Autocomplete: typeof import('./src/components/FormUI/autocomplete/index.vue')['default']
+    Combobox: typeof import('./src/components/FormUI/combobox/index.vue')['default']
     copy: typeof import('./src/components/CtForm/index copy.vue')['default']
     CtBtn: typeof import('./src/components/CtVuetify/CtBtn/index.vue')['default']
     CtDialog: typeof import('./src/components/CtDialog/index.vue')['default']

+ 16 - 0
src/api/resume.js

@@ -124,4 +124,20 @@ export const getResumeJobInterested = async () => {
   return await request.get({
     url: '/app-api/menduner/system/person/resume/get/job/interested'
   })
+}
+
+// 根据专业名称模糊搜索
+export const schoolMajorByName = async (params) => {
+  return await request.get({
+    url: '/app-api/menduner/system/major/search/by/name',
+    params
+  })
+}
+
+// 根据学校名称模糊搜索
+export const schoolSearchByName = async (params) => {
+  return await request.get({
+    url: '/app-api/menduner/system/school/search/by/name',
+    params
+  })
 }

+ 8 - 0
src/components/CtForm/index.vue

@@ -22,6 +22,13 @@
                 :item="item"
                 @change="handleChange(item)"
               ></autocompleteUI>
+              <comboboxUI
+                v-if="item.type === 'combobox'"
+                v-model="item.value"
+                :item="item"
+                @search="val => item.search ? item.search(val) : null"
+                @change="handleChange(item)"
+              ></comboboxUI>
               <radioGroupUI
                 v-if="item.type === 'ifRadio'"
                 v-model="item.value"
@@ -55,6 +62,7 @@
 defineOptions({ name:'components-ct-form'})
 import textUI from './../FormUI/TextInput'
 import autocompleteUI from './../FormUI/autocomplete'
+import comboboxUI from './../FormUI/combobox'
 import radioGroupUI from './../FormUI/radioGroup'
 import textareaUI from './../FormUI/textArea'
 import datePicker from '../FormUI/datePicker'

+ 41 - 0
src/components/FormUI/combobox/index.vue

@@ -0,0 +1,41 @@
+<template>
+  <div :style="{ width: item.width ? item.width + 'px' : '100%' }">
+    <v-combobox
+      v-model="value"
+      :rules="item.rules"
+      :attach="!item.noAttach"
+      :label="item.label"
+      :placeholder="item.placeholder || item.label"
+      :item-title="item.itemText || 'label'"
+      :item-value="item.itemValue || 'value'"
+      :items="item.items"
+      variant="outlined"
+      :density="item.dense || 'compact'"
+      :clearable="item.clearable"
+      :disabled="item.disabled"
+      :return-object="item.returnObject || false"
+      @update:search="search"
+      @update:modelValue="modelValueUpDate"
+    ></v-combobox>
+  </div>
+</template>
+<script setup>
+import { ref, defineEmits } from 'vue';
+defineOptions({ name:'FormUI-v-combobox'})
+
+const props = defineProps({item: Object, modelValue: [String, Number]})
+const emit = defineEmits(['update:modelValue', 'change', 'search'])
+const item = props.item
+const value = ref(props.modelValue)
+const modelValueUpDate = (val) => {
+  value.value = val
+  emit('update:modelValue', value.value)
+  // let obj = null // if (item.getObject) obj = item.items.find(e => e[item.itemValue] === value.value) || null
+  emit('change', value.value)
+}
+const search = (val) => {
+  emit('search', val)
+}
+</script>
+<style lang="scss" scoped>
+</style>

+ 7 - 0
src/utils/getText.js

@@ -0,0 +1,7 @@
+export const getText = (value, arr, itemText = 'label', itemValue = 'value') => { // 一维数组
+  // console.log('getText', value, arr)
+  if (!arr?.length || !(value && value !==0)) return
+  const item = arr.find(e => e[itemValue] === value)
+  if (!item) return
+  return item[itemText]
+}

+ 68 - 33
src/views/resume/components/educationExp.vue

@@ -24,9 +24,9 @@
           <div>
             <span style="font-size: 18px; font-weight: bold;">{{ item.schoolName }}</span>
             <span class="color6 font15 ml-5">
-              <span>{{ item.eduStartText }}</span>
+              <span>{{ timesTampChange(item.startTime).slice(0, 10) }}</span>
               <span class="mx-1">至</span>
-              <span>{{ item.eduEndText }}</span>
+              <span>{{ timesTampChange(item.endTime).slice(0, 10) }}</span>
             </span>
           </div>
           <div v-if="item.active">
@@ -36,8 +36,9 @@
         </div>
         <div class="level2 my-2">
           <span class="color6 font15">{{ item.major }}</span>
-          <span class="vline"  v-if="item.educationTypeText"></span>
-          <span class="color6 font15">{{ item.educationTypeText }}</span>
+          <span class="vline"  v-if="item.educationSystemType"></span>
+          <!-- <span class="color6 font15">{{ getText(item.educationSystemType, dictItemsObj.educationType) }}</span> -->
+          <span class="color6 font15">{{ getText(item.educationSystemType, dictItemsObj.educationSystemType) }}</span>
         </div>
         <div class="level3">
           <span class="color6 font15">在校经历:</span>
@@ -53,36 +54,72 @@ import CtForm from '@/components/CtForm'
 import Snackbar from '@/plugins/snackbar'
 import { getDict } from '@/hooks/web/useDictionaries'
 import { getTimeStamp, timesTampChange } from '@/utils/date'
-import { saveResumeEduExp, getResumeEduExp, deleteResumeEduExp } from '@/api/resume'
+import { saveResumeEduExp, getResumeEduExp, deleteResumeEduExp, schoolSearchByName, schoolMajorByName } from '@/api/resume'
 import Confirm from '@/plugins/confirm'
-import { nextTick, ref } from 'vue'
-import { shallowRef } from 'vue'
+import { getText } from '@/utils/getText'
+import { debounce } from 'lodash'
+import { nextTick, shallowRef, reactive, ref } from 'vue'
 const CtFormRef = ref()
+const dictItemsObj = reactive({})
+dictItemsObj.educationSystemType = [{ label: '全日制', value: '0' }, { label: '非全日制', value: '1' }]
+
+// 学校下拉列表
+const schoolName = ref('')
+const getSchoolListData = async (name) => {
+  schoolName.value = name
+  const item = formItems.value.options.find(e => e.key === 'schoolId')
+  if (!item) return
+  const data = await schoolSearchByName({ name })
+  item.items = data
+}
+const debouncedCallbackSchool = debounce(newValue => {
+  getSchoolListData(newValue)
+}, 500)
+
+// 专业下拉列表
+const major = ref('')
+const getMajorListData = async (name) => {
+  major.value = name
+  const item = formItems.value.options.find(e => e.key === 'majorId')
+  if (!item) return
+  const data = await schoolMajorByName({ name })
+  item.items = data
+}
+const debouncedCallbackMajor = debounce(newValue => {
+  getMajorListData(newValue)
+}, 500)
 
 const formItems = ref({
   options: [
     {
-      type: 'autocomplete',
-      key: 'schoolName',
+      type: 'combobox',
+      key: 'schoolId',
       value: null,
       default: null,
       label: '学校名称 *',
       col: 6,
       outlined: true,
-      itemText: 'label',
-      itemValue: 'value',
+      clearable: true,
+      itemText: 'value',
+      itemValue: 'key',
       rules: [v => !!v || '请选择学校名称'],
-      items: [{ label: '广州大学', value: '广州大学'}, { label: '同济大学', value: '同济大学'}]
+      search: debouncedCallbackSchool,
+      items: []
     },
     {
-      type: 'text',
-      key: 'major',
+      type: 'combobox',
+      key: 'majorId',
       value: null,
       default: null,
       label: '所学专业 *',
       col: 6,
       outlined: true,
-      rules: [v => !!v || '请输入所学专业']
+      clearable: true,
+      itemText: 'nameCn',
+      itemValue: 'id',
+      rules: [v => !!v || '请选择所学专业'],
+      search: debouncedCallbackMajor,
+      items: []
     },
     {
       type: 'autocomplete',
@@ -108,7 +145,7 @@ const formItems = ref({
       itemText: 'label',
       itemValue: 'value',
       rules: [v => !!v || '请选择学制类型'],
-      items: [{ label: '全日制', value: '0' }, { label: '非全日制', value: '1' }],
+      items: dictItemsObj.educationSystemType,
     },
     {
       type: 'datepicker',
@@ -131,24 +168,15 @@ const formItems = ref({
       key: 'content',
       value: null,
       default: null,
-      counter: 200,
+      counter: 1600,
       label: '在校经历 *',
-      col: 12,
       outlined: true,
       rules: [v => !!v || '请输入在校经历']
     },
   ]
 })
 
-const dictList = [
-  { type: 'menduner_education_type', key: 'educationType' }
-]
-
-// 左侧加mr
-formItems.value.options.forEach((_e, index) => {
-  if ((index + 2) % 2 === 0 && !(!_e.col || _e.col >= 12)) _e.flexStyle = 'mr-3'
-})
-
+console.log('dictItemsObj', dictItemsObj)
 // 获取数据
 const projectExp = ref([])
 const getData = async () => {
@@ -174,13 +202,13 @@ const handle = (item) => {
   })
 }
 
-const transformItems = ['educationType', 'educationSystemType']
+// const transformItems = ['educationType', 'educationSystemType']
 formItems.value.options.forEach((e, index) => {
-  if ((index + 2) % 2 === 0) e.flexStyle = 'mr-3'
+  if (((index + 2) % 2 === 0) && Boolean(e.col) && e.col !== 12) e.flexStyle = 'mr-3' // 左侧加mr
   // 回显
   if (rowObj.value[e.key]) e.value = rowObj.value[e.key]
-  // 数字转为字符串
-  if (transformItems.includes(e.key) && e.value && e.value !== 0) e.value = e.value.toString()
+  // // 数字转为字符串
+  // if (transformItems.includes(e.key) && e.value && e.value !== 0) e.value = e.value.toString()
   // 日期相关
   if (e.type === 'datepicker') e.value = timesTampChange(e.value).slice(0, 10)
 })
@@ -194,9 +222,11 @@ const handleSave = async () => {
   formItems.value.options.forEach(e => {
     if (e.noReturn) return
     else if (e.type === 'datepicker') obj[e.key] = getTimeStamp(e.value)
-    else if (transformItems.includes(e.key) && e.value && e.value !== 0) obj[e.key] = e.value - 0
     else obj[e.key] = e.value
   })
+  obj.schoolName = schoolName.value
+  obj.major = major.value
+  if (rowObj.value?.id) obj.id = rowObj.value.id
   await saveResumeEduExp(obj)
   Snackbar.success('保存成功!')
   isEdit.value = false
@@ -214,17 +244,22 @@ const handleDelete = ({ id }) => {
 }
 
 // 获取字典内容
+const dictList = [
+  { type: 'menduner_education_type', key: 'educationType' }
+]
 const getDictData = async (obj) => {
   const item = formItems.value.options.find(e => e.key === obj.key)
   if (item) { //  && !item.items?.length
     const { data } = await getDict(obj.type)
-    item.items = data
+    item.items = data || []
+    dictItemsObj[obj.key] = data || []
   }
 }
 const getOptions = () => {
   dictList.forEach(obj =>  getDictData(obj))
 }
 getOptions()
+
 </script>
 
 <style scoped lang="scss">

+ 35 - 20
src/views/resume/components/vocationalSkills.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="resume-box">
-    <div class="resume-header">
+    <div class="resume-header mb-3">
       <div class="resume-title">{{ $t('resume.vocationalSkills') }}</div>
       <v-btn variant="text" color="primary" prepend-icon="mdi-plus-box" @click="isEdit = true; type = 'add'">{{ $t('common.add') }}</v-btn>
     </div>
@@ -12,15 +12,21 @@
         <v-btn color="primary" class="half-button" @click="handleSave">{{ $t('common.save') }}</v-btn>
       </div>
     </div>
-    <div v-else v-for="(k, i) in projectExp" :key="i" class="exp mx-n2 mt-5" @mouseenter="k.active = true" @mouseleave="k.active = false">
-      <span class="float-right" v-if="k.active">
-        <v-btn variant="text" color="primary" prepend-icon="mdi-square-edit-outline" @click="handleEdit(k)">{{ $t('common.edit') }}</v-btn>
-        <v-btn variant="text" color="primary" prepend-icon="mdi-trash-can-outline" @click="handleDelete(k)">{{ $t('common.delete') }}</v-btn>
-      </span>
-      <div class="d-flex align-center justify-space-between" style="height: 40px;">
-        <div>
-          <span style="font-size: 18px; font-weight: bold;">{{ k.name }}</span>
-        </div>
+    <div v-else>
+      <div 
+        :class="['dataList-item']" 
+        v-for="(k, i) in dataList" 
+        :key="i" 
+        @mouseenter="k.active = true" 
+        @mouseleave="k.active = false"
+      >
+        <span >{{ getText(k.skillId, skills) }}</span>
+        <span class="vline"></span>
+        <span style="color: #999;">{{ getText(k.level, skillLevelArr) }}</span>
+        <span class="float-right" v-if="k.active">
+          <v-btn variant="text" color="primary" prepend-icon="mdi-square-edit-outline" @click="handleEdit(k)">{{ $t('common.edit') }}</v-btn>
+          <v-btn variant="text" color="primary" prepend-icon="mdi-trash-can-outline" @click="handleDelete(k)">{{ $t('common.delete') }}</v-btn>
+        </span>
       </div>
     </div>
   </div>
@@ -33,11 +39,13 @@ import CtForm from '@/components/CtForm'
 import Snackbar from '@/plugins/snackbar'
 import Confirm from '@/plugins/confirm'
 import { getDict } from '@/hooks/web/useDictionaries'
+import { getText } from '@/utils/getText'
 
 const isEdit = ref(false)
 const formPageRef = ref()
 const type = ref('')
 const editId = ref(null)
+const skills = [{ label: '英语四级', value: '0' }, { label: '英语六级', value: '1' }, { label: 'office办公软件', value: '2' }]
 const formItems = ref({
   options: [
     {
@@ -52,7 +60,7 @@ const formItems = ref({
       flexStyle: 'mr-3',
       col: 8,
       rules: [v => !!v || '请选择技能名称'],
-      items: [{ label: '英语四级', value: '0' }, { label: '英语六级', value: '1' }, { label: 'office办公软件', value: '2' }],
+      items: skills,
     },
     {
       type: 'autocomplete',
@@ -71,10 +79,10 @@ const formItems = ref({
 })
 
 // 获取 职业技能
-const projectExp = ref([])
+const dataList = ref([])
 const getData = async () => {
   const data = await getResumePersonSkill()
-  projectExp.value = data
+  dataList.value = data
 }
 getData()
 
@@ -84,8 +92,8 @@ const handleSave = async () => {
   if (!valid) return
   const obj = {}
   formItems.value.options.forEach(e => {
-    obj[e.key] = e.value - 0
-    // obj[e.key] = e.value
+    // obj[e.key] = e.value - 0
+    obj[e.key] = e.value
   })
   if (editId.value) obj.id = editId.value
   await saveResumePersonSkill(obj)
@@ -99,8 +107,8 @@ const handleSave = async () => {
 const handleEdit = (item) => {
   editId.value = item.id
   formItems.value.options.forEach(e => {
-    if (item[e.key]) e.value = item[e.key].toString()
-    // e.value = item[e.key]
+    // if (item[e.key]) e.value = item[e.key].toString()
+    e.value = item[e.key]
   })
   isEdit.value = true
 }
@@ -114,22 +122,29 @@ const handleDelete = ({ id }) => {
   })
 }
 
+const skillLevelArr = ref([])
 getDict('menduner_skill_level').then(({ data }) => { // 字典
   data = data?.length && data || []
+  skillLevelArr.value = data
   const obj = formItems.value.options.find(e => e.key === 'level')
   if (obj) obj.items = data
 })
 </script>
 
 <style scoped lang="scss">
-.exp {
-  font-size: 15px;
+.dataList-item {
   cursor: pointer;
   border-radius: 6px;
-  padding: 2px 10px 8px;
+  line-height: 36px;
   &:hover {
     background-color: #f8f8f8;
   }
+  span {
+    font-size: 15px;
+  }
+  .grey-text {
+    color: #999;
+  }
 }
 .label-title {
   color: #666;

+ 0 - 1
src/views/resume/index.vue

@@ -71,7 +71,6 @@ const items = [
   { text: '职业技能', icon: 'mdi-star-check-outline', id: 'vocationalSkills' }
 ]
 
-
 onMounted(() => {
   const selector = document.getElementById('leftCadId')
   if (!selector) return