Xiao_123 1 tahun lalu
induk
melakukan
9d09159287

+ 2 - 0
components.d.ts

@@ -17,6 +17,7 @@ declare module 'vue' {
     CtPagination: typeof import('./src/components/CtPagination/index.vue')['default']
     CtSearch: typeof import('./src/components/CtSearch/index.vue')['default']
     CtTextField: typeof import('./src/components/CtVuetify/CtTextField/index.vue')['default']
+    DatePicker: typeof import('./src/components/FormUI/datePicker/index.vue')['default']
     Details: typeof import('./src/components/Enterprise/details.vue')['default']
     Empty: typeof import('./src/components/Empty/index.vue')['default']
     HeadSearch: typeof import('./src/components/headSearch/index.vue')['default']
@@ -32,6 +33,7 @@ declare module 'vue' {
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
     SimilarPositions: typeof import('./src/components/Position/similarPositions.vue')['default']
+    TextArea: typeof import('./src/components/FormUI/textArea/index.vue')['default']
     TextInput: typeof import('./src/components/FormUI/TextInput/index.vue')['default']
     VerificationCode: typeof import('./src/components/VerificationCode/index.vue')['default']
   }

+ 8 - 4
package-lock.json

@@ -20,7 +20,7 @@
         "vue": "^3.4.0",
         "vue-i18n": "9",
         "vue-router": "^4.3.0",
-        "vuetify": "^3.5.0"
+        "vuetify": "^3.6.0"
       },
       "devDependencies": {
         "@rushstack/eslint-patch": "^1.8.0",
@@ -3267,12 +3267,16 @@
       }
     },
     "node_modules/vuetify": {
-      "version": "3.5.17",
-      "resolved": "https://registry.npmmirror.com/vuetify/-/vuetify-3.5.17.tgz",
-      "integrity": "sha512-/Veklxxyu/l63q7QQOqJZeZukIKI2sBxY7FKMDcNup2KSGMjyjT+oYXy1DOdl7wlU3c3fKGQMFHqVWb0HDsyDw==",
+      "version": "3.6.0",
+      "resolved": "https://registry.npmmirror.com/vuetify/-/vuetify-3.6.0.tgz",
+      "integrity": "sha512-ic7VrB+nOTo8F7APhcKPjtDEO3yBCK5CJ2LIQ/4oAC/aaAKtuGuNMBUiUVitDKQjr0tcnDgy9Ar1CrHU5d28IA==",
       "engines": {
         "node": "^12.20 || >=14.13"
       },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/johnleider"
+      },
       "peerDependencies": {
         "typescript": ">=4.7",
         "vite-plugin-vuetify": ">=1.0.0",

+ 1 - 1
package.json

@@ -21,7 +21,7 @@
     "vue": "^3.4.0",
     "vue-i18n": "9",
     "vue-router": "^4.3.0",
-    "vuetify": "^3.5.0"
+    "vuetify": "^3.6.0"
   },
   "devDependencies": {
     "@rushstack/eslint-patch": "^1.8.0",

+ 22 - 0
src/api/resume.js

@@ -6,4 +6,26 @@ export const saveResumeAdvantage = async (data) => {
     url: '/app-api/menduner/system/person/resume/save/advantage',
     data
   })
+}
+
+// 保存培训经历
+export const saveResumeTrainExp = async (data) => {
+  return await request.post({
+    url: '/app-api/menduner/system/person/resume/save/train/exp',
+    data
+  })
+}
+
+// 删除培训经历
+export const deleteResumeTrainExp = async (id) => {
+  return await request.delete({
+    url: '/app-api/menduner/system/person/resume/remove/train/exp?id=' + id
+  })
+}
+
+// 获取培训经历
+export const getResumeTrainExp = async () => {
+  return await request.get({
+    url: '/app-api/menduner/system/person/resume/get/train/exp'
+  })
 }

+ 16 - 1
src/components/CtForm/index.vue

@@ -28,6 +28,18 @@
                 :item="item"
                 @change="handleChange(item)"
               ></radioGroupUI>
+              <textareaUI
+                v-if="item.type === 'textarea'"
+                v-model="item.value"
+                :item="item"
+                @change="handleChange(item)"
+              ></textareaUI>
+              <datePicker
+                v-model="item.value"
+                v-if="item.type === 'datepicker'"
+                :item="item"
+                @change="handleChange(item)"
+              ></datePicker>
               <template v-if="item.slotName">
                 <slot :name="item.slotName" :item="item"></slot>
               </template>
@@ -44,6 +56,8 @@ defineOptions({ name:'components-ct-form'})
 import textUI from './../FormUI/TextInput'
 import autocompleteUI from './../FormUI/autocomplete'
 import radioGroupUI from './../FormUI/radioGroup'
+import textareaUI from '../FormUI/textArea'
+import datePicker from '../FormUI/datePicker'
 import { ref, defineEmits } from 'vue'
 const emit = defineEmits(['change', 'inputUpdateAutocomplete'])// 定义一个或多个自定义事件
 const props = defineProps({items: Object})
@@ -81,7 +95,8 @@ const validate = () => {
   return form && time
 }
 defineExpose({
-  validate
+  validate,
+  formRef
 })
 </script>
 <style lang="scss" scoped>

+ 0 - 1
src/components/FormUI/TextInput/index.vue

@@ -12,7 +12,6 @@
       color="primary"
       :label="item.label"
       :placeholder="item.placeholder || item.label"
-      :outlined="item.outlined"
       :autofocus="item.autofocus"
       :required="item.required"
       :class="item.class"

+ 37 - 0
src/components/FormUI/datePicker/index.vue

@@ -0,0 +1,37 @@
+<template>
+  <div :style="{ width: item.width ? item.width + 'px' : '100%' }">
+    <v-date-input
+      v-model="value"
+      :label="item.label || '请选择'" 
+      variant="outlined"
+      prepend-icon=""
+      prepend-inner-icon="$calendar"
+      view-mode="month"
+      color="primary"
+      density="compact"
+      :rules="item.rules"
+      :hide-actions="true"
+      @update:modelValue="modelValueUpDate"
+    ></v-date-input>
+  </div>
+</template>
+<script setup>
+import { defineEmits, ref, watch } from 'vue';
+defineOptions({ name:'FormUI-v-date-input'})
+
+const props = defineProps({item: Object, modelValue: String})
+const emit = defineEmits(['update:modelValue'])
+const item = props.item
+const value = ref(props.modelValue)
+
+watch(() => props.modelValue, (newVal) => {
+  value.value = newVal
+})
+const modelValueUpDate = (val) => {
+  value.value = val
+  emit('update:modelValue', value.value)
+}
+</script>
+<style lang="scss" scoped>
+
+</style>

+ 1 - 0
src/components/FormUI/radioGroup/index.vue

@@ -18,6 +18,7 @@
       :readonly="radio.readonly"
       :label="radio.label"
       :value="radio.value"
+      :color="item.color || 'primary'"
     ></v-radio>
   </v-radio-group>
 </template>

+ 47 - 0
src/components/FormUI/textArea/index.vue

@@ -0,0 +1,47 @@
+<template>
+  <div :style="{ width: item.width ? item.width + 'px' : '100%' }">
+    <v-textarea
+      v-model="value"
+      variant="outlined"
+      :counter="item.counter || 200" 
+      color="primary" 
+      :validate-on="item.validateOn || 'input'"
+      :rules="item.rules"
+      :disabled="item.disabled"
+      :style="{width: item.width}"
+      :label="item.label"
+      :placeholder="item.placeholder || item.label"
+      :autofocus="item.autofocus"
+      :required="item.required"
+      :class="item.class"
+      :suffix="item.suffix"
+      :append-inner-icon="item.appendInnerIcon"
+      :clearable="item.clearable"
+      :readonly="item.readonly"
+      :prepend-inner-icon="item.prependInnerIcon"
+      hide-spin-buttons
+      :hide-details="item.hideDetails || false"
+      @update:modelValue="modelValueUpDate"
+    ></v-textarea>
+  </div>
+</template>
+<script setup>
+import { defineEmits, ref, watch } from 'vue';
+defineOptions({ name:'FormUI-v-textarea'})
+
+const props = defineProps({item: Object, modelValue: String})
+const emit = defineEmits(['update:modelValue'])
+const item = props.item
+const value = ref(props.modelValue)
+
+watch(() => props.modelValue, (newVal) => {
+  value.value = newVal
+})
+const modelValueUpDate = (val) => {
+  value.value = val
+  emit('update:modelValue', value.value)
+}
+</script>
+<style lang="scss" scoped>
+
+</style>

+ 2 - 1
src/locales/en.js

@@ -66,6 +66,7 @@ export default {
   },
   resume: {
     personalAdvantages: 'Personal Advantages',
-    jobIntention: 'Job Intention'
+    jobIntention: 'Job Intention',
+    trainingExperience: 'Training Experience'
   }
 }

+ 2 - 1
src/locales/zh-CN.js

@@ -66,6 +66,7 @@ export default {
   },
   resume: {
     personalAdvantages: '个人优势',
-    jobIntention: '求职意向'
+    jobIntention: '求职意向',
+    trainingExperience: '培训经历'
   }
 }

+ 5 - 2
src/plugins/vuetify.js

@@ -7,8 +7,9 @@
 // Styles
 import '@mdi/font/css/materialdesignicons.css'
 import 'vuetify/styles'
-import * as components from 'vuetify/components'
+// import * as components from 'vuetify/components'
 import * as directives from 'vuetify/directives'
+import { VDateInput } from 'vuetify/labs/VDateInput'
 
 // Composables
 import { createVuetify } from 'vuetify'
@@ -28,7 +29,9 @@ const myCustomLightTheme = {
 }
 // https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides
 export default createVuetify({
-  components,
+  components: {
+    VDateInput
+  },
   directives,
   theme: {
     defaultTheme: 'myCustomLightTheme', // dark

+ 6 - 0
src/utils/date.js

@@ -10,4 +10,10 @@ export const timesTampChange = (timestamp) => {
   const s = date.getSeconds().toString().padStart(2, '0')
   const strDate = Y + M + D + h + m + s
   return strDate
+}
+
+// 将 Wed May 01 2024 00:00:00 GMT+0800 (中国标准时间) 转换为时间戳
+export const getTimeStamp = (str) => {
+  const date = new Date(str)
+  return date.getTime()
 }

+ 3 - 4
src/views/resume/components/jobIntention.vue

@@ -1,8 +1,8 @@
 <template>
   <div class="resume-box">
-    <div class="resume-header mb-3" @mouseenter="showAddBtn = true" @mouseleave="showAddBtn = false">
+    <div class="resume-header mb-3">
       <div class="resume-title">{{ $t('resume.jobIntention') }}</div>
-      <v-btn v-if="showAddBtn" variant="text" color="primary" prepend-icon="mdi-plus-box" @click="isAdd = true" :disabled="isAdd">{{ $t('common.add') }}</v-btn>
+      <v-btn v-if="!isAdd" variant="text" color="primary" prepend-icon="mdi-plus-box" @click="isAdd = true">{{ $t('common.add') }}</v-btn>
     </div>
     <div v-if="!isAdd">
       <div 
@@ -33,7 +33,7 @@
           <v-menu :close-delay="1" :open-delay="0" v-bind="$attrs">
             <template v-slot:activator="{  props }">
               <textUI
-              :modelValue="item.value"
+                :modelValue="item.value"
                 :item="item"
                 @blur="item.blur"
                 v-bind="props"
@@ -74,7 +74,6 @@ import jobTypeCard from '@/components/jobTypeCard'
 import industryTypeCard from '@/components/industryTypeCard'
 
 const isAdd = ref(false)
-const showAddBtn = ref(false)
 
 const items = ref({
   options: [

+ 0 - 1
src/views/resume/components/selfEvaluation.vue

@@ -10,7 +10,6 @@
         label="请输入您的个人优势..." 
         variant="outlined" 
         counter="200" 
-        bg-color="grey-lighten-5" 
         color="primary" 
         validate-on="input"
         :rules="advantageRules"

+ 163 - 0
src/views/resume/components/trainingExperience.vue

@@ -0,0 +1,163 @@
+<template>
+  <div class="resume-box">
+    <div class="resume-header mb-3">
+      <div class="resume-title">{{ $t('resume.trainingExperience') }}</div>
+      <v-btn variant="text" color="primary" prepend-icon="mdi-plus-box" @click="isEdit = true">{{ $t('common.add') }}</v-btn>
+    </div>
+    <div>
+      <div v-for="(k, i) in list" :key="i" class="exp" @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">{{ $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 v-for="(val, index) in desc" :key="index" class="my-2">
+          <span class="label-title">{{ val.label }}</span>
+          <span v-if="Array.isArray(val.value)">{{ k[val.value[0]] }} 至 {{ k[val.value[1]] }}</span>
+          <span>{{ k[val.value] }}</span>
+        </div>
+        <div v-if="i !== list.length -1" class="border-bottom-dashed my-3"></div>
+      </div>
+    </div>
+    <div v-if="isEdit">
+      <div class="border-bottom-dashed my-5"></div>
+      <CtForm ref="formPageRef" :items="items" style="width: 100%;"></CtForm>
+      <div class="text-end">
+        <v-btn class="half-button mr-3" variant="tonal" @click="isEdit = false">{{ $t('common.cancel') }}</v-btn>
+        <v-btn color="primary" class="half-button" @click="handleSave">{{ $t('common.save') }}</v-btn>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup name="trainingExperience">
+import { ref, shallowRef  } from 'vue'
+import { getTimeStamp } from '@/utils/date'
+import { saveResumeTrainExp, getResumeTrainExp, deleteResumeTrainExp } from '@/api/resume'
+import CtForm from '@/components/CtForm'
+import Snackbar from '@/plugins/snackbar'
+import Confirm from '@/plugins/confirm'
+
+const isEdit = ref(true)
+const formPageRef = ref()
+const items = ref({
+  options: [
+    {
+      type: 'text',
+      key: 'orgName',
+      value: '海外产品培训中心',
+      col: 6,
+      label: '培训中心 *',
+      flexStyle: 'mr-3',
+      rules: [v => !!v || '请输入培训中心']
+    },
+    {
+      type: 'text',
+      key: 'course',
+      value: '产品设计与分析',
+      col: 6,
+      label: '培训课程 *',
+      rules: [v => !!v || '请输入培训课程']
+    },
+    {
+      type: 'datepicker',
+      key: 'startTime',
+      value: shallowRef('2024-04-01'),
+      col: 6,
+      label: '培训开始时间 *',
+      flexStyle: 'mr-3',
+      rules: [v => !!v || '请选择培训开始时间']
+    },
+    {
+      type: 'datepicker',
+      key: 'endTime',
+      value: shallowRef('2024-04-30'),
+      col: 6,
+      label: '培训结束时间 *',
+      rules: [v => !!v || '请选择培训结束时间']
+    },
+    {
+      type: 'textarea',
+      key: 'content',
+      value: '同时具备常用大数据解决方案实践经验,包括实时计算(Storm/Flink )及离线计算(Hive/ETL/Spark)。 对搜索推荐/订单交易/经营分析等业务方向具备多年实践经验。 ',
+      label: '培训描述 *',
+      rules: [
+        value => {
+          if (value) return true
+          return '请输入培训描述'
+        },
+        value => {
+          if (value?.length <= 200) return true
+          return '请输入2-200个字符'
+        }
+      ]
+    }
+  ]
+})
+
+// 获取培训经历
+const getResumeTrainExpData = async () => {
+  const data = await getResumeTrainExp()
+  console.log(data, 'get')
+}
+getResumeTrainExpData()
+
+// 保存培训经历
+const handleSave = async () => {
+  const { valid } = await formPageRef.value.formRef.validate()
+  if (!valid) return
+  const obj = {}
+  items.value.options.forEach(e => {
+    if (e.key === 'startTime' || e.key == 'endTime') obj[e.key] = getTimeStamp(e.value)
+    else obj[e.key] = e.value
+  })
+  // console.log(obj, 'obj')
+  await saveResumeTrainExp(obj)
+  Snackbar.success('保存成功!')
+  isEdit.value = false
+  getResumeTrainExpData()
+}
+
+// 删除培训经历
+const handleDelete = ({ id }) => {
+  console.log(id, 'delete')
+  Confirm('系统提示', '是否确认删除此项培训经历?').then(async () => {
+    await deleteResumeTrainExp(id)
+    Snackbar.success('删除成功!')
+    getResumeTrainExpData()
+  })
+}
+
+const desc = [
+  { label: '培训中心:', value: 'orgName' },
+  { label: '培训课程:', value: 'course' },
+  { label: '培训时间:', value: ['startTime', 'endTime'] },
+  { label: '培训描述:', value: 'content' }
+]
+const list = ref([
+  {
+    orgName: '海外产品培训中心',
+    course: '产品设计与分析',
+    startTime: '2024-04',
+    endTime: '2024-05',
+    active: false,
+    content: '熟悉 Java  技术平台,并稳定形成生产力,CCF  中国计算机学会会员,获得微软 MCP、MCSA  认证。 熟悉通用缓存/DB /搜索引擎/消息队列等中间件。对业内高可用高并发及分布式技术解决方案有充分理解。'
+  },
+  {
+    orgName: '海外产品培训中心',
+    course: '产品设计与分析',
+    startTime: '2024-04',
+    endTime: '2024-05',
+    active: false,
+    content: '同时具备常用大数据解决方案实践经验,包括实时计算(Storm/Flink )及离线计算(Hive/ETL/Spark)。 对搜索推荐/订单交易/经营分析等业务方向具备多年实践经验。 具备较强的横向协作能力及团队管理能力,责任心'
+  }
+])
+</script>
+
+<style scoped lang="scss">
+.exp {
+  font-size: 15px;
+}
+.label-title {
+  color: #777;
+}
+</style>

+ 7 - 5
src/views/resume/dynamic/right.vue

@@ -1,8 +1,9 @@
 <template>
   <div>
-    <basicInfo class="mb-3"></basicInfo>
-    <selfEvaluation class="mt"></selfEvaluation>
-    <jobIntention class="mt"></jobIntention>
+    <basicInfo></basicInfo>
+    <selfEvaluation class="my"></selfEvaluation>
+    <jobIntention></jobIntention>
+    <trainingExperience class="my"></trainingExperience>
   </div>
 </template>
 
@@ -11,10 +12,11 @@ defineOptions({ name: 'resume-right'})
 import basicInfo from '../components/basicInfo.vue'
 import selfEvaluation from '../components/selfEvaluation.vue'
 import jobIntention from '../components/jobIntention.vue'
+import trainingExperience from '../components/trainingExperience.vue'
 </script>
 
 <style scoped lang="scss">
-.mt {
-  margin-top: 12px;
+.my {
+  margin: 12px 0;
 }
 </style>