前后端全面升级

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,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>