perf: 优化会员选择器

This commit is contained in:
kuaifan 2023-06-16 17:43:30 +08:00
parent 29bbbd804a
commit 04632182b4
10 changed files with 1041 additions and 423 deletions

View File

@ -2,14 +2,18 @@
All notable changes to this project will be documented in this file.
## [0.27.31]
## [0.27.36]
### Bug Fixes
- 打开会话面板报错
- 子任务通知无法打开
### Performance
- 优化会员选择器
- 优化会员选择器
- 优化图片压缩
- 回复图片显示图片搜略图
- 优化会员选择器
- 会员选择下拉框提示

View File

@ -56,9 +56,15 @@ class ProjectController extends AbstractController
* - all全部
* - no未归档默认
* - yes已归档
* @apiParam {String} [getcolumn] 同时取项目列表
* @apiParam {String} [getcolumn] 同时取列表
* - no不取默认
* - yes取列表
* @apiParam {String} [getuserid] 同时取成员ID
* - no不取默认
* - yes取列表
* @apiParam {String} [getstatistics] 同时取任务统计
* - no不取
* - yes取统计默认
* @apiParam {Object} [keys] 搜索条件
* - keys.name: 项目名称
* @apiParam {String} [timerange] 时间范围1678248944,1678248944
@ -110,6 +116,8 @@ class ProjectController extends AbstractController
$type = Request::input('type', 'all');
$archived = Request::input('archived', 'no');
$getcolumn = Request::input('getcolumn', 'no');
$getuserid = Request::input('getuserid', 'no');
$getstatistics = Request::input('getstatistics', 'yes');
$keys = Request::input('keys');
$timerange = TimeRange::parse(Request::input('timerange'));
//
@ -151,8 +159,15 @@ class ProjectController extends AbstractController
}
//
$list = $builder->orderByDesc('projects.id')->paginate(Base::getPaginate(100, 50));
$list->transform(function (Project $project) use ($user) {
return array_merge($project->toArray(), $project->getTaskStatistics($user->userid));
$list->transform(function (Project $project) use ($getstatistics, $getuserid, $user) {
$array = $project->toArray();
if ($getuserid == 'yes') {
$array['userid_list'] = ProjectUser::whereProjectId($project->id)->pluck('userid')->toArray();
}
if ($getstatistics == 'yes') {
$array = array_merge($array, $project->getTaskStatistics($user->userid));
}
return $array;
});
//
$data = $list->toArray();

View File

@ -1,6 +1,6 @@
{
"name": "DooTask",
"version": "0.27.31",
"version": "0.27.36",
"description": "DooTask is task management system.",
"scripts": {
"start": "./cmd dev",

View File

@ -1,57 +1,151 @@
<template>
<div class="common-user-select" :class="{'select-border': border}">
<ul @click="onSelect(true)" :style="warpStyle">
<div class="common-user-select" :class="warpClass">
<ul v-if="!module" @click="onSelection">
<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>
<li v-if="addIcon || values.length === 0" class="add-icon" :style="addStyle" @click.stop="onSelection"></li>
</ul>
<Modal
v-model="showModal"
:mask-closable="false"
class-name="common-user-select-modal"
:title="localTitle"
:fullscreen="windowWidth < 576">
:mask-closable="false"
:closable="!isFullscreen"
:fullscreen="isFullscreen"
:footer-hide="isFullscreen"
width="640">
<!-- 顶部 -->
<template #header>
<div v-if="isFullscreen" class="user-modal-header">
<div class="user-modal-close" @click="showModal=false">{{$L('关闭')}}</div>
<div class="user-modal-title">{{localTitle}}</div>
<div class="user-modal-submit" @click="onSubmit">
<div v-if="loadIng > 0" class="submit-loading"><Loading /></div>
{{$L('确定')}}
<template v-if="selects.length > 0">
({{selects.length}}<span v-if="multipleMax">/{{multipleMax}}</span>)
</template>
</div>
</div>
<div v-else class="ivu-modal-header-inner">{{localTitle}}</div>
</template>
<template #close>
<i class="ivu-icon ivu-icon-ios-close"></i>
</template>
<!-- 搜索 -->
<div class="user-modal-search">
<Input v-model="searchKey" :placeholder="localPlaceholder" clearable>
<Scrollbar ref="selected" class="search-selected" v-if="selects.length > 0" enable-x :enable-y="false">
<ul>
<li v-for="item in formatSelect(selects)" :data-id="item.userid" @click.stop="onRemoveItem(item.userid)">
<template v-if="item.type=='group'">
<EAvatar v-if="item.avatar" class="img-avatar" :src="item.avatar" :size="32"></EAvatar>
<i v-else-if="item.group_type=='department'" class="taskfont icon-avatar department">&#xe75c;</i>
<i v-else-if="item.group_type=='project'" class="taskfont icon-avatar project">&#xe6f9;</i>
<i v-else-if="item.group_type=='task'" class="taskfont icon-avatar task">&#xe6f4;</i>
<Icon v-else class="icon-avatar" type="ios-people" />
</template>
<UserAvatar v-else :userid="item.userid" tooltip-disabled/>
</li>
</ul>
</Scrollbar>
<Input class="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>
<!-- 切换 -->
<ul v-if="isWhole" class="user-modal-switch">
<li
v-for="item in switchItems" :key="item.key"
:class="{active:switchActive===item.key}"
@click="switchActive=item.key">{{ $L(item.label) }}</li>
</ul>
<!-- 列表 -->
<Scrollbar v-if="lists.length > 0" class="user-modal-list">
<!-- 项目 -->
<ul v-if="switchActive == 'project'" class="user-modal-project">
<li
v-for="item in lists"
:class="selectClass(item.userid_list)"
@click="onSelectProject(item.userid_list)">
<Icon class="user-modal-icon" :type="selectIcon(item.userid_list)" />
<div class="user-modal-avatar">
<i class="taskfont icon-avatar">&#xe6f9;</i>
<div class="project-name">
<div class="label">{{item.name}}</div>
<div class="subtitle">
{{item.userid_list.length}} {{$L('项目成员')}}
<em class="all">{{$L('已全选')}}</em>
<em class="some">{{$L('已选部分')}}</em>
</div>
</div>
</div>
</li>
</ul>
<!-- 会员会话 -->
<ul v-else>
<li
v-if="showSelectAll"
:class="selectClass('all')"
@click="onSelectAll">
<Icon class="user-modal-icon" :type="selectIcon('all')" />
<div class="user-modal-all">{{$L('全选')}}</div>
</li>
<li
v-for="item in lists"
:class="{
selected: selects.includes(item.userid),
disabled: inUncancelable(item.userid) || isDisabled(item.userid)
disabled: isUncancelable(item.userid) || isDisabled(item.userid)
}"
@click="selectUser(item)">
@click="onSelectItem(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>
<div v-if="item.type=='group'" class="user-modal-avatar">
<EAvatar v-if="item.avatar" class="img-avatar" :src="item.avatar" :size="40"></EAvatar>
<i v-else-if="item.group_type=='department'" class="taskfont icon-avatar department">&#xe75c;</i>
<i v-else-if="item.group_type=='project'" class="taskfont icon-avatar project">&#xe6f9;</i>
<i v-else-if="item.group_type=='task'" class="taskfont icon-avatar task">&#xe6f4;</i>
<Icon v-else class="icon-avatar" type="ios-people" />
<div class="avatar-name">
<span>{{item.name}}</span>
</div>
</div>
<UserAvatar v-else class="user-modal-avatar" :userid="item.userid" :size="40" show-name tooltip-disabled/>
</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 v-else class="user-modal-empty">
<Loading v-if="waitIng > 0"/>
<template v-else>
<div class="empty-icon"><Icon type="ios-cafe-outline" /></div>
<div class="empty-text">{{$L('暂无结果')}}</div>
</template>
</div>
<!-- 底部 -->
<template #footer>
<Button type="primary" :loading="submittIng > 0" @click="onSubmit">
{{$L('确定')}}
<template v-if="selects.length > 0">
({{selects.length}}<span v-if="multipleMax">/{{multipleMax}}</span>)
</template>
</Button>
</template>
</Modal>
</div>
</template>
<script>
import {mapState} from "vuex";
export default {
name: 'UserSelect',
props: {
@ -75,6 +169,7 @@ export default {
return [];
}
},
// ID
projectId: {
type: Number,
@ -90,6 +185,7 @@ export default {
type: Number,
default: 0
},
//
showBot: {
type: Boolean,
@ -120,16 +216,11 @@ export default {
type: Boolean,
default: false
},
// true
// true
addIcon: {
type: Boolean,
default: true
},
//
onlyAddIconClick: {
type: Boolean,
default: false
},
//
border: {
type: Boolean,
@ -145,25 +236,49 @@ export default {
type: String,
},
//
showSelectAll: {
type: Boolean,
default: true
},
// d:{ID} module=true 使
showDialog: {
type: Boolean,
default: false
},
// api
module: {
type: Boolean,
default: false
},
//
beforeSubmit: Function
},
data() {
return {
loadIng: 0,
submittIng: 0,
switchItems: [
{key: 'recent', label: '最近'},
{key: 'contact', label: '通讯录'},
{key: 'project', label: '项目成员'},
],
switchActive: 'recent',
loadIng: 0, //
waitIng: 0, //
submittIng: 0, //
values: [],
lists: [],
selects: [],
recents: [],
contacts: [],
projects: [],
showModal: false,
multipleCheck: false,
searchTimer: null,
searchKey: null,
searchHistory: [],
searchCache: [],
}
},
watch: {
@ -179,73 +294,210 @@ export default {
},
immediate: true
},
isWhole: {
handler(value) {
if (value) {
this.switchActive = 'recent'
} else {
this.switchActive = 'contact'
}
},
immediate: true
},
showModal(value) {
if (value) {
this.searchUser()
this.searchBefore()
} else {
this.searchKey = ""
}
},
searchKey() {
this.searchUser()
this.searchBefore()
},
'lists.length'() {
this.calcMultiple()
},
'selects.length'() {
this.calcMultiple()
switchActive() {
this.searchBefore()
},
},
computed: {
warpStyle() {
if (!this.onlyAddIconClick) {
return {
cursor: 'pointer'
}
}
...mapState([
'cacheDialogs',
]),
isFullscreen({windowWidth}) {
return windowWidth < 576
},
addStyle() {
isWhole({projectId, noProjectId, dialogId}) {
return projectId === 0 && noProjectId === 0 && dialogId === 0
},
lists({switchActive, searchKey, recents, contacts, projects}) {
switch (switchActive) {
case 'recent':
if (searchKey) {
return recents.filter(item => {
return `${item.name}`.indexOf(searchKey) > -1
})
}
return recents
case 'contact':
return contacts
case 'project':
return projects
}
return []
},
isSelectAll({lists, selects}) {
return lists.length > 0 && lists.filter(item => selects.includes(item.userid)).length === lists.length;
},
warpClass() {
return {
width: this.avatarSize + 'px',
height: this.avatarSize + 'px',
'select-module': this.module,
'select-border': this.border,
'select-whole': this.isWhole,
}
},
localTitle() {
if (this.title === undefined) {
addStyle({avatarSize}) {
return {
width: avatarSize + 'px',
height: avatarSize + 'px',
}
},
localTitle({title}) {
if (title === undefined) {
return this.$L('选择会员')
} else {
return this.title;
return title;
}
},
localPlaceholder() {
if (this.placeholder === undefined) {
return this.$L('搜索会员')
localPlaceholder({placeholder}) {
if (placeholder === undefined) {
return this.$L('搜索')
} else {
return this.placeholder;
return placeholder;
}
}
},
methods: {
searchUser() {
isUncancelable(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)
},
formatSelect(list) {
return list.map(userid => {
if ($A.leftExists(userid, 'd:')) {
return this.recents.find(item => item.userid === userid)
}
return {
type: 'user',
userid,
}
})
},
selectIcon(value) {
if (value === 'all') {
return this.isSelectAll ? 'ios-checkmark-circle' : 'ios-radio-button-off';
}
if ($A.isArray(value) && value.length > 0) {
const len = value.filter(value => this.selects.includes(value)).length
if (len === value.length) {
return 'ios-checkmark-circle';
}
if (len > 0) {
return 'ios-radio-button-on';
}
}
return 'ios-radio-button-off';
},
selectClass(value) {
switch (this.selectIcon(value)) {
case 'ios-checkmark-circle':
return 'selected';
case 'ios-radio-button-on':
return 'somed';
}
return '';
},
searchBefore() {
if (!this.showModal) {
return
}
//
if (this.switchActive === 'recent') {
this.searchRecent()
} else if (this.switchActive === 'contact') {
this.searchContact()
} else if (this.switchActive === 'project') {
this.searchProject()
}
},
searchRecent() {
this.recents = this.cacheDialogs.filter(dialog => {
if (dialog.name === undefined || dialog.dialog_delete === 1) {
return false
}
if (!this.showBot && dialog.bot) {
return false
}
return this.showDialog || dialog.type === 'user'
}).sort((a, b) => {
if (a.top_at || b.top_at) {
return $A.Date(b.top_at) - $A.Date(a.top_at);
}
if (a.todo_num > 0 || b.todo_num > 0) {
return b.todo_num - a.todo_num;
}
return $A.Date(b.last_at) - $A.Date(a.last_at);
}).map(({id, name, type, group_type, avatar, dialog_user}) => {
return {
name,
type,
group_type,
avatar,
userid: type === 'user' ? dialog_user.userid : `d:${id}`,
}
});
},
searchContact() {
let key = this.searchKey;
const history = this.searchHistory.find(item => item.key == key);
if (history) {
this.lists = history.data
const cache = this.searchCache.find(item => item.type === 'contact' && item.key == key);
if (cache) {
this.contacts = cache.data
}
//
if (this.searchTimer) {
clearTimeout(this.searchTimer);
}
this.searchTimer = setTimeout(() => {
this.waitIng++
setTimeout(() => {
if (this.searchKey != key) {
this.waitIng--
return;
}
setTimeout(() => {
this.loadIng++;
this.loadIng++
}, 300)
this.$store.dispatch("call", {
url: 'users/search',
@ -261,35 +513,154 @@ export default {
take: 50
},
}).then(({data}) => {
this.lists = data
data = data.map(item => Object.assign(item, {type: 'user'}))
this.contacts = data
//
const index = this.searchHistory.findIndex(item => item.key == key);
const tmpData = {key, data, time: $A.Time()};
const index = this.searchCache.findIndex(item => item.key == key);
const tmpData = {type: 'contact', key, data, time: $A.Time()};
if (index > -1) {
this.searchHistory.splice(index, 1, tmpData)
this.searchCache.splice(index, 1, tmpData)
} else {
this.searchHistory.push(tmpData)
this.searchCache.push(tmpData)
}
}).catch(({msg}) => {
this.lists = []
this.contacts = []
$A.messageWarning(msg)
}).finally(_ => {
this.loadIng--;
this.waitIng--;
});
}, this.searchHistory.length > 0 ? 300 : 0)
}, this.searchCache.length > 0 ? 300 : 0)
},
onSelect(warp = false) {
if (warp === true) {
if (this.onlyAddIconClick) {
searchProject() {
let key = this.searchKey;
const cache = this.searchCache.find(item => item.type === 'project' && item.key == key);
if (cache) {
this.projects = cache.data
}
//
this.waitIng++
setTimeout(() => {
if (this.searchKey != key) {
this.waitIng--
return;
}
setTimeout(() => {
this.loadIng++
}, 300)
this.$store.dispatch("call", {
url: 'project/lists',
data: {
type: 'team',
keys: {
name: key,
},
getuserid: 'yes',
getstatistics: 'no'
},
}).then(({data}) => {
data = data.data.map(item => Object.assign(item, {type: 'project'}))
this.projects = data
//
const index = this.searchCache.findIndex(item => item.key == key);
const tmpData = {type: 'project', key, data, time: $A.Time()};
if (index > -1) {
this.searchCache.splice(index, 1, tmpData)
} else {
this.searchCache.push(tmpData)
}
}).catch(({msg}) => {
this.projects = []
$A.messageWarning(msg)
}).finally(_ => {
this.loadIng--;
this.waitIng--;
});
}, this.searchCache.length > 0 ? 300 : 0)
},
onSelection() {
this.$nextTick(_ => {
this.selects = $A.cloneJSON(this.values)
this.showModal = true
})
},
onSelectAll() {
if (this.isSelectAll) {
this.selects = $A.cloneJSON(this.uncancelable)
return
}
let optional = this.multipleMax - this.selects.length
this.lists.some(item => {
if (this.isUncancelable(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--
}
})
},
onSelectItem({userid}) {
if (this.selects.includes(userid)) {
if (this.isUncancelable(userid)) {
return
}
this.selects = this.selects.filter(value => value != userid)
} else {
if (this.isDisabled(userid)) {
return
}
if (this.multipleMax && this.selects.length >= this.multipleMax) {
$A.messageWarning("已超过最大选择数量")
return
}
this.selects.push(userid)
this.$nextTick(() => {
$A.scrollIntoViewIfNeeded(this.$refs.selected.querySelector(`li[data-id="${userid}"]`))
})
}
this.selects = $A.cloneJSON(this.values)
this.showModal = true
},
onSelectProject(userid_list) {
switch (this.selectIcon(userid_list)) {
case 'ios-checkmark-circle':
//
const removeList = userid_list.filter(userid => !this.isUncancelable(userid))
if (removeList.length != userid_list.length) {
$A.messageWarning("部分成员禁止取消")
}
this.selects = this.selects.filter(userid => !removeList.includes(userid))
break;
default:
//
const addList = userid_list.filter(userid => !this.isDisabled(userid))
if (addList.length != userid_list.length) {
$A.messageWarning("部分成员禁止选择")
}
this.selects = this.selects.concat(addList.filter(userid => !this.selects.includes(userid)))
break;
}
},
onRemoveItem(userid) {
this.selects = this.selects.filter(value => value != userid)
},
onSubmit() {
if (this.submittIng > 0) {
return
}
const clone = $A.cloneJSON(this.values)
this.values = $A.cloneJSON(this.selects)
this.$emit('input', this.values)
@ -313,69 +684,6 @@ export default {
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

@ -1,78 +0,0 @@
<template>
<Form ref="forwardForm" :model="value" label-width="auto" @submit.native.prevent>
<FormItem prop="dialogids" :label="$L('最近聊天')">
<Select
v-model="value.dialogids"
:placeholder="$L('选择转发最近聊天')"
:multiple-max="20"
multiple
filterable
class="dialog-wrapper-dialogids"
transfer-class-name="dialog-wrapper-forward">
<div slot="drop-prepend" class="forward-drop-prepend">{{$L('最多只能选择20个')}}</div>
<Option
v-for="(dialog, key) in dialogList"
:value="dialog.id"
:key="key"
:key-value="dialog.name"
:label="dialog.name">
<div class="forward-option">
<div class="forward-avatar">
<template v-if="dialog.type=='group'">
<i v-if="dialog.group_type=='department'" class="taskfont icon-avatar department">&#xe75c;</i>
<i v-else-if="dialog.group_type=='project'" class="taskfont icon-avatar project">&#xe6f9;</i>
<i v-else-if="dialog.group_type=='task'" class="taskfont icon-avatar task">&#xe6f4;</i>
<Icon v-else class="icon-avatar" type="ios-people" />
</template>
<div v-else-if="dialog.dialog_user" class="user-avatar"><UserAvatar :userid="dialog.dialog_user.userid" :size="26"/></div>
<Icon v-else class="icon-avatar" type="md-person" />
</div>
<div class="forward-name">{{ dialog.name }}</div>
</div>
</Option>
</Select>
</FormItem>
<FormItem prop="userids" :label="`(${$L('或')}) ${$L('指定成员')}`">
<UserSelect v-model="value.userids" :multiple-max="20" :avatar-size="24" :title="$L('选择转发指定成员')" border/>
</FormItem>
</Form>
</template>
<script>
import {mapState} from "vuex";
import UserSelect from "../../../components/UserSelect.vue";
export default {
name: "DialogSelect",
components: {UserSelect},
props: {
value: {
type: Object,
default: () => {
return {};
}
},
},
computed: {
...mapState([
'cacheDialogs',
]),
dialogList() {
return this.cacheDialogs.filter(dialog => {
return !(dialog.name === undefined || dialog.dialog_delete === 1);
}).sort((a, b) => {
if (a.top_at || b.top_at) {
return $A.Date(b.top_at) - $A.Date(a.top_at);
}
if (a.todo_num > 0 || b.todo_num > 0) {
return b.todo_num - a.todo_num;
}
return $A.Date(b.last_at) - $A.Date(a.last_at);
});
},
}
}
</script>

View File

@ -376,16 +376,15 @@
</Modal>
<!-- 转发 -->
<Modal
v-model="forwardShow"
<UserSelect
ref="forwardSelect"
v-model="forwardData"
:multiple-max="50"
:title="$L('转发')"
:mask-closable="false">
<DialogSelect v-model="forwardData"/>
<div slot="footer" class="adaption">
<Button type="default" @click="forwardShow=false">{{$L('取消')}}</Button>
<Button type="primary" :loading="forwardLoad" @click="onForward('submit')">{{$L('转发')}}</Button>
</div>
</Modal>
:before-submit="onForward"
:show-select-all="false"
show-dialog
module/>
<!-- 设置待办 -->
<Modal
@ -517,7 +516,6 @@ import ChatInput from "./ChatInput";
import VirtualList from 'vue-virtual-scroll-list-hi'
import {Store} from "le5le-store";
import DialogSelect from "./DialogSelect";
import ImgUpload from "../../../components/ImgUpload.vue";
import {choiceEmojiOne} from "./ChatInput/one";
@ -529,7 +527,6 @@ export default {
components: {
UserSelect,
ImgUpload,
DialogSelect,
DialogRespond,
DialogItem,
VirtualList,
@ -592,12 +589,7 @@ export default {
modifyData: {},
modifyLoad: 0,
forwardShow: false,
forwardLoad: false,
forwardData: {
dialogids: [],
userids: [],
},
forwardData: [],
openId: 0,
dialogDrag: false,
@ -2045,34 +2037,32 @@ export default {
}
},
onForward(type) {
if (type === 'open') {
this.forwardData = {
dialogids: [],
userids: [],
msg_id: this.operateItem.id
};
this.forwardShow = true;
} else if (type === 'submit') {
if ($A.arrayLength(this.forwardData.dialogids) === 0 && $A.arrayLength(this.forwardData.userids) === 0) {
$A.messageWarning("请选择转发对话或成员");
onForward() {
return new Promise((resolve, reject) => {
if (this.forwardData.length === 0) {
$A.messageError("请选择转发对话或成员");
reject();
return
}
this.forwardLoad = true;
const dialogids = this.forwardData.filter(value => $A.leftExists(value, 'd:')).map(value => value.replace('d:', ''));
const userids = this.forwardData.filter(value => !$A.leftExists(value, 'd:'));
this.$store.dispatch("call", {
url: 'dialog/msg/forward',
data: this.forwardData
data: {
dialogids,
userids,
msg_id: this.operateItem.id
}
}).then(({data, msg}) => {
this.forwardShow = false;
this.$store.dispatch("saveDialogMsg", data.msgs);
this.$store.dispatch("updateDialogLastMsg", data.msgs);
$A.messageSuccess(msg);
resolve();
}).catch(({msg}) => {
$A.modalError(msg);
}).finally(_ => {
this.forwardLoad = false;
reject();
});
}
})
},
onScroll(event) {
@ -2237,7 +2227,8 @@ export default {
break;
case "forward":
this.onForward('open')
this.forwardData = [];
this.$refs.forwardSelect.onSelection()
break;
case "withdraw":

View File

@ -322,16 +322,15 @@
</Modal>
<!-- 文件发送 -->
<Modal
v-model="sendShow"
<UserSelect
ref="sendFile"
v-model="sendData"
:multiple-max="50"
:title="$L('发送文件')"
:mask-closable="false">
<DialogSelect v-model="sendData"/>
<div slot="footer" class="adaption">
<Button type="default" @click="sendShow=false">{{$L('取消')}}</Button>
<Button type="primary" :loading="sendLoad" @click="onSendFile">{{$L('发送文件')}}</Button>
</div>
</Modal>
:before-submit="onSendFile"
:show-select-all="false"
show-dialog
module/>
<!--文件链接-->
<Modal
@ -401,7 +400,6 @@ import {sortBy} from "lodash";
import DrawerOverlay from "../../components/DrawerOverlay";
import PreviewImage from "../../components/PreviewImage";
import longpress from "../../directives/longpress";
import DialogSelect from "./components/DialogSelect";
import UserSelect from "../../components/UserSelect.vue";
const FilePreview = () => import('./components/FilePreview');
@ -409,7 +407,7 @@ const FileContent = () => import('./components/FileContent');
const FileObject = {sort: null, mode: null, shared: null};
export default {
components: {UserSelect, DialogSelect, PreviewImage, FilePreview, DrawerOverlay, FileContent},
components: {UserSelect, PreviewImage, FilePreview, DrawerOverlay, FileContent},
directives: {longpress},
data() {
return {
@ -477,12 +475,8 @@ export default {
shareList: [],
shareLoad: 0,
sendShow: false,
sendLoad: false,
sendData: {
dialogids: [],
userids: [],
},
sendFileId: 0,
sendData: [],
linkShow: false,
linkData: {},
@ -1197,12 +1191,9 @@ export default {
break;
case 'send':
this.sendData = {
dialogids: [],
userids: [],
file_id: item.id
};
this.sendShow = true;
this.sendFileId = item.id;
this.sendData = [];
this.$refs.sendFile.onSelection()
break;
case 'share':
@ -1269,24 +1260,31 @@ export default {
},
onSendFile() {
if ($A.arrayLength(this.sendData.dialogids) === 0 && $A.arrayLength(this.sendData.userids) === 0) {
$A.messageWarning("请选择转发对话或成员");
return
}
this.sendLoad = true;
this.$store.dispatch("call", {
url: 'dialog/msg/sendfileid',
data: this.sendData
}).then(({data, msg}) => {
this.sendShow = false;
this.$store.dispatch("saveDialogMsg", data.msgs);
this.$store.dispatch("updateDialogLastMsg", data.msgs);
$A.messageSuccess(msg);
}).catch(({msg}) => {
$A.modalError(msg);
}).finally(_ => {
this.sendLoad = false;
});
return new Promise((resolve, reject) => {
if (this.sendData.length === 0) {
$A.messageError("请选择转发对话或成员");
reject();
return
}
const dialogids = this.sendData.filter(value => $A.leftExists(value, 'd:')).map(value => value.replace('d:', ''));
const userids = this.sendData.filter(value => !$A.leftExists(value, 'd:'));
this.$store.dispatch("call", {
url: 'dialog/msg/sendfileid',
data: {
dialogids,
userids,
file_id: this.sendFileId
}
}).then(({data, msg}) => {
this.$store.dispatch("saveDialogMsg", data.msgs);
this.$store.dispatch("updateDialogLastMsg", data.msgs);
$A.messageSuccess(msg);
resolve();
}).catch(({msg}) => {
$A.modalError(msg);
reject();
});
})
},
linkGet(refresh) {

View File

@ -1,152 +1,447 @@
.common-user-select {
&.select-module {
display: none;
}
&.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;
cursor: pointer;
> 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;
border-radius: 50%;
}
}
}
}
@media screen and (max-width: 576px) {
.common-user-select-modal {
.common-user-select-modal {
.ivu-modal {
max-width: 90%;
.user-modal-header {
display: flex;
align-items: center;
justify-content: center;
font-weight: 500;
.user-modal-close {
flex-shrink: 0;
color: $primary-text-color;
}
.user-modal-title {
flex: 1;
text-align: center;
font-size: 16px;
padding: 0 16px;
color: $primary-title-color;
}
.user-modal-submit {
flex-shrink: 0;
color: $primary-color;
display: flex;
align-items: center;
.submit-loading {
width: 14px;
height: 14px;
margin-right: 6px;
}
}
}
.ivu-modal-body {
padding: 0 24px !important;
display: flex;
flex-direction: column;
.user-modal-search {
flex-shrink: 0;
display: flex;
align-items: center;
.search-selected {
flex: none;
height: auto;
flex-shrink: 0;
max-width: 60%;
margin-right: 12px;
ul {
display: flex;
align-items: center;
cursor: pointer;
> li {
list-style: none;
margin-right: 6px;
&:last-child {
margin-right: 0;
}
.img-avatar,
.icon-avatar {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
font-size: 20px;
background-color: #61B2F9;
color: #ffffff;
&.department {
background-color: #5BC7B0;
}
&.project {
background-color: #6E99EB;
}
&.task {
background-color: #9B96DF;
font-size: 24px;
}
> img {
width: 100%;
height: 100%;
}
}
}
}
}
.search-input {
.ivu-input {
height: 36px;
border-color: #f4f5f7;
background-color: #f4f5f7;
}
.search-pre {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
.common-loading {
width: 14px;
height: 14px;
margin: 0;
}
}
}
}
.user-modal-switch {
display: flex;
flex-direction: row;
align-items: center;
margin-top: 12px;
border-radius: 4px;
background: #F4F5F7;
> li {
flex: 1;
list-style: none;
color: $primary-text-color;
border-radius: 2px;
margin: 2px;
padding: 4px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
&.active {
font-weight: 500;
color: $primary-title-color;
background: #ffffff;
}
}
}
.user-modal-list {
max-height: none;
flex: 1;
display: flex;
flex-direction: column;
max-height: 400px;
ul {
padding: 16px 0;
&.user-modal-project {
> li {
&:last-child {
.user-modal-avatar {
.project-name {
&:before {
display: none;
}
}
}
}
}
}
> li {
list-style: none;
margin: 0;
padding: 16px 0 0;
height: 46px;
box-sizing: content-box;
display: flex;
align-items: center;
cursor: pointer;
&:first-child {
padding-top: 0;
}
&.selected {
.user-modal-icon {
color: $primary-color;
}
.user-modal-avatar {
.project-name {
.subtitle {
> em.all {
display: inline-block;
}
}
}
}
}
&.somed {
.user-modal-icon {
color: $primary-color;
}
.user-modal-avatar {
.project-name {
.subtitle {
> em.some {
display: inline-block;
}
}
}
}
}
&.disabled,
&.disabled:hover {
color: #c5c8ce;
cursor: not-allowed;
}
.user-modal-icon {
flex-shrink: 0;
font-size: 22px;
margin-right: 10px;
color: rgba($primary-desc-color, 0.7);
}
.user-modal-all {
font-size: 15px;
font-weight: 500;
}
.user-modal-avatar {
flex: 1;
display: flex;
align-items: center;
.img-avatar,
.icon-avatar {
flex-shrink: 0;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
font-size: 26px;
background-color: #61B2F9;
color: #ffffff;
&.department {
background-color: #5BC7B0;
}
&.project {
background-color: #6E99EB;
}
&.task {
background-color: #9B96DF;
font-size: 24px;
}
> img {
width: 100%;
height: 100%;
}
}
.avatar-name,
.project-name {
flex: 1;
width: 0;
margin-left: 12px;
> span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.avatar-name {
display: flex;
align-items: center;
}
.project-name {
display: flex;
flex-direction: column;
position: relative;
&:before {
content: "";
position: absolute;
left: 0;
right: 0;
bottom: -9px;
height: 1px;
background-color: #f2f2f2;
}
.label {
font-size: 15px;
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.subtitle {
padding-top: 2px;
font-size: 12px;
color: $primary-desc-color;
display: flex;
align-items: center;
> em {
display: none;
font-style: normal;
padding-left: 4px;
color: $primary-color;
}
}
}
}
}
}
}
.user-modal-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 36px 36px 20px;
min-height: 200px;
.empty-icon {
background-color: #f4f5f7;
padding: 20px;
border-radius: 50%;
.ivu-icon {
color: #d1d8dd;
font-size: 46px;
}
}
.empty-text {
margin-top: 16px;
color: #bec6cc;
background-color: #f4f5f7;
padding: 4px 15px;
border-radius: 14px;
}
}
}
.ivu-modal-footer {
border-top: 1px solid #f2f2f2 !important;
padding: 12px 0 !important;
margin: 0 24px !important;
}
&.ivu-modal-fullscreen {
max-width: none;
.ivu-modal-content {
margin-top: 46px;
border-top-left-radius: 18px !important;
border-top-right-radius: 18px !important;
}
.ivu-modal-header {
border-bottom: 1px solid #f2f2f2;
}
.ivu-modal-body {
top: 60px;
.user-modal-search {
padding-top: 12px;
}
.user-modal-list {
max-height: none;
ul {
> li {
&:last-child {
padding-bottom: 16px;
}
}
}
}
}
}
}

View File

@ -53,6 +53,7 @@ body.dark-mode-reverse {
.overlay-close {
> a {
color: #323232;
&:hover {
color: #000;
}
@ -69,6 +70,7 @@ body.dark-mode-reverse {
background-color: #000;
}
}
.teditor-loadedstyle {
.tox-sidebar-wrap {
box-shadow: none
@ -96,6 +98,7 @@ body.dark-mode-reverse {
}
}
}
.project-column {
> ul {
> li {
@ -107,6 +110,7 @@ body.dark-mode-reverse {
color: #ed4014;
background-color: transparent;
}
&.today {
color: #ff9900;
background-color: transparent;
@ -118,6 +122,7 @@ body.dark-mode-reverse {
}
}
}
.project-table {
.project-table-body {
.task-row {
@ -128,6 +133,7 @@ body.dark-mode-reverse {
color: #ed4014;
background-color: transparent;
}
&.today {
color: #ff9900;
background-color: transparent;
@ -149,6 +155,7 @@ body.dark-mode-reverse {
color: #1c1917;
}
}
.dialog-title {
.main-title {
> h2 {
@ -159,22 +166,26 @@ body.dark-mode-reverse {
}
}
}
.dialog-scroller {
.dialog-item {
.dialog-view {
.dialog-head {
background-color: #e1e1e1;
.dialog-reply {
.reply-desc {
color: #ffffff;
}
}
.dialog-content {
.content-text,
.content-record,
.content-meeting {
color: #ffffff;
}
.open-approve-details {
.cause {
border-bottom: 1px solid #7f7f7f;
@ -182,9 +193,11 @@ body.dark-mode-reverse {
}
}
}
.dialog-emoji {
> li {
background-color: rgba(#f3f3f3, 0.5);
&.hasme {
background-color: #f3f3f3;
}
@ -192,18 +205,22 @@ body.dark-mode-reverse {
}
}
}
&.self {
.dialog-view {
.dialog-head {
background-color: $primary-color;
.dialog-content {
.content-text {
> pre {
a {
color: #0027a1;
}
.mention {
color: #000000;
&.file,
&[data-denotation-char="~"] {
color: #0027a1 !important;
@ -212,16 +229,20 @@ body.dark-mode-reverse {
}
}
}
.dialog-emoji {
> li {
background-color: rgba(#b2ff93, 0.5);
&.hasme {
background-color: #b2ff93;
}
.emoji-users {
&:before {
background-color: rgba(#000000, 0.7);
}
> ul {
> li {
color: #000000;
@ -253,73 +274,95 @@ body.dark-mode-reverse {
&:before {
background-image: url("../images/file/dark/other.svg");
}
&.archive:before {
background-image: url("../images/file/dark/archive.svg");
}
&.cad:before {
background-image: url("../images/file/dark/cad.svg");
}
&.code:before {
background-image: url("../images/file/dark/code.svg");
}
&.document:before {
background-image: url("../images/file/dark/document.svg");
}
&.drawio:before {
background-image: url("../images/file/dark/drawio.svg");
}
&.excel:before {
background-image: url("../images/file/dark/excel.svg");
}
&.flow:before {
background-image: url("../images/file/dark/flow.svg");
}
&.folder {
&:before {
background-image: url("../images/file/dark/folder.svg");
}
&.share {
&:before {
background-image: url("../images/file/dark/folder-share.svg");
}
}
}
&.media:before {
background-image: url("../images/file/dark/media.svg");
}
&.mind:before {
background-image: url("../images/file/dark/mind.svg");
}
&.ofd:before {
background-image: url("../images/file/dark/ofd.svg");
}
&.pdf:before {
background-image: url("../images/file/dark/pdf.svg");
}
&.picture:before {
background-image: url("../images/file/dark/picture.svg");
}
&.ppt:before {
background-image: url("../images/file/dark/ppt.svg");
}
&.sheet:before {
background-image: url("../images/file/dark/sheet.svg");
}
&.tif:before {
background-image: url("../images/file/dark/tif.svg");
}
&.txt:before {
background-image: url("../images/file/dark/txt.svg");
}
&.updir:before {
background-image: url("../images/file/dark/updir.svg");
}
&.upload:before {
background-image: url("../images/file/dark/upload.svg");
}
&.word:before {
background-image: url("../images/file/dark/word.svg");
}
&.wps:before {
background-image: url("../images/file/dark/wps.svg");
}
@ -353,10 +396,12 @@ body.dark-mode-reverse {
color: rgba(0, 0, 0, 0.6);
font-size: 12px;
}
.block-data {
.block-num {
color: #000;
}
.taskfont {
color: rgba(0, 0, 0, 0.7);
}
@ -376,6 +421,7 @@ body.dark-mode-reverse {
.icon-avatar {
color: #1c1917;
}
.dialog-box {
.dialog-text {
color: #555;
@ -402,6 +448,7 @@ body.dark-mode-reverse {
.page-index {
.page-warp {
background-color: #efefef;
.page-header {
.header-nav {
.header-nav-box {
@ -409,28 +456,33 @@ body.dark-mode-reverse {
.header-right-uplog {
color: #000000;
}
.header-right-1 {
.header-right-1-dropdown {
color: #000000;
}
}
.header-right-2 {
.header-right-2-dropdown {
color: #000000;
}
}
.header-right-3 {
color: #000000;
}
}
}
.header-content {
.header-title, .header-tips {
color: #000000;
}
}
}
.page-header-bottom{
.page-header-bottom {
background-color: #efefef;
}
}
@ -441,16 +493,19 @@ body.dark-mode-reverse {
.ql-container {
.ql-editor {
color: #ccc;
&.ql-blank {
&::before {
color: #333;
}
}
.mention {
color: #333;
}
}
}
.chat-toolbar {
> li {
&.chat-send {
@ -462,4 +517,34 @@ body.dark-mode-reverse {
}
}
}
.common-user-select-modal {
.ivu-modal {
.ivu-modal-body {
.user-modal-search {
.search-selected {
ul {
> li {
.icon-avatar {
color: #1c1917;
}
}
}
}
}
.user-modal-list {
ul {
> li {
.user-modal-avatar {
.icon-avatar {
color: #1c1917;
}
}
}
}
}
}
}
}
}

@ -1 +1 @@
Subproject commit 71ad67db7859b6e4488311a8b03d9f4851627166
Subproject commit 777a8d12477bda3e34ec6c9290ea5dfd84b8fd81