perf: 优化会员选择器

This commit is contained in:
kuaifan 2023-06-13 23:16:28 +08:00
parent ae4be9e08e
commit e507c148ca
23 changed files with 717 additions and 663 deletions

View File

@ -1,321 +0,0 @@
<template>
<div :class="['common-user', maxHiddenClass]">
<Select
ref="select"
v-model="selects"
:transfer="transfer"
:placeholder="placeholder"
:size="size"
:loading="loadIng > 0"
:loading-text="$L('加载中...')"
:default-label="value"
:default-event-object="true"
:multiple-max="multipleMax"
:multiple-uncancelable="uncancelable"
:remote-method="remoteMethod"
@on-query-change="searchUser"
@on-open-change="openChange"
multiple
filterable
:search-in-option="windowTouch"
:search-placeholder="$L('输入关键词搜索')"
transfer-class-name="common-user-transfer">
<div v-if="multipleMax" slot="drop-prepend" class="user-drop-prepend">
<div class="user-drop-text">{{selects.length}} / {{multipleMax}}</div>
<Checkbox class="user-drop-check" v-model="multipleCheck" @on-change="onMultipleChange"></Checkbox>
</div>
<slot name="option-prepend"></slot>
<Option
v-for="(item, key) in list"
:value="item.userid"
:key="key"
:key-value="`${item.email}|${item.pinyin}`"
:label="item.nickname"
:avatar="item.userimg"
:disabled="isDisabled(item.userid)">
<div class="user-input-option">
<div class="user-input-avatar"><EAvatar class="avatar" :src="item.userimg"/></div>
<div v-if="item.bot" class="taskfont user-input-bot">&#xe68c;</div>
<div v-if="item.disable_at" class="user-input-disable">[{{$L('离职')}}]</div>
<div class="user-input-nickname">{{ item.nickname }}</div>
<div class="user-input-userid">ID: {{ item.userid }}</div>
</div>
</Option>
</Select>
<div v-if="loadIng > 0" class="common-user-loading"><Loading/></div>
</div>
</template>
<script>
import {Store} from 'le5le-store';
export default {
name: 'UserInput',
props: {
value: {
type: [String, Number, Array],
default: ''
},
uncancelable: {
type: Array,
default: () => {
return [];
}
},
disabledChoice: {
type: Array,
default: () => {
return [];
}
},
placeholder: {
default: ''
},
size: {
default: 'default'
},
transfer: {
type: Boolean,
default: true
},
multipleMax: {
type: Number,
},
maxHiddenInput: {
type: Boolean,
default: true
},
maxHiddenSelect: {
type: Boolean,
default: false
},
projectId: {
type: Number,
default: 0
},
noProjectId: {
type: Number,
default: 0
},
dialogId: {
type: Number,
default: 0
},
showBot: {
type: Boolean,
default: false
},
showDisable: {
type: Boolean,
default: false
},
},
data() {
return {
loadIng: 0,
selects: [],
list: [],
multipleCheck: false,
searchKey: null,
searchHistory: [],
subscribe: null,
}
},
mounted() {
this.subscribe = Store.subscribe('cacheUserActive', (data) => {
let index = this.list.findIndex(({userid}) => userid == data.userid);
if (index > -1) {
this.$set(this.list, index, Object.assign({}, this.list[index], data));
this.handleSelectData();
}
});
},
beforeDestroy() {
if (this.subscribe) {
this.subscribe.unsubscribe();
this.subscribe = null;
}
},
computed: {
maxHiddenClass() {
const {multipleMax, maxHiddenInput, selects} = this;
if (multipleMax && maxHiddenInput) {
if (selects.length >= multipleMax) {
return 'hidden-input'
}
}
return '';
}
},
watch: {
value: {
handler() {
const tmpId = this._tmpId = $A.randomString(6)
setTimeout(() => {
if (tmpId === this._tmpId) this.valueChange()
}, 10)
},
immediate: true,
},
selects(val) {
this.$emit('input', val)
if (this.maxHiddenSelect && val.length >= this.maxHiddenSelect && this.$refs.select) {
this.$refs.select.hideMenu()
}
this.calcMultipleSelect()
}
},
methods: {
searchUser(key) {
if (typeof key !== "string") key = "";
this.searchKey = key;
//
const history = this.searchHistory.find(item => item.key == key);
if (history) {
this.list = history.data
this.calcMultipleSelect()
}
//
if (!history) this.loadIng++;
setTimeout(() => {
if (this.searchKey != key) {
if (!history) this.loadIng--;
return;
}
this.$store.dispatch("call", {
url: 'users/search',
data: {
keys: {
key,
project_id: this.projectId,
no_project_id: this.noProjectId,
dialog_id: this.dialogId,
bot: this.showBot && key ? 2 : 0,
disable: this.showDisable && key ? 2 : 0,
},
take: 50
},
}).then(({data}) => {
this.list = data
this.calcMultipleSelect()
//
const index = this.searchHistory.findIndex(item => item.key == key);
const tmpData = {
key,
data,
time: $A.Time()
};
if (index > -1) {
this.searchHistory.splice(index, 1, tmpData)
} else {
this.searchHistory.push(tmpData)
}
}).catch(({msg}) => {
this.list = []
this.calcMultipleSelect()
$A.messageWarning(msg)
}).finally(_ => {
if (!history) this.loadIng--;
});
}, this.searchHistory.length > 0 ? 300 : 0)
},
isDisabled(userid) {
if (this.disabledChoice.length === 0) {
return false;
}
return this.disabledChoice.includes(userid)
},
openChange(show) {
if (show) {
this.$nextTick(this.searchUser);
}
this.calcMultipleSelect()
},
remoteMethod() {
//
},
valueChange() {
if (this.selects == this.value) {
return
}
if ($A.isArray(this.value)) {
this.selects = $A.cloneJSON(this.value);
} else if (this.value) {
this.selects = [this.value]
} else {
this.selects = [];
}
this.selects.some(userid => {
if (!this.list.find(item => item.userid == userid)) {
this.list.push({userid, nickname: userid})
this.calcMultipleSelect()
this.$store.dispatch("getUserBasic", {userid})
}
})
},
handleSelectData() {
this.__handleSelectTimeout && clearTimeout(this.__handleSelectTimeout);
this.__handleSelectTimeout = setTimeout(() => {
if (!this.$refs.select) {
return;
}
const list = this.$refs.select.getValue();
list && list.some(option => {
const data = this.list.find(({userid}) => userid == option.value)
if (data) {
this.$set(option, 'label', data.nickname)
this.$set(option, 'avatar', data.userimg)
}
})
}, 100);
},
calcMultipleSelect() {
if (this.multipleMax && this.list.length > 0) {
this.calcMultipleTime && clearTimeout(this.calcMultipleTime)
this.calcMultipleTime = setTimeout(_ => {
let allSelected = true
this.$refs.select.selectOptions.some(({componentInstance}) => {
if (!this.selects.includes(componentInstance.value)) {
allSelected = false
}
})
this.multipleCheck = allSelected
}, 10)
} else {
this.multipleCheck = false
}
},
onMultipleChange(val) {
if (val) {
let optional = this.multipleMax - this.selects.length
this.$refs.select.selectOptions.some(({componentInstance}) => {
if (this.multipleMax && optional <= 0) {
this.$nextTick(_ => {
$A.messageWarning("已超过最大选择数量")
this.multipleCheck = false
})
return true
}
if (!this.selects.includes(componentInstance.value)) {
componentInstance.select()
optional--
}
})
} else {
this.selects = []
}
}
}
};
</script>

View File

@ -0,0 +1,381 @@
<template>
<div class="common-user-select" :class="{'select-border': border}">
<ul @click="onSelect(true)" :style="warpStyle">
<li v-for="userid in values">
<UserAvatar :userid="userid" :size="avatarSize" :show-icon="avatarIcon" :show-name="avatarName" tooltip-disabled/>
</li>
<li v-if="addIcon || values.length === 0" class="add-icon" :style="addStyle" @click.stop="onSelect"></li>
</ul>
<Modal
v-model="showModal"
:mask-closable="false"
class-name="common-user-select-modal"
:title="localTitle"
:fullscreen="windowWidth < 576">
<div class="user-modal-search">
<Input v-model="searchKey" :placeholder="localPlaceholder" clearable>
<div class="search-pre" slot="prefix">
<Loading v-if="loadIng > 0"/>
<Icon v-else type="ios-search" />
</div>
</Input>
</div>
<Scrollbar class="user-modal-list">
<ul>
<li
v-for="item in lists"
:class="{
selected: selects.includes(item.userid),
disabled: inUncancelable(item.userid) || isDisabled(item.userid)
}"
@click="selectUser(item)">
<Icon v-if="selects.includes(item.userid)" class="user-modal-icon" type="ios-checkmark-circle" />
<Icon v-else class="user-modal-icon" type="ios-radio-button-off" />
<UserAvatar class="user-modal-avatar" :userid="item.userid" show-name tooltip-disabled/>
<div class="user-modal-userid">ID: {{item.userid}}</div>
</li>
</ul>
</Scrollbar>
<div v-if="multipleMax" class="user-modal-multiple">
<Checkbox class="multiple-check" v-model="multipleCheck" @on-change="onMultipleChange" :disabled="lists.length === 0">{{$L(multipleCheck ? '取消全选' : '全选')}}</Checkbox>
<div class="multiple-text">
<span>{{$L('最多只能选择' + multipleMax + '个')}}</span>
<em v-if="selects.length">({{$L(`已选${selects.length}`)}})</em>
</div>
</div>
<div slot="footer" class="adaption">
<Button type="default" :loading="submittIng > 0" @click="showModal=false">{{$L('取消')}}</Button>
<Button type="primary" :loading="submittIng > 0" @click="onSubmit">{{$L('确定')}}</Button>
</div>
</Modal>
</div>
</template>
<script>
export default {
name: 'UserSelect',
props: {
value: {
type: [String, Number, Array],
default: () => {
return [];
}
},
//
uncancelable: {
type: Array,
default: () => {
return [];
}
},
//
disabledChoice: {
type: Array,
default: () => {
return [];
}
},
// ID
projectId: {
type: Number,
default: 0
},
// ID
noProjectId: {
type: Number,
default: 0
},
// ID
dialogId: {
type: Number,
default: 0
},
//
showBot: {
type: Boolean,
default: false
},
//
showDisable: {
type: Boolean,
default: false
},
//
multipleMax: {
type: Number,
},
//
avatarSize: {
type: Number,
default: 28
},
//
avatarIcon: {
type: Boolean,
default: true
},
//
avatarName: {
type: Boolean,
default: false
},
// true
addIcon: {
type: Boolean,
default: true
},
//
onlyAddIconClick: {
type: Boolean,
default: false
},
//
border: {
type: Boolean,
default: false
},
//
title: {
type: String,
},
//
placeholder: {
type: String,
},
//
beforeSubmit: Function
},
data() {
return {
loadIng: 0,
submittIng: 0,
values: [],
lists: [],
selects: [],
showModal: false,
multipleCheck: false,
searchTimer: null,
searchKey: null,
searchHistory: [],
}
},
watch: {
value: {
handler(value) {
if (typeof value === 'number') {
this.$emit('input', value > 0 ? [value] : [])
} else if (typeof value === 'string') {
value = value.indexOf(',') > -1 ? value.split(',') : [value]
this.$emit('input', value.map(item => $A.runNum(item)).filter(item => item > 0))
}
this.values = value
},
immediate: true
},
showModal(value) {
if (value) {
this.searchUser()
} else {
this.searchKey = ""
}
},
searchKey() {
this.searchUser()
},
'lists.length'() {
this.calcMultiple()
},
'selects.length'() {
this.calcMultiple()
},
},
computed: {
warpStyle() {
if (!this.onlyAddIconClick) {
return {
cursor: 'pointer'
}
}
},
addStyle() {
return {
width: this.avatarSize + 'px',
height: this.avatarSize + 'px',
}
},
localTitle() {
if (this.title === undefined) {
return this.$L('选择会员')
} else {
return this.title;
}
},
localPlaceholder() {
if (this.placeholder === undefined) {
return this.$L('搜索会员')
} else {
return this.placeholder;
}
}
},
methods: {
searchUser() {
if (!this.showModal) {
return
}
//
let key = this.searchKey;
const history = this.searchHistory.find(item => item.key == key);
if (history) {
this.lists = history.data
}
//
if (this.searchTimer) {
clearTimeout(this.searchTimer);
}
this.searchTimer = setTimeout(() => {
if (this.searchKey != key) {
return;
}
setTimeout(() => {
this.loadIng++;
}, 300)
this.$store.dispatch("call", {
url: 'users/search',
data: {
keys: {
key,
project_id: this.projectId,
no_project_id: this.noProjectId,
dialog_id: this.dialogId,
bot: this.showBot && key ? 2 : 0,
disable: this.showDisable && key ? 2 : 0,
},
take: 50
},
}).then(({data}) => {
this.lists = data
//
const index = this.searchHistory.findIndex(item => item.key == key);
const tmpData = {key, data, time: $A.Time()};
if (index > -1) {
this.searchHistory.splice(index, 1, tmpData)
} else {
this.searchHistory.push(tmpData)
}
}).catch(({msg}) => {
this.lists = []
$A.messageWarning(msg)
}).finally(_ => {
this.loadIng--;
});
}, this.searchHistory.length > 0 ? 300 : 0)
},
onSelect(warp = false) {
if (warp === true) {
if (this.onlyAddIconClick) {
return
}
}
this.selects = $A.cloneJSON(this.values)
this.showModal = true
},
onSubmit() {
const clone = $A.cloneJSON(this.values)
this.values = $A.cloneJSON(this.selects)
this.$emit('input', this.values)
if (!this.beforeSubmit) {
this.showModal = false
return
}
const before = this.beforeSubmit();
if (before && before.then) {
this.submittIng++
before.then(() => {
this.showModal = false
}).catch(() => {
this.values = clone
this.$emit('input', this.values)
}).finally(() => {
this.submittIng--
})
} else {
this.showModal = false
}
},
onMultipleChange(check) {
if (check) {
let optional = this.multipleMax - this.selects.length
this.lists.some(item => {
if (this.inUncancelable(item.userid)) {
return false
}
if (this.isDisabled(item.userid)) {
return false
}
if (optional <= 0) {
$A.messageWarning("已超过最大选择数量")
return true
}
if (!this.selects.includes(item.userid)) {
this.selects.push(item.userid)
optional--
}
})
this.calcMultiple()
} else {
this.selects = $A.cloneJSON(this.uncancelable)
}
},
calcMultiple() {
this.$nextTick(() => {
this.multipleCheck = this.lists.length > 0 && this.lists.filter(item => this.selects.includes(item.userid)).length === this.lists.length;
})
},
selectUser(item) {
if (this.selects.includes(item.userid)) {
if (this.inUncancelable(item.userid)) {
return
}
this.selects = this.selects.filter(userid => userid != item.userid)
} else {
if (this.isDisabled(item.userid)) {
return
}
if (this.multipleMax && this.selects.length >= this.multipleMax) {
$A.messageWarning("已超过最大选择数量")
return
}
this.selects.push(item.userid)
}
},
inUncancelable(value) {
if (this.uncancelable.length === 0) {
return false;
}
return this.uncancelable.includes(value);
},
isDisabled(userid) {
if (this.disabledChoice.length === 0) {
return false;
}
return this.disabledChoice.includes(userid)
},
}
};
</script>

View File

@ -71,8 +71,8 @@
</DropdownItem> </DropdownItem>
<DropdownItem name="exportTask">{{$L('导出任务统计')}}</DropdownItem> <DropdownItem name="exportTask">{{$L('导出任务统计')}}</DropdownItem>
<DropdownItem name="exportOverdueTask">{{$L('导出超期任务')}}</DropdownItem> <DropdownItem name="exportOverdueTask">{{$L('导出超期任务')}}</DropdownItem>
<DropdownItem name="exportCheckin">{{$L('导出签到数据')}}</DropdownItem>
<DropdownItem name="exportApprove">{{$L('导出审批数据')}}</DropdownItem> <DropdownItem name="exportApprove">{{$L('导出审批数据')}}</DropdownItem>
<DropdownItem name="exportCheckin">{{$L('导出签到数据')}}</DropdownItem>
</DropdownMenu> </DropdownMenu>
</Dropdown> </Dropdown>
<!-- 其他菜单 --> <!-- 其他菜单 -->

View File

@ -53,10 +53,8 @@
} }
</style> </style>
<script> <script>
import UserInput from "../../../components/UserInput";
export default { export default {
name: "ApproveExport", name: "ApproveExport",
components: {UserInput},
props: { props: {
value: { value: {
type: Boolean, type: Boolean,

View File

@ -5,7 +5,7 @@
:mask-closable="false"> :mask-closable="false">
<Form ref="export" :model="formData" label-width="auto" @submit.native.prevent> <Form ref="export" :model="formData" label-width="auto" @submit.native.prevent>
<FormItem :label="$L('导出成员')"> <FormItem :label="$L('导出成员')">
<UserInput v-model="formData.userid" :multiple-max="100" show-disable :placeholder="$L('请选择成员')"/> <UserSelect v-model="formData.userid" :multiple-max="100" avatar-name show-disable :title="$L('请选择成员')"/>
<div class="form-tip">{{$L('每次最多选择导出100个成员')}}</div> <div class="form-tip">{{$L('每次最多选择导出100个成员')}}</div>
</FormItem> </FormItem>
<FormItem :label="$L('签到日期')"> <FormItem :label="$L('签到日期')">
@ -60,10 +60,10 @@
} }
</style> </style>
<script> <script>
import UserInput from "../../../components/UserInput"; import UserSelect from "../../../components/UserSelect.vue";
export default { export default {
name: "CheckinExport", name: "CheckinExport",
components: {UserInput}, components: {UserSelect},
props: { props: {
value: { value: {
type: Boolean, type: Boolean,

View File

@ -41,7 +41,7 @@
:mask-closable="false"> :mask-closable="false">
<Form :model="addData" label-width="auto" @submit.native.prevent> <Form :model="addData" label-width="auto" @submit.native.prevent>
<FormItem prop="userids" :label="$L('新增成员')"> <FormItem prop="userids" :label="$L('新增成员')">
<UserInput v-model="addData.userids" :disabledChoice="addData.disabledChoice" :multiple-max="100" show-bot :placeholder="$L('选择成员')"/> <UserSelect v-model="addData.userids" :disabledChoice="addData.disabledChoice" :multiple-max="100" show-bot :title="$L('选择成员')"/>
<div v-if="dialogData.group_type === 'department'" class="form-tip">{{$L('此操作仅加入群成员并不会加入部门')}}</div> <div v-if="dialogData.group_type === 'department'" class="form-tip">{{$L('此操作仅加入群成员并不会加入部门')}}</div>
<div v-else-if="dialogData.group_type === 'project'" class="form-tip">{{$L('此操作仅加入群成员并不会加入项目')}}</div> <div v-else-if="dialogData.group_type === 'project'" class="form-tip">{{$L('此操作仅加入群成员并不会加入项目')}}</div>
<div v-else-if="dialogData.group_type === 'task'" class="form-tip">{{$L('此操作仅加入群成员并不会加入任务负责人')}}</div> <div v-else-if="dialogData.group_type === 'task'" class="form-tip">{{$L('此操作仅加入群成员并不会加入任务负责人')}}</div>
@ -57,11 +57,11 @@
<script> <script>
import {mapState} from "vuex"; import {mapState} from "vuex";
import UserInput from "../../../components/UserInput"; import UserSelect from "../../../components/UserSelect.vue";
export default { export default {
name: "DialogGroupInfo", name: "DialogGroupInfo",
components: {UserInput}, components: {UserSelect},
props: { props: {
dialogId: { dialogId: {
type: Number, type: Number,

View File

@ -32,20 +32,20 @@
</Option> </Option>
</Select> </Select>
</FormItem> </FormItem>
<FormItem prop="userids" :label="$L('指定成员')"> <FormItem prop="userids" :label="`(${$L('或')}) ${$L('指定成员')}`">
<UserInput v-model="value.userids" :multiple-max="20" :placeholder="`(${$L('或')}) ${$L('选择转发指定成员')}`"/> <UserSelect v-model="value.userids" :multiple-max="20" :avatar-size="24" :title="$L('选择转发指定成员')" border/>
</FormItem> </FormItem>
</Form> </Form>
</template> </template>
<script> <script>
import UserInput from "../../../components/UserInput";
import {mapState} from "vuex"; import {mapState} from "vuex";
import UserSelect from "../../../components/UserSelect.vue";
export default { export default {
name: "DialogSelect", name: "DialogSelect",
components: {UserInput}, components: {UserSelect},
props: { props: {
value: { value: {
type: Object, type: Object,

View File

@ -335,12 +335,12 @@
<FormItem prop="avatar" :label="$L('群头像')"> <FormItem prop="avatar" :label="$L('群头像')">
<ImgUpload v-model="createGroupData.avatar" :num="1" :width="512" :height="512" :whcut="1"/> <ImgUpload v-model="createGroupData.avatar" :num="1" :width="512" :height="512" :whcut="1"/>
</FormItem> </FormItem>
<FormItem prop="userids" :label="$L('群成员')">
<UserSelect v-model="createGroupData.userids" :uncancelable="createGroupData.uncancelable" :multiple-max="100" show-bot :title="$L('选择项目成员')"/>
</FormItem>
<FormItem prop="chat_name" :label="$L('群名称')"> <FormItem prop="chat_name" :label="$L('群名称')">
<Input v-model="createGroupData.chat_name" :placeholder="$L('输入群名称(选填)')"/> <Input v-model="createGroupData.chat_name" :placeholder="$L('输入群名称(选填)')"/>
</FormItem> </FormItem>
<FormItem prop="userids" :label="$L('群成员')">
<UserInput v-model="createGroupData.userids" :uncancelable="createGroupData.uncancelable" :multiple-max="100" show-bot :placeholder="$L('选择项目成员')"/>
</FormItem>
</Form> </Form>
<div slot="footer" class="adaption"> <div slot="footer" class="adaption">
<Button type="default" @click="createGroupShow=false">{{$L('取消')}}</Button> <Button type="default" @click="createGroupShow=false">{{$L('取消')}}</Button>
@ -397,6 +397,7 @@
<RadioGroup v-model="todoSettingData.type"> <RadioGroup v-model="todoSettingData.type">
<Radio label="all">{{$L('所有成员')}}</Radio> <Radio label="all">{{$L('所有成员')}}</Radio>
<Radio label="user">{{$L('指定成员')}}</Radio> <Radio label="user">{{$L('指定成员')}}</Radio>
<br/>
<Radio v-if="todoSettingData.my_id" label="my"> <Radio v-if="todoSettingData.my_id" label="my">
<div class="dialog-wrapper-todo"> <div class="dialog-wrapper-todo">
<div> <div>
@ -414,8 +415,8 @@
</Radio> </Radio>
</RadioGroup> </RadioGroup>
</FormItem> </FormItem>
<FormItem v-if="todoSettingData.type === 'user'" prop="userids"> <FormItem prop="userids" :label="$L('指定成员')" v-if="todoSettingData.type === 'user'">
<UserInput v-model="todoSettingData.userids" :dialog-id="dialogId" :placeholder="$L('选择指定成员')"/> <UserSelect v-model="todoSettingData.userids" :dialog-id="dialogId" :title="$L('选择指定成员')"/>
</FormItem> </FormItem>
</Form> </Form>
<div slot="footer" class="adaption"> <div slot="footer" class="adaption">
@ -439,7 +440,7 @@
:mask-closable="false"> :mask-closable="false">
<Form :model="groupTransferData" label-width="auto" @submit.native.prevent> <Form :model="groupTransferData" label-width="auto" @submit.native.prevent>
<FormItem prop="userid" :label="$L('新的群主')"> <FormItem prop="userid" :label="$L('新的群主')">
<UserInput v-model="groupTransferData.userid" :disabledChoice="groupTransferData.disabledChoice" :multiple-max="1" max-hidden-select :placeholder="$L('选择新的群主')"/> <UserSelect v-model="groupTransferData.userid" :disabledChoice="groupTransferData.disabledChoice" :multiple-max="1" :title="$L('选择新的群主')"/>
</FormItem> </FormItem>
</Form> </Form>
<div slot="footer" class="adaption"> <div slot="footer" class="adaption">
@ -509,7 +510,6 @@
import {mapGetters, mapState} from "vuex"; import {mapGetters, mapState} from "vuex";
import DialogItem from "./DialogItem"; import DialogItem from "./DialogItem";
import DialogUpload from "./DialogUpload"; import DialogUpload from "./DialogUpload";
import UserInput from "../../../components/UserInput";
import DrawerOverlay from "../../../components/DrawerOverlay"; import DrawerOverlay from "../../../components/DrawerOverlay";
import DialogGroupInfo from "./DialogGroupInfo"; import DialogGroupInfo from "./DialogGroupInfo";
import DialogRespond from "./DialogRespond"; import DialogRespond from "./DialogRespond";
@ -522,10 +522,12 @@ import ImgUpload from "../../../components/ImgUpload.vue";
import {choiceEmojiOne} from "./ChatInput/one"; import {choiceEmojiOne} from "./ChatInput/one";
import ApproveDetails from "../../../pages/manage/approve/details.vue"; import ApproveDetails from "../../../pages/manage/approve/details.vue";
import UserSelect from "../../../components/UserSelect.vue";
export default { export default {
name: "DialogWrapper", name: "DialogWrapper",
components: { components: {
UserSelect,
ImgUpload, ImgUpload,
DialogSelect, DialogSelect,
DialogRespond, DialogRespond,
@ -534,7 +536,6 @@ export default {
ChatInput, ChatInput,
DialogGroupInfo, DialogGroupInfo,
DrawerOverlay, DrawerOverlay,
UserInput,
DialogUpload, DialogUpload,
ApproveDetails ApproveDetails
}, },

View File

@ -21,7 +21,7 @@
<Input v-model="addData.name" :maxlength="50" :placeholder="$L('选填')"/> <Input v-model="addData.name" :maxlength="50" :placeholder="$L('选填')"/>
</FormItem> </FormItem>
<FormItem prop="userids" :label="$L('邀请成员')"> <FormItem prop="userids" :label="$L('邀请成员')">
<UserInput v-model="addData.userids" :uncancelable="[userId]" :multiple-max="20" :placeholder="$L('选择邀请成员')"/> <UserSelect v-model="addData.userids" :uncancelable="[userId]" :multiple-max="20" :title="$L('选择邀请成员')"/>
</FormItem> </FormItem>
</template> </template>
<FormItem prop="tracks"> <FormItem prop="tracks">
@ -103,7 +103,7 @@
:mask-closable="false"> :mask-closable="false">
<Form ref="invitationForm" :model="invitationData" label-width="auto" @submit.native.prevent> <Form ref="invitationForm" :model="invitationData" label-width="auto" @submit.native.prevent>
<FormItem prop="userids" :label="$L('邀请成员')"> <FormItem prop="userids" :label="$L('邀请成员')">
<UserInput v-model="invitationData.userids" :multiple-max="20" :placeholder="$L('选择邀请成员')"/> <UserSelect v-model="invitationData.userids" :multiple-max="20" :title="$L('选择邀请成员')"/>
</FormItem> </FormItem>
</Form> </Form>
<div slot="footer" class="adaption"> <div slot="footer" class="adaption">
@ -115,14 +115,14 @@
</template> </template>
<script> <script>
import UserInput from "../../../components/UserInput";
import {Store} from "le5le-store"; import {Store} from "le5le-store";
import MeetingPlayer from "./MeetingPlayer"; import MeetingPlayer from "./MeetingPlayer";
import DragBallComponent from "../../../components/DragBallComponent"; import DragBallComponent from "../../../components/DragBallComponent";
import UserSelect from "../../../components/UserSelect.vue";
export default { export default {
name: "MeetingManager", name: "MeetingManager",
components: {DragBallComponent, MeetingPlayer, UserInput}, components: {UserSelect, DragBallComponent, MeetingPlayer},
data() { data() {
return { return {
loadIng: 0, loadIng: 0,

View File

@ -349,7 +349,7 @@
:mask-closable="false"> :mask-closable="false">
<Form :model="userData" label-width="auto" @submit.native.prevent> <Form :model="userData" label-width="auto" @submit.native.prevent>
<FormItem prop="userids" :label="$L('项目成员')"> <FormItem prop="userids" :label="$L('项目成员')">
<UserInput v-model="userData.userids" :uncancelable="userData.uncancelable" :multiple-max="100" :placeholder="$L('选择项目成员')"/> <UserSelect v-model="userData.userids" :uncancelable="userData.uncancelable" :multiple-max="100" :title="$L('选择项目成员')"/>
</FormItem> </FormItem>
</Form> </Form>
<div slot="footer" class="adaption"> <div slot="footer" class="adaption">
@ -414,8 +414,8 @@
:title="$L('移交项目')" :title="$L('移交项目')"
:mask-closable="false"> :mask-closable="false">
<Form :model="transferData" label-width="auto" @submit.native.prevent> <Form :model="transferData" label-width="auto" @submit.native.prevent>
<FormItem prop="owner_userid" :label="$L('项目负责人')"> <FormItem prop="owner_userid" :label="$L('项目负责人')">
<UserInput v-model="transferData.owner_userid" :multiple-max="1" max-hidden-select :placeholder="$L('选择项目负责人')"/> <UserSelect v-model="transferData.owner_userid" :multiple-max="1" :title="$L('选择项目负责人')"/>
</FormItem> </FormItem>
</Form> </Form>
<div slot="footer" class="adaption"> <div slot="footer" class="adaption">
@ -464,7 +464,6 @@ import Draggable from 'vuedraggable'
import TaskPriority from "./TaskPriority"; import TaskPriority from "./TaskPriority";
import {mapGetters, mapState} from "vuex"; import {mapGetters, mapState} from "vuex";
import {Store} from 'le5le-store'; import {Store} from 'le5le-store';
import UserInput from "../../../components/UserInput";
import TaskAddSimple from "./TaskAddSimple"; import TaskAddSimple from "./TaskAddSimple";
import TaskRow from "./TaskRow"; import TaskRow from "./TaskRow";
import TaskArchived from "./TaskArchived"; import TaskArchived from "./TaskArchived";
@ -475,15 +474,17 @@ import TaskMenu from "./TaskMenu";
import TaskDeleted from "./TaskDeleted"; import TaskDeleted from "./TaskDeleted";
import ProjectGantt from "./ProjectGantt"; import ProjectGantt from "./ProjectGantt";
import MarkdownPreviewNostyle from "../../../components/MDEditor/components/preview/nostyle.vue"; import MarkdownPreviewNostyle from "../../../components/MDEditor/components/preview/nostyle.vue";
import UserSelect from "../../../components/UserSelect.vue";
export default { export default {
name: "ProjectPanel", name: "ProjectPanel",
components: { components: {
UserSelect,
MarkdownPreviewNostyle, MarkdownPreviewNostyle,
TaskMenu, TaskMenu,
ProjectWorkflow, ProjectWorkflow,
DrawerOverlay, DrawerOverlay,
ProjectLog, TaskArchived, TaskRow, Draggable, TaskAddSimple, UserInput, TaskPriority, TaskDeleted, ProjectGantt}, ProjectLog, TaskArchived, TaskRow, Draggable, TaskAddSimple, TaskPriority, TaskDeleted, ProjectGantt},
data() { data() {
return { return {
loading: false, loading: false,
@ -1265,7 +1266,7 @@ export default {
break; break;
case "transfer": case "transfer":
this.$set(this.transferData, 'owner_userid', [this.projectData.owner_userid]); this.$set(this.transferData, 'owner_userid', []);
this.transferShow = true; this.transferShow = true;
break; break;

View File

@ -151,7 +151,7 @@
:mask-closable="false"> :mask-closable="false">
<Form :model="userData" label-width="auto" @submit.native.prevent> <Form :model="userData" label-width="auto" @submit.native.prevent>
<FormItem prop="userids" :label="$L('状态负责人')"> <FormItem prop="userids" :label="$L('状态负责人')">
<UserInput v-model="userData.userids" :project-id="projectId" :multiple-max="5" :placeholder="$L('选择状态负责人')"/> <UserSelect v-model="userData.userids" :project-id="projectId" :multiple-max="5" :title="$L('选择状态负责人')"/>
</FormItem> </FormItem>
<FormItem prop="usertype" :label="$L('流转模式')"> <FormItem prop="usertype" :label="$L('流转模式')">
<RadioGroup v-model="userData.usertype"> <RadioGroup v-model="userData.usertype">
@ -179,12 +179,11 @@
<script> <script>
import Draggable from "vuedraggable"; import Draggable from "vuedraggable";
import UserInput from "../../../components/UserInput"; import UserSelect from "../../../components/UserSelect.vue";
import {mapState} from "vuex";
export default { export default {
name: "ProjectWorkflow", name: "ProjectWorkflow",
components: {UserInput, Draggable}, components: {UserSelect, Draggable},
props: { props: {
projectId: { projectId: {
type: Number, type: Number,

View File

@ -30,11 +30,7 @@
</FormItem> </FormItem>
<FormItem :label="$L('汇报对象')"> <FormItem :label="$L('汇报对象')">
<div class="report-users"> <div class="report-users">
<UserInput <UserSelect v-model="reportData.receive" :disabledChoice="[userId]" :title="$L('选择接收人')"/>
v-model="reportData.receive"
:disabledChoice="[userId]"
:placeholder="$L('选择接收人')"
:transfer="false"/>
<a class="report-user-link" href="javascript:void(0);" @click="getLastSubmitter"> <a class="report-user-link" href="javascript:void(0);" @click="getLastSubmitter">
<Icon v-if="receiveLoad > 0" type="ios-loading" class="icon-loading"/> <Icon v-if="receiveLoad > 0" type="ios-loading" class="icon-loading"/>
<Icon v-else type="ios-share-outline" /> <Icon v-else type="ios-share-outline" />
@ -52,14 +48,14 @@
</template> </template>
<script> <script>
import UserInput from "../../../components/UserInput" import UserSelect from "../../../components/UserSelect.vue";
import {mapState} from "vuex";
const TEditor = () => import('../../../components/TEditor'); const TEditor = () => import('../../../components/TEditor');
export default { export default {
name: "ReportEdit", name: "ReportEdit",
components: { components: {
TEditor, UserInput UserSelect,
TEditor
}, },
props: { props: {
id: { id: {

View File

@ -79,12 +79,13 @@
@on-change="taskTimeChange(addData.times)"/> @on-change="taskTimeChange(addData.times)"/>
</FormItem> </FormItem>
<FormItem :label="$L('任务负责人')"> <FormItem :label="$L('任务负责人')">
<UserInput <UserSelect
v-model="addData.owner" v-model="addData.owner"
:multiple-max="10" :multiple-max="10"
:placeholder="$L('选择任务负责人')" :title="$L('选择任务负责人')"
:project-id="addData.project_id" :project-id="addData.project_id"
:transfer="false"/> :avatar-size="24"
border/>
<div v-if="showAddAssist" class="task-add-assist"> <div v-if="showAddAssist" class="task-add-assist">
<Checkbox v-model="addData.add_assist" :true-value="1" :false-value="0">{{$L('加入任务协助人员列表')}}</Checkbox> <Checkbox v-model="addData.add_assist" :true-value="1" :false-value="0">{{$L('加入任务协助人员列表')}}</Checkbox>
<ETooltip :disabled="$isEEUiApp || windowTouch" :content="$L('你不是任务负责人时建议加入任务协助人员列表')"> <ETooltip :disabled="$isEEUiApp || windowTouch" :content="$L('你不是任务负责人时建议加入任务协助人员列表')">
@ -96,8 +97,8 @@
<div v-if="addData.subtasks.length > 0" class="sublist"> <div v-if="addData.subtasks.length > 0" class="sublist">
<Row> <Row>
<Col span="12">{{$L('任务描述')}}</Col> <Col span="12">{{$L('任务描述')}}</Col>
<Col span="6">{{$L('计划时间')}}</Col> <Col span="8">{{$L('计划时间')}}</Col>
<Col span="6">{{$L('负责人')}}</Col> <Col span="4">{{$L('负责人')}}</Col>
</Row> </Row>
<Row v-for="(item, key) in addData.subtasks" :key="key"> <Row v-for="(item, key) in addData.subtasks" :key="key">
<Col span="12"> <Col span="12">
@ -107,7 +108,7 @@
clearable clearable
@on-clear="addData.subtasks.splice(key, 1)"/> @on-clear="addData.subtasks.splice(key, 1)"/>
</Col> </Col>
<Col span="6"> <Col span="8">
<DatePicker <DatePicker
v-model="item.times" v-model="item.times"
:options="timeOptions" :options="timeOptions"
@ -117,14 +118,14 @@
type="datetimerange" type="datetimerange"
@on-change="taskTimeChange(item.times)"/> @on-change="taskTimeChange(item.times)"/>
</Col> </Col>
<Col span="6"> <Col span="4">
<UserInput <UserSelect
v-model="item.owner" v-model="item.owner"
:multiple-max="1" :multiple-max="1"
:placeholder="$L('选择负责人')" :title="$L('选择负责人')"
:project-id="addData.project_id" :project-id="addData.project_id"
:transfer="false" :avatar-size="24"
max-hidden-select/> border/>
</Col> </Col>
</Row> </Row>
</div> </div>
@ -158,12 +159,12 @@
<script> <script>
import TEditor from "../../../components/TEditor"; import TEditor from "../../../components/TEditor";
import UserInput from "../../../components/UserInput";
import {mapState} from "vuex"; import {mapState} from "vuex";
import UserSelect from "../../../components/UserSelect.vue";
export default { export default {
name: "TaskAdd", name: "TaskAdd",
components: {UserInput, TEditor}, components: {UserSelect, TEditor},
props: { props: {
value: { value: {
type: Boolean, type: Boolean,

View File

@ -41,33 +41,15 @@
</div> </div>
<Icon v-else class="clock" type="ios-clock-outline" @click="openTime" /> <Icon v-else class="clock" type="ios-clock-outline" @click="openTime" />
</DatePicker> </DatePicker>
<Poptip <UserSelect
ref="owner"
class="subtask-avatar" class="subtask-avatar"
popper-class="task-detail-user-popper"
:title="$L('修改负责人')"
:width="240"
placement="bottom"
@on-popper-show="openOwner"
@on-ok="onOwner"
transfer>
<div slot="content">
<UserInput
v-model="ownerData.owner_userid" v-model="ownerData.owner_userid"
:multiple-max="10" :multiple-max="10"
:avatar-size="20"
:title="$L('修改负责人')"
:add-icon="false"
:project-id="taskDetail.project_id" :project-id="taskDetail.project_id"
:placeholder="$L('选择任务负责人')" :before-submit="onOwner"/>
:transfer="false"
max-hidden-select/>
<div class="task-detail-avatar-buttons">
<Button size="small" type="primary" @click="$refs.owner.ok()">{{$L('确定')}}</Button>
</div>
</div>
<template v-if="getOwner.length > 0">
<UserAvatar v-for="item in getOwner" :key="item.userid" :userid="item.userid" :size="20" tooltipDisabled/>
</template>
<div v-else>--</div>
</Poptip>
</li> </li>
<!--主任务--> <!--主任务-->
<div <div
@ -191,63 +173,29 @@
<div class="item-label" slot="label"> <div class="item-label" slot="label">
<i class="taskfont">&#xe6e4;</i>{{$L('负责人')}} <i class="taskfont">&#xe6e4;</i>{{$L('负责人')}}
</div> </div>
<Poptip <UserSelect
ref="owner"
:title="$L('修改负责人')"
:width="240"
class="item-content user" class="item-content user"
popper-class="task-detail-user-popper"
placement="bottom"
@on-popper-show="openOwner"
@on-ok="onOwner"
transfer>
<div slot="content">
<UserInput
v-model="ownerData.owner_userid" v-model="ownerData.owner_userid"
:multiple-max="10" :multiple-max="10"
:avatar-size="28"
:title="$L('修改负责人')"
:project-id="taskDetail.project_id" :project-id="taskDetail.project_id"
:placeholder="$L('选择任务负责人')" :before-submit="onOwner"/>
:transfer="false"/>
<div class="task-detail-avatar-buttons">
<Button size="small" type="primary" @click="$refs.owner.ok()">{{$L('确定')}}</Button>
</div>
</div>
<div class="user-list">
<UserAvatar v-for="item in getOwner" :key="item.userid" :userid="item.userid" :size="28" :showName="getOwner.length === 1" tooltipDisabled/>
</div>
</Poptip>
</FormItem> </FormItem>
<FormItem v-if="getAssist.length > 0 || assistForce"> <FormItem v-if="getAssist.length > 0 || assistForce">
<div class="item-label" slot="label"> <div class="item-label" slot="label">
<i class="taskfont">&#xe63f;</i>{{$L('协助人员')}} <i class="taskfont">&#xe63f;</i>{{$L('协助人员')}}
</div> </div>
<Poptip <UserSelect
ref="assist" ref="assist"
:title="$L(getAssist.length > 0 ? '修改协助人员' : '添加协助人员')"
:width="280"
class="item-content user" class="item-content user"
popper-class="task-detail-user-popper"
placement="bottom"
@on-popper-show="openAssist"
@on-ok="onAssist"
transfer>
<div slot="content">
<UserInput
v-model="assistData.assist_userid" v-model="assistData.assist_userid"
:multiple-max="10" :multiple-max="10"
:avatar-size="28"
:title="$L(getAssist.length > 0 ? '修改协助人员' : '添加协助人员')"
:project-id="taskDetail.project_id" :project-id="taskDetail.project_id"
:disabled-choice="assistData.disabled" :disabled-choice="assistData.disabled"
:placeholder="$L('选择任务协助人员')" :before-submit="onAssist"/>
:transfer="false"/>
<div class="task-detail-avatar-buttons">
<Button size="small" type="primary" @click="$refs.assist.ok()">{{$L('确定')}}</Button>
</div>
</div>
<div v-if="getAssist.length > 0" class="user-list">
<UserAvatar v-for="item in getAssist" :key="item.userid" :userid="item.userid" :size="28" :showName="getAssist.length === 1" tooltipDisabled/>
</div>
<div v-else>--</div>
</Poptip>
</FormItem> </FormItem>
<FormItem v-if="taskDetail.end_at || timeForce"> <FormItem v-if="taskDetail.end_at || timeForce">
<div class="item-label" slot="label"> <div class="item-label" slot="label">
@ -460,17 +408,19 @@
import {mapState} from "vuex"; import {mapState} from "vuex";
import TEditor from "../../../components/TEditor"; import TEditor from "../../../components/TEditor";
import TaskPriority from "./TaskPriority"; import TaskPriority from "./TaskPriority";
import UserInput from "../../../components/UserInput";
import TaskUpload from "./TaskUpload"; import TaskUpload from "./TaskUpload";
import DialogWrapper from "./DialogWrapper"; import DialogWrapper from "./DialogWrapper";
import ProjectLog from "./ProjectLog"; import ProjectLog from "./ProjectLog";
import {Store} from "le5le-store"; import {Store} from "le5le-store";
import TaskMenu from "./TaskMenu"; import TaskMenu from "./TaskMenu";
import ChatInput from "./ChatInput"; import ChatInput from "./ChatInput";
import UserSelect from "../../../components/UserSelect.vue";
export default { export default {
name: "TaskDetail", name: "TaskDetail",
components: {ChatInput, TaskMenu, ProjectLog, DialogWrapper, TaskUpload, UserInput, TaskPriority, TEditor}, components: {
UserSelect,
ChatInput, TaskMenu, ProjectLog, DialogWrapper, TaskUpload, TaskPriority, TEditor},
props: { props: {
taskId: { taskId: {
type: Number, type: Number,
@ -819,13 +769,28 @@ export default {
this.assistForce = false; this.assistForce = false;
this.addsubForce = false; this.addsubForce = false;
this.receiveShow = false; this.receiveShow = false;
this.$refs.owner && this.$refs.owner.handleClose();
this.$refs.assist && this.$refs.assist.handleClose();
this.$refs.chatInput && this.$refs.chatInput.hidePopover(); this.$refs.chatInput && this.$refs.chatInput.hidePopover();
} }
}, },
immediate: true immediate: true
}, },
getOwner: {
handler(arr) {
const list = arr.map(({userid}) => userid)
this.$set(this.taskDetail, 'owner_userid', list)
this.$set(this.ownerData, 'owner_userid', list)
this.$set(this.assistData, 'disabled', arr.map(({userid}) => userid).filter(userid => userid != this.userId))
},
immediate: true
},
getAssist: {
handler(arr) {
const list = arr.map(({userid}) => userid)
this.$set(this.taskDetail, 'assist_userid', list)
this.$set(this.assistData, 'assist_userid', list);
},
immediate: true
},
receiveShow(val) { receiveShow(val) {
if (val) { if (val) {
this.timeValue = this.taskDetail.end_at ? [this.taskDetail.start_at, this.taskDetail.end_at] : []; this.timeValue = this.taskDetail.end_at ? [this.taskDetail.start_at, this.taskDetail.end_at] : [];
@ -1026,12 +991,6 @@ export default {
}); });
}, },
openOwner() {
const list = this.getOwner.map(({userid}) => userid)
this.$set(this.taskDetail, 'owner_userid', list)
this.$set(this.ownerData, 'owner_userid', list)
},
onOwner(pick) { onOwner(pick) {
let data = { let data = {
task_id: this.taskDetail.id, task_id: this.taskDetail.id,
@ -1059,47 +1018,52 @@ export default {
if ($A.jsonStringify(this.taskDetail.owner_userid) === $A.jsonStringify(this.ownerData.owner_userid)) { if ($A.jsonStringify(this.taskDetail.owner_userid) === $A.jsonStringify(this.ownerData.owner_userid)) {
return; return;
} }
if ($A.count(data.owner) == 0) {
data.owner = '';
}
// //
if ($A.count(data.owner) == 0) data.owner = '';
this.ownerLoad++; this.ownerLoad++;
return new Promise((resolve, reject) => {
this.$store.dispatch("taskUpdate", data).then(({msg}) => { this.$store.dispatch("taskUpdate", data).then(({msg}) => {
$A.messageSuccess(msg); $A.messageSuccess(msg);
this.ownerLoad--; this.ownerLoad--;
this.receiveShow = false; this.receiveShow = false;
this.$store.dispatch("getTaskOne", this.taskDetail.id).catch(() => {}) this.$store.dispatch("getTaskOne", this.taskDetail.id).catch(() => {})
resolve()
}).catch(({msg}) => { }).catch(({msg}) => {
$A.modalError(msg); $A.modalError(msg);
this.ownerLoad--; this.ownerLoad--;
this.receiveShow = false; this.receiveShow = false;
reject()
})
}) })
},
openAssist() {
const list = this.getAssist.map(({userid}) => userid)
this.$set(this.taskDetail, 'assist_userid', list)
this.$set(this.assistData, 'assist_userid', list);
this.$set(this.assistData, 'disabled', this.getOwner.map(({userid}) => userid).filter(userid => userid != this.userId))
}, },
onAssist() { onAssist() {
if ($A.jsonStringify(this.taskDetail.assist_userid) === $A.jsonStringify(this.assistData.assist_userid)) { if ($A.jsonStringify(this.taskDetail.assist_userid) === $A.jsonStringify(this.assistData.assist_userid)) {
return; return;
} }
return new Promise((resolve, reject) => {
if (this.getOwner.find(({userid}) => userid === this.userId) && this.assistData.assist_userid.find(userid => userid === this.userId)) { if (this.getOwner.find(({userid}) => userid === this.userId) && this.assistData.assist_userid.find(userid => userid === this.userId)) {
$A.modalConfirm({ $A.modalConfirm({
content: '你当前是负责人,确定要转为协助人员吗?', content: '你当前是负责人,确定要转为协助人员吗?',
cancelText: '取消', cancelText: '取消',
okText: '确定', okText: '确定',
onOk: () => { onOk: () => {
this.onAssistConfirm() this.onAssistConfirm().then(resolve).catch(reject)
},
onCancel: () => {
reject()
} }
}) })
return } else {
this.onAssistConfirm().then(resolve).catch(reject)
} }
this.onAssistConfirm() })
}, },
onAssistConfirm() { onAssistConfirm() {
return new Promise((resolve, reject) => {
let assist = this.assistData.assist_userid; let assist = this.assistData.assist_userid;
if (assist.length === 0) assist = false; if (assist.length === 0) assist = false;
this.assistLoad++; this.assistLoad++;
@ -1110,9 +1074,12 @@ export default {
$A.messageSuccess(msg); $A.messageSuccess(msg);
this.assistLoad--; this.assistLoad--;
this.$store.dispatch("getTaskOne", this.taskDetail.id).catch(() => {}) this.$store.dispatch("getTaskOne", this.taskDetail.id).catch(() => {})
resolve()
}).catch(({msg}) => { }).catch(({msg}) => {
$A.modalError(msg); $A.modalError(msg);
this.assistLoad--; this.assistLoad--;
reject()
})
}) })
}, },
@ -1215,9 +1182,8 @@ export default {
case 'assist': case 'assist':
this.assistForce = true; this.assistForce = true;
this.openAssist();
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.assist.handleClick(); this.$refs.assist.onSelect();
}); });
break; break;

View File

@ -5,7 +5,7 @@
:mask-closable="false"> :mask-closable="false">
<Form ref="exportTask" :model="formData" label-width="auto" @submit.native.prevent> <Form ref="exportTask" :model="formData" label-width="auto" @submit.native.prevent>
<FormItem :label="$L('导出成员')"> <FormItem :label="$L('导出成员')">
<UserInput v-model="formData.userid" :multiple-max="100" show-disable :placeholder="$L('请选择成员')"/> <UserSelect v-model="formData.userid" :multiple-max="100" avatar-name show-disable :title="$L('请选择成员')"/>
<div class="form-tip">{{$L('每次最多选择导出100个成员')}}</div> <div class="form-tip">{{$L('每次最多选择导出100个成员')}}</div>
</FormItem> </FormItem>
<FormItem :label="$L('时间范围')"> <FormItem :label="$L('时间范围')">
@ -52,10 +52,10 @@
} }
</style> </style>
<script> <script>
import UserInput from "../../../components/UserInput"; import UserSelect from "../../../components/UserSelect.vue";
export default { export default {
name: "TaskExport", name: "TaskExport",
components: {UserInput}, components: {UserSelect},
props: { props: {
value: { value: {
type: Boolean, type: Boolean,

View File

@ -172,7 +172,7 @@
<div v-if="departmentParentDisabled" class="form-tip" style="margin-bottom:-16px">{{$L('含有子部门无法修改上级部门')}}</div> <div v-if="departmentParentDisabled" class="form-tip" style="margin-bottom:-16px">{{$L('含有子部门无法修改上级部门')}}</div>
</FormItem> </FormItem>
<FormItem prop="owner_userid" :label="$L('部门负责人')"> <FormItem prop="owner_userid" :label="$L('部门负责人')">
<UserInput v-model="departmentData.owner_userid" :multiple-max="1" max-hidden-select :placeholder="$L('请选择部门负责人')"/> <UserSelect v-model="departmentData.owner_userid" :multiple-max="1" :title="$L('请选择部门负责人')"/>
</FormItem> </FormItem>
<template v-if="departmentData.id == 0"> <template v-if="departmentData.id == 0">
<Divider orientation="left">{{$L('群组设置')}}</Divider> <Divider orientation="left">{{$L('群组设置')}}</Divider>
@ -273,7 +273,7 @@
type="datetime"/> type="datetime"/>
</FormItem> </FormItem>
<FormItem :label="$L('交接人')"> <FormItem :label="$L('交接人')">
<UserInput v-model="disableData.transfer_userid" :disabled-choice="[disableData.userid]" :multiple-max="1" max-hidden-select :placeholder="$L('选择交接人')"/> <UserSelect v-model="disableData.transfer_userid" :disabled-choice="[disableData.userid]" :multiple-max="1" :title="$L('选择交接人')"/>
<div class="form-tip">{{ $L(`${disableData.nickname} 负责的部门、项目、任务和文件将移交给交接人;同时退出所有群(如果是群主则转让给交接人)`) }}</div> <div class="form-tip">{{ $L(`${disableData.nickname} 负责的部门、项目、任务和文件将移交给交接人;同时退出所有群(如果是群主则转让给交接人)`) }}</div>
</FormItem> </FormItem>
</Form> </Form>
@ -298,11 +298,11 @@
</template> </template>
<script> <script>
import UserInput from "../../../components/UserInput"; import UserSelect from "../../../components/UserSelect.vue";
export default { export default {
name: "TeamManagement", name: "TeamManagement",
components: {UserInput}, components: {UserSelect},
props: { props: {
checkinMac: { checkinMac: {
type: Boolean, type: Boolean,

View File

@ -278,19 +278,19 @@
footer-hide> footer-hide>
<Form class="page-file-share-form" :model="shareInfo" @submit.native.prevent inline> <Form class="page-file-share-form" :model="shareInfo" @submit.native.prevent inline>
<FormItem prop="userids" class="share-userid"> <FormItem prop="userids" class="share-userid">
<UserInput <RadioGroup v-model="shareInfo.type">
<Radio label="all">{{$L('所有人')}}</Radio>
<Radio label="custom">{{$L('指定成员')}}</Radio>
</RadioGroup>
<UserSelect
v-if="shareInfo.type === 'custom'"
v-model="shareInfo.userids" v-model="shareInfo.userids"
:disabledChoice="shareAlready" :disabledChoice="shareAlready"
:multiple-max="100" :multiple-max="100"
:placeholder="$L('选择共享成员')"> :placeholder="$L('选择共享成员')"
<Option slot="option-prepend" :value="0" :label="$L('所有人')" :disabled="shareAlready.includes(0)"> :avatar-size="24"
<div class="user-input-option"> border>
<div class="user-input-avatar"><EAvatar class="avatar" icon="el-icon-s-custom"/></div> </UserSelect>
<div class="user-input-nickname">{{ $L('所有人') }}</div>
<div class="user-input-userid">All</div>
</div>
</Option>
</UserInput>
</FormItem> </FormItem>
<FormItem> <FormItem>
<Select v-model="shareInfo.permission" :placeholder="$L('权限')"> <Select v-model="shareInfo.permission" :placeholder="$L('权限')">
@ -302,7 +302,7 @@
<Button type="primary" :loading="shareLoad > 0" @click="onShare">{{$L('共享')}}</Button> <Button type="primary" :loading="shareLoad > 0" @click="onShare">{{$L('共享')}}</Button>
</FormItem> </FormItem>
</Form> </Form>
<div v-if="shareList.length > 0"> <div v-if="shareList.length > 0" class="page-file-share-items">
<div class="page-file-share-title">{{ $L('已共享成员') }}:</div> <div class="page-file-share-title">{{ $L('已共享成员') }}:</div>
<ul class="page-file-share-list"> <ul class="page-file-share-list">
<li v-for="item in shareList"> <li v-for="item in shareList">
@ -398,18 +398,18 @@
<script> <script>
import {mapState} from "vuex"; import {mapState} from "vuex";
import {sortBy} from "lodash"; import {sortBy} from "lodash";
import UserInput from "../../components/UserInput";
import DrawerOverlay from "../../components/DrawerOverlay"; import DrawerOverlay from "../../components/DrawerOverlay";
import PreviewImage from "../../components/PreviewImage"; import PreviewImage from "../../components/PreviewImage";
import longpress from "../../directives/longpress"; import longpress from "../../directives/longpress";
import DialogSelect from "./components/DialogSelect"; import DialogSelect from "./components/DialogSelect";
import UserSelect from "../../components/UserSelect.vue";
const FilePreview = () => import('./components/FilePreview'); const FilePreview = () => import('./components/FilePreview');
const FileContent = () => import('./components/FileContent'); const FileContent = () => import('./components/FileContent');
const FileObject = {sort: null, mode: null, shared: null}; const FileObject = {sort: null, mode: null, shared: null};
export default { export default {
components: {DialogSelect, PreviewImage, FilePreview, DrawerOverlay, UserInput, FileContent}, components: {UserSelect, DialogSelect, PreviewImage, FilePreview, DrawerOverlay, FileContent},
directives: {longpress}, directives: {longpress},
data() { data() {
return { return {
@ -473,7 +473,7 @@ export default {
columns: [], columns: [],
shareShow: false, shareShow: false,
shareInfo: {id: 0, userid: 0, permission: 1}, shareInfo: {id: 0, type: 'all', userid: 0, permission: 1},
shareList: [], shareList: [],
shareLoad: 0, shareLoad: 0,
@ -1208,6 +1208,7 @@ export default {
case 'share': case 'share':
this.shareInfo = { this.shareInfo = {
id: item.id, id: item.id,
type: 'all',
userid: item.userid, userid: item.userid,
permission: 1, permission: 1,
}; };
@ -1538,6 +1539,9 @@ export default {
}, },
onShare(force = false) { onShare(force = false) {
if (this.shareInfo.type === 'all') {
this.shareInfo.userids = [0];
}
if (this.shareInfo.userids.length == 0) { if (this.shareInfo.userids.length == 0) {
$A.messageWarning("请选择共享成员") $A.messageWarning("请选择共享成员")
return; return;

View File

@ -11,7 +11,7 @@
@import "quick-edit"; @import "quick-edit";
@import "tag-input"; @import "tag-input";
@import "user-avatar"; @import "user-avatar";
@import "user-input"; @import "user-select";
@import "report"; @import "report";
@import "resize-line"; @import "resize-line";
@import "right-bottom"; @import "right-bottom";

View File

@ -168,8 +168,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
.common-user { .common-user-select {
flex: 1;
margin-right: 12px; margin-right: 12px;
} }

View File

@ -1,99 +0,0 @@
.common-user {
position: relative;
white-space: normal;
.common-user-loading {
position: absolute;
top: 2px;
bottom: 0;
right: 10px;
display: flex;
align-items: center;
.common-loading {
width: 14px;
height: 14px;
}
}
&.hidden-input {
.ivu-select-selection {
padding: 0 4px;
.ivu-select-input {
display: none;
}
}
}
}
.common-user-transfer {
.user-input-option {
display: flex;
align-items: center;
.user-input-avatar {
display: flex;
align-items: center;
.avatar {
width: 26px;
height: 26px;
line-height: 26px;
}
}
.user-input-bot {
font-size: 16px;
margin-left: 10px;
margin-right: -6px;
color: $primary-color;
}
.user-input-disable {
font-size: 12px;
margin-left: 10px;
margin-right: -6px;
color: #ff0000;
}
.user-input-nickname {
margin-left: 10px;
flex: 1;
}
.user-input-userid {
margin-left: 10px;
font-size: 12px;
color: #cccccc;
transition: margin 0.1s;
}
}
.ivu-select-item {
&.ivu-select-item-selected {
&:after {
top: 8px;
}
.user-input-option {
.user-input-userid {
margin-right: 16px;
}
}
}
}
.user-drop-prepend {
display: flex;
align-items: center;
justify-content: center;
position: sticky;
top: 0;
left: 0;
right: 0;
z-index: 1;
transform: translateY(-5px);
background-color: #ffffff;
padding: 5px 15px;
border-bottom: 1px solid #f1f1f1;
.user-drop-text {
flex: 1;
color: #c5c8ce;
line-height: 20px;
font-size: 12px;
padding: 0 4px;
}
.user-drop-check {
margin-right: 0;
transform: scale(0.9);
transform-origin: right center;
}
}
}

View File

@ -0,0 +1,153 @@
.common-user-select {
&.select-border {
border: 1px solid #e8e8e8;
border-radius: 4px;
padding: 0 6px;
}
> ul {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: flex-start;
align-items: flex-start;
> li {
list-style: none;
padding: 0;
margin: 3px 6px 3px 0;
&:last-child {
margin-right: 0;
}
&.add-icon {
cursor: pointer;
border-radius: 50%;
width: 26px;
height: 26px;
background: #F2F3F5 url("data:image/svg+xml;base64,PHN2ZyB0PSIxNjg2NjIxNjA3NDE0IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik04NzQuNjY2NjY3IDQ3My42aC0zMjQuMjY2NjY3VjE0OS4zMzMzMzNjMC0yMS4zMzMzMzMtMTcuMDY2NjY3LTM4LjQtMzguNC0zOC40cy0zOC40IDE3LjA2NjY2Ny0zOC40IDM4LjR2MzI0LjI2NjY2N0gxNDkuMzMzMzMzYy0yMS4zMzMzMzMgMC0zOC40IDE3LjA2NjY2Ny0zOC40IDM4LjRzMTcuMDY2NjY3IDM4LjQgMzguNCAzOC40aDMyNC4yNjY2Njd2MzI0LjI2NjY2N2MwIDIxLjMzMzMzMyAxNy4wNjY2NjcgMzguNCAzOC40IDM4LjRzMzguNC0xNy4wNjY2NjcgMzguNC0zOC40di0zMjQuMjY2NjY3aDMyNC4yNjY2NjdjMjEuMzMzMzMzIDAgMzguNC0xNy4wNjY2NjcgMzguNC0zOC40cy0xNy4wNjY2NjctMzguNC0zOC40LTM4LjR6IiAgZmlsbD0iIzYwNjI2NiI+PC9wYXRoPjwvc3ZnPg==") no-repeat center;
background-size: 50%;
}
}
}
}
.common-user-select-modal {
.ivu-modal-body {
padding: 0 16px !important;
display: flex;
flex-direction: column;
.user-modal-search {
flex-shrink: 0;
position: sticky;
top: 0;
z-index: 9;
padding: 8px 16px;
background: #ffffff;
.search-pre {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
.common-loading {
width: 14px;
height: 14px;
margin: 0;
}
}
}
.user-modal-list {
flex: 1;
display: flex;
flex-direction: column;
max-height: 400px;
ul {
padding: 8px 0;
> li {
list-style: none;
padding: 8px 16px;
margin: 0;
display: flex;
align-items: center;
border-radius: 6px;
cursor: pointer;
&:hover {
background: #f3f3f3;
}
&.selected {
.user-modal-icon {
color: $primary-color;
}
}
&.disabled,
&.disabled:hover {
color: #c5c8ce;
cursor: not-allowed;
}
.user-modal-icon {
flex-shrink: 0;
font-size: 24px;
margin-right: 10px;
color: rgba($primary-desc-color, 0.7);
}
.user-modal-avatar {
flex: 1;
.avatar-name {
margin-left: 10px;
}
}
.user-modal-userid {
flex-shrink: 0;
margin-left: 10px;
font-size: 12px;
color: #cccccc;
transition: margin 0.1s;
}
}
}
}
.user-modal-multiple {
flex-shrink: 0;
position: sticky;
bottom: 0;
z-index: 9;
padding: 6px 16px 0;
background: #ffffff;
color: $primary-desc-color;
display: flex;
align-items: center;
justify-content: center;
.multiple-check {
margin-left: 4px;
}
.multiple-text {
flex: 1;
text-align: right;
line-height: 20px;
font-size: 12px;
padding-left: 6px;
> em {
padding-left: 2px;
font-style: normal;
}
}
}
}
.ivu-modal-fullscreen {
.ivu-modal-content {
margin-top: 46px;
border-top-left-radius: 18px !important;
border-top-right-radius: 18px !important;
.ivu-modal-body {
bottom: 80px;
}
}
}
}
@media screen and (max-width: 576px) {
.common-user-select-modal {
.ivu-modal-body {
.user-modal-list {
max-height: none;
}
}
}
}

View File

@ -245,13 +245,6 @@
&.user { &.user {
margin-top: 1px; margin-top: 1px;
cursor: pointer; cursor: pointer;
.user-list {
display: flex;
align-items: center;
> div {
margin-right: 6px;
}
}
} }
&.file { &.file {
margin-bottom: -3px; margin-bottom: -3px;
@ -422,15 +415,6 @@
opacity: 0; opacity: 0;
} }
} }
.subtask-avatar {
cursor: pointer;
display: flex;
flex-direction: column;
.avatar-wrapper {
margin-top: 3px;
margin-bottom: -2px;
}
}
.ivu-icon-ios-loading { .ivu-icon-ios-loading {
animation: animation-icon-loading 0.6s infinite linear; animation: animation-icon-loading 0.6s infinite linear;
} }
@ -832,25 +816,6 @@
} }
} }
.task-detail-user-popper {
.ivu-poptip-body-content {
overflow: visible;
}
}
.task-detail-avatar-buttons {
margin-top: 12px;
margin-bottom: 4px;
text-align: right;
position: absolute;
top: 5px;
right: 14px;
> button {
font-size: 12px;
transform: scale(0.9);
}
}
.task-detail-loop { .task-detail-loop {
> li { > li {
text-align: center; text-align: center;

View File

@ -634,9 +634,13 @@
.page-file-share-form { .page-file-share-form {
display: flex; display: flex;
align-items: flex-end;
margin-bottom: 12px; margin-bottom: 12px;
.share-userid { .share-userid {
flex: 1; flex: 1;
.common-user-select {
margin-top: 10px;
}
} }
> div { > div {
flex-shrink: 0; flex-shrink: 0;
@ -646,6 +650,12 @@
} }
} }
.page-file-share-items {
border-top: 1px solid #eee;
margin-top: -12px;
padding-top: 24px;
}
.page-file-share-title { .page-file-share-title {
margin-top: -8px; margin-top: -8px;
margin-bottom: 14px; margin-bottom: 14px;