前后端全面升级

This commit is contained in:
pycook
2023-07-10 17:42:15 +08:00
parent c444fed436
commit db5ff60aff
629 changed files with 97789 additions and 23995 deletions

View File

@@ -0,0 +1,371 @@
<template>
<div class="ops-setting-companyinfo" :style="{ height: `${windowHeight - 64}px` }">
<a-form-model ref="infoData" :model="infoData" :label-col="labelCol" :wrapper-col="wrapperCol" :rules="rule">
<SpanTitle>公司描述</SpanTitle>
<a-form-model-item label="名称" prop="name">
<a-input v-model="infoData.name" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item label="描述">
<a-input v-model="infoData.description" type="textarea" :disabled="!isEditable" />
</a-form-model-item>
<SpanTitle>公司地址</SpanTitle>
<a-form-model-item label="国家/地区">
<a-input v-model="infoData.country" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item label="城市">
<a-input v-model="infoData.city" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item label="地址">
<a-input v-model="infoData.address" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item label="邮编">
<a-input v-model="infoData.postCode" :disabled="!isEditable" />
</a-form-model-item>
<SpanTitle>联系方式</SpanTitle>
<a-form-model-item label="网站">
<a-input v-model="infoData.website" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item label="电话号码" prop="phone">
<a-input v-model="infoData.phone" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item label="传真号码" prop="faxCode">
<a-input v-model="infoData.faxCode" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item label="电子邮箱" prop="email">
<a-input v-model="infoData.email" :disabled="!isEditable" />
</a-form-model-item>
<SpanTitle>公司标识</SpanTitle>
<a-form-model-item label="部署域名" prop="domainName">
<a-input v-model="infoData.domainName" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item label="公司logo">
<a-space>
<a-upload
:disabled="!isEditable"
name="avatar"
list-type="picture-card"
class="avatar-uploader"
:show-upload-list="false"
:customRequest="customRequest"
:before-upload="beforeUpload"
:style="{ width: '400px', height: '80px' }"
accept=".png,.jpg,.jpeg"
>
<div
class="ops-setting-companyinfo-upload-show"
v-if="infoData.logoName"
:style="{ width: '400px', height: '80px' }"
@click="eidtImageOption.type = 'Logo'"
>
<img :src="`/api/common-setting/v1/file/${infoData.logoName}`" alt="avatar" />
<a-icon
v-if="isEditable"
type="minus-circle"
theme="filled"
class="delete-icon"
@click.stop="deleteLogo"
/>
</div>
<div v-else @click="eidtImageOption.type = 'Logo'">
<a-icon type="plus" />
<div class="ant-upload-text">上传</div>
</div>
</a-upload>
<a-upload
:disabled="!isEditable"
name="avatar"
list-type="picture-card"
class="avatar-uploader"
:show-upload-list="false"
:customRequest="customRequest"
:before-upload="beforeUpload"
:style="{ width: '82px', height: '82px' }"
accept=".png,.jpg,.jpeg"
>
<div
class="ops-setting-companyinfo-upload-show"
v-if="infoData.smallLogoName"
:style="{ width: '82px', height: '82px' }"
@click="eidtImageOption.type = 'SmallLogo'"
>
<img :src="`/api/common-setting/v1/file/${infoData.smallLogoName}`" alt="avatar" />
<a-icon
v-if="isEditable"
type="minus-circle"
theme="filled"
class="delete-icon"
@click.stop="deleteSmallLogo"
/>
</div>
<div v-else @click="eidtImageOption.type = 'SmallLogo'">
<a-icon type="plus" />
<div class="ant-upload-text">上传</div>
</div>
</a-upload>
</a-space>
</a-form-model-item>
<a-form-model-item :wrapper-col="{ span: 14, offset: 3 }" v-if="isEditable">
<a-button type="primary" @click="onSubmit"> 保存 </a-button>
<a-button ghost type="primary" style="margin-left: 28px" @click="resetForm"> 重置 </a-button>
</a-form-model-item>
</a-form-model>
<edit-image
v-if="showEditImage"
:show="showEditImage"
:image="editImage"
:title="eidtImageOption.title"
:eidtImageOption="eidtImageOption"
@save="submitImage"
@close="showEditImage = false"
/>
</div>
</template>
<script>
import { getCompanyInfo, postCompanyInfo, putCompanyInfo, postImageFile } from '@/api/company'
import { mapMutations, mapState } from 'vuex'
import SpanTitle from '../components/spanTitle.vue'
import EditImage from '../components/EditImage.vue'
import { mixinPermissions } from '@/utils/mixin'
export default {
name: 'CompanyInfo',
mixins: [mixinPermissions],
components: { SpanTitle, EditImage },
data() {
return {
labelCol: { span: 3 },
wrapperCol: { span: 10 },
infoData: {
name: '',
description: '',
address: '',
city: '',
postCode: '',
country: '',
website: '',
phone: '',
faxCode: '',
email: '',
logoName: '',
smallLogoName: '',
},
rule: {
name: [{ required: true, whitespace: true, message: '请输入名称', trigger: 'blur' }],
phone: [
{
required: false,
whitespace: true,
pattern: new RegExp('^([0-9]|-)+$', 'g'),
message: '请输入正确的电话号码',
trigger: 'blur',
},
],
faxCode: [
{
required: false,
whitespace: true,
pattern: new RegExp('^([0-9]|-)+$', 'g'),
message: '请输入正确的传真号码',
trigger: 'blur',
},
],
email: [
{
required: false,
whitespace: true,
pattern: new RegExp('^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(.[a-zA-Z0-9-]+)*.[a-zA-Z0-9]{2,6}$', 'g'),
message: '请输入正确的邮箱地址',
trigger: 'blur',
},
],
},
getId: -1,
showEditImage: false,
editImage: null,
eidtImageOption: {
type: 'Logo',
fixedNumber: [15, 4],
title: '编辑企业logo',
previewWidth: '200px',
previewHeight: '40px',
autoCropWidth: 200,
autoCropHeight: 40,
},
}
},
async mounted() {
const res = await getCompanyInfo()
if (!res.id) {
this.getId = -1
} else {
this.infoData = res.info
this.getId = res.id
}
},
computed: {
...mapState({
windowHeight: (state) => state.windowHeight,
}),
isEditable() {
return this.hasDetailPermission('backend', '公司信息', ['update'])
},
},
methods: {
...mapMutations(['SET_FILENAME', 'SET_SMALL_FILENAME']),
deleteLogo() {
this.infoData.logoName = ''
},
deleteSmallLogo() {
this.infoData.smallLogoName = ''
},
async onSubmit() {
this.$refs.infoData.validate(async (valid) => {
if (valid) {
if (this.getId === -1) {
await postCompanyInfo(this.infoData)
} else {
await putCompanyInfo(this.getId, this.infoData)
}
this.SET_FILENAME(this.infoData.logoName)
this.SET_SMALL_FILENAME(this.infoData.smallFileName)
this.$message.success('保存成功')
} else {
this.$message.warning('检查您的输入是否正确!')
return false
}
})
},
resetForm() {
this.infoData = {
name: '',
description: '',
address: '',
city: '',
postCode: '',
country: '',
website: '',
phone: '',
faxCode: '',
email: '',
logoName: '',
smallLogoName: '',
}
},
customRequest(file) {
const reader = new FileReader()
var self = this
if (this.eidtImageOption.type === 'Logo') {
this.eidtImageOption = {
type: 'Logo',
fixedNumber: [20, 4],
title: '编辑企业logo',
previewWidth: '200px',
previewHeight: '40px',
autoCropWidth: 200,
autoCropHeight: 40,
}
} else if (this.eidtImageOption.type === 'SmallLogo') {
this.eidtImageOption = {
type: 'SmallLogo',
fixedNumber: [4, 4],
title: '编辑企业logo缩略图',
previewWidth: '80px',
previewHeight: '80px',
autoCropWidth: 250,
autoCropHeight: 250,
}
}
reader.onload = function(e) {
let result
if (typeof e.target.result === 'object') {
// 把Array Buffer转化为blob 如果是base64不需要
result = window.URL.createObjectURL(new Blob([e.target.result]))
} else {
result = e.target.result
}
self.editImage = result
self.showEditImage = true
}
reader.readAsDataURL(file.file)
},
submitImage(file) {
postImageFile(file).then((res) => {
if (res.file_name) {
if (this.eidtImageOption.type === 'Logo') {
this.infoData.logoName = res.file_name
} else if (this.eidtImageOption.type === 'SmallLogo') {
this.infoData.smallLogoName = res.file_name
}
} else {
}
})
},
beforeUpload(file) {
const isLt2M = file.size / 1024 / 1024 < 2
if (!isLt2M) {
this.$message.error('图片大小不可超过2MB!')
}
return isLt2M
},
},
}
</script>
<style lang="less">
.ops-setting-companyinfo {
padding-top: 15px;
background-color: #fff;
border-radius: 15px;
overflow: auto;
margin-bottom: -24px;
.ops-setting-companyinfo-upload-show {
position: relative;
width: 290px;
height: 100px;
max-height: 100px;
img {
width: 100%;
height: 100%;
}
.delete-icon {
display: none;
}
}
.ant-upload:hover .delete-icon {
display: block;
position: absolute;
top: 5px;
right: 5px;
color: rgb(247, 85, 85);
}
.ant-form-item {
margin-bottom: 10px;
}
.ant-form-item label {
padding-right: 10px;
}
.avatar-uploader > .ant-upload {
// max-width: 100px;
max-height: 100px;
}
// .ant-upload.ant-upload-select-picture-card {
// width: 100%;
// > .ant-upload {
// padding: 0px;
.ant-upload-picture-card-wrapper {
height: 100px;
.ant-upload.ant-upload-select-picture-card {
width: 100%;
height: 100%;
margin: 0;
> .ant-upload {
padding: 0px;
}
}
}
}
</style>

View File

@@ -0,0 +1,190 @@
<template>
<a-modal
destroyOnClose
dialogClass="ops-modal"
width="500px"
v-model="visible"
:title="title"
layout="inline"
@cancel="close"
>
<a-form-model v-if="visible && batchProps.type === 'department_id'">
<div :style="{ width: '420px', display: 'inline-block', margin: '0 7px' }">
<div :style="{ height: '40px', lineHeight: '40px' }">选择部门:</div>
<DepartmentTreeSelect v-model="batchForm.value"> </DepartmentTreeSelect>
</div>
</a-form-model>
<a-form-model v-else-if="batchProps.type === 'direct_supervisor_id'" ref="ruleForm">
<div :style="{ width: '420px', display: 'inline-block', margin: '0 7px' }">
<div :style="{ height: '40px', lineHeight: '40px' }">选择上级:</div>
<EmployeeTreeSelect v-model="batchForm.value" />
</div>
</a-form-model>
<a-form-model v-else-if="batchProps.type === 'position_name'">
<a-form-model-item label="编辑岗位">
<a-input v-model="batchForm.value" />
</a-form-model-item>
</a-form-model>
<a-form-model v-else-if="batchProps.type === 'annual_leave'">
<a-form-model-item label="编辑年假">
<a-input-number
:min="0"
:step="1"
:style="{ width: '100%' }"
v-model="batchForm.value"
placeholder="请输入年假"
:formatter="(value) => `${value} 天`"
/>
</a-form-model-item>
</a-form-model>
<a-form-model v-else-if="batchProps.type === 'password'" ref="batchForm" :model="batchForm" :rules="rules">
<a-form-model-item label="重置密码" prop="password">
<a-input-password v-model="batchForm.value" />
</a-form-model-item>
<a-form-model-item label="确认密码" prop="repeatPassword">
<a-input-password v-model="batchForm.confirmValue" />
</a-form-model-item>
</a-form-model>
<a-form-model v-else-if="batchProps.type === 'block' && batchProps.state === 1">
<a-icon type="info-circle" :style="{ color: '#FF9E58', fontSize: '16px', marginRight: '10px' }" />
<span v-if="batchProps.selectedRowKeys.length > 1">这些用户将会被禁用是否继续?</span>
<span v-else>该用户将会被禁用是否继续?</span>
</a-form-model>
<a-form-model v-else-if="batchProps.type === 'block' && batchProps.state === 0">
<a-icon type="info-circle" :style="{ color: '#FF9E58', fontSize: '16px', marginRight: '10px' }" />
<span v-if="batchProps.selectedRowKeys.length > 1">这些用户将会被恢复是否继续?</span>
<span v-else>该用户将会被恢复是否继续?</span>
</a-form-model>
<template slot="footer">
<a-button key="back" @click="close"> 取消 </a-button>
<a-button key="submit" type="primary" @click="batchModalHandleOk"> 确定 </a-button>
</template>
</a-modal>
</template>
<script>
import Treeselect from '@riophae/vue-treeselect'
import { batchEditEmployee } from '@/api/employee'
import EmployeeTreeSelect from '../components/employeeTreeSelect.vue'
import DepartmentTreeSelect from '../components/departmentTreeSelect.vue'
import { getDirectorName } from '@/utils/util'
import Bus from './eventBus/bus'
export default {
components: { Treeselect, DepartmentTreeSelect, EmployeeTreeSelect },
inject: ['provide_allFlatEmployees'],
data() {
const validatePass = (rule, value, callback) => {
console.log(this.batchForm)
if (this.batchForm.value === '') {
callback(new Error('请输入密码'))
} else {
this.$refs.batchForm.validateField('repeatPassword')
callback()
}
}
const validatePass2 = (rule, value, callback) => {
console.log(this.batchForm)
if (this.batchForm.confirmValue === '') {
callback(new Error('请输入密码'))
} else if (this.batchForm.confirmValue !== this.batchForm.value) {
callback(new Error('两次密码不一致'))
} else {
callback()
}
}
return {
visible: false,
batchProps: {},
batchForm: {
value: '',
confirmValue: '',
},
title: '',
rules: {
password: [{ required: true, validator: validatePass, trigger: 'blur' }],
repeatPassword: [{ required: true, validator: validatePass2, trigger: 'blur' }],
},
}
},
computed: {
allFlatEmployees() {
return this.provide_allFlatEmployees()
},
},
methods: {
open(batchProps) {
this.visible = true
this.batchProps = batchProps
const { type, selectedRowKeys, state } = batchProps
this.title = '批量编辑'
if (type === 'department_id') {
this.batchForm.value = null
} else if (type === 'direct_supervisor_id') {
this.batchForm.value = undefined
} else if (type === 'password') {
if (selectedRowKeys.length <= 1) {
this.title = '重置密码'
}
} else if (type === 'block') {
this.batchForm.value = state
if (selectedRowKeys.length <= 1) {
this.title = state ? '禁用' : '恢复'
}
}
},
close() {
this.batchForm.value = ''
this.batchForm.confirmValue = ''
this.visible = false
},
async batchModalHandleOk() {
if (this.batchProps.type === 'direct_supervisor_id') {
this.batchForm.value = this.batchForm.value
? this.batchForm.value.includes('-')
? Number(this.batchForm.value.split('-')[1])
: Number(this.batchForm.value)
: 0
}
if (this.batchProps.type === 'password') {
this.$refs.batchForm.validate(async (valid) => {
if (valid) {
this.sendReq()
}
})
} else {
this.sendReq()
}
},
async sendReq() {
const employeeIdList = this.batchProps.selectedRowKeys.map((item) => item.employee_id)
const res = await batchEditEmployee({
column_name: this.batchProps.type,
column_value: this.batchForm.value,
employee_id_list: employeeIdList,
})
if (res.length) {
this.$notification.error({
message: '操作失败',
description: res
.map((item) => `${getDirectorName(this.allFlatEmployees, item.employee_id)}${item.err}`)
.join('\n'),
duration: null,
style: {
width: '600px',
marginLeft: `${335 - 600}px`,
whiteSpace: 'pre-line',
},
})
} else {
this.$message.success('操作成功')
}
if (this.batchProps.type === 'department_id') {
Bus.$emit('clickSelectGroup', 1)
} else {
this.$emit('refresh')
}
Bus.$emit('updataAllIncludeEmployees')
this.close()
},
},
}
</script>

View File

@@ -0,0 +1,550 @@
<template>
<a-modal
:visible="visible"
title="批量导入"
dialogClass="ops-modal setting-structure-upload"
:width="800"
@cancel="close"
>
<div class="setting-structure-upload-steps">
<div
:class="{ 'setting-structure-upload-step': true, selected: index + 1 <= currentStep }"
v-for="(step, index) in stepList"
:key="step.value"
>
<div :class="{ 'setting-structure-upload-step-icon': true }">
<ops-icon :type="step.icon" />
</div>
<span>{{ step.label }}</span>
</div>
</div>
<template v-if="currentStep === 1">
<a-upload :multiple="false" :customRequest="customRequest" accept=".xlsx" :showUploadList="false">
<a-button :style="{ marginBottom: '20px' }" type="primary"> <a-icon type="upload" />选择文件</a-button>
</a-upload>
<p><a @click="download">点击下载员工导入模板</a></p>
</template>
<div
:style="{ height: '60px', display: 'flex', justifyContent: 'center', alignItems: 'center' }"
v-if="currentStep === 3"
>
导入总数据{{ allCount }}, 导入成功 <span :style="{ color: '#2362FB' }">{{ allCount - errorCount }}</span> ,
导入失败<span :style="{ color: '#D81E06' }">{{ errorCount }}</span
>
</div>
<vxe-table
v-if="currentStep === 2 || has_error"
ref="employeeTable"
stripe
:data="importData"
show-overflow
show-header-overflow
highlight-hover-row
size="small"
class="ops-stripe-table"
:max-height="400"
:column-config="{ resizable: true }"
>
<vxe-column field="email" title="邮箱" min-width="120" fixed="left"></vxe-column>
<vxe-column field="username" title="用户名" min-width="80" ></vxe-column>
<vxe-column field="nickname" title="姓名" min-width="80"></vxe-column>
<vxe-column field="password" title="密码" min-width="80"></vxe-column>
<vxe-column field="sex" title="性别" min-width="60"></vxe-column>
<vxe-column field="mobile" title="手机号" min-width="80"></vxe-column>
<vxe-column field="position_name" title="岗位" min-width="80"></vxe-column>
<vxe-column field="department_name" title="部门" min-width="80"></vxe-column>
<vxe-column field="current_company" v-if="useDFC" title="目前所属主体" min-width="120"></vxe-column>
<vxe-column field="dfc_entry_date" v-if="useDFC" title="初始入职日期" min-width="120"></vxe-column>
<vxe-column field="entry_date" title="目前主体入职日期" min-width="120"></vxe-column>
<vxe-column field="is_internship" title="正式/实习生" min-width="120"></vxe-column>
<vxe-column field="leave_date" title="离职日期" min-width="120"></vxe-column>
<vxe-column field="id_card" title="身份证号码" min-width="120"></vxe-column>
<vxe-column field="nation" title="民族" min-width="80"></vxe-column>
<vxe-column field="id_place" title="籍贯" min-width="80"></vxe-column>
<vxe-column field="party" title="组织关系" min-width="80"></vxe-column>
<vxe-column field="household_registration_type" title="户籍类型" min-width="80"></vxe-column>
<vxe-column field="hometown" title="户口所在地" min-width="80"></vxe-column>
<vxe-column field="marry" title="婚姻情况" min-width="80"></vxe-column>
<vxe-column field="max_degree" title="最高学历" min-width="80"></vxe-column>
<vxe-column field="emergency_person" title="紧急联系人" min-width="120"></vxe-column>
<vxe-column field="emergency_phone" title="紧急联系电话" min-width="120"></vxe-column>
<vxe-column field="bank_card_number" title="卡号" min-width="120"></vxe-column>
<vxe-column field="bank_card_name" title="银行" min-width="80"></vxe-column>
<vxe-column field="opening_bank" title="开户行" min-width="80"></vxe-column>
<vxe-column field="account_opening_location" title="开户地" min-width="120"></vxe-column>
<vxe-column field="school" title="学校" min-width="80"></vxe-column>
<vxe-column field="major" title="专业" min-width="80"></vxe-column>
<vxe-column field="education" title="学历" min-width="80"></vxe-column>
<vxe-column field="graduation_year" title="毕业年份" min-width="120"></vxe-column>
<vxe-column v-if="has_error" field="err" title="失败原因" min-width="120" fixed="right">
<template #default="{ row }">
<span :style="{ color: '#D81E06' }">{{ row.err }}</span>
</template>
</vxe-column>
</vxe-table>
<a-space slot="footer">
<a-button size="small" type="primary" ghost @click="close">取消</a-button>
<a-button v-if="currentStep !== 1" size="small" type="primary" ghost @click="goPre">上一步</a-button>
<a-button v-if="currentStep !== 3" size="small" type="primary" @click="goNext">下一步</a-button>
<a-button v-else size="small" type="primary" @click="close">完成</a-button>
</a-space>
</a-modal>
</template>
<script>
import { downloadExcel, excel2Array } from '@/utils/download'
import { importEmployee } from '@/api/employee'
import appConfig from '@/config/app'
export default {
name: 'BatchUpload',
data() {
const stepList = [
{
value: 1,
label: '上传文件',
icon: 'icon-shidi-tianjia',
},
{
value: 2,
label: '确认数据',
icon: 'icon-shidi-yunshangchuan',
},
{
value: 3,
label: '上传完成',
icon: 'icon-shidi-queren',
},
]
const dfc_importParamsList = [
'email',
'username',
'nickname',
'password',
'sex',
'mobile',
'position_name',
'department_name',
'current_company',
'dfc_entry_date',
'entry_date',
'is_internship',
'leave_date',
'id_card',
'nation',
'id_place',
'party',
'household_registration_type',
'hometown',
'marry',
'max_degree',
'emergency_person',
'emergency_phone',
'bank_card_number',
'bank_card_name',
'opening_bank',
'account_opening_location',
'school',
'major',
'education',
'graduation_year',
]
const common_importParamsList = [
'email',
'username',
'nickname',
'password',
'sex',
'mobile',
'position_name',
'department_name',
'entry_date',
'is_internship',
'leave_date',
'id_card',
'nation',
'id_place',
'party',
'household_registration_type',
'hometown',
'marry',
'max_degree',
'emergency_person',
'emergency_phone',
'bank_card_number',
'bank_card_name',
'opening_bank',
'account_opening_location',
'school',
'major',
'education',
'graduation_year',
]
return {
stepList,
dfc_importParamsList,
common_importParamsList,
visible: false,
currentStep: 1,
importData: [],
has_error: false,
allCount: 0,
errorCount: 0,
useDFC: appConfig.useDFC,
}
},
methods: {
open() {
this.importData = []
this.has_error = false
this.errorCount = 0
this.visible = true
},
close() {
this.currentStep = 1
this.visible = false
},
async goNext() {
if (this.currentStep === 2) {
// 此处调用后端接口
this.allCount = this.importData.length
const importData = this.importData.map((item) => {
const { _X_ROW_KEY, ...rest } = item
const keyArr = Object.keys(rest)
keyArr.forEach((key) => {
if (rest[key]) {
rest[key] = rest[key] + ''
}
})
rest.educational_experience = [{
'school': rest.school,
'major': rest.major,
'education': rest.education,
'graduation_year': rest.graduation_year
}]
delete rest.school
delete rest.major
delete rest.education
delete rest.graduation_year
return rest
})
const res = await importEmployee({ employee_list: importData })
if (res.length) {
const errData = res.filter((item) => {
return item.err.length
})
console.log('err', errData)
this.has_error = true
this.errorCount = errData.length
this.currentStep += 1
this.importData = errData
this.$message.error('数据存在错误')
} else {
this.currentStep += 1
this.$message.success('操作成功')
}
this.$emit('refresh')
}
},
goPre() {
this.has_error = false
this.errorCount = 0
this.currentStep -= 1
},
download() {
const data = [
[
{
v:
'1、表头标“*”的红色字体为必填项\n2、邮箱、用户名不允许重复\n3、登录密码密码由6-20位字母、数字组成\n4、部门上下级部门间用"/"隔开,且从最上级部门开始,例如“深圳分公司/IT部/IT二部”。如出现相同的部门则默认导入组织架构中顺序靠前的部门',
t: 's',
s: {
alignment: {
wrapText: true,
vertical: 'center',
},
},
},
],
[
{
v: '*邮箱',
t: 's',
s: {
font: {
color: {
rgb: 'FF0000',
},
},
},
},
{
v: '*用户名',
t: 's',
s: {
font: {
color: {
rgb: 'FF0000',
},
},
},
},
{
v: '*姓名',
t: 's',
s: {
font: {
color: {
rgb: 'FF0000',
},
},
},
},
{
v: '*密码',
t: 's',
s: {
font: {
color: {
rgb: 'FF0000',
},
},
},
},
{
v: '性别',
t: 's',
},
{
v: '手机号',
t: 's',
},
{
v: '岗位',
t: 's',
},
{
v: '部门',
t: 's',
},
{
v: '目前所属主体',
t: 's',
},
{
v: '初始入职日期',
t: 's',
},
{
v: '目前主体入职日期',
t: 's',
},
{
v: '正式/实习生',
t: 's',
},
{
v: '离职日期',
t: 's',
},
{
v: '身份证号码',
t: 's',
},
{
v: '民族',
t: 's',
},
{
v: '籍贯',
t: 's',
},
{
v: '组织关系',
t: 's',
},
{
v: '户籍类型',
t: 's',
},
{
v: '户口所在地',
t: 's',
},
{
v: '婚姻情况',
t: 's',
},
{
v: '最高学历',
t: 's',
},
{
v: '紧急联系人',
t: 's',
},
{
v: '紧急联系电话',
t: 's',
},
{
v: '卡号',
t: 's',
},
{
v: '银行',
t: 's',
},
{
v: '开户行',
t: 's',
},
{
v: '开户地',
t: 's',
},
{
v: '学校',
t: 's',
},
{
v: '专业',
t: 's',
},
{
v: '学历',
t: 's',
},
{
v: '毕业年份',
t: 's',
},
],
]
if (this.useDFC) {
downloadExcel(data, '员工导入模板')
} else {
data[1] = data[1].filter(item => item['v'] !== '目前所属主体')
data[1] = data[1].filter(item => item['v'] !== '初始入职日期')
downloadExcel(data, '员工导入模板')
}
},
customRequest(data) {
this.fileList = [data.file]
excel2Array(data.file).then((res) => {
res = res.filter((item) => item.length)
this.importData = res.slice(2).map((item) => {
const obj = {}
// 格式化日期字段
if (this.useDFC) {
item[9] = this.formatDate(item[9]) // 初始入职日期日期
item[10] = this.formatDate(item[10]) // 目前主体入职日期
item[12] = this.formatDate(item[12]) // 离职日期
item[30] = this.formatDate(item[30]) // 毕业年份
item.forEach((ele, index) => {
obj[this.dfc_importParamsList[index]] = ele
})
} else {
item[8] = this.formatDate(item[8]) // 目前主体入职日期
item[10] = this.formatDate(item[10]) // 离职日期
item[28] = this.formatDate(item[28]) // 毕业年份
item.forEach((ele, index) => {
obj[this.common_importParamsList[index]] = ele
})
}
return obj
})
this.currentStep = 2
})
},
formatDate(numb) {
if (numb) {
const time = new Date((numb - 1) * 24 * 3600000 + 1)
time.setYear(time.getFullYear() - 70)
time.setMonth(time.getMonth())
time.setHours(time.getHours() - 8)
time.setMinutes(time.getMinutes())
time.setMilliseconds(time.getMilliseconds())
// return time.valueOf()
// 日期格式
const format = 'Y-m-d'
const year = time.getFullYear()
// 由于 getMonth 返回值会比正常月份小 1
let month = time.getMonth() + 1
let day = time.getDate()
month = month > 9 ? month : `0${month}`
day = day > 9 ? day : `0${day}`
const hash = {
'Y': year,
'm': month,
'd': day,
}
return format.replace(/\w/g, o => {
return hash[o]
})
} else {
return null
}
}
},
}
</script>
<style lang="less">
.setting-structure-upload {
.ant-modal-body {
padding: 24px 48px;
}
.setting-structure-upload-steps {
display: flex;
flex-direction: row;
justify-content: space-between;
margin-bottom: 20px;
.setting-structure-upload-step {
display: inline-block;
text-align: center;
position: relative;
.setting-structure-upload-step-icon {
width: 86px;
height: 86px;
display: flex;
align-items: center;
justify-content: center;
background-image: url('../../../assets/icon-bg.png');
margin-bottom: 20px;
> i {
font-size: 40px;
color: #fff;
}
}
> span {
font-size: 16px;
font-weight: 600;
color: rgba(0, 0, 0, 0.5);
}
}
.setting-structure-upload-step:not(:first-child)::before {
content: '';
height: 2px;
width: 223px;
position: absolute;
background-color: #e7ecf3;
left: -223px;
top: 43px;
z-index: 0;
}
.selected.setting-structure-upload-step {
&:not(:first-child)::before {
background-color: #7eb0ff;
}
}
.selected {
.setting-structure-upload-step-icon {
background-image: url('../../../assets/icon-bg-selected.png');
}
> span {
color: rgba(0, 0, 0, 0.8);
}
}
}
}
</style>

View File

@@ -0,0 +1,321 @@
<template>
<li class="ops-setting-companystructure-sidebar-tree">
<div
:class="{
'ops-setting-companystructure-sidebar-group-tree-item': true,
'ops-setting-companystructure-sidebar-group-tree-line': showLine,
isSelected: activeId === TreeData.id || asFatherSelected,
}"
>
<div class="ops-setting-companystructure-sidebar-group-tree-info" @click.stop="selectItem(TreeData)">
<!-- <div class="info-title"> -->
<span :title="TreeData.title">
<ops-icon :style="{ marginRight: '8px' }" :type="icon" />
{{ TreeData.title }}
</span>
<!-- </div> -->
<!-- <span class="item-title"
:title="TreeData.title"
><ops-icon :style="{ marginRight: '8px' }" :type="icon" />{{ TreeData.title }}{{ TreeData.count }}</span
> -->
<div class="ops-setting-companystructure-sidebar-group-tree-info-count-toggle">
<div class="item-count-before">{{ TreeData.count }}</div>
<!-- 显示折叠展开的图标如果没有下级目录的话则不显示 -->
<div class="item-folder">
<span v-if="isFolder" @click.stop="toggle">
<a-icon :style="{ color: '#a1bcfb' }" :type="open ? 'up-circle' : 'down-circle'" theme="filled" />
</span>
</div>
</div>
</div>
<ul v-if="isFolder && open" :style="{ marginLeft: '12px' }">
<draggable v-model="TreeData.children" @end="handleEndDrag(TreeData.children)" :disabled="!isEditable">
<CategroyTree
v-for="(SubTree, SubIndex) in TreeData.children"
:id="SubTree.id"
:key="SubTree.id"
:TreeData="SubTree"
:showLine="SubIndex !== TreeData.children.length - 1"
icon="setting-structure-depart2"
/>
</draggable>
</ul>
</div>
</li>
</template>
<script>
import draggable from 'vuedraggable'
import Bus from '@/views/setting/companyStructure/eventBus/bus'
import { updateDepartmentsSort } from '@/api/company'
import { mixinPermissions } from '@/utils/mixin'
export default {
name: 'CategroyTree',
mixins: [mixinPermissions],
components: { draggable },
props: {
TreeData: {
type: Object,
required: true,
},
showLine: {
type: Boolean,
},
icon: {
type: String,
default: 'setting-structure-depart2',
},
},
data() {
return {
// 默认不显示下级目录
open: false,
activeId: null,
asFatherSelected: false,
// isClick: 'item-count-before',
}
},
computed: {
// 控制是否有下级目录和显示下级目录
isFolder() {
return this.TreeData.hasSub
},
isEditable() {
return this.hasDetailPermission('backend', '公司架构', ['update'])
},
},
created() {
Bus.$on('changeActiveId', (cid) => {
this.activeId = cid
})
Bus.$on('asFatherSelected', (cid) => {
this.fatherSelected(cid)
})
Bus.$on('resettoggle', (isToggle) => {
this.open = isToggle
})
},
destroyed() {
Bus.$off('changeActiveId')
Bus.$off('asFatherSelected')
},
methods: {
// 点击折叠展开的方法
toggle() {
if (this.isFolder) {
this.selectItem(this.TreeData)
if (!this.open) {
Bus.$emit('reqChildren')
}
this.open = !this.open
}
},
selectItem(selectDepartment) {
Bus.$emit('selectDepartment', selectDepartment)
this.activeId = selectDepartment.id
Bus.$emit('changeActiveId', selectDepartment.id)
Bus.$emit('asFatherSelected', this.TreeData.id)
},
fatherSelected(childId) {
this.asFatherSelected = this.ownIdInChildren(childId, this.TreeData, false)
},
ownIdInChildren(cid, TreeData, flag = false) {
if (TreeData.children) {
if (TreeData.children.map((item) => item.id).includes(cid)) {
flag = true
return true
} else {
return TreeData.children
.map((item) => {
return this.ownIdInChildren(cid, item, flag)
})
.includes(true)
}
} else {
return flag
}
},
handleEndDrag(data) {
updateDepartmentsSort({
department_list: data.map((item, index) => {
return { id: item.id, sort_value: index }
}),
}).then(() => {
Bus.$emit('updateAllIncludeDepartment')
})
},
// mouseOver: function() {
// this.isClick = 'item-count-after'
// },
// mouseLeave: function() {
// this.isClick = 'item-count-before'
// },
},
}
</script>
<style lang="less">
@import '~@/style/static.less';
ul,
li {
list-style: none;
margin: 0;
padding: 0;
}
.ops-setting-companystructure-sidebar-tree {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
min-height: 30px;
position: relative;
// padding: 7px 0 7px 10px;
padding-left: 10px;
color: rgba(0, 0, 0, 0.7);
font-size: 14px;
.ops-setting-companystructure-sidebar-group-tree-info:hover {
color: #custom_colors[color_1];
> .ops-setting-companystructure-sidebar-group-tree-info::before {
background-color: #custom_colors[color_1];
}
}
// .ops-setting-companystructure-sidebar-group-tree-info:first-child::before {
// content: '';
// position: absolute;
// top: 50%;
// left: 0;
// transform: translateY(-50%);
// display: inline-block;
// width: 5px;
// height: 5px;
// background-color: #cacaca;
// border-radius: 50%;
// }
.ops-setting-companystructure-sidebar-group-tree-item {
display: inline-block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
cursor: pointer;
user-select: none;
.ops-setting-companystructure-sidebar-group-tree-info {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
position: relative;
display: flex;
justify-content: space-between;
margin-bottom: 10px;
> span:first-child {
font-size: 15px;
display: inline-block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: calc(100% - 14px - 15px);
// margin-bottom: 10px;
// line-height: 10px;
// height: 5%;
}
.info-title {
display: flex;
align-items: center;
justify-content: center;
// > span:first-child {
// font-size: 15px;
// display: inline-block;
// white-space: nowrap;
// overflow: hidden;
// text-overflow: ellipsis;
// width: calc(100% - 14px - 15px);
// margin-bottom: 10px;
// }
}
//flex-wrap: wrap;
// align-items: center;
// .item-title{
// display: inline-block;
// white-space: nowrap;
// overflow: hidden;
// text-overflow: ellipsis;
// }
.item-count-after {
//position: absolute;
display: inline-block;
margin: 0 auto;
width: 27px;
height: 15px;
background: #ffffff;
border-radius: 2px;
text-align: center;
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-size: 10px;
line-height: 12px;
color: #2f54eb;
}
.ops-setting-companystructure-sidebar-group-tree-info-count-toggle {
display: flex;
align-items: center;
justify-content: center;
.item-count-before {
display: flex;
align-items: center;
justify-content: center;
// display: inline-block;
margin: 0 auto;
width: 27px;
height: 15px;
background: #e1efff;
border-radius: 2px;
text-align: center;
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-size: 10px;
line-height: 12px;
color: #9094a6;
margin-right: 5px;
}
.item-folder {
display: flex;
align-items: center;
justify-content: center;
width: 14px;
height: 14px;
// // display: inline-block;
// justify-content: center;
// align-items: center;
}
}
// > span:nth-child(2) {
// color: #a1bcfb!important;
// }
}
}
// .ops-setting-companystructure-sidebar-group-tree-line::after {
// content: '';
// position: absolute;
// width: 1px;
// height: 100%;
// background-color: rgba(0, 0, 0, 0.1);
// top: 12px;
// left: 12px;
// }
.isSelected {
color: #2f54eb;
> .ops-setting-companystructure-sidebar-group-tree-info {
> span:nth-child(2) > i {
color: #a1bcfb !important;
}
}
> .ops-setting-companystructure-sidebar-group-tree-info::before {
background-color: #custom_colors[color_1];
}
}
}
</style>

View File

@@ -0,0 +1,143 @@
<template>
<a-modal
destroyOnClose
width="500px"
v-model="visible"
:title="type === 'add' ? '创建子部门' : '编辑部门'"
layout="inline"
@cancel="close"
>
<a-form-model
ref="departmentFormData"
:model="departmentFormData"
:rules="rules"
:label-col="labelCol"
:wrapper-col="wrapperCol"
>
<a-form-model-item ref="title" label="部门名称" prop="department_name">
<a-input v-model="departmentFormData.department_name" placeholder="请输入部门名称" />
</a-form-model-item>
<a-form-model-item label="上级部门" prop="department_parent_id">
<DepartmentTreeSelect v-model="departmentFormData.department_parent_id" />
<!-- <Treeselect
v-else
:multiple="false"
:options="currentDepartmentParentList"
v-model="departmentFormData.department_parent_id"
class="ops-setting-treeselect"
placeholder="请选择上级部门"
:normalizer="
(node) => {
return {
id: node.department_id,
label: node.department_name,
children: node.children,
}
}
"
/> -->
</a-form-model-item>
<a-form-model-item label="部门负责人" prop="department_director_id">
<EmployeeTreeSelect v-model="departmentFormData.department_director_id" />
</a-form-model-item>
</a-form-model>
<template slot="footer">
<a-button key="back" @click="close"> 取消 </a-button>
<a-button key="submit" type="primary" @click="departmentModalHandleOk"> 确定 </a-button>
</template>
</a-modal>
</template>
<script>
import Treeselect from '@riophae/vue-treeselect'
import { getParentDepartmentList, putDepartmentById, postDepartment } from '@/api/company'
import EmployeeTreeSelect from '../components/employeeTreeSelect.vue'
import DepartmentTreeSelect from '../components/departmentTreeSelect.vue'
import Bus from './eventBus/bus'
export default {
name: 'DepartmentModal',
components: { Treeselect, EmployeeTreeSelect, DepartmentTreeSelect },
data() {
return {
labelCol: { span: 5 },
wrapperCol: { span: 19 },
visible: false,
departmentFormData: {
department_name: '',
department_parent_id: '',
department_director_id: undefined,
},
rules: {
department_name: [{ required: true, message: '请输入部门名称' }],
department_parent_id: [{ required: true, message: '请选择上级部门' }],
},
currentDepartmentParentList: [],
selectDepartment: {},
type: 'add',
}
},
inject: ['provide_allFlatEmployees'],
computed: {
allFlatEmployees() {
return this.provide_allFlatEmployees()
},
},
mounted() {},
methods: {
async open({ type, selectDepartment }) {
this.selectDepartment = selectDepartment
this.type = type
const { title, parentId, leaderId, id } = selectDepartment
let department_director_id
if (type === 'add') {
this.departmentFormData = {
department_name: '',
department_parent_id: id,
department_director_id,
}
} else if (type === 'edit') {
const res = await getParentDepartmentList({ department_id: id })
this.currentDepartmentParentList = res
if (leaderId) {
const _find = this.allFlatEmployees.find((item) => item.employee_id === leaderId)
department_director_id = `${_find.department_id}-${leaderId}`
}
this.departmentFormData = {
department_name: title,
department_parent_id: parentId,
department_director_id,
}
}
this.visible = true
},
close() {
this.selectDepartment = {}
this.visible = false
},
async departmentModalHandleOk() {
this.$refs.departmentFormData.validate(async (valid) => {
if (valid) {
const { department_director_id } = this.departmentFormData
const params = {
...this.departmentFormData,
department_director_id: department_director_id
? String(department_director_id).split('-')[String(department_director_id).split('-').length - 1]
: undefined,
}
if (this.type === 'edit') {
await putDepartmentById(this.selectDepartment.id, params)
} else if (this.type === 'add') {
await postDepartment(params)
}
this.$message.success('操作成功')
this.$emit('refresh')
Bus.$emit('updateAllIncludeDepartment')
this.close()
}
})
},
},
}
</script>
<style></style>

View File

@@ -0,0 +1,695 @@
<template>
<a-modal
dialogClass="ops-modal"
destroyOnClose
width="810px"
v-model="visible"
:title="title"
layout="inline"
@cancel="close"
:body-style="{ height: `${windowHeight - 320}px`, overflow: 'hidden', overflowY: 'scroll' }"
>
<a-form-model ref="employeeFormData" :model="employeeFormData" :rules="rules" :colon="false">
<a-form-model-item
ref="email"
label="邮箱"
prop="email"
:style="formModalItemStyle"
v-if="attributes.findIndex((v) => v == 'email') !== -1"
>
<a-input v-model="employeeFormData.email" placeholder="请输入邮箱" />
</a-form-model-item>
<a-form-model-item
ref="username"
label="用户名"
prop="username"
:style="formModalItemStyle"
v-if="attributes.findIndex((v) => v == 'username') !== -1"
>
<a-input v-model="employeeFormData.username" placeholder="请输入用户名" />
</a-form-model-item>
<a-form-model-item
v-if="type === 'add'"
ref="password"
label="登录密码"
prop="password"
:style="formModalItemStyle"
>
<a-input-password v-model="employeeFormData.password" placeholder="请输入登录密码" />
</a-form-model-item>
<a-form-model-item
ref="nickname"
label="姓名"
prop="nickname"
:style="formModalItemStyle"
v-if="attributes.findIndex((v) => v == 'nickname') !== -1"
>
<a-input v-model="employeeFormData.nickname" placeholder="请输入姓名" />
</a-form-model-item>
<a-form-model-item
label="性别"
prop="sex"
:style="formModalItemStyle"
v-if="attributes.findIndex((v) => v == 'sex') !== -1"
>
<a-select v-model="employeeFormData.sex" placeholder="请选择性别">
<a-select-option value=""> </a-select-option>
<a-select-option value=""> </a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item
ref="mobile"
label="手机号"
prop="mobile"
:style="formModalItemStyle"
v-if="attributes.findIndex((v) => v == 'mobile') !== -1"
>
<a-input v-model="employeeFormData.mobile" placeholder="请输入手机号" />
</a-form-model-item>
<div
:style="{ width: '361px', display: 'inline-block', margin: '0 7px' }"
v-if="attributes.findIndex((v) => v == 'department_id') !== -1"
>
<div :style="{ height: '41px', lineHeight: '40px' }">部门</div>
<DepartmentTreeSelect v-model="employeeFormData.department_id" />
</div>
<a-form-model-item
ref="position_name"
label="岗位"
prop="position_name"
:style="formModalItemStyle"
v-if="attributes.findIndex((v) => v == 'position_name') !== -1"
>
<a-input v-model="employeeFormData.position_name" placeholder="请输入岗位" />
</a-form-model-item>
<div
:style="{ width: '361px', display: 'inline-block', margin: '0 7px' }"
v-if="attributes.findIndex((v) => v == 'direct_supervisor_id') !== -1"
>
<div :style="{ height: '41px', lineHeight: '40px' }">上级</div>
<EmployeeTreeSelect v-model="employeeFormData.direct_supervisor_id" />
</div>
<a-form-model-item
ref="annual_leave"
label="年假"
prop="annual_leave"
:style="formModalItemStyle"
v-if="attributes.findIndex((v) => v == 'annual_leave') !== -1"
>
<a-input-number
:min="0"
:step="1"
:style="{ width: '100%' }"
v-model="employeeFormData.annual_leave"
placeholder="请输入年假"
:formatter="(value) => `${value} 天`"
/>
</a-form-model-item>
<a-form-model-item
ref="virtual_annual_leave"
label="虚拟年假"
prop="virtual_annual_leave"
:style="formModalItemStyle"
v-if="attributes.findIndex((v) => v == 'virtual_annual_leave') !== -1"
>
<a-input-number
:min="0"
:step="1"
:style="{ width: '100%' }"
v-model="employeeFormData.virtual_annual_leave"
placeholder="请输入虚拟年假"
:formatter="(value) => `${value} 天`"
/>
</a-form-model-item>
<a-form-model-item
ref="parenting_leave"
label="育儿假"
prop="parenting_leave"
:style="formModalItemStyle"
v-if="attributes.findIndex((v) => v == 'parenting_leave') !== -1"
>
<a-input-number
:min="0"
:step="1"
:style="{ width: '100%' }"
v-model="employeeFormData.parenting_leave"
placeholder="请输入育儿假"
:formatter="(value) => `${value} 天`"
/>
</a-form-model-item>
<a-form-model-item
v-if="useDFC && attributes.findIndex((v) => v == 'current_company') !== -1"
ref="current_company"
label="目前所属主体"
prop="current_company"
:style="formModalItemStyle"
>
<a-input v-model="employeeFormData.current_company" placeholder="请输入目前所属主体" />
</a-form-model-item>
<a-form-model-item
v-if="useDFC && attributes.findIndex((v) => v == 'dfc_entry_date') !== -1"
ref="dfc_entry_date"
label="初始入职日期"
prop="dfc_entry_date"
:style="formModalItemStyle"
>
<a-date-picker
placeholder="请选择初始入职日期"
v-model="employeeFormData.dfc_entry_date"
:style="{ width: '100%' }"
@change="onChange($event, 'dfc_entry_date')"
></a-date-picker>
</a-form-model-item>
<a-form-model-item
ref="entry_date"
label="目前主体入职日期"
prop="entry_date"
:style="formModalItemStyle"
v-if="attributes.findIndex((v) => v == 'entry_date') !== -1"
>
<a-date-picker
placeholder="请选择目前主体入职日期"
v-model="employeeFormData.entry_date"
:style="{ width: '100%' }"
@change="onChange($event, 'entry_date')"
></a-date-picker>
</a-form-model-item>
<a-form-model-item
ref="is_internship"
label="正式/实习生"
prop="is_internship"
:style="formModalItemStyle"
v-if="attributes.findIndex((v) => v == 'is_internship') !== -1"
>
<a-select v-model="employeeFormData.is_internship" placeholder="请选择是否正式/实习生">
<a-select-option :value="0"> 正式 </a-select-option>
<a-select-option :value="1"> 实习生 </a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item
ref="leave_date"
label="离职日期"
prop="leave_date"
:style="formModalItemStyle"
v-if="attributes.findIndex((v) => v == 'leave_date') !== -1"
>
<a-date-picker
v-model="employeeFormData.leave_date"
placeholder="请选择离职日期"
:style="{ width: '100%' }"
@change="onChange($event, 'leave_date')"
></a-date-picker>
</a-form-model-item>
<a-form-model-item
ref="id_card"
label="身份证号码"
prop="id_card"
:style="formModalItemStyle"
v-if="attributes.findIndex((v) => v == 'id_card') !== -1"
>
<a-input v-model="employeeFormData.id_card" placeholder="请输入身份证号码" />
</a-form-model-item>
<a-form-model-item
ref="nation"
label="民族"
prop="nation"
:style="formModalItemStyle"
v-if="attributes.findIndex((v) => v == 'nation') !== -1"
>
<a-input v-model="employeeFormData.nation" placeholder="请输入民族" />
</a-form-model-item>
<a-form-model-item
ref="id_place"
label="籍贯"
prop="id_place"
:style="formModalItemStyle"
v-if="attributes.findIndex((v) => v == 'id_place') !== -1"
>
<a-input v-model="employeeFormData.id_place" placeholder="请输入籍贯" />
</a-form-model-item>
<a-form-model-item
ref="party"
label="组织关系"
prop="party"
:style="formModalItemStyle"
v-if="attributes.findIndex((v) => v == 'party') !== -1"
>
<a-select v-model="employeeFormData.party" placeholder="请选择组织关系">
<a-select-option value="党员"> 党员 </a-select-option>
<a-select-option value="团员"> 团员 </a-select-option>
<a-select-option value="群众"> 群众 </a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item
ref="household_registration_type"
label="户籍类型"
prop="household_registration_type"
:style="formModalItemStyle"
v-if="attributes.findIndex((v) => v == 'household_registration_type') !== -1"
>
<a-select v-model="employeeFormData.household_registration_type" placeholder="请选择户籍类型">
<a-select-option value="城镇"> 城镇 </a-select-option>
<a-select-option value="农业"> 农业 </a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item
ref="hometown"
label="户口所在地"
prop="hometown"
:style="formModalItemStyle"
v-if="attributes.findIndex((v) => v == 'hometown') !== -1"
>
<a-input v-model="employeeFormData.hometown" placeholder="请输入户口所在地" />
</a-form-model-item>
<a-form-model-item
ref="marry"
label="婚姻情况"
prop="marry"
:style="formModalItemStyle"
v-if="attributes.findIndex((v) => v == 'marry') !== -1"
>
<a-select v-model="employeeFormData.marry" placeholder="请选择婚姻情况">
<a-select-option value="未婚"> 未婚 </a-select-option>
<a-select-option value="已婚"> 已婚 </a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item
ref="max_degree"
label="最高学历"
prop="max_degree"
:style="formModalItemStyle"
v-if="attributes.findIndex((v) => v == 'max_degree') !== -1"
>
<a-select v-model="employeeFormData.max_degree" placeholder="请选择最高学历">
<a-select-option value="博士"> 博士 </a-select-option>
<a-select-option value="硕士"> 硕士 </a-select-option>
<a-select-option value="本科"> 本科 </a-select-option>
<a-select-option value="专科"> 专科 </a-select-option>
<a-select-option value="高中"> 高中 </a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item
ref="emergency_person"
label="紧急联系人"
prop="emergency_person"
:style="formModalItemStyle"
v-if="attributes.findIndex((v) => v == 'emergency_person') !== -1"
>
<a-input v-model="employeeFormData.emergency_person" placeholder="请输入紧急联系人" />
</a-form-model-item>
<a-form-model-item
ref="emergency_phone"
label="紧急联系电话"
prop="emergency_phone"
:style="formModalItemStyle"
v-if="attributes.findIndex((v) => v == 'emergency_phone') !== -1"
>
<a-input v-model="employeeFormData.emergency_phone" placeholder="请输入紧急联系电话" />
</a-form-model-item>
<a-form-model-item
label="教育经历"
prop="employeeFormData"
:style="{ display: 'inline-block', width: '100%', margin: '0 7px' }"
v-if="attributes.findIndex((v) => v == 'educational_experience') !== -1"
>
<a-row :gutter="[8, { xs: 8 }]" v-for="item in educational_experience" :key="item.id">
<a-col :span="5">
<a-input v-model="item.school" placeholder="学校" allowClear></a-input>
</a-col>
<a-col :span="5">
<a-input v-model="item.major" placeholder="专业" allowClear></a-input>
</a-col>
<a-col :span="5">
<a-select v-model="item.education" placeholder="学历" allowClear>
<a-select-option value="小学"> 小学 </a-select-option>
<a-select-option value="初中"> 初中 </a-select-option>
<a-select-option value="中专/高中"> 中专/高中 </a-select-option>
<a-select-option value="专科"> 专科 </a-select-option>
<a-select-option value="本科"> 本科 </a-select-option>
<a-select-option value="硕士"> 硕士 </a-select-option>
<a-select-option value="博士"> 博士 </a-select-option>
</a-select>
</a-col>
<a-col :span="5">
<a-month-picker
v-model="item.graduation_year"
placeholder="毕业年份"
@change="onChange($event, 'graduation_year', item.id)"
></a-month-picker>
</a-col>
<a-col :span="1">
<a @click="addEducation">
<a-icon type="plus-circle" />
</a>
</a-col>
<a-col :span="1" v-if="educational_experience.length > 1">
<a @click="() => removeEducation(item.id)" :style="{ color: 'red' }">
<a-icon type="delete" />
</a>
</a-col>
</a-row>
</a-form-model-item>
<a-form-model-item
label="子女信息"
prop="employeeFormData"
:style="{ display: 'inline-block', width: '100%', margin: '0 7px' }"
v-if="attributes.findIndex((v) => v == 'children_information') !== -1"
>
<!-- <a-space
v-for="(item,index) in educational_experience"
:key="index"
align="baseline"
> -->
<a-row :gutter="[8, { xs: 8 }]" v-for="item in children_information" :key="item.id">
<a-col :span="5">
<a-input v-model="item.name" placeholder="姓名" allowClear></a-input>
</a-col>
<a-col :span="5">
<a-select v-model="item.gender" placeholder="请选择性别" allowClear>
<a-select-option value=""> </a-select-option>
<a-select-option value=""> </a-select-option>
</a-select>
</a-col>
<a-col :span="5">
<a-date-picker
v-model="item.birthday"
placeholder="出生日期"
@change="onChange($event, 'birth_date', item.id)"
></a-date-picker>
</a-col>
<a-col :span="5">
<a-input-number
:min="0"
:step="1"
:style="{ width: '100%' }"
v-model="item.parental_leave_left"
placeholder="请输入剩余育儿假"
:formatter="(value) => `${value} 天`"
/>
</a-col>
<a-col :span="1">
<a @click="addChildren">
<a-icon type="plus-circle" />
</a>
</a-col>
<a-col :span="1" v-if="children_information.length > 1">
<a @click="() => removeChildren(item.id)" :style="{ color: 'red' }">
<a-icon type="delete" />
</a>
</a-col>
</a-row>
</a-form-model-item>
<a-form-model-item
label="银行卡"
prop="bank_card"
:style="{ display: 'inline-block', width: '98%', margin: '0 7px 24px' }"
v-if="
attributes.findIndex((v) => v == 'bank_card_number') !== -1 ||
attributes.findIndex((v) => v == 'bank_card_name') !== -1 ||
attributes.findIndex((v) => v == 'opening_bank') !== -1 ||
attributes.findIndex((v) => v == 'account_opening_location') !== -1
"
>
<a-row :gutter="[8, { xs: 8 }]">
<a-col :span="6" v-if="attributes.findIndex((v) => v == 'bank_card_number') !== -1">
<a-input v-model="employeeFormData.bank_card_number" placeholder="卡号" allowClear></a-input>
</a-col>
<a-col :span="6" v-if="attributes.findIndex((v) => v == 'bank_card_name') !== -1">
<a-input v-model="employeeFormData.bank_card_name" placeholder="银行" allowClear></a-input>
</a-col>
<a-col :span="6" v-if="attributes.findIndex((v) => v == 'opening_bank') !== -1">
<a-input v-model="employeeFormData.opening_bank" placeholder="开户行" allowClear></a-input>
</a-col>
<a-col :span="6" v-if="attributes.findIndex((v) => v == 'account_opening_location') !== -1">
<a-input v-model="employeeFormData.account_opening_location" placeholder="开户地" allowClear></a-input>
</a-col>
</a-row>
</a-form-model-item>
</a-form-model>
<template slot="footer">
<a-button key="back" @click="close"> 取消 </a-button>
<a-button type="primary" @click="employeeModalHandleOk"> 确定 </a-button>
</template>
</a-modal>
</template>
<script>
import { mapState } from 'vuex'
import _ from 'lodash'
import { postEmployee, putEmployee } from '@/api/employee'
import Bus from './eventBus/bus'
import EmployeeTreeSelect from '../components/employeeTreeSelect.vue'
import DepartmentTreeSelect from '../components/departmentTreeSelect.vue'
import appConfig from '@/config/app'
import moment from 'moment'
import { v4 as uuidv4 } from 'uuid'
export default {
components: { EmployeeTreeSelect, DepartmentTreeSelect },
data() {
return {
visible: false,
employeeFormData: {},
formModalItemStyle: { display: 'inline-block', width: '48%', margin: '0 7px 24px', overflow: 'hidden' },
rules: {
email: [
{ required: true, whitespace: true, message: '请输入邮箱', trigger: 'blur' },
{
pattern: /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/,
message: '邮箱格式错误',
trigger: 'blur',
},
{ max: 50, message: '字符数须小于50' },
],
username: [
{ required: true, whitespace: true, message: '请输入用户名', trigger: 'blur' },
{ max: 20, message: '字符数须小于20' },
],
password: [{ required: true, whitespace: true, message: '请输入密码', trigger: 'blur' }],
nickname: [
{ required: true, whitespace: true, message: '请输入姓名', trigger: 'blur' },
{ max: 20, message: '字符数须小于20' },
],
mobile: [
{
pattern: /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/,
message: '请输入正确的手机号',
trigger: 'blur',
},
],
},
type: 'add',
useDFC: appConfig.useDFC,
educational_experience: [],
children_information: [],
file_is_show: true,
attributes: [],
}
},
created() {
Bus.$on('getAttributes', (attributes) => {
this.attributes = attributes
})
},
inject: ['provide_allTreeDepartment', 'provide_allFlatEmployees'],
computed: {
...mapState({
windowHeight: (state) => state.windowHeight,
}),
departemntTreeSelectOption() {
return this.provide_allTreeDepartment()
},
allFlatEmployees() {
return this.provide_allFlatEmployees()
},
title() {
if (this.type === 'add') {
return '新建员工'
}
return '编辑员工'
},
},
beforeDestroy() {
Bus.$off('getAttributes')
},
methods: {
async open(getData, type) {
// 提交时去掉school, major, education, graduation_year, name, gender, birthday, parental_leave_left
const { school, major, education, graduation_year, name, gender, birthday, parental_leave_left, ...newGetData } =
getData
const _getData = _.cloneDeep(newGetData)
const { direct_supervisor_id } = newGetData
if (direct_supervisor_id) {
const _find = this.allFlatEmployees.find((item) => item.employee_id === direct_supervisor_id)
_getData.direct_supervisor_id = `${_find.department_id}-${direct_supervisor_id}`
} else {
_getData.direct_supervisor_id = undefined
}
this.employeeFormData = _.cloneDeep(_getData)
// if (type !== 'add' && this.employeeFormData.educational_experience.length !== 0) {
// this.educational_experience = this.employeeFormData.educational_experience
// }
this.children_information = this.formatChildrenInformationList() || [
{
id: uuidv4(),
name: '',
gender: undefined,
birthday: null,
parental_leave_left: 0,
},
]
this.educational_experience = this.formatEducationalExperienceList() || [
{
id: uuidv4(),
school: '',
major: '',
education: undefined,
graduation_year: null,
},
]
this.type = type
this.visible = true
},
close() {
this.$refs.employeeFormData.resetFields()
this.educational_experience = [
{
school: '',
major: '',
education: undefined,
graduation_year: null,
},
]
this.children_information = [
{
id: uuidv4(),
name: '',
gender: undefined,
birthday: null,
parental_leave_left: 0,
},
]
this.visible = false
},
formatChildrenInformationList() {
let arr = []
arr = this.employeeFormData.children_information ? this.employeeFormData.children_information : undefined
if (arr && arr.length) {
arr.forEach((item) => {
item.id = uuidv4()
})
return arr
}
return null
},
formatEducationalExperienceList() {
let arr = []
arr = this.employeeFormData.educational_experience ? this.employeeFormData.educational_experience : undefined
if (arr && arr.length) {
arr.forEach((item) => {
item.id = uuidv4()
})
return arr
}
return null
},
addEducation() {
const newEducational_experience = {
id: uuidv4(),
school: '',
major: '',
education: undefined,
graduation_year: null,
}
this.educational_experience.push(newEducational_experience)
},
removeEducation(removeId) {
const _idx = this.educational_experience.findIndex((item) => item.id === removeId)
if (_idx !== -1) {
this.educational_experience.splice(_idx, 1)
}
},
addChildren() {
const newChildrenInfo = {
id: uuidv4(),
name: '',
gender: undefined,
birthday: null,
parental_leave_left: 0,
}
this.children_information.push(newChildrenInfo)
},
removeChildren(removeId) {
const _idx = this.children_information.findIndex((item) => item.id === removeId)
if (_idx !== -1) {
this.children_information.splice(_idx, 1)
}
},
onChange(date, param, id) {
// if (param === 'graduation_year') {
// if (date === null) {
// this.educational_experience[index].graduation_year = null
// } else {
// this.educational_experience[index].graduation_year = moment(date).format('YYYY-MM-DD')
// }
// } else {
// if (date === null) {
// this.employeeFormData[param] = null
// } else {
// this.employeeFormData[param] = moment(date).format('YYYY-MM-DD')
// }
// }
if (date !== null) {
if (param === 'graduation_year') {
const _idx = this.educational_experience.findIndex((item) => item.id === id)
this.educational_experience[_idx].graduation_year = moment(date).format('YYYY-MM')
} else if (param === 'birth_date') {
const _idx = this.children_information.findIndex((item) => item.id === id)
this.children_information[_idx].birthday = moment(date).format('YYYY-MM-DD')
} else {
this.employeeFormData[param] = moment(date).format('YYYY-MM-DD')
}
}
},
async employeeModalHandleOk() {
if (this.attributes.includes('educational_experience')) {
this.employeeFormData.educational_experience = this.educational_experience
}
if (this.attributes.includes('children_information')) {
this.employeeFormData.children_information = this.children_information
}
// if (!this.employeeFormData.annual_leave) {
// this.employeeFormData.annual_leave = 0
// }
const getFormData = this.employeeFormData
getFormData.direct_supervisor_id = getFormData.direct_supervisor_id
? (getFormData.direct_supervisor_id + '').includes('-')
? +getFormData.direct_supervisor_id.split('-')[1]
: +getFormData.direct_supervisor_id
: 0
this.$refs.employeeFormData.validate(async (valid) => {
if (valid) {
if (this.type === 'add') {
await postEmployee(getFormData)
}
if (this.type === 'edit') {
await putEmployee(getFormData.employee_id, getFormData)
}
this.$message.success('操作成功')
this.$emit('refresh')
Bus.$emit('updataAllIncludeEmployees')
this.close()
} else {
this.$message.warning('检查您的输入是否正确!')
return false
}
})
},
},
}
</script>
<style lang="less" scoped>
.el-date-picker {
width: 100%;
height: 36px;
}
</style>

View File

@@ -0,0 +1,3 @@
// 用于树状递归组件的通信
import Vue from 'vue'
export default new Vue()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,140 @@
<template>
<a-modal
class="ops-modal"
v-loading="loading"
:title="title"
width="600px"
:append-to-body="true"
:close-on-click-modal="false"
:visible.sync="showDialog"
@cancel="hiddenView"
>
<div class="ops-modal-cropper-box">
<vueCropper
ref="cropper"
:can-move="true"
:auto-crop="true"
:fixed="true"
:img="cropperImg"
output-type="png"
@realTime="realTime"
v-bind="eidtImageOption"
/>
<div class="ops-modal-preview">
<div class="ops-modal-preview-name">预览</div>
<img
:style="{
width: eidtImageOption.previewWidth,
height: eidtImageOption.previewHeight,
border: '1px solid #f2f2f2',
}"
:src="previewImg"
class="ops-modal-preview-img"
/>
</div>
</div>
<div slot="footer" class="ops-modal-dialog-footer">
<a-button type="primary" @click="submitImage()">确定</a-button>
</div>
</a-modal>
</template>
<script type="text/javascript">
import { VueCropper } from 'vue-cropper'
export default {
name: 'EditImage', // 处理头像
components: {
VueCropper,
},
props: {
title: {
type: String,
default: '编辑头像',
},
show: {
type: Boolean,
default: false,
},
image: {
type: String,
default: '',
},
eidtImageOption: {
type: Object,
default: () => {},
},
},
data() {
return {
loading: false,
showDialog: false,
cropperImg: '',
previewImg: '',
}
},
computed: {},
watch: {
show: {
handler(val) {
this.showDialog = val
},
deep: true,
immediate: true,
},
image: function(val) {
this.cropperImg = val
},
},
mounted() {
this.cropperImg = this.image
},
methods: {
realTime(data) {
this.$refs.cropper.getCropData((cropperData) => {
this.previewImg = cropperData
})
},
submitImage() {
// 获取截图的blob数据
this.$refs.cropper.getCropBlob((data) => {
const form = new FormData()
form.append('file', data)
this.$emit('save', form)
this.hiddenView()
})
},
hiddenView() {
this.$emit('close')
},
},
}
</script>
<style lang="less" scoped>
.ops-modal {
.ops-modal-cropper-box {
position: relative;
width: 300px;
height: 300px;
}
.ops-modal-preview {
position: absolute;
bottom: 0;
left: 325px;
.ops-modal-preview-name {
margin-bottom: 8px;
font-size: 13px;
color: #666;
}
.ops-modal-preview-img {
display: block;
}
}
.ops-modal-content {
position: relative;
padding: 0 30px;
}
}
</style>

View File

@@ -0,0 +1,80 @@
<template>
<div>
<div :style="{ marginLeft: '10px'}">
<FilterComp
ref="filterComp"
:canSearchPreferenceAttrList="canSearchPreferenceAttrList"
:placement="placement"
@setExpFromFilter="setExpFromFilter"
>
<div slot="popover_item" class="search-form-bar-filter">
<a-icon :class="filterExp.length ? 'search-form-bar-filter-icon' : 'search-form-bar-filter-icon_selected'" type="filter"/>
条件过滤
<a-icon :class="filterExp.length ? 'search-form-bar-filter-icon' : 'search-form-bar-filter-icon_selected'" type="down"/>
</div>
</FilterComp>
</div>
</div>
</template>
<script>
import FilterComp from './settingFilterComp'
export default {
name: 'SearchForm',
components: {
FilterComp,
},
props: {
canSearchPreferenceAttrList: {
type: Array,
required: true,
default: () => [],
},
placement: {
type: String,
default: 'bottomLeft'
}
},
data() {
return {
filterExp: []
}
},
methods: {
setExpFromFilter(filterExp) {
// const regSort = /(?<=sort=).+/g
// const expSort = this.expression.match(regSort) ? this.expression.match(regSort)[0] : undefined
// let expression = ''
// if (filterExp) {
// expression = `q=${filterExp}`
// }
// if (expSort) {
// expression += `&sort=${expSort}`
// }
this.filterExp = filterExp
this.emitRefresh(filterExp)
},
emitRefresh(filterExp) {
this.$nextTick(() => {
this.$emit('refresh', filterExp)
})
},
},
}
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.search-form-bar-filter {
background-color: rgb(240, 245, 255);
.ops_display_wrapper();
.search-form-bar-filter-icon {
color: #custom_colors[color_1];
font-size: 12px;
}
.search-form-bar-filter-icon_selected{
color:#606266;
font-size: 12px;
}
}
</style>

View File

@@ -0,0 +1,67 @@
<template>
<treeselect
:multiple="false"
:options="departemntTreeSelectOption"
placeholder="请选择部门"
v-model="treeValue"
:normalizer="normalizer"
noChildrenText=""
noOptionsText=""
class="ops-setting-treeselect"
v-bind="$attrs"
appendToBody
:zIndex="1050"
/>
</template>
<script>
import Treeselect from '@riophae/vue-treeselect'
export default {
name: 'DepartmentTreeSelect',
components: { Treeselect },
model: {
prop: 'value',
event: 'change',
},
props: {
value: {
type: [String, Array, Number, null],
default: null,
},
},
data() {
return {
normalizer: (node) => {
if (node.sub_departments && node.sub_departments.length) {
return {
id: node.department_id,
label: node.department_name,
children: node.sub_departments,
}
}
return {
id: node.department_id,
label: node.department_name,
}
},
}
},
inject: ['provide_allTreeDepartment'],
computed: {
departemntTreeSelectOption() {
return this.provide_allTreeDepartment()
},
treeValue: {
get() {
return this.value
},
set(val) {
this.$emit('change', val)
return val
},
},
},
}
</script>
<style></style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,74 @@
<template>
<treeselect
:disable-branch-nodes="multiple ? false : true"
:multiple="multiple"
:options="employeeTreeSelectOption"
placeholder="请选择员工"
v-model="treeValue"
:max-height="200"
noChildrenText=""
noOptionsText=""
:class="className ? className: 'ops-setting-treeselect'"
value-consists-of="LEAF_PRIORITY"
:limit="20"
:limitText="(count) => `+ ${count}`"
v-bind="$attrs"
appendToBody
:zIndex="1050"
>
</treeselect>
</template>
<script>
import Treeselect from '@riophae/vue-treeselect'
import { formatOption } from '@/utils/util'
export default {
name: 'EmployeeTreeSelect',
components: {
Treeselect,
},
model: {
prop: 'value',
event: 'change',
},
props: {
value: {
type: [String, Array, Number, null],
default: null,
},
multiple: {
type: Boolean,
default: false,
},
className: {
type: String,
default: 'ops-setting-treeselect'
}
},
data() {
return {}
},
inject: ['provide_allTreeDepAndEmp'],
computed: {
treeValue: {
get() {
return this.value
},
set(val) {
this.$emit('change', val)
return val
},
},
allTreeDepAndEmp() {
return this.provide_allTreeDepAndEmp()
},
employeeTreeSelectOption() {
return formatOption(this.allTreeDepAndEmp)
},
},
methods: {},
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,37 @@
<template>
<a-modal title="关联员工" :visible="visible" @cancel="handleCancel" @ok="handleOK">
<EmployeeTreeSelect v-model="values" :multiple="true" />
</a-modal>
</template>
<script>
import EmployeeTreeSelect from './employeeTreeSelect.vue'
export default {
name: 'RelateEmployee',
components: { EmployeeTreeSelect },
data() {
return {
visible: false,
values: [],
}
},
methods: {
open() {
this.visible = true
},
handleCancel() {
this.visible = false
this.values = []
},
handleOK() {
this.$emit('relate', {
action: 'add',
employee_id_list: this.values.filter((item) => String(item).includes('-')).map((item) => item.split('-')[1]),
})
this.handleCancel()
},
},
}
</script>
<style></style>

View File

@@ -0,0 +1,33 @@
export const ruleTypeList = [
{ value: '&', label: '' },
{ value: '|', label: '' },
// { value: 'not', label: '' },
]
export const expList = [
{ value: 1, label: '等于' },
{ value: 2, label: '不等于' },
// { value: 'contain', label: '包含' },
// { value: '~contain', label: '不包含' },
// { value: 'start_with', label: '以...开始' },
// { value: '~start_with', label: '不以...开始' },
// { value: 'end_with', label: '以...结束' },
// { value: '~end_with', label: '不以...结束' },
{ value: 7, label: '为空' }, // 为空的定义有点绕
{ value: 8, label: '不为空' },
]
export const advancedExpList = [
// { value: 'in', label: 'in查询' },
// { value: '~in', label: '非in查询' },
// { value: 'range', label: '范围' },
// { value: '~range', label: '范围外' },
{ value: 'compare', label: '比较' },
]
export const compareTypeList = [
{ value: 5, label: '大于' },
// { value: '2', label: '>=' },
{ value: 6, label: '小于' },
// { value: '4', label: '<=' },
]

View File

@@ -0,0 +1,380 @@
<template>
<a-popover
v-model="visible"
trigger="click"
:placement="placement"
overlayClassName="table-filter"
@visibleChange="visibleChange"
>
<slot name="popover_item">
<a-button type="primary" ghost>条件过滤<a-icon type="filter"/></a-button>
</slot>
<template slot="content">
<svg
:style="{ position: 'absolute', top: '0', left: '0', width: '110px', height: '100%', zIndex: '-1' }"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
height="100%"
width="100%"
id="svgDom"
></svg>
<a-space :style="{ display: 'flex', marginBottom: '10px' }" v-for="(item, index) in ruleList" :key="item.id">
<div :style="{ width: '50px', height: '24px', position: 'relative' }">
<treeselect
v-if="index"
class="custom-treeselect"
:style="{ width: '50px', '--custom-height': '24px', position: 'absolute', top: '-17px', left: 0 }"
v-model="item.relation"
:multiple="false"
:clearable="false"
searchable
:options="ruleTypeList"
:normalizer="
(node) => {
return {
id: node.value,
label: node.label,
children: node.children,
}
}
"
>
</treeselect>
</div>
<treeselect
class="custom-treeselect"
:style="{ width: '130px', '--custom-height': '24px' }"
v-model="item.column"
:multiple="false"
:clearable="false"
searchable
:options="canSearchPreferenceAttrList"
:normalizer="
(node) => {
return {
id: node.value,
label: node.label,
children: node.children,
}
}
"
>value
<div
:title="node.label"
slot="option-label"
slot-scope="{ node }"
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
>
<!-- <ValueTypeMapIcon :attr="node.raw" /> -->
{{ node.label }}
</div>
<div
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
slot="value-label"
slot-scope="{ node }"
>
<!-- <ValueTypeMapIcon :attr="node.raw" /> -->
{{ node.label }}
</div>
</treeselect>
<treeselect
class="custom-treeselect"
:style="{ width: '100px', '--custom-height': '24px' }"
v-model="item.operator"
:multiple="false"
:clearable="false"
searchable
:options="[...expList, ...compareTypeList]"
:normalizer="
(node) => {
return {
id: node.value,
label: node.label,
children: node.children,
}
}
"
@select="(value) => handleChangeExp(value, item, index)"
>
</treeselect>
<treeselect
class="custom-treeselect"
:style="{ width: '175px', '--custom-height': '24px' }"
v-model="item.value"
:multiple="false"
:clearable="false"
searchable
v-if="isChoiceByProperty(item.column) && (item.operator === 1 || item.operator === 2)"
:options="getChoiceValueByProperty(item.column)"
placeholder="请选择"
:normalizer="
(node) => {
return {
id: node.value,
label: node.label,
children: node.children,
}
}
"
>
<div
:title="node.label"
slot="option-label"
slot-scope="{ node }"
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
>
{{ node.label }}
</div>
</treeselect>
<div v-else-if="item.column === 'direct_supervisor_id' && (item.operator === 1 || item.operator === 2)" style="width: 175px">
<EmployeeTreeSelect v-model="item.value" className="custom-treeselect"/>
</div>
<a-input
v-else-if="item.operator !== 7 && item.operator !== 8"
size="small"
v-model="item.value"
:placeholder="item.exp === 'in' || item.exp === '~in' ? '以 ; 分隔' : ''"
class="ops-input"
></a-input>
<!-- <div v-else :style="{ width: '175px' }"></div> -->
<a-tooltip title="复制">
<a class="operation" @click="handleCopyRule(item)"><a-icon type="copy"/></a>
</a-tooltip>
<a-tooltip title="删除">
<a class="operation" @click="handleDeleteRule(item)" :style="{ color: 'red' }"><a-icon type="delete"/></a>
</a-tooltip>
</a-space>
<div class="table-filter-add">
<a @click="handleAddRule">+ 新增</a>
</div>
<a-divider :style="{ margin: '10px 0' }" />
<div style="width:534px">
<a-space :style="{ display: 'flex', justifyContent: 'flex-end' }">
<a-button type="primary" size="small" @click="handleSubmit">确定</a-button>
<a-button size="small" @click="handleClear">清空</a-button>
</a-space>
</div>
</template>
</a-popover>
</template>
<script>
import _ from 'lodash'
import { v4 as uuidv4 } from 'uuid'
import Treeselect from '@riophae/vue-treeselect'
import { ruleTypeList, expList, advancedExpList, compareTypeList } from './constants'
import DepartmentTreeSelect from '../../components/departmentTreeSelect.vue'
import EmployeeTreeSelect from '../../components/employeeTreeSelect.vue'
export default {
name: 'FilterComp',
components: {
// ValueTypeMapIcon,
Treeselect,
DepartmentTreeSelect,
EmployeeTreeSelect },
props: {
canSearchPreferenceAttrList: {
type: Array,
required: true,
default: () => [],
},
regQ: {
type: String,
default: '(?<=q=).+(?=&)|(?<=q=).+$',
},
placement: {
type: String,
default: 'bottomRight',
},
},
data() {
return {
ruleTypeList,
expList,
advancedExpList,
compareTypeList,
visible: false,
ruleList: [],
filterExp: '',
}
},
inject: ['provide_allFlatEmployees'],
computed: {
allFlatEmployees() {
return this.provide_allFlatEmployees()
}
},
methods: {
visibleChange(open) {
// const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
const _exp = this.ruleList.length
? this.ruleList
: null
if (open && _exp) {
_exp.forEach((item, index) => {
if (item.column === 'direct_supervisor_id' && item.value) {
if (!(item.value + '').includes('-')) {
const _find = this.allFlatEmployees.find((v) => v.employee_id === item.value)
_exp[index].value = `${_find.department_id}-${item.value}`
}
}
})
this.ruleList = _exp
} else if (open) {
this.ruleList = [
{
id: uuidv4(),
relation: '&',
column: this.canSearchPreferenceAttrList[0].value,
operator: 1,
value: null,
},
]
}
},
handleAddRule() {
this.ruleList.push({
id: uuidv4(),
relation: '&',
column: this.canSearchPreferenceAttrList[0].value,
operator: 1,
value: null,
})
},
handleCopyRule(item) {
this.ruleList.push({ ...item, id: uuidv4() })
},
handleDeleteRule(item) {
const idx = this.ruleList.findIndex((r) => r.id === item.id)
if (idx > -1) {
this.ruleList.splice(idx, 1)
}
},
handleClear() {
this.ruleList = [
{
id: uuidv4(),
relation: '&',
column: this.canSearchPreferenceAttrList[0].value,
operator: 1,
value: null,
},
]
this.filterExp = []
this.visible = false
this.$emit('setExpFromFilter', this.filterExp)
},
handleSubmit() {
if (this.ruleList && this.ruleList.length) {
const getDataFromRuleList = this.ruleList
getDataFromRuleList.forEach((item, index) => {
if (item.column === 'direct_supervisor_id') {
getDataFromRuleList[index].value = item.value ? (item.value + '').includes('-') ? +item.value.split('-')[1] : +item.value : 0
}
})
getDataFromRuleList[0].relation = '&' // 增删后以防万一第一个不是and
this.$emit('setExpFromFilter', getDataFromRuleList)
} else {
this.$emit('setExpFromFilter', '')
}
this.visible = false
},
handleChangeExp({ value }, item, index) {
const _ruleList = _.cloneDeep(this.ruleList)
if (value === 7 || value === 8) {
_ruleList[index] = {
..._ruleList[index],
value: null,
operator: value
}
} else {
_ruleList[index] = {
..._ruleList[index],
operator: value,
}
}
this.ruleList = _ruleList
},
filterOption(input, option) {
return option.componentOptions.children[1].children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
},
// getExpListByProperty(column) {
// if (column) {
// const _find = this.canSearchPreferenceAttrList.find((item) => item.value === column)
// if (_find && ['0', '1', '3', '4', '5'].includes(_find.value_type)) {
// return [
// { value: 'is', label: '等于' },
// { value: '~is', label: '不等于' },
// { value: '~value', label: '为空' }, // 为空的定义有点绕
// { value: 'value', label: '不为空' },
// ]
// }
// return this.expList
// }
// return this.expList
// },
isChoiceByProperty(column) {
const _find = this.canSearchPreferenceAttrList.find((item) => item.value === column)
if (_find) {
return _find.is_choice
}
return false
},
getChoiceValueByProperty(column) {
const _find = this.canSearchPreferenceAttrList.find((item) => item.value === column)
if (_find) {
return _find.choice_value
}
return []
},
},
}
</script>
<style lang="less" scoped>
.table-filter {
.table-filter-add {
margin-top: 10px;
& > a {
padding: 2px 8px;
&:hover {
background-color: #f0faff;
border-radius: 5px;
}
}
}
.table-filter-extra-icon {
padding: 0px 2px;
&:hover {
display: inline-block;
border-radius: 5px;
background-color: #f0faff;
}
}
}
</style>
<style lang="less" scoped>
.table-filter-extra-operation {
.ant-popover-inner-content {
padding: 3px 4px;
.operation {
cursor: pointer;
width: 90px;
height: 30px;
line-height: 30px;
padding: 3px 4px;
border-radius: 5px;
transition: all 0.3s;
&:hover {
background-color: #f0faff;
}
> .anticon {
margin-right: 10px;
}
}
}
}
</style>

View File

@@ -0,0 +1,32 @@
<template>
<a-row>
<a-col :span="span">
<div class="ops-setting-spantitle"><slot></slot></div>
</a-col>
</a-row>
</template>
<script>
export default {
name: 'SpanTitle',
props: {
span: {
type: Number,
default: 3,
},
},
}
</script>
<style lang="less" scoped>
.ops-setting-spantitle {
height: 28px;
margin-bottom: 12px;
line-height: 28px;
padding-left: 24px;
border-radius: 0px 20px 20px 0px;
font-weight: 700;
color: #0637bf;
background-color: #e0e9ff;
}
</style>

View File

@@ -0,0 +1,367 @@
<template>
<div class="setting-person">
<div class="setting-person-left">
<div
@click="
() => {
$refs.personForm.clearValidate()
$nextTick(() => {
current = '1'
})
}
"
:class="{ 'setting-person-left-item': true, 'setting-person-left-item-selected': current === '1' }"
>
<ops-icon type="icon-shidi-yonghu" />个人信息
</div>
<div
@click="
() => {
$refs.personForm.clearValidate()
$nextTick(() => {
current = '2'
})
}
"
:class="{ 'setting-person-left-item': true, 'setting-person-left-item-selected': current === '2' }"
>
<a-icon type="unlock" theme="filled" />账号密码
</div>
</div>
<div class="setting-person-right">
<a-form-model
ref="personForm"
:model="form"
:rules="current === '1' ? rules1 : rules2"
:colon="false"
labelAlign="left"
:labelCol="{ span: 4 }"
:wrapperCol="{ span: 10 }"
>
<div v-show="current === '1'">
<a-form-model-item label="头像" :style="{ display: 'flex', alignItems: 'center' }">
<a-space>
<a-avatar v-if="form.avatar" :src="`/api/common-setting/v1/file/${form.avatar}`" :size="64"> </a-avatar>
<a-avatar v-else style="backgroundColor:#F0F5FF" :size="64">
<ops-icon type="icon-shidi-yonghu" :style="{ color: '#2F54EB' }" />
</a-avatar>
<a-upload
name="avatar"
:show-upload-list="false"
:customRequest="customRequest"
:before-upload="beforeUpload"
:style="{ width: '310px', height: '100px' }"
accept="png,jpg,jpeg"
>
<a-button type="primary" ghost size="small">更换头像</a-button>
</a-upload>
</a-space>
</a-form-model-item>
<a-form-model-item label="姓名" prop="nickname">
<a-input v-model="form.nickname" />
</a-form-model-item>
<a-form-model-item label="用户名">
<div class="setting-person-right-disabled">{{ form.username }}</div>
</a-form-model-item>
<a-form-model-item label="邮箱">
<div class="setting-person-right-disabled">{{ form.email }}</div>
</a-form-model-item>
<a-form-model-item label="直属上级">
<div class="setting-person-right-disabled">
{{ getDirectorName(allFlatEmployees, form.direct_supervisor_id) }}
</div>
</a-form-model-item>
<a-form-model-item label="性别">
<a-select v-model="form.sex">
<a-select-option value=""></a-select-option>
<a-select-option value=""></a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item label="手机号" prop="mobile">
<a-input v-model="form.mobile" />
</a-form-model-item>
<a-form-model-item label="部门">
<div class="setting-person-right-disabled">
{{ getDepartmentName(allFlatDepartments, form.department_id) }}
</div>
</a-form-model-item>
<a-form-model-item label="岗位">
<div class="setting-person-right-disabled">{{ form.position_name }}</div>
</a-form-model-item>
<a-form-model-item label="绑定信息">
<a-space>
<div :class="{ 'setting-person-bind': true, 'setting-person-bind-existed': form.wx_id }">
<ops-icon type="ops-setting-notice-wx" />
</div>
<div @click="handleBindWx" class="setting-person-bind-button">
{{ form.wx_id ? '重新绑定' : '绑定' }}
</div>
</a-space>
</a-form-model-item>
</div>
<div v-show="current === '2'">
<a-form-model-item label="新密码" prop="password1">
<a-input v-model="form.password1" />
</a-form-model-item>
<a-form-model-item label="确认密码" prop="password2">
<a-input v-model="form.password2" />
</a-form-model-item>
</div>
<div style="margin-right: 120px">
<a-form-model-item label=" ">
<a-button type="primary" @click="handleSave" :style="{ width: '100%' }">保存</a-button>
</a-form-model-item>
</div>
</a-form-model>
</div>
<EditImage
v-if="showEditImage"
:fixed-number="eidtImageOption.fixedNumber"
:show="showEditImage"
:image="editImage"
:title="eidtImageOption.title"
:preview-width="eidtImageOption.previewWidth"
:preview-height="eidtImageOption.previewHeight"
preview-radius="0"
width="550px"
save-button-title="确定"
@save="submitImage"
@close="showEditImage = false"
/>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
import { getAllDepartmentList, postImageFile } from '@/api/company'
import {
getEmployeeList,
getEmployeeByUid,
updateEmployeeByUid,
updatePasswordByUid,
bindWxByUid,
} from '@/api/employee'
import { getDepartmentName, getDirectorName } from '@/utils/util'
import EditImage from '../components/EditImage.vue'
export default {
name: 'Person',
components: { EditImage },
data() {
const validatePassword = (rule, value, callback) => {
if (!value) {
callback(new Error('请二次确认新密码'))
}
if (value !== this.form.password1) {
callback(new Error('两次输入密码不一致'))
}
callback()
}
return {
current: '1',
form: {},
rules1: {
nickname: [
{ required: true, whitespace: true, message: '请输入姓名', trigger: 'blur' },
{ max: 20, message: '字符数须小于20' },
],
mobile: [
{
pattern: /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/,
message: '请输入正确的手机号',
trigger: 'blur',
},
],
},
rules2: {
password1: [{ required: true, message: '请输入新密码', trigger: 'blur' }],
password2: [{ required: true, message: '两次输入密码不一致', trigger: 'blur', validator: validatePassword }],
},
allFlatEmployees: [],
allFlatDepartments: [],
showEditImage: false,
eidtImageOption: {
type: 'avatar',
fixedNumber: [4, 4],
title: '编辑头像',
previewWidth: '60px',
previewHeight: '60px',
},
editImage: null,
}
},
computed: {
...mapGetters(['uid']),
},
mounted() {
this.getAllFlatEmployees()
this.getAllFlatDepartment()
this.getEmployeeByUid()
},
methods: {
...mapActions(['GetInfo']),
getDepartmentName,
getDirectorName,
getEmployeeByUid() {
getEmployeeByUid(this.uid).then((res) => {
this.form = { ...res }
})
},
getAllFlatEmployees() {
getEmployeeList({ block_status: 0, page_size: 99999 }).then((res) => {
this.allFlatEmployees = res.data_list
})
},
getAllFlatDepartment() {
getAllDepartmentList({ is_tree: 0 }).then((res) => {
this.allFlatDepartments = res
})
},
async handleSave() {
await this.$refs.personForm.validate(async (valid) => {
if (valid) {
const { nickname, mobile, sex, avatar, password1 } = this.form
const params = { nickname, mobile, sex, avatar }
if (this.current === '1') {
await updateEmployeeByUid(this.uid, params).then((res) => {
this.$message.success('保存成功!')
this.getEmployeeByUid()
this.GetInfo()
})
} else {
await updatePasswordByUid(this.uid, { password: password1 }).then((res) => {
this.$message.success('保存成功!')
})
}
}
})
},
customRequest(file) {
const reader = new FileReader()
var self = this
reader.onload = function(e) {
let result
if (typeof e.target.result === 'object') {
// 把Array Buffer转化为blob 如果是base64不需要
result = window.URL.createObjectURL(new Blob([e.target.result]))
} else {
result = e.target.result
}
self.editImage = result
self.showEditImage = true
}
reader.readAsDataURL(file.file)
},
beforeUpload(file) {
const isLt2M = file.size / 1024 / 1024 < 2
if (!isLt2M) {
this.$message.error('图片大小不可超过2MB!')
}
return isLt2M
},
submitImage(file) {
postImageFile(file).then((res) => {
if (res.file_name) {
this.form.avatar = res.file_name
}
})
},
async handleBindWx() {
await this.$refs.personForm.validate(async (valid) => {
if (valid) {
const { nickname, mobile, sex, avatar } = this.form
const params = { nickname, mobile, sex, avatar }
await updateEmployeeByUid(this.uid, params)
bindWxByUid(this.uid)
.then(() => {
this.$message.success('绑定成功!')
})
.finally(() => {
this.getEmployeeByUid()
this.GetInfo()
})
}
})
},
},
}
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.setting-person {
display: flex;
flex-direction: row;
.setting-person-left {
width: 200px;
height: 400px;
margin-right: 24px;
background-color: #fff;
border-radius: 15px;
padding-top: 15px;
.setting-person-left-item {
cursor: pointer;
padding: 10px 20px;
color: #a5a9bc;
border-left: 4px solid #fff;
margin-bottom: 5px;
&:hover {
.ops_popover_item_selected();
border-color: #custom_colors[color_1];
}
> i {
margin-right: 10px;
}
}
.setting-person-left-item-selected {
.ops_popover_item_selected();
border-color: #custom_colors[color_1];
}
}
.setting-person-right {
width: 800px;
height: 700px;
background-color: #fff;
border-radius: 15px;
padding: 24px 48px;
.setting-person-right-disabled {
background-color: #custom_colors[color_2];
border-radius: 4px;
height: 30px;
line-height: 30px;
margin-top: 4px;
padding: 0 10px;
color: #a5a9bc;
}
.setting-person-bind {
width: 40px;
height: 40px;
background: #a5a9bc;
border-radius: 4px;
color: #fff;
font-size: 30px;
text-align: center;
}
.setting-person-bind-existed {
background: #008cee;
}
.setting-person-bind-button {
height: 40px;
width: 72px;
background: #f0f5ff;
border-radius: 4px;
padding: 0 8px;
text-align: center;
cursor: pointer;
}
}
}
</style>
<style lang="less">
.setting-person-right .ant-form-item {
margin-bottom: 12px;
display: flex;
justify-content: center;
align-items: center;
}
</style>